From 32b3ed0a1362a4b0798ad71fac3450fb77cb7e41 Mon Sep 17 00:00:00 2001 From: Thomas Groman Date: Thu, 19 Sep 2019 00:41:48 -0700 Subject: merged from 0.6.7 codebase --- .arcconfig | 4 +- .clang-format | 2 +- .github/ISSUE_TEMPLATE.md | 12 +- .gitignore | 2 + .gitmodules | 8 +- CMakeLists.txt | 190 +- COPYING.md | 350 +- README.md | 6 +- api/gui/CMakeLists.txt | 27 +- api/gui/DesktopServices.cpp | 174 +- api/gui/DesktopServices.h | 42 +- api/gui/SkinUtils.cpp | 26 +- api/gui/SkinUtils.h | 2 +- api/gui/icons/IconList.cpp | 581 +- api/gui/icons/IconList.h | 74 +- api/gui/icons/MMCIcon.cpp | 124 +- api/gui/icons/MMCIcon.h | 40 +- api/logic/BaseInstaller.cpp | 38 +- api/logic/BaseInstaller.h | 20 +- api/logic/BaseInstance.cpp | 261 +- api/logic/BaseInstance.h | 395 +- api/logic/BaseInstanceProvider.h | 57 - api/logic/BaseVersion.h | 56 +- api/logic/BaseVersionList.cpp | 88 +- api/logic/BaseVersionList.h | 136 +- api/logic/CMakeLists.txt | 771 +- api/logic/Commandline.cpp | 732 +- api/logic/Commandline.h | 326 +- api/logic/DefaultVariable.h | 54 +- api/logic/Env.cpp | 236 +- api/logic/Env.h | 44 +- api/logic/Exception.h | 36 +- api/logic/ExponentialSeries.h | 43 + api/logic/FileSystem.cpp | 641 +- api/logic/FileSystem.h | 50 +- api/logic/FileSystem_test.cpp | 274 +- api/logic/Filter.cpp | 16 +- api/logic/Filter.h | 30 +- api/logic/FolderInstanceProvider.cpp | 462 -- api/logic/FolderInstanceProvider.h | 75 - api/logic/GZip.cpp | 208 +- api/logic/GZip.h | 4 +- api/logic/GZip_test.cpp | 74 +- api/logic/InstanceCopyTask.cpp | 65 +- api/logic/InstanceCopyTask.h | 22 +- api/logic/InstanceCreationTask.cpp | 34 +- api/logic/InstanceCreationTask.h | 10 +- api/logic/InstanceImportTask.cpp | 678 +- api/logic/InstanceImportTask.h | 53 +- api/logic/InstanceList.cpp | 982 ++- api/logic/InstanceList.h | 189 +- api/logic/InstanceTask.h | 84 +- api/logic/Json.cpp | 268 +- api/logic/Json.h | 160 +- api/logic/LoggedProcess.cpp | 214 +- api/logic/LoggedProcess.h | 72 +- api/logic/MMCStrings.cpp | 126 +- api/logic/MMCStrings.h | 2 +- api/logic/MMCZip.cpp | 400 +- api/logic/MMCZip.h | 72 +- api/logic/MessageLevel.cpp | 54 +- api/logic/MessageLevel.h | 20 +- api/logic/NullInstance.h | 139 +- api/logic/ProblemProvider.h | 52 +- api/logic/QObjectPtr.h | 104 +- api/logic/RWStorage.h | 108 +- api/logic/RecursiveFileSystemWatcher.cpp | 142 +- api/logic/RecursiveFileSystemWatcher.h | 80 +- api/logic/SeparatorPrefixTree.h | 544 +- api/logic/Usable.h | 58 +- api/logic/Version.cpp | 94 +- api/logic/Version.h | 179 +- api/logic/Version_test.cpp | 98 +- api/logic/WatchLock.h | 20 + api/logic/icons/IIconList.h | 24 +- api/logic/icons/IconUtils.cpp | 62 + api/logic/icons/IconUtils.h | 14 + api/logic/java/JavaChecker.cpp | 250 +- api/logic/java/JavaChecker.h | 72 +- api/logic/java/JavaCheckerJob.cpp | 34 +- api/logic/java/JavaCheckerJob.h | 55 +- api/logic/java/JavaInstall.cpp | 28 +- api/logic/java/JavaInstall.h | 48 +- api/logic/java/JavaInstallList.cpp | 224 +- api/logic/java/JavaInstallList.h | 62 +- api/logic/java/JavaUtils.cpp | 518 +- api/logic/java/JavaUtils.h | 14 +- api/logic/java/JavaVersion.cpp | 164 +- api/logic/java/JavaVersion.h | 68 +- api/logic/java/JavaVersion_test.cpp | 194 +- api/logic/java/launch/CheckJava.cpp | 196 +- api/logic/java/launch/CheckJava.h | 30 +- api/logic/launch/LaunchStep.cpp | 14 +- api/logic/launch/LaunchStep.h | 36 +- api/logic/launch/LaunchTask.cpp | 332 +- api/logic/launch/LaunchTask.h | 136 +- api/logic/launch/LogModel.cpp | 218 +- api/logic/launch/LogModel.h | 70 +- api/logic/launch/steps/PostLaunchCommand.cpp | 96 +- api/logic/launch/steps/PostLaunchCommand.h | 28 +- api/logic/launch/steps/PreLaunchCommand.cpp | 98 +- api/logic/launch/steps/PreLaunchCommand.h | 28 +- api/logic/launch/steps/TextPrint.cpp | 18 +- api/logic/launch/steps/TextPrint.h | 20 +- api/logic/launch/steps/Update.cpp | 86 +- api/logic/launch/steps/Update.h | 24 +- api/logic/meta/BaseEntity.cpp | 202 +- api/logic/meta/BaseEntity.h | 54 +- api/logic/meta/Index.cpp | 164 +- api/logic/meta/Index.h | 50 +- api/logic/meta/Index_test.cpp | 52 +- api/logic/meta/JsonFormat.cpp | 260 +- api/logic/meta/JsonFormat.h | 46 +- api/logic/meta/Version.cpp | 106 +- api/logic/meta/Version.h | 138 +- api/logic/meta/VersionList.cpp | 278 +- api/logic/meta/VersionList.h | 118 +- api/logic/minecraft/AssetsUtils.cpp | 442 +- api/logic/minecraft/AssetsUtils.h | 31 +- api/logic/minecraft/Component.cpp | 608 +- api/logic/minecraft/Component.h | 138 +- api/logic/minecraft/ComponentList.cpp | 1919 ++--- api/logic/minecraft/ComponentList.h | 144 +- api/logic/minecraft/ComponentList_p.h | 45 +- api/logic/minecraft/ComponentUpdateTask.cpp | 1129 +-- api/logic/minecraft/ComponentUpdateTask.h | 30 +- api/logic/minecraft/ComponentUpdateTask_p.h | 32 +- api/logic/minecraft/GradleSpecifier.h | 266 +- api/logic/minecraft/GradleSpecifier_test.cpp | 126 +- api/logic/minecraft/LaunchProfile.cpp | 324 +- api/logic/minecraft/LaunchProfile.h | 140 +- api/logic/minecraft/Library.cpp | 537 +- api/logic/minecraft/Library.h | 336 +- api/logic/minecraft/Library_test.cpp | 498 +- api/logic/minecraft/MinecraftInstance.cpp | 1458 ++-- api/logic/minecraft/MinecraftInstance.h | 210 +- api/logic/minecraft/MinecraftLoadAndCheck.cpp | 50 +- api/logic/minecraft/MinecraftLoadAndCheck.h | 21 +- api/logic/minecraft/MinecraftUpdate.cpp | 250 +- api/logic/minecraft/MinecraftUpdate.h | 36 +- api/logic/minecraft/Mod.cpp | 378 - api/logic/minecraft/Mod.h | 142 - api/logic/minecraft/ModList.cpp | 367 - api/logic/minecraft/ModList.h | 121 - api/logic/minecraft/ModList_test.cpp | 53 - api/logic/minecraft/MojangDownloadInfo.h | 110 +- api/logic/minecraft/MojangVersionFormat.cpp | 578 +- api/logic/minecraft/MojangVersionFormat.h | 20 +- api/logic/minecraft/MojangVersionFormat_test.cpp | 76 +- api/logic/minecraft/OneSixVersionFormat.cpp | 650 +- api/logic/minecraft/OneSixVersionFormat.h | 28 +- api/logic/minecraft/OpSys.cpp | 38 +- api/logic/minecraft/OpSys.h | 10 +- api/logic/minecraft/ParseUtils.cpp | 36 +- api/logic/minecraft/ParseUtils_test.cpp | 58 +- api/logic/minecraft/ProfileUtils.cpp | 264 +- api/logic/minecraft/Rule.cpp | 108 +- api/logic/minecraft/Rule.h | 96 +- api/logic/minecraft/SkinUpload.cpp | 80 +- api/logic/minecraft/SkinUpload.h | 33 +- api/logic/minecraft/VersionFile.cpp | 68 +- api/logic/minecraft/VersionFile.h | 118 +- api/logic/minecraft/VersionFilterData.cpp | 104 +- api/logic/minecraft/VersionFilterData.h | 24 +- api/logic/minecraft/World.cpp | 618 +- api/logic/minecraft/World.h | 117 +- api/logic/minecraft/WorldList.cpp | 519 +- api/logic/minecraft/WorldList.h | 178 +- api/logic/minecraft/auth/AuthSession.cpp | 32 +- api/logic/minecraft/auth/AuthSession.h | 70 +- api/logic/minecraft/auth/MojangAccount.cpp | 444 +- api/logic/minecraft/auth/MojangAccount.h | 172 +- api/logic/minecraft/auth/MojangAccountList.cpp | 684 +- api/logic/minecraft/auth/MojangAccountList.h | 306 +- api/logic/minecraft/auth/YggdrasilTask.cpp | 358 +- api/logic/minecraft/auth/YggdrasilTask.h | 213 +- .../minecraft/auth/flows/AuthenticateTask.cpp | 314 +- api/logic/minecraft/auth/flows/AuthenticateTask.h | 16 +- api/logic/minecraft/auth/flows/RefreshTask.cpp | 190 +- api/logic/minecraft/auth/flows/RefreshTask.h | 14 +- api/logic/minecraft/auth/flows/ValidateTask.cpp | 34 +- api/logic/minecraft/auth/flows/ValidateTask.h | 14 +- api/logic/minecraft/forge/ForgeXzDownload.cpp | 644 +- api/logic/minecraft/forge/ForgeXzDownload.h | 48 +- api/logic/minecraft/gameoptions/GameOptions.cpp | 144 + api/logic/minecraft/gameoptions/GameOptions.h | 34 + api/logic/minecraft/launch/ClaimAccount.cpp | 20 +- api/logic/minecraft/launch/ClaimAccount.h | 24 +- .../launch/CreateServerResourcePacksFolder.cpp | 14 +- .../launch/CreateServerResourcePacksFolder.h | 18 +- api/logic/minecraft/launch/DirectJavaLaunch.cpp | 198 +- api/logic/minecraft/launch/DirectJavaLaunch.h | 40 +- api/logic/minecraft/launch/ExtractNatives.cpp | 122 +- api/logic/minecraft/launch/ExtractNatives.h | 20 +- api/logic/minecraft/launch/LauncherPartLaunch.cpp | 294 +- api/logic/minecraft/launch/LauncherPartLaunch.h | 44 +- api/logic/minecraft/launch/ModMinecraftJar.cpp | 92 +- api/logic/minecraft/launch/ModMinecraftJar.h | 22 +- api/logic/minecraft/launch/PrintInstanceInfo.cpp | 97 +- api/logic/minecraft/launch/PrintInstanceInfo.h | 20 +- api/logic/minecraft/launch/ReconstructAssets.cpp | 36 + api/logic/minecraft/launch/ReconstructAssets.h | 33 + api/logic/minecraft/launch/ScanModFolders.cpp | 54 + api/logic/minecraft/launch/ScanModFolders.h | 42 + api/logic/minecraft/legacy/LegacyInstance.cpp | 314 +- api/logic/minecraft/legacy/LegacyInstance.h | 220 +- api/logic/minecraft/legacy/LegacyModList.cpp | 231 +- api/logic/minecraft/legacy/LegacyModList.h | 41 +- api/logic/minecraft/legacy/LegacyUpgradeTask.cpp | 201 +- api/logic/minecraft/legacy/LegacyUpgradeTask.h | 20 +- api/logic/minecraft/mod/LocalModParseTask.cpp | 298 + api/logic/minecraft/mod/LocalModParseTask.h | 37 + api/logic/minecraft/mod/Mod.cpp | 151 + api/logic/minecraft/mod/Mod.h | 117 + api/logic/minecraft/mod/ModDetails.h | 17 + api/logic/minecraft/mod/ModFolderLoadTask.cpp | 18 + api/logic/minecraft/mod/ModFolderLoadTask.h | 29 + api/logic/minecraft/mod/ModFolderModel.cpp | 554 ++ api/logic/minecraft/mod/ModFolderModel.h | 145 + api/logic/minecraft/mod/ModFolderModel_test.cpp | 53 + api/logic/minecraft/testdata/lib-native-arch.json | 88 +- api/logic/minecraft/testdata/lib-native.json | 100 +- api/logic/minecraft/testdata/lib-simple.json | 18 +- api/logic/minecraft/update/AssetUpdateTask.cpp | 129 +- api/logic/minecraft/update/AssetUpdateTask.h | 22 +- api/logic/minecraft/update/FMLLibrariesTask.cpp | 189 +- api/logic/minecraft/update/FMLLibrariesTask.h | 21 +- api/logic/minecraft/update/FoldersTask.cpp | 20 +- api/logic/minecraft/update/FoldersTask.h | 10 +- api/logic/minecraft/update/LibrariesTask.cpp | 123 +- api/logic/minecraft/update/LibrariesTask.h | 17 +- api/logic/modplatform/flame/FileResolvingTask.cpp | 150 +- api/logic/modplatform/flame/FileResolvingTask.h | 24 +- api/logic/modplatform/flame/PackManifest.cpp | 148 +- api/logic/modplatform/flame/PackManifest.h | 69 +- api/logic/modplatform/flame/UrlResolvingTask.cpp | 175 + api/logic/modplatform/flame/UrlResolvingTask.h | 43 + api/logic/modplatform/ftb/FtbPackFetchTask.cpp | 243 +- api/logic/modplatform/ftb/FtbPackFetchTask.h | 31 +- api/logic/modplatform/ftb/FtbPackInstallTask.cpp | 297 +- api/logic/modplatform/ftb/FtbPackInstallTask.h | 51 +- .../modplatform/ftb/FtbPrivatePackManager.cpp | 37 + api/logic/modplatform/ftb/FtbPrivatePackManager.h | 40 + api/logic/modplatform/ftb/PackHelpers.h | 47 +- api/logic/net/ByteArraySink.h | 88 +- api/logic/net/ChecksumValidator.h | 78 +- api/logic/net/Download.cpp | 418 +- api/logic/net/Download.h | 62 +- api/logic/net/FileSink.cpp | 142 +- api/logic/net/FileSink.h | 24 +- api/logic/net/HttpMetaCache.cpp | 368 +- api/logic/net/HttpMetaCache.h | 154 +- api/logic/net/MetaCacheSink.cpp | 78 +- api/logic/net/MetaCacheSink.h | 14 +- api/logic/net/Mode.h | 4 +- api/logic/net/NetAction.h | 134 +- api/logic/net/NetJob.cpp | 314 +- api/logic/net/NetJob.h | 98 +- api/logic/net/PasteUpload.cpp | 128 +- api/logic/net/PasteUpload.h | 63 +- api/logic/net/Sink.h | 102 +- api/logic/net/URLConstants.cpp | 4 +- api/logic/net/URLConstants.h | 26 +- api/logic/net/Validator.h | 12 +- api/logic/news/NewsChecker.cpp | 146 +- api/logic/news/NewsChecker.h | 118 +- api/logic/news/NewsEntry.cpp | 74 +- api/logic/news/NewsEntry.h | 54 +- api/logic/notifications/NotificationChecker.cpp | 158 +- api/logic/notifications/NotificationChecker.h | 74 +- api/logic/pathmatcher/FSTreeMatcher.h | 18 +- api/logic/pathmatcher/IPathMatcher.h | 6 +- api/logic/pathmatcher/MultiMatcher.h | 42 +- api/logic/pathmatcher/RegexpMatcher.h | 66 +- api/logic/screenshots/ImgurAlbumCreation.cpp | 112 +- api/logic/screenshots/ImgurAlbumCreation.h | 46 +- api/logic/screenshots/ImgurUpload.cpp | 160 +- api/logic/screenshots/ImgurUpload.h | 28 +- api/logic/screenshots/Screenshot.h | 16 +- api/logic/settings/INIFile.cpp | 204 +- api/logic/settings/INIFile.h | 18 +- api/logic/settings/INIFile_test.cpp | 97 +- api/logic/settings/INISettingsObject.cpp | 110 +- api/logic/settings/INISettingsObject.h | 50 +- api/logic/settings/OverrideSetting.cpp | 30 +- api/logic/settings/OverrideSetting.h | 20 +- api/logic/settings/PassthroughSetting.cpp | 60 +- api/logic/settings/PassthroughSetting.h | 20 +- api/logic/settings/Setting.cpp | 34 +- api/logic/settings/Setting.h | 148 +- api/logic/settings/SettingsObject.cpp | 142 +- api/logic/settings/SettingsObject.h | 314 +- api/logic/status/StatusChecker.cpp | 168 +- api/logic/status/StatusChecker.h | 38 +- api/logic/tasks/SequentialTask.cpp | 56 +- api/logic/tasks/SequentialTask.h | 21 +- api/logic/tasks/Task.cpp | 168 +- api/logic/tasks/Task.h | 108 +- api/logic/tools/BaseExternalTool.cpp | 10 +- api/logic/tools/BaseExternalTool.h | 34 +- api/logic/tools/BaseProfiler.cpp | 27 +- api/logic/tools/BaseProfiler.h | 21 +- api/logic/tools/JProfiler.cpp | 116 +- api/logic/tools/JProfiler.h | 10 +- api/logic/tools/JVisualVM.cpp | 100 +- api/logic/tools/JVisualVM.h | 10 +- api/logic/tools/MCEditTool.cpp | 84 +- api/logic/tools/MCEditTool.h | 12 +- api/logic/translations/POTranslator.cpp | 373 + api/logic/translations/POTranslator.h | 16 + api/logic/translations/TranslationsModel.cpp | 804 +- api/logic/translations/TranslationsModel.h | 54 +- api/logic/updater/DownloadTask.cpp | 191 +- api/logic/updater/DownloadTask.h | 111 +- api/logic/updater/DownloadTask_test.cpp | 336 +- api/logic/updater/GoUpdate.cpp | 382 +- api/logic/updater/GoUpdate.h | 140 +- api/logic/updater/UpdateChecker.cpp | 400 +- api/logic/updater/UpdateChecker.h | 170 +- api/logic/updater/UpdateChecker_test.cpp | 243 +- api/logic/updater/testdata/1.json | 82 +- api/logic/updater/testdata/2.json | 58 +- api/logic/updater/testdata/channels.json | 42 +- api/logic/updater/testdata/errorChannels.json | 42 +- api/logic/updater/testdata/garbageChannels.json | 40 +- api/logic/updater/testdata/index.json | 14 +- api/logic/updater/testdata/noChannels.json | 6 +- api/logic/updater/testdata/oneChannel.json | 18 +- application/BuildConfig.cpp.in | 83 +- application/BuildConfig.h | 89 +- application/CMakeLists.txt | 654 +- application/ColorCache.cpp | 30 +- application/ColorCache.h | 178 +- application/ColumnResizer.cpp | 280 +- application/ColumnResizer.h | 20 +- application/GuiUtil.cpp | 192 +- application/HoeDown.h | 96 +- application/InstancePageProvider.h | 95 +- application/InstanceProxyModel.cpp | 36 +- application/InstanceProxyModel.h | 6 +- application/InstanceWindow.cpp | 280 +- application/InstanceWindow.h | 54 +- application/JavaCommon.cpp | 142 +- application/JavaCommon.h | 74 +- application/KonamiCode.cpp | 48 +- application/KonamiCode.h | 10 +- application/LaunchController.cpp | 483 +- application/LaunchController.h | 84 +- application/MainWindow.cpp | 2929 +++---- application/MainWindow.h | 219 +- application/MultiMC.cpp | 1913 +++-- application/MultiMC.h | 253 +- application/SettingsUI.h | 26 - application/UpdateController.cpp | 736 +- application/UpdateController.h | 54 +- application/VersionProxyModel.cpp | 677 +- application/VersionProxyModel.h | 80 +- application/dialogs/AboutDialog.cpp | 158 +- application/dialogs/AboutDialog.h | 24 +- application/dialogs/AboutDialog.ui | 4 +- application/dialogs/CopyInstanceDialog.cpp | 104 +- application/dialogs/CopyInstanceDialog.h | 32 +- application/dialogs/CustomMessageBox.cpp | 24 +- application/dialogs/CustomMessageBox.h | 8 +- application/dialogs/EditAccountDialog.cpp | 26 +- application/dialogs/EditAccountDialog.h | 36 +- application/dialogs/ExportInstanceDialog.cpp | 806 +- application/dialogs/ExportInstanceDialog.h | 26 +- application/dialogs/IconPickerDialog.cpp | 189 +- application/dialogs/IconPickerDialog.h | 28 +- application/dialogs/LoginDialog.cpp | 84 +- application/dialogs/LoginDialog.h | 32 +- application/dialogs/ModEditDialogCommon.cpp | 40 - application/dialogs/ModEditDialogCommon.h | 9 - application/dialogs/NewComponentDialog.cpp | 88 +- application/dialogs/NewComponentDialog.h | 22 +- application/dialogs/NewComponentDialog.ui | 2 +- application/dialogs/NewInstanceDialog.cpp | 253 +- application/dialogs/NewInstanceDialog.h | 56 +- application/dialogs/NotificationDialog.cpp | 116 +- application/dialogs/NotificationDialog.h | 32 +- application/dialogs/ProfileSelectDialog.cpp | 126 +- application/dialogs/ProfileSelectDialog.h | 100 +- application/dialogs/ProgressDialog.cpp | 210 +- application/dialogs/ProgressDialog.h | 42 +- application/dialogs/SkinUploadDialog.cpp | 180 +- application/dialogs/SkinUploadDialog.h | 18 +- application/dialogs/UpdateDialog.cpp | 258 +- application/dialogs/UpdateDialog.h | 42 +- application/dialogs/VersionSelectDialog.cpp | 102 +- application/dialogs/VersionSelectDialog.h | 50 +- application/groupview/AccessibleGroupView.cpp | 774 ++ application/groupview/AccessibleGroupView.h | 6 + application/groupview/AccessibleGroupView_p.h | 116 + application/groupview/GroupView.cpp | 1527 ++-- application/groupview/GroupView.h | 187 +- application/groupview/GroupedProxyModel.cpp | 36 +- application/groupview/GroupedProxyModel.h | 10 +- application/groupview/InstanceDelegate.cpp | 637 +- application/groupview/InstanceDelegate.h | 21 +- application/groupview/VisualGroup.cpp | 464 +- application/groupview/VisualGroup.h | 108 +- application/install_prereqs.cmake.in | 28 +- application/main.cpp | 63 +- application/package/linux/MultiMC | 116 +- application/package/linux/multimc.desktop | 1 + application/package/ubuntu/multimc/DEBIAN/control | 12 + application/package/ubuntu/multimc/DEBIAN/postrm | 3 + .../package/ubuntu/multimc/opt/multimc/icon.svg | 353 + .../package/ubuntu/multimc/opt/multimc/run.sh | 33 + .../multimc/usr/share/applications/multimc.desktop | 16 + application/package/ubuntu/readme.md | 12 + application/pagedialog/PageDialog.cpp | 47 +- application/pagedialog/PageDialog.h | 12 +- application/pages/BasePage.h | 56 +- application/pages/BasePageContainer.h | 8 +- application/pages/BasePageProvider.h | 67 +- application/pages/global/AccountListPage.cpp | 194 +- application/pages/global/AccountListPage.h | 83 +- application/pages/global/AccountListPage.ui | 182 +- application/pages/global/CustomCommandsPage.cpp | 49 +- application/pages/global/CustomCommandsPage.h | 48 +- application/pages/global/ExternalToolsPage.cpp | 307 +- application/pages/global/ExternalToolsPage.h | 72 +- application/pages/global/ExternalToolsPage.ui | 4 +- application/pages/global/JavaPage.cpp | 167 +- application/pages/global/JavaPage.h | 58 +- application/pages/global/LanguagePage.cpp | 51 + application/pages/global/LanguagePage.h | 60 + application/pages/global/MinecraftPage.cpp | 45 +- application/pages/global/MinecraftPage.h | 52 +- application/pages/global/MultiMCPage.cpp | 565 +- application/pages/global/MultiMCPage.h | 100 +- application/pages/global/MultiMCPage.ui | 15 +- application/pages/global/PackagesPage.cpp | 288 +- application/pages/global/PackagesPage.h | 40 +- application/pages/global/PasteEEPage.cpp | 63 +- application/pages/global/PasteEEPage.h | 52 +- application/pages/global/ProxyPage.cpp | 97 +- application/pages/global/ProxyPage.h | 52 +- application/pages/instance/GameOptionsPage.cpp | 40 + application/pages/instance/GameOptionsPage.h | 63 + application/pages/instance/GameOptionsPage.ui | 88 + .../pages/instance/InstanceSettingsPage.cpp | 422 +- application/pages/instance/InstanceSettingsPage.h | 66 +- application/pages/instance/InstanceSettingsPage.ui | 15 +- application/pages/instance/LegacyUpgradePage.cpp | 44 +- application/pages/instance/LegacyUpgradePage.h | 50 +- application/pages/instance/LogPage.cpp | 446 +- application/pages/instance/LogPage.h | 82 +- application/pages/instance/ModFolderPage.cpp | 377 +- application/pages/instance/ModFolderPage.h | 129 +- application/pages/instance/ModFolderPage.ui | 275 +- application/pages/instance/NotesPage.cpp | 13 +- application/pages/instance/NotesPage.h | 52 +- application/pages/instance/NotesPage.ui | 40 +- application/pages/instance/OtherLogsPage.cpp | 420 +- application/pages/instance/OtherLogsPage.h | 78 +- application/pages/instance/ResourcePackPage.h | 25 +- application/pages/instance/ScreenshotsPage.cpp | 608 +- application/pages/instance/ScreenshotsPage.h | 91 +- application/pages/instance/ScreenshotsPage.ui | 162 +- application/pages/instance/ServersPage.cpp | 760 ++ application/pages/instance/ServersPage.h | 93 + application/pages/instance/ServersPage.ui | 191 + application/pages/instance/TexturePackPage.h | 23 +- application/pages/instance/VersionPage.cpp | 971 +-- application/pages/instance/VersionPage.h | 108 +- application/pages/instance/VersionPage.ui | 516 +- application/pages/instance/WorldListPage.cpp | 432 +- application/pages/instance/WorldListPage.h | 105 +- application/pages/instance/WorldListPage.ui | 266 +- application/pages/modplatform/FTBPage.cpp | 434 +- application/pages/modplatform/FTBPage.h | 104 +- application/pages/modplatform/FTBPage.ui | 159 +- application/pages/modplatform/FtbListModel.cpp | 275 +- application/pages/modplatform/FtbListModel.h | 68 +- application/pages/modplatform/ImportPage.cpp | 161 +- application/pages/modplatform/ImportPage.h | 56 +- application/pages/modplatform/TechnicPage.cpp | 13 +- application/pages/modplatform/TechnicPage.h | 50 +- application/pages/modplatform/TwitchPage.cpp | 47 +- application/pages/modplatform/TwitchPage.h | 58 +- application/pages/modplatform/TwitchPage.ui | 33 +- application/pages/modplatform/VanillaPage.cpp | 94 +- application/pages/modplatform/VanillaPage.h | 64 +- application/pages/modplatform/VanillaPage.ui | 20 + application/resources/OSX/OSX.qrc | 67 +- application/resources/OSX/scalable/language.svg | 40 + application/resources/assets/assets.qrc | 2 +- application/resources/backgrounds/backgrounds.qrc | 1 + application/resources/backgrounds/catbgrnd2.png | Bin 78285 -> 62973 bytes application/resources/backgrounds/catmas.png | Bin 0 -> 72818 bytes application/resources/documents/documents.qrc | 6 +- application/resources/flat/flat.qrc | 83 +- application/resources/flat/scalable/language.svg | 103 + application/resources/iOS/iOS.qrc | 69 +- application/resources/iOS/scalable/language.svg | 32 + .../resources/multimc/128x128/unknown_server.png | Bin 0 -> 11085 bytes application/resources/multimc/index.theme | 12 +- application/resources/multimc/multimc.qrc | 560 +- .../resources/multimc/scalable/instances/fox.svg | 290 + .../resources/multimc/scalable/language.svg | 109 + application/resources/pe_blue/pe_blue.qrc | 69 +- .../resources/pe_blue/scalable/language.svg | 46 + application/resources/pe_colored/pe_colored.qrc | 69 +- .../resources/pe_colored/scalable/language.svg | 44 + application/resources/pe_dark/pe_dark.qrc | 69 +- .../resources/pe_dark/scalable/language.svg | 45 + application/resources/pe_light/pe_light.qrc | 69 +- .../resources/pe_light/scalable/language.svg | 80 + application/setupwizard/AnalyticsWizardPage.cpp | 70 +- application/setupwizard/AnalyticsWizardPage.h | 16 +- application/setupwizard/BaseWizardPage.h | 42 +- application/setupwizard/JavaWizardPage.cpp | 90 +- application/setupwizard/JavaWizardPage.h | 24 +- application/setupwizard/LanguageWizardPage.cpp | 54 +- application/setupwizard/LanguageWizardPage.h | 23 +- application/setupwizard/SetupWizard.cpp | 90 +- application/setupwizard/SetupWizard.h | 18 +- application/themes/BrightTheme.cpp | 44 +- application/themes/BrightTheme.h | 18 +- application/themes/CustomTheme.cpp | 368 +- application/themes/CustomTheme.h | 38 +- application/themes/DarkTheme.cpp | 44 +- application/themes/DarkTheme.h | 18 +- application/themes/FusionTheme.cpp | 2 +- application/themes/FusionTheme.h | 4 +- application/themes/ITheme.cpp | 68 +- application/themes/ITheme.h | 32 +- application/themes/SystemTheme.cpp | 62 +- application/themes/SystemTheme.h | 28 +- application/widgets/Common.cpp | 42 +- application/widgets/Common.h | 2 +- application/widgets/CustomCommands.cpp | 34 +- application/widgets/CustomCommands.h | 20 +- application/widgets/CustomCommands.ui | 12 + application/widgets/FocusLineEdit.cpp | 22 +- application/widgets/FocusLineEdit.h | 16 +- application/widgets/IconLabel.cpp | 40 +- application/widgets/IconLabel.h | 16 +- application/widgets/JavaSettingsWidget.cpp | 654 +- application/widgets/JavaSettingsWidget.h | 122 +- application/widgets/LabeledToolButton.cpp | 104 +- application/widgets/LabeledToolButton.h | 22 +- application/widgets/LanguageSelectionWidget.cpp | 68 + application/widgets/LanguageSelectionWidget.h | 41 + application/widgets/LineSeparator.cpp | 30 +- application/widgets/LineSeparator.h | 14 +- application/widgets/LogView.cpp | 186 +- application/widgets/LogView.h | 38 +- application/widgets/MCModInfoFrame.cpp | 212 +- application/widgets/MCModInfoFrame.h | 30 +- application/widgets/ModListView.cpp | 76 +- application/widgets/ModListView.h | 10 +- application/widgets/PageContainer.cpp | 310 +- application/widgets/PageContainer.h | 81 +- application/widgets/PageContainer_p.h | 158 +- application/widgets/ProgressWidget.cpp | 80 +- application/widgets/ProgressWidget.h | 22 +- application/widgets/ServerStatus.cpp | 214 +- application/widgets/ServerStatus.h | 34 +- application/widgets/VersionListView.cpp | 185 +- application/widgets/VersionListView.h | 49 +- application/widgets/VersionSelectWidget.cpp | 213 +- application/widgets/VersionSelectWidget.h | 70 +- application/widgets/WideBar.cpp | 116 + application/widgets/WideBar.h | 26 + changelog.md | 254 +- cmake/GetGitRevisionDescription.cmake | 150 +- cmake/GetGitRevisionDescription.cmake.in | 30 +- cmake/GitFunctions.cmake | 38 +- cmake/MacOSXBundleInfo.plist.in | 68 +- cmake/UnitTest.cmake | 81 +- cmake/UnitTest/TestUtil.h | 20 +- cmake/UnitTest/generate_test_data.cmake | 30 +- libraries/LocalPeer/CMakeLists.txt | 14 +- libraries/LocalPeer/include/LocalPeer.h | 58 +- libraries/LocalPeer/src/LocalPeer.cpp | 248 +- libraries/LocalPeer/src/LockedFile.cpp | 152 +- libraries/LocalPeer/src/LockedFile.h | 34 +- libraries/LocalPeer/src/LockedFile_unix.cpp | 96 +- libraries/LocalPeer/src/LockedFile_win.cpp | 272 +- libraries/classparser/CMakeLists.txt | 5 +- libraries/classparser/include/classparser.h | 2 +- libraries/classparser/include/classparser_config.h | 2 +- libraries/classparser/src/annotations.cpp | 132 +- libraries/classparser/src/annotations.h | 384 +- libraries/classparser/src/classfile.h | 272 +- libraries/classparser/src/classparser.cpp | 96 +- libraries/classparser/src/constants.h | 399 +- libraries/classparser/src/javaendian.h | 44 +- libraries/classparser/src/membuffer.h | 94 +- libraries/hoedown/include/hoedown/autolink.h | 8 +- libraries/hoedown/include/hoedown/buffer.h | 32 +- libraries/hoedown/include/hoedown/document.h | 188 +- libraries/hoedown/include/hoedown/html.h | 40 +- libraries/hoedown/include/hoedown/stack.h | 6 +- libraries/hoedown/src/autolink.c | 414 +- libraries/hoedown/src/buffer.c | 310 +- libraries/hoedown/src/document.c | 4030 +++++----- libraries/hoedown/src/escape.c | 218 +- libraries/hoedown/src/html.c | 914 +-- libraries/hoedown/src/html_smartypants.c | 546 +- libraries/hoedown/src/stack.c | 56 +- libraries/hoedown/src/version.c | 6 +- libraries/iconfix/CMakeLists.txt | 8 +- libraries/iconfix/internal/qhexstring_p.h | 60 +- libraries/iconfix/internal/qiconloader.cpp | 934 +-- libraries/iconfix/internal/qiconloader_p.h | 222 +- libraries/iconfix/xdgicon.cpp | 108 +- libraries/iconfix/xdgicon.h | 12 +- libraries/javacheck/CMakeLists.txt | 2 +- libraries/javacheck/JavaCheck.java | 38 +- libraries/launcher/CMakeLists.txt | 18 +- libraries/launcher/net/minecraft/Launcher.java | 270 +- libraries/launcher/org/multimc/EntryPoint.java | 234 +- libraries/launcher/org/multimc/Launcher.java | 4 +- libraries/launcher/org/multimc/LegacyFrame.java | 2 +- .../launcher/org/multimc/NotFoundException.java | 2 +- libraries/launcher/org/multimc/ParamBucket.java | 110 +- libraries/launcher/org/multimc/ParseException.java | 7 +- libraries/launcher/org/multimc/Utils.java | 152 +- .../org/multimc/onesix/OneSixLauncher.java | 410 +- libraries/pack200/CMakeLists.txt | 42 +- libraries/pack200/anti200.cpp | 62 +- libraries/pack200/src/bands.cpp | 642 +- libraries/pack200/src/bands.h | 604 +- libraries/pack200/src/bytes.cpp | 246 +- libraries/pack200/src/bytes.h | 472 +- libraries/pack200/src/coding.cpp | 1762 ++--- libraries/pack200/src/coding.h | 348 +- libraries/pack200/src/constants.h | 712 +- libraries/pack200/src/unpack.cpp | 8363 ++++++++++---------- libraries/pack200/src/unpack.h | 992 +-- libraries/pack200/src/unpack200.cpp | 188 +- libraries/pack200/src/utils.cpp | 28 +- libraries/pack200/src/utils.h | 6 +- libraries/pack200/src/zip.cpp | 838 +- libraries/pack200/src/zip.h | 136 +- libraries/quazip | 2 +- libraries/rainbow/CMakeLists.txt | 8 +- libraries/rainbow/include/rainbow.h | 12 +- libraries/rainbow/include/rainbow_config.h | 14 +- libraries/rainbow/src/rainbow.cpp | 504 +- libraries/systeminfo/CMakeLists.txt | 18 +- libraries/systeminfo/include/distroutils.h | 8 +- libraries/systeminfo/include/sys.h | 50 +- libraries/systeminfo/src/distroutils.cpp | 418 +- libraries/systeminfo/src/sys_apple.cpp | 44 +- libraries/systeminfo/src/sys_test.cpp | 28 +- libraries/systeminfo/src/sys_unix.cpp | 98 +- libraries/systeminfo/src/sys_win32.cpp | 50 +- libraries/xz-embedded/CMakeLists.txt | 16 +- libraries/xz-embedded/include/xz.h | 36 +- libraries/xz-embedded/src/xz_config.h | 24 +- libraries/xz-embedded/src/xz_crc32.c | 38 +- libraries/xz-embedded/src/xz_crc64.c | 38 +- libraries/xz-embedded/src/xz_dec_bcj.c | 902 +-- libraries/xz-embedded/src/xz_dec_lzma2.c | 1810 ++--- libraries/xz-embedded/src/xz_dec_stream.c | 1198 +-- libraries/xz-embedded/src/xz_lzma2.h | 46 +- libraries/xz-embedded/src/xz_private.h | 6 +- libraries/xz-embedded/src/xz_stream.h | 8 +- libraries/xz-embedded/xzminidec.c | 216 +- travis/prepare.sh | 68 +- 666 files changed, 62476 insertions(+), 55585 deletions(-) delete mode 100644 api/logic/BaseInstanceProvider.h create mode 100644 api/logic/ExponentialSeries.h delete mode 100644 api/logic/FolderInstanceProvider.cpp delete mode 100644 api/logic/FolderInstanceProvider.h create mode 100644 api/logic/WatchLock.h create mode 100644 api/logic/icons/IconUtils.cpp create mode 100644 api/logic/icons/IconUtils.h delete mode 100644 api/logic/minecraft/Mod.cpp delete mode 100644 api/logic/minecraft/Mod.h delete mode 100644 api/logic/minecraft/ModList.cpp delete mode 100644 api/logic/minecraft/ModList.h delete mode 100644 api/logic/minecraft/ModList_test.cpp create mode 100644 api/logic/minecraft/gameoptions/GameOptions.cpp create mode 100644 api/logic/minecraft/gameoptions/GameOptions.h create mode 100644 api/logic/minecraft/launch/ReconstructAssets.cpp create mode 100644 api/logic/minecraft/launch/ReconstructAssets.h create mode 100644 api/logic/minecraft/launch/ScanModFolders.cpp create mode 100644 api/logic/minecraft/launch/ScanModFolders.h create mode 100644 api/logic/minecraft/mod/LocalModParseTask.cpp create mode 100644 api/logic/minecraft/mod/LocalModParseTask.h create mode 100644 api/logic/minecraft/mod/Mod.cpp create mode 100644 api/logic/minecraft/mod/Mod.h create mode 100644 api/logic/minecraft/mod/ModDetails.h create mode 100644 api/logic/minecraft/mod/ModFolderLoadTask.cpp create mode 100644 api/logic/minecraft/mod/ModFolderLoadTask.h create mode 100644 api/logic/minecraft/mod/ModFolderModel.cpp create mode 100644 api/logic/minecraft/mod/ModFolderModel.h create mode 100644 api/logic/minecraft/mod/ModFolderModel_test.cpp create mode 100644 api/logic/modplatform/flame/UrlResolvingTask.cpp create mode 100644 api/logic/modplatform/flame/UrlResolvingTask.h create mode 100644 api/logic/modplatform/ftb/FtbPrivatePackManager.cpp create mode 100644 api/logic/modplatform/ftb/FtbPrivatePackManager.h create mode 100644 api/logic/translations/POTranslator.cpp create mode 100644 api/logic/translations/POTranslator.h delete mode 100644 application/SettingsUI.h delete mode 100644 application/dialogs/ModEditDialogCommon.cpp delete mode 100644 application/dialogs/ModEditDialogCommon.h create mode 100644 application/groupview/AccessibleGroupView.cpp create mode 100644 application/groupview/AccessibleGroupView.h create mode 100644 application/groupview/AccessibleGroupView_p.h create mode 100644 application/package/ubuntu/multimc/DEBIAN/control create mode 100755 application/package/ubuntu/multimc/DEBIAN/postrm create mode 100644 application/package/ubuntu/multimc/opt/multimc/icon.svg create mode 100755 application/package/ubuntu/multimc/opt/multimc/run.sh create mode 100755 application/package/ubuntu/multimc/usr/share/applications/multimc.desktop create mode 100644 application/package/ubuntu/readme.md create mode 100644 application/pages/global/LanguagePage.cpp create mode 100644 application/pages/global/LanguagePage.h create mode 100644 application/pages/instance/GameOptionsPage.cpp create mode 100644 application/pages/instance/GameOptionsPage.h create mode 100644 application/pages/instance/GameOptionsPage.ui create mode 100644 application/pages/instance/ServersPage.cpp create mode 100644 application/pages/instance/ServersPage.h create mode 100644 application/pages/instance/ServersPage.ui create mode 100644 application/resources/OSX/scalable/language.svg create mode 100644 application/resources/backgrounds/catmas.png create mode 100644 application/resources/flat/scalable/language.svg create mode 100644 application/resources/iOS/scalable/language.svg create mode 100644 application/resources/multimc/128x128/unknown_server.png create mode 100644 application/resources/multimc/scalable/instances/fox.svg create mode 100644 application/resources/multimc/scalable/language.svg create mode 100644 application/resources/pe_blue/scalable/language.svg create mode 100644 application/resources/pe_colored/scalable/language.svg create mode 100644 application/resources/pe_dark/scalable/language.svg create mode 100644 application/resources/pe_light/scalable/language.svg create mode 100644 application/widgets/LanguageSelectionWidget.cpp create mode 100644 application/widgets/LanguageSelectionWidget.h create mode 100644 application/widgets/WideBar.cpp create mode 100644 application/widgets/WideBar.h diff --git a/.arcconfig b/.arcconfig index 0e628a13..ab5cbe89 100644 --- a/.arcconfig +++ b/.arcconfig @@ -1,5 +1,5 @@ { - "project_id": "MultiMC5", - "conduit_uri": "http://ph.multimc.org" + "project_id": "MultiMC5", + "conduit_uri": "http://ph.multimc.org" } diff --git a/.clang-format b/.clang-format index 8e33c4d1..f4732803 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,4 @@ -UseTab: true +UseTab: false IndentWidth: 4 TabWidth: 4 ConstructorInitializerIndentWidth: 4 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index d95423cf..58596fa0 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,16 +2,16 @@ 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. + - 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. + - 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. + - We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored. 4. Given the issue a descriptive title. - - A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!". - Use of UPPERCASE is discouraged, as it reads like someone is screaming. + - A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!". + Use of UPPERCASE is discouraged, as it reads like someone is screaming. 5. Place all information below the ---- of lines. - - It makes the issue look pretty + - It makes the issue look pretty If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug --> diff --git a/.gitignore b/.gitignore index d49ca85b..a2c7ec41 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ CMakeLists.txt.user CMakeLists.txt.user.* /.project /.settings +/.idea +cmake-build-*/ # Build dirs build diff --git a/.gitmodules b/.gitmodules index 5f2a1b8f..bd51ef80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "depends/libnbtplusplus"] - path = libraries/libnbtplusplus - url = https://github.com/MultiMC/libnbtplusplus.git + path = libraries/libnbtplusplus + url = https://github.com/MultiMC/libnbtplusplus.git [submodule "libraries/quazip"] - path = libraries/quazip - url = https://github.com/MultiMC/quazip.git + path = libraries/quazip + url = https://github.com/MultiMC/quazip.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 85bdf447..005487a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,12 +2,12 @@ cmake_minimum_required(VERSION 3.1) string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD) if(IS_IN_SOURCE_BUILD) - message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!") + message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!") endif() if(WIN32) - # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows - cmake_policy(SET CMP0020 OLD) + # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows + cmake_policy(SET CMP0020 OLD) endif() project(MultiMC) @@ -22,7 +22,7 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") # Output all executables and shared libs in the main build folder, not in subfolders. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) if(UNIX) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) endif() set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars) @@ -32,21 +32,21 @@ set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD 11) set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) -set(CMAKE_CXX_FLAGS " -Wall -D_GLIBCXX_USE_CXX11_ABI=0 ${CMAKE_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") if(UNIX AND APPLE) - set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}") endif() set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type") ##################################### Set Application options ##################################### ######## Set URLs ######## -set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.") +set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.") ######## Set version numbers ######## -set(MultiMC_VERSION_MAJOR 0) -set(MultiMC_VERSION_MINOR 6) -set(MultiMC_VERSION_HOTFIX 2) +set(MultiMC_VERSION_MAJOR 0) +set(MultiMC_VERSION_MINOR 6) +set(MultiMC_VERSION_HOTFIX 7) # Build number set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") @@ -106,124 +106,124 @@ set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (aut set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle) if(MultiMC_LAYOUT STREQUAL "auto") - if(UNIX AND APPLE) - set(MultiMC_LAYOUT_REAL "mac-bundle") - elseif(UNIX) - set(MultiMC_LAYOUT_REAL "lin-nodeps") - elseif(WIN32) - set(MultiMC_LAYOUT_REAL "win-bundle") - else() - message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.") - endif() + if(UNIX AND APPLE) + set(MultiMC_LAYOUT_REAL "mac-bundle") + elseif(UNIX) + set(MultiMC_LAYOUT_REAL "lin-nodeps") + elseif(WIN32) + set(MultiMC_LAYOUT_REAL "win-bundle") + else() + message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.") + endif() else() - set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT}) + set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT}) endif() if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle") - set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS") - set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS") - set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS") - set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources") - set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars") + set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS") + set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS") + set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS") + set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources") + set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars") - set(BUNDLE_DEST_DIR ".") + set(BUNDLE_DEST_DIR ".") - # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app") + # Apps to bundle + set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app") - # Mac bundle settings - set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC") - set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.") - set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5") - set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}") - set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}") - set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}") - set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns) - set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2018 MultiMC Contributors") + # Mac bundle settings + set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC") + set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.") + set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5") + set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}") + set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}") + set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}") + set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns) + set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2019 MultiMC Contributors") - # directories to look for dependencies - set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + # directories to look for dependencies + set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - # install as bundle - set(INSTALL_BUNDLE "full") + # install as bundle + set(INSTALL_BUNDLE "full") - # Add the icon - install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR}) + # Add the icon + install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR}) elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle") - set(BINARY_DEST_DIR "bin") - set(LIBRARY_DEST_DIR "bin") - set(PLUGIN_DEST_DIR "plugins") - set(BUNDLE_DEST_DIR ".") - set(RESOURCES_DEST_DIR ".") - set(JARS_DEST_DIR "bin/jars") + set(BINARY_DEST_DIR "bin") + set(LIBRARY_DEST_DIR "bin") + set(PLUGIN_DEST_DIR "plugins") + set(BUNDLE_DEST_DIR ".") + set(RESOURCES_DEST_DIR ".") + set(JARS_DEST_DIR "bin/jars") - # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC") + # Apps to bundle + set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC") - # directories to look for dependencies - set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + # directories to look for dependencies + set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - # install as bundle - set(INSTALL_BUNDLE "full") + # install as bundle + set(INSTALL_BUNDLE "full") - # Set RPATH - SET(MultiMC_BINARY_RPATH "$ORIGIN/") + # Set RPATH + SET(MultiMC_BINARY_RPATH "$ORIGIN/") - # Install basic runner script - install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR}) + # Install basic runner script + install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR}) elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps") - set(BINARY_DEST_DIR "bin") - set(LIBRARY_DEST_DIR "bin") - set(PLUGIN_DEST_DIR "plugins") - set(BUNDLE_DEST_DIR ".") - set(RESOURCES_DEST_DIR ".") - set(JARS_DEST_DIR "bin/jars") + set(BINARY_DEST_DIR "bin") + set(LIBRARY_DEST_DIR "bin") + set(PLUGIN_DEST_DIR "plugins") + set(BUNDLE_DEST_DIR ".") + set(RESOURCES_DEST_DIR ".") + set(JARS_DEST_DIR "bin/jars") - # install as bundle with no dependencies included - set(INSTALL_BUNDLE "nodeps") + # install as bundle with no dependencies included + set(INSTALL_BUNDLE "nodeps") - # Set RPATH - SET(MultiMC_BINARY_RPATH "$ORIGIN/") + # Set RPATH + SET(MultiMC_BINARY_RPATH "$ORIGIN/") - # Install basic runner script - install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR}) + # Install basic runner script + install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR}) elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system") - set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary") - set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory") - set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory") - set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory") - set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars") + set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary") + set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory") + set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory") + set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory") + set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars") - set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR}) - set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR}) + set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR}) + set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR}) - MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR") - SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR") + MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR") + SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR") - # install as bundle with no dependencies included - set(INSTALL_BUNDLE "nodeps") + # install as bundle with no dependencies included + set(INSTALL_BUNDLE "nodeps") elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle") - set(BINARY_DEST_DIR ".") - set(LIBRARY_DEST_DIR ".") - set(PLUGIN_DEST_DIR ".") - set(BUNDLE_DEST_DIR ".") - set(RESOURCES_DEST_DIR ".") - set(JARS_DEST_DIR "jars") + set(BINARY_DEST_DIR ".") + set(LIBRARY_DEST_DIR ".") + set(PLUGIN_DEST_DIR ".") + set(BUNDLE_DEST_DIR ".") + set(RESOURCES_DEST_DIR ".") + set(JARS_DEST_DIR "jars") - # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe") + # Apps to bundle + set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe") - # directories to look for dependencies - set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + # directories to look for dependencies + set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - # install as bundle - set(INSTALL_BUNDLE "full") + # install as bundle + set(INSTALL_BUNDLE "full") else() - message(FATAL_ERROR "No sensible install layout set.") + message(FATAL_ERROR "No sensible install layout set.") endif() ################################ Included Libs ################################ diff --git a/COPYING.md b/COPYING.md index ca367df3..8aa7e75e 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,254 +1,254 @@ # MultiMC - Copyright 2012-2018 MultiMC Contributors - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + Copyright 2012-2019 MultiMC Contributors + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - 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. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. # MinGW runtime (Windows) - Copyright (c) 2012 MinGW.org project + Copyright (c) 2012 MinGW.org project - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice, this permission notice and the below disclaimer - shall be included in all copies or substantial portions of the Software. + The above copyright notice, this permission notice and the below disclaimer + shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. # Qt 5 - Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). - Contact: http://www.qt-project.org/legal + Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). + Contact: http://www.qt-project.org/legal - Licensed under LGPL v2.1 + Licensed under LGPL v2.1 # libnbt++ - libnbt++ - A library for the Minecraft Named Binary Tag format. - Copyright (C) 2013, 2015 ljfa-ag + libnbt++ - A library for the Minecraft Named Binary Tag format. + Copyright (C) 2013, 2015 ljfa-ag - libnbt++ is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + libnbt++ is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - libnbt++ is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. + libnbt++ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public License - along with libnbt++. If not, see . + You should have received a copy of the GNU Lesser General Public License + along with libnbt++. If not, see . # rainbow (KGuiAddons) - Copyright (C) 2007 Matthew Woehlke - Copyright (C) 2007 Olaf Schmidt - Copyright (C) 2007 Thomas Zander - Copyright (C) 2007 Zack Rusin - Copyright (C) 2015 Petr Mrazek + Copyright (C) 2007 Matthew Woehlke + Copyright (C) 2007 Olaf Schmidt + Copyright (C) 2007 Thomas Zander + Copyright (C) 2007 Zack Rusin + Copyright (C) 2015 Petr Mrazek - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. # Hoedown - Copyright (c) 2008, Natacha Porté - Copyright (c) 2011, Vicent Martí - Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors + Copyright (c) 2008, Natacha Porté + Copyright (c) 2011, Vicent Martí + Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors - Permission to use, copy, modify, and distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # Batch icon set - You are free to use Batch (the "icon set") or any part thereof (the "icons") - in any personal, open-source or commercial work without obligation of payment - (monetary or otherwise) or attribution. Do not sell the icon set, host - the icon set or rent the icon set (either in existing or modified form). + You are free to use Batch (the "icon set") or any part thereof (the "icons") + in any personal, open-source or commercial work without obligation of payment + (monetary or otherwise) or attribution. Do not sell the icon set, host + the icon set or rent the icon set (either in existing or modified form). - While attribution is optional, it is always appreciated. + While attribution is optional, it is always appreciated. - Intellectual property rights are not transferred with the download of the icons. + Intellectual property rights are not transferred with the download of the icons. - EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL ADAM WHITCROFT - BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, - PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS, - EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL ADAM WHITCROFT + BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, + PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS, + EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. # Material Design Icons - Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/), - with Reserved Font Name Material Design Icons. - Copyright (c) 2014, Google (http://www.google.com/design/) - uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE + Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/), + with Reserved Font Name Material Design Icons. + Copyright (c) 2014, Google (http://www.google.com/design/) + uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE - This Font Software is licensed under the SIL Open Font License, Version 1.1. - This license is copied below, and is also available with a FAQ at: - http://scripts.sil.org/OFL + This Font Software is licensed under the SIL Open Font License, Version 1.1. + This license is copied below, and is also available with a FAQ at: + http://scripts.sil.org/OFL # Pack200 - The GNU General Public License (GPL) + The GNU General Public License (GPL) - Version 2, June 1991 + Version 2, June 1991 - + "CLASSPATH" EXCEPTION TO THE GPL + + "CLASSPATH" EXCEPTION TO THE GPL - Certain source files distributed by Oracle America and/or its affiliates are - subject to the following clarification and special exception to the GPL, but - only where Oracle has expressly included in the particular source file's header - the words "Oracle designates this particular file as subject to the "Classpath" - exception as provided by Oracle in the LICENSE file that accompanied this code." + Certain source files distributed by Oracle America and/or its affiliates are + subject to the following clarification and special exception to the GPL, but + only where Oracle has expressly included in the particular source file's header + the words "Oracle designates this particular file as subject to the "Classpath" + exception as provided by Oracle in the LICENSE file that accompanied this code." - Linking this library statically or dynamically with other modules is making - a combined work based on this library. Thus, the terms and conditions of - the GNU General Public License cover the whole combination. + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. - As a special exception, the copyright holders of this library give you - permission to link this library with independent modules to produce an - executable, regardless of the license terms of these independent modules, - and to copy and distribute the resulting executable under terms of your - choice, provided that you also meet, for each linked independent module, - the terms and conditions of the license of that module. An independent - module is a module which is not derived from or based on this library. If - you modify this library, you may extend this exception to your version of - the library, but you are not obligated to do so. If you do not wish to do - so, delete this exception statement from your version. + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. # Quazip - Copyright (C) 2005-2011 Sergey A. Tachenov + Copyright (C) 2005-2011 Sergey A. Tachenov - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2 of the License, or (at - your option) any later version. + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at + your option) any later version. - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser - General Public License for more details. + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + General Public License for more details. - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - See COPYING file for the full LGPL text. + See COPYING file for the full LGPL text. - Original ZIP package is copyrighted by Gilles Vollant, see - quazip/(un)zip.h files for details, basically it's zlib license. + Original ZIP package is copyrighted by Gilles Vollant, see + quazip/(un)zip.h files for details, basically it's zlib license. # xz-minidec - XZ decompressor + XZ decompressor - Authors: Lasse Collin - Igor Pavlov + Authors: Lasse Collin + Igor Pavlov - This file has been put into the public domain. - You can do whatever you want with this file. + This file has been put into the public domain. + You can do whatever you want with this file. # ColumnResizer - Copyright (c) 2011-2016 Aurélien Gâteau and contributors. + Copyright (c) 2011-2016 Aurélien Gâteau and contributors. - All rights reserved. + All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted (subject to the limitations in the - disclaimer below) provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without + modification, are permitted (subject to the limitations in the + disclaimer below) provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the - distribution. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. - * The name of the contributors may not be used to endorse or - promote products derived from this software without specific prior - written permission. + * The name of the contributors may not be used to endorse or + promote products derived from this software without specific prior + written permission. - NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE - GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT - HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN - IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT + HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # lionshead - Code has been taken from https://github.com/natefoo/lionshead and loosely - translated to C++ laced with Qt. + Code has been taken from https://github.com/natefoo/lionshead and loosely + translated to C++ laced with Qt. - MIT License + MIT License - Copyright (c) 2017 Nate Coraor + Copyright (c) 2017 Nate Coraor - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/README.md b/README.md index 00f40a4c..8f017f06 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,7 @@ We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the ## Translations -Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5) or using our [translation server](http://translate.multimc.org). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC). - -Currently, MultiMC is [![Translation Status](http://translate.multimc.org/widgets/multimc/-/shields-badge.svg)](http://translate.multimc.org/engage/multimc/?utm_source=widget) +Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5-translate). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC). ## Forking/Redistributing We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license. @@ -40,7 +38,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i ## License -Copyright © 2013-2018 MultiMC Contributors +Copyright © 2013-2019 MultiMC Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/api/gui/CMakeLists.txt b/api/gui/CMakeLists.txt index deae735f..ad116a43 100644 --- a/api/gui/CMakeLists.txt +++ b/api/gui/CMakeLists.txt @@ -1,17 +1,17 @@ project(MultiMC_gui LANGUAGES CXX) set(GUI_SOURCES - DesktopServices.h - DesktopServices.cpp + DesktopServices.h + DesktopServices.cpp - # Icons - icons/MMCIcon.h - icons/MMCIcon.cpp - icons/IconList.h - icons/IconList.cpp + # Icons + icons/MMCIcon.h + icons/MMCIcon.cpp + icons/IconList.h + icons/IconList.cpp - SkinUtils.cpp - SkinUtils.h + SkinUtils.cpp + SkinUtils.h ) ################################ COMPILE ################################ @@ -21,15 +21,14 @@ set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBI generate_export_header(MultiMC_gui) # Link -target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic) -qt5_use_modules(MultiMC_gui Gui) +target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui) # Mark and export headers target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") # Install it install( - TARGETS MultiMC_gui - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} + TARGETS MultiMC_gui + RUNTIME DESTINATION ${LIBRARY_DEST_DIR} + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} ) \ No newline at end of file diff --git a/api/gui/DesktopServices.cpp b/api/gui/DesktopServices.cpp index 3154ea01..5368ddc8 100644 --- a/api/gui/DesktopServices.cpp +++ b/api/gui/DesktopServices.cpp @@ -17,132 +17,132 @@ template bool IndirectOpen(T callable, qint64 *pid_forked = nullptr) { - auto pid = fork(); - if(pid_forked) - { - if(pid > 0) - *pid_forked = pid; - else - *pid_forked = 0; - } - if(pid == -1) - { - qWarning() << "IndirectOpen failed to fork: " << errno; - return false; - } - // child - do the stuff - if(pid == 0) - { - // unset all this garbage so it doesn't get passed to the child process - qunsetenv("LD_PRELOAD"); - qunsetenv("LD_LIBRARY_PATH"); - qunsetenv("LD_DEBUG"); - qunsetenv("QT_PLUGIN_PATH"); - qunsetenv("QT_FONTPATH"); + auto pid = fork(); + if(pid_forked) + { + if(pid > 0) + *pid_forked = pid; + else + *pid_forked = 0; + } + if(pid == -1) + { + qWarning() << "IndirectOpen failed to fork: " << errno; + return false; + } + // child - do the stuff + if(pid == 0) + { + // unset all this garbage so it doesn't get passed to the child process + qunsetenv("LD_PRELOAD"); + qunsetenv("LD_LIBRARY_PATH"); + qunsetenv("LD_DEBUG"); + qunsetenv("QT_PLUGIN_PATH"); + qunsetenv("QT_FONTPATH"); - // open the URL - auto status = callable(); + // open the URL + auto status = callable(); - // detach from the parent process group. - setsid(); + // detach from the parent process group. + setsid(); - // die. now. do not clean up anything, it would just hang forever. - _exit(status ? 0 : 1); - } - else - { - //parent - assume it worked. - int status; - while (waitpid(pid, &status, 0)) - { - if(WIFEXITED(status)) - { - return WEXITSTATUS(status) == 0; - } - if(WIFSIGNALED(status)) - { - return false; - } - } - return true; - } + // die. now. do not clean up anything, it would just hang forever. + _exit(status ? 0 : 1); + } + else + { + //parent - assume it worked. + int status; + while (waitpid(pid, &status, 0)) + { + if(WIFEXITED(status)) + { + return WEXITSTATUS(status) == 0; + } + if(WIFSIGNALED(status)) + { + return false; + } + } + return true; + } } #endif namespace DesktopServices { bool openDirectory(const QString &path, bool ensureExists) { - qDebug() << "Opening directory" << path; - QDir parentPath; - QDir dir(path); - if (!dir.exists()) - { - parentPath.mkpath(dir.absolutePath()); - } - auto f = [&]() - { - return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); - }; + qDebug() << "Opening directory" << path; + QDir parentPath; + QDir dir(path); + if (!dir.exists()) + { + parentPath.mkpath(dir.absolutePath()); + } + auto f = [&]() + { + return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); + }; #if defined(Q_OS_LINUX) - return IndirectOpen(f); + return IndirectOpen(f); #else - return f(); + return f(); #endif } bool openFile(const QString &path) { - qDebug() << "Opening file" << path; - auto f = [&]() - { - return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); - }; + qDebug() << "Opening file" << path; + auto f = [&]() + { + return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + }; #if defined(Q_OS_LINUX) - return IndirectOpen(f); + return IndirectOpen(f); #else - return f(); + return f(); #endif } bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid) { - qDebug() << "Opening file" << path << "using" << application; + qDebug() << "Opening file" << path << "using" << application; #if defined(Q_OS_LINUX) - // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - return IndirectOpen([&]() - { - return QProcess::startDetached(application, QStringList() << path, workingDirectory); - }, pid); + // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave + return IndirectOpen([&]() + { + return QProcess::startDetached(application, QStringList() << path, workingDirectory); + }, pid); #else - return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid); + return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid); #endif } bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid) { - qDebug() << "Running" << application << "with args" << args.join(' '); + qDebug() << "Running" << application << "with args" << args.join(' '); #if defined(Q_OS_LINUX) - // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - return IndirectOpen([&]() - { - return QProcess::startDetached(application, args, workingDirectory); - }, pid); + // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave + return IndirectOpen([&]() + { + return QProcess::startDetached(application, args, workingDirectory); + }, pid); #else - return QProcess::startDetached(application, args, workingDirectory, pid); + return QProcess::startDetached(application, args, workingDirectory, pid); #endif } bool openUrl(const QUrl &url) { - qDebug() << "Opening URL" << url.toString(); - auto f = [&]() - { - return QDesktopServices::openUrl(url); - }; + qDebug() << "Opening URL" << url.toString(); + auto f = [&]() + { + return QDesktopServices::openUrl(url); + }; #if defined(Q_OS_LINUX) - return IndirectOpen(f); + return IndirectOpen(f); #else - return f(); + return f(); #endif } diff --git a/api/gui/DesktopServices.h b/api/gui/DesktopServices.h index 9daf192a..606fa52c 100644 --- a/api/gui/DesktopServices.h +++ b/api/gui/DesktopServices.h @@ -10,28 +10,28 @@ */ namespace DesktopServices { - /** - * Open a file in whatever application is applicable - */ - MULTIMC_GUI_EXPORT bool openFile(const QString &path); + /** + * 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); + /** + * 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); + /** + * 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 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); -}; + /** + * Open the URL, most likely in a browser. Maybe. + */ + MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url); +} diff --git a/api/gui/SkinUtils.cpp b/api/gui/SkinUtils.cpp index 3950cbc0..9b6f63a4 100644 --- a/api/gui/SkinUtils.cpp +++ b/api/gui/SkinUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,19 +29,19 @@ namespace SkinUtils */ QPixmap getFaceFromCache(QString username, int height, int width) { - QFile fskin(ENV.metacache() - ->resolveEntry("skins", username + ".png") - ->getFullPath()); + QFile fskin(ENV.metacache() + ->resolveEntry("skins", username + ".png") + ->getFullPath()); - if (fskin.exists()) - { - QPixmap skin(fskin.fileName()); - if(!skin.isNull()) - { - return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio); - } - } + if (fskin.exists()) + { + QPixmap skin(fskin.fileName()); + if(!skin.isNull()) + { + return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio); + } + } - return QPixmap(); + return QPixmap(); } } diff --git a/api/gui/SkinUtils.h b/api/gui/SkinUtils.h index f042b908..a7f680e6 100644 --- a/api/gui/SkinUtils.h +++ b/api/gui/SkinUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/gui/icons/IconList.cpp b/api/gui/icons/IconList.cpp index 997a03db..72edb46f 100644 --- a/api/gui/icons/IconList.cpp +++ b/api/gui/icons/IconList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,394 +27,393 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent) { - QSet builtinNames; - - // add builtin icons - for(auto & builtinPath: builtinPaths) - { - 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()); - is_watching = false; - connect(m_watcher.get(), SIGNAL(directoryChanged(QString)), - SLOT(directoryChanged(QString))); - connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); - - directoryChanged(path); + QSet builtinNames; + + // add builtin icons + for(auto & builtinPath: builtinPaths) + { + 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()); + is_watching = false; + connect(m_watcher.get(), SIGNAL(directoryChanged(QString)), + SLOT(directoryChanged(QString))); + connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); + + directoryChanged(path); } void IconList::directoryChanged(const QString &path) { - QDir new_dir (path); - if(m_dir.absolutePath() != new_dir.absolutePath()) - { - m_dir.setPath(path); - m_dir.refresh(); - if(is_watching) - stopWatching(); - startWatching(); - } - if(!m_dir.exists()) - if(!FS::ensureFolderPathExists(m_dir.absolutePath())) - return; - m_dir.refresh(); - auto new_list = m_dir.entryList(QDir::Files, QDir::Name); - for (auto it = new_list.begin(); it != new_list.end(); it++) - { - QString &foo = (*it); - foo = m_dir.filePath(foo); - } - auto new_set = new_list.toSet(); - QList current_list; - for (auto &it : icons) - { - if (!it.has(IconType::FileBased)) - continue; - current_list.push_back(it.m_images[IconType::FileBased].filename); - } - QSet current_set = current_list.toSet(); - - QSet to_remove = current_set; - to_remove -= new_set; - - QSet to_add = new_set; - to_add -= current_set; - - for (auto remove : to_remove) - { - qDebug() << "Removing " << remove; - QFileInfo rmfile(remove); - QString key = rmfile.baseName(); - int idx = getIconIndex(key); - if (idx == -1) - continue; - icons[idx].remove(IconType::FileBased); - if (icons[idx].type() == IconType::ToBeDeleted) - { - beginRemoveRows(QModelIndex(), idx, idx); - icons.remove(idx); - reindex(); - endRemoveRows(); - } - else - { - dataChanged(index(idx), index(idx)); - } - m_watcher->removePath(remove); - emit iconUpdated(key); - } - - for (auto add : to_add) - { - qDebug() << "Adding " << add; - QFileInfo addfile(add); - QString key = addfile.baseName(); - if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) - { - m_watcher->addPath(add); - emit iconUpdated(key); - } - } + QDir new_dir (path); + if(m_dir.absolutePath() != new_dir.absolutePath()) + { + m_dir.setPath(path); + m_dir.refresh(); + if(is_watching) + stopWatching(); + startWatching(); + } + if(!m_dir.exists()) + if(!FS::ensureFolderPathExists(m_dir.absolutePath())) + return; + m_dir.refresh(); + auto new_list = m_dir.entryList(QDir::Files, QDir::Name); + for (auto it = new_list.begin(); it != new_list.end(); it++) + { + QString &foo = (*it); + foo = m_dir.filePath(foo); + } + auto new_set = new_list.toSet(); + QList current_list; + for (auto &it : icons) + { + if (!it.has(IconType::FileBased)) + continue; + current_list.push_back(it.m_images[IconType::FileBased].filename); + } + QSet current_set = current_list.toSet(); + + QSet to_remove = current_set; + to_remove -= new_set; + + QSet to_add = new_set; + to_add -= current_set; + + for (auto remove : to_remove) + { + qDebug() << "Removing " << remove; + QFileInfo rmfile(remove); + QString key = rmfile.baseName(); + int idx = getIconIndex(key); + if (idx == -1) + continue; + icons[idx].remove(IconType::FileBased); + if (icons[idx].type() == IconType::ToBeDeleted) + { + beginRemoveRows(QModelIndex(), idx, idx); + icons.remove(idx); + reindex(); + endRemoveRows(); + } + else + { + dataChanged(index(idx), index(idx)); + } + m_watcher->removePath(remove); + emit iconUpdated(key); + } + + for (auto add : to_add) + { + qDebug() << "Adding " << add; + QFileInfo addfile(add); + QString key = addfile.baseName(); + if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) + { + m_watcher->addPath(add); + emit iconUpdated(key); + } + } } void IconList::fileChanged(const QString &path) { - qDebug() << "Checking " << path; - QFileInfo checkfile(path); - if (!checkfile.exists()) - return; - QString key = checkfile.baseName(); - int idx = getIconIndex(key); - if (idx == -1) - return; - QIcon icon(path); - if (!icon.availableSizes().size()) - return; - - icons[idx].m_images[IconType::FileBased].icon = icon; - dataChanged(index(idx), index(idx)); - emit iconUpdated(key); + qDebug() << "Checking " << path; + QFileInfo checkfile(path); + if (!checkfile.exists()) + return; + QString key = checkfile.baseName(); + int idx = getIconIndex(key); + if (idx == -1) + return; + QIcon icon(path); + if (!icon.availableSizes().size()) + return; + + icons[idx].m_images[IconType::FileBased].icon = icon; + dataChanged(index(idx), index(idx)); + emit iconUpdated(key); } void IconList::SettingChanged(const Setting &setting, QVariant value) { - if(setting.id() != "IconsDir") - return; + if(setting.id() != "IconsDir") + return; - directoryChanged(value.toString()); + directoryChanged(value.toString()); } void IconList::startWatching() { - auto abs_path = m_dir.absolutePath(); - FS::ensureFolderPathExists(abs_path); - is_watching = m_watcher->addPath(abs_path); - if (is_watching) - { - qDebug() << "Started watching " << abs_path; - } - else - { - qDebug() << "Failed to start watching " << abs_path; - } + auto abs_path = m_dir.absolutePath(); + FS::ensureFolderPathExists(abs_path); + is_watching = m_watcher->addPath(abs_path); + if (is_watching) + { + qDebug() << "Started watching " << abs_path; + } + else + { + qDebug() << "Failed to start watching " << abs_path; + } } void IconList::stopWatching() { - m_watcher->removePaths(m_watcher->files()); - m_watcher->removePaths(m_watcher->directories()); - is_watching = false; + m_watcher->removePaths(m_watcher->files()); + m_watcher->removePaths(m_watcher->directories()); + is_watching = false; } QStringList IconList::mimeTypes() const { - QStringList types; - types << "text/uri-list"; - return types; + QStringList types; + types << "text/uri-list"; + return types; } Qt::DropActions IconList::supportedDropActions() const { - return Qt::CopyAction; + return Qt::CopyAction; } -bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent) +bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { - if (action == Qt::IgnoreAction) - return true; - // check if the action is supported - if (!data || !(action & supportedDropActions())) - return false; - - // files dropped from outside? - if (data->hasUrls()) - { - auto urls = data->urls(); - QStringList iconFiles; - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - continue; - iconFiles += url.toLocalFile(); - } - installIcons(iconFiles); - return true; - } - return false; + if (action == Qt::IgnoreAction) + return true; + // check if the action is supported + if (!data || !(action & supportedDropActions())) + return false; + + // files dropped from outside? + if (data->hasUrls()) + { + auto urls = data->urls(); + QStringList iconFiles; + for (auto url : urls) + { + // only local files may be dropped... + if (!url.isLocalFile()) + continue; + iconFiles += url.toLocalFile(); + } + installIcons(iconFiles); + return true; + } + return false; } Qt::ItemFlags IconList::flags(const QModelIndex &index) const { - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsDropEnabled | defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + if (index.isValid()) + return Qt::ItemIsDropEnabled | defaultFlags; + else + return Qt::ItemIsDropEnabled | defaultFlags; } QVariant IconList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - - if (row < 0 || row >= icons.size()) - return QVariant(); - - switch (role) - { - case Qt::DecorationRole: - return icons[row].icon(); - case Qt::DisplayRole: - return icons[row].name(); - case Qt::UserRole: - return icons[row].m_key; - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + + if (row < 0 || row >= icons.size()) + return QVariant(); + + switch (role) + { + case Qt::DecorationRole: + return icons[row].icon(); + case Qt::DisplayRole: + return icons[row].name(); + case Qt::UserRole: + return icons[row].m_key; + default: + return QVariant(); + } } int IconList::rowCount(const QModelIndex &parent) const { - return icons.size(); + return icons.size(); } void IconList::installIcons(const QStringList &iconFiles) { - for (QString file : iconFiles) - { - QFileInfo fileinfo(file); - if (!fileinfo.isReadable() || !fileinfo.isFile()) - continue; - QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); - - QString suffix = fileinfo.suffix(); - if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg") - continue; - - if (!QFile::copy(file, target)) - continue; - } + for (QString file : iconFiles) + { + QFileInfo fileinfo(file); + if (!fileinfo.isReadable() || !fileinfo.isFile()) + continue; + QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); + + QString suffix = fileinfo.suffix(); + if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") + continue; + + if (!QFile::copy(file, target)) + continue; + } } void IconList::installIcon(const QString &file, const QString &name) { - QFileInfo fileinfo(file); - if(!fileinfo.isReadable() || !fileinfo.isFile()) - return; + QFileInfo fileinfo(file); + if(!fileinfo.isReadable() || !fileinfo.isFile()) + return; - QString target = FS::PathCombine(m_dir.dirName(), name); + QString target = FS::PathCombine(m_dir.dirName(), name); - QFile::copy(file, target); + QFile::copy(file, target); } bool IconList::iconFileExists(const QString &key) const { - auto iconEntry = icon(key); - if(!iconEntry) - { - return false; - } - return iconEntry->has(IconType::FileBased); + auto iconEntry = icon(key); + if(!iconEntry) + { + return false; + } + return iconEntry->has(IconType::FileBased); } const MMCIcon *IconList::icon(const QString &key) const { - int iconIdx = getIconIndex(key); - if (iconIdx == -1) - return nullptr; - return &icons[iconIdx]; + int iconIdx = getIconIndex(key); + if (iconIdx == -1) + return nullptr; + return &icons[iconIdx]; } bool IconList::deleteIcon(const QString &key) { - int iconIdx = getIconIndex(key); - if (iconIdx == -1) - return false; - auto &iconEntry = icons[iconIdx]; - if (iconEntry.has(IconType::FileBased)) - { - return QFile::remove(iconEntry.m_images[IconType::FileBased].filename); - } - return false; + int iconIdx = getIconIndex(key); + if (iconIdx == -1) + return false; + auto &iconEntry = icons[iconIdx]; + if (iconEntry.has(IconType::FileBased)) + { + return QFile::remove(iconEntry.m_images[IconType::FileBased].filename); + } + return false; } 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; - } + 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); - if (icon.isNull()) - return false; - auto iter = name_index.find(key); - if (iter != name_index.end()) - { - auto &oldOne = icons[*iter]; - oldOne.replace(type, icon, path); - 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 = name; - mmc_icon.m_key = key; - mmc_icon.replace(type, icon, path); - icons.push_back(mmc_icon); - name_index[key] = icons.size() - 1; - } - endInsertRows(); - return true; - } + // replace the icon even? is the input valid? + QIcon icon(path); + if (icon.isNull()) + return false; + auto iter = name_index.find(key); + if (iter != name_index.end()) + { + auto &oldOne = icons[*iter]; + oldOne.replace(type, icon, path); + 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 = name; + mmc_icon.m_key = key; + mmc_icon.replace(type, icon, path); + icons.push_back(mmc_icon); + name_index[key] = icons.size() - 1; + } + endInsertRows(); + return true; + } } 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); + auto icon = getIcon(key); + auto pixmap = icon.pixmap(128, 128); + pixmap.save(path, format); } void IconList::reindex() { - name_index.clear(); - int i = 0; - for (auto &iter : icons) - { - name_index[iter.m_key] = i; - i++; - } + name_index.clear(); + int i = 0; + for (auto &iter : icons) + { + name_index[iter.m_key] = i; + i++; + } } QIcon IconList::getIcon(const QString &key) const { - int icon_index = getIconIndex(key); + int icon_index = getIconIndex(key); - if (icon_index != -1) - return icons[icon_index].icon(); + if (icon_index != -1) + return icons[icon_index].icon(); - // Fallback for icons that don't exist. - icon_index = getIconIndex("infinity"); + // Fallback for icons that don't exist. + icon_index = getIconIndex("infinity"); - if (icon_index != -1) - return icons[icon_index].icon(); - return QIcon(); + if (icon_index != -1) + return icons[icon_index].icon(); + return QIcon(); } int IconList::getIconIndex(const QString &key) const { - auto iter = name_index.find(key == "default" ? "infinity" : key); - if (iter != name_index.end()) - return *iter; + auto iter = name_index.find(key == "default" ? "infinity" : key); + if (iter != name_index.end()) + return *iter; - return -1; + return -1; } QString IconList::getDirectory() const { - return m_dir.absolutePath(); + return m_dir.absolutePath(); } //#include "IconList.moc" diff --git a/api/gui/icons/IconList.h b/api/gui/icons/IconList.h index fad3336f..274a9f02 100644 --- a/api/gui/icons/IconList.h +++ b/api/gui/icons/IconList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,57 +32,57 @@ class QFileSystemWatcher; class MULTIMC_GUI_EXPORT IconList : public QAbstractListModel, public IIconList { - Q_OBJECT + Q_OBJECT public: - explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0); - virtual ~IconList() {}; + explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0); + virtual ~IconList() {}; - QIcon getIcon(const QString &key) const; - int getIconIndex(const QString &key) const; - QString getDirectory() const; + QIcon getIcon(const QString &key) const; + int getIconIndex(const QString &key) const; + QString getDirectory() const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - 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; + 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 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; + 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(const QStringList &iconFiles) override; - void installIcon(const QString &file, const QString &name) override; + void installIcons(const QStringList &iconFiles) override; + void installIcon(const QString &file, const QString &name) override; - const MMCIcon * icon(const QString &key) const; + const MMCIcon * icon(const QString &key) const; - void startWatching(); - void stopWatching(); + void startWatching(); + void stopWatching(); signals: - void iconUpdated(QString key); + void iconUpdated(QString key); private: - // hide copy constructor - IconList(const IconList &) = delete; - // hide assign op - IconList &operator=(const IconList &) = delete; - void reindex(); + // hide copy constructor + IconList(const IconList &) = delete; + // hide assign op + IconList &operator=(const IconList &) = delete; + void reindex(); public slots: - void directoryChanged(const QString &path); + void directoryChanged(const QString &path); protected slots: - void fileChanged(const QString &path); - void SettingChanged(const Setting & setting, QVariant value); + void fileChanged(const QString &path); + void SettingChanged(const Setting & setting, QVariant value); private: - std::shared_ptr m_watcher; - bool is_watching; - QMap name_index; - QVector icons; - QDir m_dir; + shared_qobject_ptr m_watcher; + bool is_watching; + QMap name_index; + QVector icons; + QDir m_dir; }; diff --git a/api/gui/icons/MMCIcon.cpp b/api/gui/icons/MMCIcon.cpp index 92518e01..5058717a 100644 --- a/api/gui/icons/MMCIcon.cpp +++ b/api/gui/icons/MMCIcon.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,86 +19,100 @@ 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 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; + return m_current_type; } QString MMCIcon::name() const { - if (m_name.size()) - return m_name; - return m_key; + if (m_name.size()) + return m_name; + return m_key; } bool MMCIcon::has(IconType _type) const { - return m_images[_type].present(); + 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); + 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; + 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(); + 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; + 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; +} + +QString MMCIcon::getFilePath() const +{ + if(m_current_type == IconType::ToBeDeleted){ + return QString(); + } + return m_images[m_current_type].filename; +} + + +bool MMCIcon::isBuiltIn() const +{ + return m_current_type == IconType::Builtin; } diff --git a/api/gui/icons/MMCIcon.h b/api/gui/icons/MMCIcon.h index 8c6752ea..9fd1b9a2 100644 --- a/api/gui/icons/MMCIcon.h +++ b/api/gui/icons/MMCIcon.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,27 +23,29 @@ struct MULTIMC_GUI_EXPORT MMCImage { - QIcon icon; - QString key; - QString filename; - bool present() const - { - return !icon.isNull() || !key.isEmpty(); - } + QIcon icon; + QString key; + QString filename; + bool present() const + { + return !icon.isNull() || !key.isEmpty(); + } }; struct MULTIMC_GUI_EXPORT MMCIcon { - QString m_key; - QString m_name; - MMCImage m_images[ICONS_TOTAL]; - IconType m_current_type = ToBeDeleted; + QString m_key; + QString m_name; + MMCImage m_images[ICONS_TOTAL]; + IconType m_current_type = ToBeDeleted; - IconType type() const; - QString name() const; - bool has(IconType _type) const; - QIcon icon() const; - void remove(IconType rm_type); - void replace(IconType new_type, QIcon icon, QString path = QString()); - void replace(IconType new_type, const QString &key); + IconType type() const; + QString name() const; + bool has(IconType _type) const; + QIcon icon() const; + void remove(IconType rm_type); + void replace(IconType new_type, QIcon icon, QString path = QString()); + void replace(IconType new_type, const QString &key); + bool isBuiltIn() const; + QString getFilePath() const; }; diff --git a/api/logic/BaseInstaller.cpp b/api/logic/BaseInstaller.cpp index 51f66293..3819a4e4 100644 --- a/api/logic/BaseInstaller.cpp +++ b/api/logic/BaseInstaller.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,37 +25,37 @@ BaseInstaller::BaseInstaller() bool BaseInstaller::isApplied(MinecraftInstance *on) { - return QFile::exists(filename(on->instanceRoot())); + return QFile::exists(filename(on->instanceRoot())); } bool BaseInstaller::add(MinecraftInstance *to) { - if (!patchesDir(to->instanceRoot()).exists()) - { - QDir(to->instanceRoot()).mkdir("patches"); - } - - if (isApplied(to)) - { - if (!remove(to)) - { - return false; - } - } - - return true; + if (!patchesDir(to->instanceRoot()).exists()) + { + QDir(to->instanceRoot()).mkdir("patches"); + } + + if (isApplied(to)) + { + if (!remove(to)) + { + return false; + } + } + + return true; } bool BaseInstaller::remove(MinecraftInstance *from) { - return QFile::remove(filename(from->instanceRoot())); + return QFile::remove(filename(from->instanceRoot())); } QString BaseInstaller::filename(const QString &root) const { - return patchesDir(root).absoluteFilePath(id() + ".json"); + return patchesDir(root).absoluteFilePath(id() + ".json"); } QDir BaseInstaller::patchesDir(const QString &root) const { - return QDir(root + "/patches/"); + return QDir(root + "/patches/"); } diff --git a/api/logic/BaseInstaller.h b/api/logic/BaseInstaller.h index afe11d55..59e00e06 100644 --- a/api/logic/BaseInstaller.h +++ b/api/logic/BaseInstaller.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,17 +30,17 @@ typedef std::shared_ptr BaseVersionPtr; class MULTIMC_LOGIC_EXPORT BaseInstaller { public: - BaseInstaller(); - virtual ~BaseInstaller(){}; - bool isApplied(MinecraftInstance *on); + BaseInstaller(); + virtual ~BaseInstaller(){}; + bool isApplied(MinecraftInstance *on); - virtual bool add(MinecraftInstance *to); - virtual bool remove(MinecraftInstance *from); + virtual bool add(MinecraftInstance *to); + virtual bool remove(MinecraftInstance *from); - virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; + virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; protected: - virtual QString id() const = 0; - QString filename(const QString &root) const; - QDir patchesDir(const QString &root) const; + virtual QString id() const = 0; + QString filename(const QString &root) const; + QDir patchesDir(const QString &root) const; }; diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp index 7e652e0d..7a95e255 100644 --- a/api/logic/BaseInstance.cpp +++ b/api/logic/BaseInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,281 +27,228 @@ #include "Commandline.h" BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : QObject() + : QObject() { - m_settings = settings; - m_rootDir = rootDir; - - m_settings->registerSetting("name", "Unnamed Instance"); - m_settings->registerSetting("iconKey", "default"); - m_settings->registerSetting("notes", ""); - m_settings->registerSetting("lastLaunchTime", 0); - m_settings->registerSetting("totalTimePlayed", 0); - - // Custom Commands - auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); - m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting); - m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting); - m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting); - - // Console - auto consoleSetting = m_settings->registerSetting("OverrideConsole", false); - m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting); - - m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); - m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); + m_settings = settings; + m_rootDir = rootDir; + + m_settings->registerSetting("name", "Unnamed Instance"); + m_settings->registerSetting("iconKey", "default"); + m_settings->registerSetting("notes", ""); + m_settings->registerSetting("lastLaunchTime", 0); + m_settings->registerSetting("totalTimePlayed", 0); + + // Custom Commands + auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); + m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting); + m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting); + m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting); + + // Console + auto consoleSetting = m_settings->registerSetting("OverrideConsole", false); + m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting); + m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting); + m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting); + m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting); + + m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); + m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); } QString BaseInstance::getPreLaunchCommand() { - return settings()->get("PreLaunchCommand").toString(); + return settings()->get("PreLaunchCommand").toString(); } QString BaseInstance::getWrapperCommand() { - return settings()->get("WrapperCommand").toString(); + return settings()->get("WrapperCommand").toString(); } QString BaseInstance::getPostExitCommand() { - return settings()->get("PostExitCommand").toString(); + return settings()->get("PostExitCommand").toString(); } int BaseInstance::getConsoleMaxLines() const { - auto lineSetting = settings()->getSetting("ConsoleMaxLines"); - bool conversionOk = false; - int maxLines = lineSetting->get().toInt(&conversionOk); - if(!conversionOk) - { - maxLines = lineSetting->defValue().toInt(); - qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; - } - return maxLines; + auto lineSetting = settings()->getSetting("ConsoleMaxLines"); + bool conversionOk = false; + int maxLines = lineSetting->get().toInt(&conversionOk); + if(!conversionOk) + { + maxLines = lineSetting->defValue().toInt(); + qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; + } + return maxLines; } bool BaseInstance::shouldStopOnConsoleOverflow() const { - return settings()->get("ConsoleOverflowStop").toBool(); + return settings()->get("ConsoleOverflowStop").toBool(); } void BaseInstance::iconUpdated(QString key) { - if(iconKey() == key) - { - emit propertiesChanged(this); - } + if(iconKey() == key) + { + emit propertiesChanged(this); + } } 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()); + changeStatus(Status::Gone); + qDebug() << "Instance" << id() << "has been invalidated."; } void BaseInstance::changeStatus(BaseInstance::Status newStatus) { - Status status = currentStatus(); - if(status != newStatus) - { - m_status = newStatus; - emit statusChanged(status, newStatus); - } + Status status = currentStatus(); + if(status != newStatus) + { + m_status = newStatus; + emit statusChanged(status, newStatus); + } } BaseInstance::Status BaseInstance::currentStatus() const { - return m_status; + return m_status; } QString BaseInstance::id() const { - return QFileInfo(instanceRoot()).fileName(); + return QFileInfo(instanceRoot()).fileName(); } bool BaseInstance::isRunning() const { - return m_isRunning; + return m_isRunning; } void BaseInstance::setRunning(bool running) { - if(running == m_isRunning) - return; - - m_isRunning = running; - - if(running) - { - m_timeStarted = QDateTime::currentDateTime(); - } - else - { - qint64 current = settings()->get("totalTimePlayed").toLongLong(); - QDateTime timeEnded = QDateTime::currentDateTime(); - settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded)); - emit propertiesChanged(this); - } - - emit runningStatusChanged(running); + if(running == m_isRunning) + return; + + m_isRunning = running; + + if(running) + { + m_timeStarted = QDateTime::currentDateTime(); + } + else + { + qint64 current = settings()->get("totalTimePlayed").toLongLong(); + QDateTime timeEnded = QDateTime::currentDateTime(); + settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded)); + emit propertiesChanged(this); + } + + emit runningStatusChanged(running); } int64_t BaseInstance::totalTimePlayed() const { - qint64 current = settings()->get("totalTimePlayed").toLongLong(); - if(m_isRunning) - { - QDateTime timeNow = QDateTime::currentDateTime(); - return current + m_timeStarted.secsTo(timeNow); - } - return current; + qint64 current = settings()->get("totalTimePlayed").toLongLong(); + if(m_isRunning) + { + QDateTime timeNow = QDateTime::currentDateTime(); + return current + m_timeStarted.secsTo(timeNow); + } + return current; } void BaseInstance::resetTimePlayed() { - settings()->reset("totalTimePlayed"); + settings()->reset("totalTimePlayed"); } QString BaseInstance::instanceType() const { - return m_settings->get("InstanceType").toString(); + return m_settings->get("InstanceType").toString(); } QString BaseInstance::instanceRoot() const { - return m_rootDir; -} - -InstancePtr BaseInstance::getSharedPtr() -{ - return shared_from_this(); + return m_rootDir; } SettingsObjectPtr BaseInstance::settings() const { - return m_settings; + return m_settings; } bool BaseInstance::canLaunch() const { - return (!hasVersionBroken() && !isRunning()); + return (!hasVersionBroken() && !isRunning()); } bool BaseInstance::reloadSettings() { - return m_settings->reload(); + return m_settings->reload(); } qint64 BaseInstance::lastLaunch() const { - return m_settings->get("lastLaunchTime").value(); + return m_settings->get("lastLaunchTime").value(); } void BaseInstance::setLastLaunch(qint64 val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("lastLaunchTime", val); - emit propertiesChanged(this); -} - -void BaseInstance::setGroupInitial(QString val) -{ - if(m_group == val) - { - return; - } - m_group = val; - emit propertiesChanged(this); -} - -void BaseInstance::setGroupPost(QString val) -{ - if(m_group == val) - { - return; - } - setGroupInitial(val); - emit groupChanged(); -} - -QString BaseInstance::group() const -{ - return m_group; + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("lastLaunchTime", val); + emit propertiesChanged(this); } void BaseInstance::setNotes(QString val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("notes", val); + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("notes", val); } QString BaseInstance::notes() const { - return m_settings->get("notes").toString(); + return m_settings->get("notes").toString(); } void BaseInstance::setIconKey(QString val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("iconKey", val); - emit propertiesChanged(this); + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("iconKey", val); + emit propertiesChanged(this); } QString BaseInstance::iconKey() const { - return m_settings->get("iconKey").toString(); + return m_settings->get("iconKey").toString(); } void BaseInstance::setName(QString val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("name", val); - emit propertiesChanged(this); + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("name", val); + emit propertiesChanged(this); } QString BaseInstance::name() const { - return m_settings->get("name").toString(); + return m_settings->get("name").toString(); } QString BaseInstance::windowTitle() const { - return "MultiMC: " + name(); + return "MultiMC: " + name().replace(QRegExp("[ \n\r\t]+"), " "); } // FIXME: why is this here? move it to MinecraftInstance!!! QStringList BaseInstance::extraArguments() const { - return Commandline::splitArgs(settings()->get("JvmArgs").toString()); -} - -std::shared_ptr 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; + return Commandline::splitArgs(settings()->get("JvmArgs").toString()); } -BaseInstanceProvider* BaseInstance::provider() const +shared_qobject_ptr BaseInstance::getLaunchTask() { - return m_provider; + return m_launchProcess; } diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h index 282bfb70..3c342cb3 100644 --- a/api/logic/BaseInstance.h +++ b/api/logic/BaseInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ class QDir; class Task; class LaunchTask; class BaseInstance; -class BaseInstanceProvider; // pointer for lazy people typedef std::shared_ptr InstancePtr; @@ -53,231 +52,217 @@ typedef std::shared_ptr InstancePtr; */ class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this { - Q_OBJECT + Q_OBJECT protected: - /// no-touchy! - BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); + /// no-touchy! + BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); public: /* types */ - enum class Status - { - Present, - Gone // either nuked or invalidated - }; + enum class Status + { + Present, + Gone // either nuked or invalidated + }; public: - /// virtual destructor to make sure the destruction is COMPLETE - virtual ~BaseInstance() {}; + /// virtual destructor to make sure the destruction is COMPLETE + virtual ~BaseInstance() {}; - virtual void init() = 0; - virtual void saveNow() = 0; + virtual void saveNow() = 0; - /// nuke thoroughly - deletes the instance contents, notifies the list/model which is - /// responsible of cleaning up the husk - void nuke(); + /*** + * the instance has been invalidated - it is no longer tracked by MultiMC for some reason, + * but it has not necessarily been deleted. + * + * Happens when the instance folder changes to some other location, or the instance is removed by external means. + */ + void invalidate(); - /*** - * the instance has been invalidated - it is no longer tracked by MultiMC for some reason, - * but it has not necessarily been deleted. - * - * Happens when the instance folder changes to some other location, or the instance is removed by external means. - */ - void invalidate(); - - /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to - /// be unique. - virtual QString id() const; - - void setRunning(bool running); - bool isRunning() const; - int64_t totalTimePlayed() const; - void resetTimePlayed(); - - void setProvider(BaseInstanceProvider * provider); - BaseInstanceProvider * provider() const; - - /// get the type of this instance - QString instanceType() const; - - /// Path to the instance's root directory. - QString instanceRoot() const; - - QString name() const; - void setName(QString val); - - /// Value used for instance window titles - QString windowTitle() const; - - QString iconKey() const; - void setIconKey(QString val); - - QString notes() const; - void setNotes(QString val); - - QString group() const; - void setGroupInitial(QString val); - void setGroupPost(QString val); - - QString getPreLaunchCommand(); - QString getPostExitCommand(); - QString getWrapperCommand(); - - /// guess log level from a line of game log - virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) - { - return level; - }; - - virtual QStringList extraArguments() const; - - /// Traits. Normally inside the version, depends on instance implementation. - virtual QSet traits() const = 0; - - /** - * Gets the time that the instance was last launched. - * Stored in milliseconds since epoch. - */ - qint64 lastLaunch() const; - /// Sets the last launched time to 'val' milliseconds since epoch - void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); - - InstancePtr getSharedPtr(); - - /*! - * \brief Gets this instance's settings object. - * This settings object stores instance-specific settings. - * \return A pointer to this instance's settings object. - */ - virtual SettingsObjectPtr settings() const; - - /// returns a valid update task - virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) = 0; - - /// returns a valid launcher (task container) - virtual std::shared_ptr createLaunchTask(AuthSessionPtr account) = 0; - - /// returns the current launch task (if any) - std::shared_ptr getLaunchTask(); - - /*! - * Create envrironment variables for running the instance - */ - virtual QProcessEnvironment createEnvironment() = 0; - - /*! - * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' - */ - virtual IPathMatcher::Ptr getLogFileMatcher() = 0; - - /*! - * Returns the root folder to use for looking up log files - */ - virtual QString getLogFileRoot() = 0; - - virtual QString getStatusbarDescription() = 0; - - /// FIXME: this really should be elsewhere... - virtual QString instanceConfigFolder() const = 0; - - /// get variables this instance exports - virtual QMap getVariables() const = 0; - - virtual QString typeName() const = 0; - - bool hasVersionBroken() const - { - return m_hasBrokenVersion; - } - void setVersionBroken(bool value) - { - if(m_hasBrokenVersion != value) - { - m_hasBrokenVersion = value; - emit propertiesChanged(this); - } - } - - bool hasUpdateAvailable() const - { - return m_hasUpdate; - } - void setUpdateAvailable(bool value) - { - if(m_hasUpdate != value) - { - m_hasUpdate = value; - emit propertiesChanged(this); - } - } - - bool hasCrashed() const - { - return m_crashed; - } - void setCrashed(bool value) - { - if(m_crashed != value) - { - m_crashed = value; - emit propertiesChanged(this); - } - } - - virtual bool canLaunch() const; - virtual bool canEdit() const = 0; - virtual bool canExport() const = 0; - - bool reloadSettings(); - - /** - * 'print' a verbose desription of the instance into a QStringList - */ - virtual QStringList verboseDescription(AuthSessionPtr session) = 0; - - Status currentStatus() const; - - int getConsoleMaxLines() const; - bool shouldStopOnConsoleOverflow() const; + /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to + /// be unique. + virtual QString id() const; + + void setRunning(bool running); + bool isRunning() const; + int64_t totalTimePlayed() const; + void resetTimePlayed(); + + /// get the type of this instance + QString instanceType() const; + + /// Path to the instance's root directory. + QString instanceRoot() const; + + /// Path to the instance's game root directory. + virtual QString gameRoot() const + { + return instanceRoot(); + } + + QString name() const; + void setName(QString val); + + /// Value used for instance window titles + QString windowTitle() const; + + QString iconKey() const; + void setIconKey(QString val); + + QString notes() const; + void setNotes(QString val); + + QString getPreLaunchCommand(); + QString getPostExitCommand(); + QString getWrapperCommand(); + + /// guess log level from a line of game log + virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) + { + return level; + }; + + virtual QStringList extraArguments() const; + + /// Traits. Normally inside the version, depends on instance implementation. + virtual QSet traits() const = 0; + + /** + * Gets the time that the instance was last launched. + * Stored in milliseconds since epoch. + */ + qint64 lastLaunch() const; + /// Sets the last launched time to 'val' milliseconds since epoch + void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); + + /*! + * \brief Gets this instance's settings object. + * This settings object stores instance-specific settings. + * \return A pointer to this instance's settings object. + */ + virtual SettingsObjectPtr settings() const; + + /// returns a valid update task + virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) = 0; + + /// returns a valid launcher (task container) + virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account) = 0; + + /// returns the current launch task (if any) + shared_qobject_ptr getLaunchTask(); + + /*! + * Create envrironment variables for running the instance + */ + virtual QProcessEnvironment createEnvironment() = 0; + + /*! + * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' + */ + virtual IPathMatcher::Ptr getLogFileMatcher() = 0; + + /*! + * Returns the root folder to use for looking up log files + */ + virtual QString getLogFileRoot() = 0; + + virtual QString getStatusbarDescription() = 0; + + /// FIXME: this really should be elsewhere... + virtual QString instanceConfigFolder() const = 0; + + /// get variables this instance exports + virtual QMap getVariables() const = 0; + + virtual QString typeName() const = 0; + + bool hasVersionBroken() const + { + return m_hasBrokenVersion; + } + void setVersionBroken(bool value) + { + if(m_hasBrokenVersion != value) + { + m_hasBrokenVersion = value; + emit propertiesChanged(this); + } + } + + bool hasUpdateAvailable() const + { + return m_hasUpdate; + } + void setUpdateAvailable(bool value) + { + if(m_hasUpdate != value) + { + m_hasUpdate = value; + emit propertiesChanged(this); + } + } + + bool hasCrashed() const + { + return m_crashed; + } + void setCrashed(bool value) + { + if(m_crashed != value) + { + m_crashed = value; + emit propertiesChanged(this); + } + } + + virtual bool canLaunch() const; + virtual bool canEdit() const = 0; + virtual bool canExport() const = 0; + + bool reloadSettings(); + + /** + * 'print' a verbose desription of the instance into a QStringList + */ + virtual QStringList verboseDescription(AuthSessionPtr session) = 0; + + Status currentStatus() const; + + int getConsoleMaxLines() const; + bool shouldStopOnConsoleOverflow() const; protected: - void changeStatus(Status newStatus); + void changeStatus(Status newStatus); signals: - /*! - * \brief Signal emitted when properties relevant to the instance view change - */ - void propertiesChanged(BaseInstance *inst); - /*! - * \brief Signal emitted when groups are affected in any way - */ - void groupChanged(); + /*! + * \brief Signal emitted when properties relevant to the instance view change + */ + void propertiesChanged(BaseInstance *inst); - void launchTaskChanged(std::shared_ptr); + void launchTaskChanged(shared_qobject_ptr); - void runningStatusChanged(bool running); + void runningStatusChanged(bool running); - void statusChanged(Status from, Status to); + void statusChanged(Status from, Status to); protected slots: - void iconUpdated(QString key); + void iconUpdated(QString key); protected: /* data */ - QString m_rootDir; - QString m_group; - SettingsObjectPtr m_settings; - // InstanceFlags m_flags; - bool m_isRunning = false; - std::shared_ptr m_launchProcess; - QDateTime m_timeStarted; - BaseInstanceProvider * m_provider = nullptr; + QString m_rootDir; + SettingsObjectPtr m_settings; + // InstanceFlags m_flags; + bool m_isRunning = false; + shared_qobject_ptr m_launchProcess; + QDateTime m_timeStarted; private: /* data */ - Status m_status = Status::Present; - bool m_crashed = false; - bool m_hasUpdate = false; - bool m_hasBrokenVersion = false; + Status m_status = Status::Present; + bool m_crashed = false; + bool m_hasUpdate = false; + bool m_hasBrokenVersion = false; }; -Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(shared_qobject_ptr) //Q_DECLARE_METATYPE(BaseInstance::InstanceFlag) //Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags) diff --git a/api/logic/BaseInstanceProvider.h b/api/logic/BaseInstanceProvider.h deleted file mode 100644 index 34489c5d..00000000 --- a/api/logic/BaseInstanceProvider.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include -#include "BaseInstance.h" -#include "settings/SettingsObject.h" - -#include "multimc_logic_export.h" - -using InstanceId = QString; -using InstanceLocator = std::pair; - -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 discoverInstances() = 0; - virtual InstancePtr loadInstance(const InstanceId &id) = 0; - virtual void loadGroupList() = 0; - virtual void saveGroupList() = 0; - - virtual QString getStagedInstancePath() - { - return QString(); - } - virtual bool commitStagedInstance(const QString & path, const QString& instanceName, const QString & groupName) - { - return false; - } - virtual bool destroyStagingPath(const QString & path) - { - return true; - } - -signals: - // Emit this when the list of provided instances changed - void instancesChanged(); - // Emit when the set of groups your provider supplies changes. - void groupsChanged(QSet groups); - -protected: - SettingsObjectPtr m_globalSettings; -}; diff --git a/api/logic/BaseVersion.h b/api/logic/BaseVersion.h index e49d6277..15e64bc0 100644 --- a/api/logic/BaseVersion.h +++ b/api/logic/BaseVersion.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,33 +25,33 @@ class BaseVersion { public: - virtual ~BaseVersion() {} - /*! - * A string used to identify this version in config files. - * This should be unique within the version list or shenanigans will occur. - */ - virtual QString descriptor() = 0; - - /*! - * The name of this version as it is displayed to the user. - * For example: "1.5.1" - */ - virtual QString name() = 0; - - /*! - * This should return a string that describes - * the kind of version this is (Stable, Beta, Snapshot, whatever) - */ - virtual QString typeString() const = 0; - - virtual bool operator<(BaseVersion &a) - { - return name() < a.name(); - }; - virtual bool operator>(BaseVersion &a) - { - return name() > a.name(); - }; + virtual ~BaseVersion() {} + /*! + * A string used to identify this version in config files. + * This should be unique within the version list or shenanigans will occur. + */ + virtual QString descriptor() = 0; + + /*! + * The name of this version as it is displayed to the user. + * For example: "1.5.1" + */ + virtual QString name() = 0; + + /*! + * This should return a string that describes + * the kind of version this is (Stable, Beta, Snapshot, whatever) + */ + virtual QString typeString() const = 0; + + virtual bool operator<(BaseVersion &a) + { + return name() < a.name(); + }; + virtual bool operator>(BaseVersion &a) + { + return name() > a.name(); + }; }; typedef std::shared_ptr BaseVersionPtr; diff --git a/api/logic/BaseVersionList.cpp b/api/logic/BaseVersionList.cpp index 31a635d7..b3f44463 100644 --- a/api/logic/BaseVersionList.cpp +++ b/api/logic/BaseVersionList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,78 +22,78 @@ BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor) { - for (int i = 0; i < count(); i++) - { - if (at(i)->descriptor() == descriptor) - return at(i); - } - return BaseVersionPtr(); + for (int i = 0; i < count(); i++) + { + if (at(i)->descriptor() == descriptor) + return at(i); + } + return BaseVersionPtr(); } BaseVersionPtr BaseVersionList::getRecommended() const { - if (count() <= 0) - return BaseVersionPtr(); - else - return at(0); + if (count() <= 0) + return BaseVersionPtr(); + else + return at(0); } QVariant BaseVersionList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); + if (!index.isValid()) + return QVariant(); - if (index.row() > count()) - return QVariant(); + if (index.row() > count()) + return QVariant(); - BaseVersionPtr version = at(index.row()); + BaseVersionPtr version = at(index.row()); - switch (role) - { - case VersionPointerRole: - return qVariantFromValue(version); + switch (role) + { + case VersionPointerRole: + return qVariantFromValue(version); - case VersionRole: - return version->name(); + case VersionRole: + return version->name(); - case VersionIdRole: - return version->descriptor(); + case VersionIdRole: + return version->descriptor(); - case TypeRole: - return version->typeString(); + case TypeRole: + return version->typeString(); - default: - return QVariant(); - } + default: + return QVariant(); + } } BaseVersionList::RoleList BaseVersionList::providesRoles() const { - return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole}; + return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole}; } int BaseVersionList::rowCount(const QModelIndex &parent) const { - // Return count - return count(); + // Return count + return count(); } int BaseVersionList::columnCount(const QModelIndex &parent) const { - return 1; + return 1; } QHash BaseVersionList::roleNames() const { - QHash roles = QAbstractListModel::roleNames(); - roles.insert(VersionRole, "version"); - roles.insert(VersionIdRole, "versionId"); - roles.insert(ParentVersionRole, "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; + QHash roles = QAbstractListModel::roleNames(); + roles.insert(VersionRole, "version"); + roles.insert(VersionIdRole, "versionId"); + roles.insert(ParentVersionRole, "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; } diff --git a/api/logic/BaseVersionList.h b/api/logic/BaseVersionList.h index b609e039..64a33d3e 100644 --- a/api/logic/BaseVersionList.h +++ b/api/logic/BaseVersionList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,85 +38,85 @@ */ class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum ModelRoles - { - VersionPointerRole = Qt::UserRole, - VersionRole, - VersionIdRole, - ParentVersionRole, - RecommendedRole, - LatestRole, - TypeRole, - BranchRole, - PathRole, - ArchitectureRole, - SortRole - }; - typedef QList RoleList; + enum ModelRoles + { + VersionPointerRole = Qt::UserRole, + VersionRole, + VersionIdRole, + ParentVersionRole, + RecommendedRole, + LatestRole, + TypeRole, + BranchRole, + PathRole, + ArchitectureRole, + SortRole + }; + typedef QList RoleList; - explicit BaseVersionList(QObject *parent = 0); + explicit BaseVersionList(QObject *parent = 0); - /*! - * \brief Gets a task that will reload the version list. - * Simply execute the task to load the list. - * The task returned by this function should reset the model when it's done. - * \return A pointer to a task that reloads the version list. - */ - virtual shared_qobject_ptr getLoadTask() = 0; + /*! + * \brief Gets a task that will reload the version list. + * Simply execute the task to load the list. + * The task returned by this function should reset the model when it's done. + * \return A pointer to a task that reloads the version list. + */ + virtual shared_qobject_ptr getLoadTask() = 0; - //! Checks whether or not the list is loaded. If this returns false, the list should be - //loaded. - virtual bool isLoaded() = 0; + //! Checks whether or not the list is loaded. If this returns false, the list should be + //loaded. + virtual bool isLoaded() = 0; - //! Gets the version at the given index. - virtual const BaseVersionPtr at(int i) const = 0; + //! Gets the version at the given index. + virtual const BaseVersionPtr at(int i) const = 0; - //! Returns the number of versions in the list. - virtual int count() const = 0; + //! Returns the number of versions in the list. + virtual int count() const = 0; - //////// List Model Functions //////// - QVariant data(const QModelIndex &index, int role) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QHash roleNames() const override; + //////// List Model Functions //////// + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QHash roleNames() const override; - //! which roles are provided by this version list? - virtual RoleList providesRoles() const; + //! which roles are provided by this version list? + virtual RoleList providesRoles() const; - /*! - * \brief Finds a version by its descriptor. - * \param descriptor The descriptor of the version to find. - * \return A const pointer to the version with the given descriptor. NULL if - * one doesn't exist. - */ - virtual BaseVersionPtr findVersion(const QString &descriptor); + /*! + * \brief Finds a version by its descriptor. + * \param descriptor The descriptor of the version to find. + * \return A const pointer to the version with the given descriptor. NULL if + * one doesn't exist. + */ + virtual BaseVersionPtr findVersion(const QString &descriptor); - /*! - * \brief Gets the recommended version from this list - * If the list doesn't support recommended versions, this works exactly as getLatestStable - */ - virtual BaseVersionPtr getRecommended() const; + /*! + * \brief Gets the recommended version from this list + * If the list doesn't support recommended versions, this works exactly as getLatestStable + */ + virtual BaseVersionPtr getRecommended() const; - /*! - * Sorts the version list. - */ - virtual void sortVersions() = 0; + /*! + * Sorts the version list. + */ + virtual void sortVersions() = 0; protected slots: - /*! - * Updates this list with the given list of versions. - * This is done by copying each version in the given list and inserting it - * into this one. - * We need to do this so that we can set the parents of the versions are set to this - * version list. This can't be done in the load task, because the versions the load - * task creates are on the load task's thread and Qt won't allow their parents - * to be set to something created on another thread. - * To get around that problem, we invoke this method on the GUI thread, which - * then copies the versions and sets their parents correctly. - * \param versions List of versions whose parents should be set. - */ - virtual void updateListData(QList versions) = 0; + /*! + * Updates this list with the given list of versions. + * This is done by copying each version in the given list and inserting it + * into this one. + * We need to do this so that we can set the parents of the versions are set to this + * version list. This can't be done in the load task, because the versions the load + * task creates are on the load task's thread and Qt won't allow their parents + * to be set to something created on another thread. + * To get around that problem, we invoke this method on the GUI thread, which + * then copies the versions and sets their parents correctly. + * \param versions List of versions whose parents should be set. + */ + virtual void updateListData(QList versions) = 0; }; diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt index d6b61d23..db1a7dec 100644 --- a/api/logic/CMakeLists.txt +++ b/api/logic/CMakeLists.txt @@ -3,444 +3,467 @@ project(MultiMC_logic) include (UnitTest) set(CORE_SOURCES - # LOGIC - Base classes and infrastructure - BaseInstaller.h - BaseInstaller.cpp - BaseVersionList.h - BaseVersionList.cpp - InstanceList.h - InstanceList.cpp - InstanceTask.h - InstanceTask.cpp - LoggedProcess.h - LoggedProcess.cpp - MessageLevel.cpp - MessageLevel.h - BaseInstanceProvider.h - FolderInstanceProvider.h - FolderInstanceProvider.cpp - BaseVersion.h - BaseInstance.h - BaseInstance.cpp - NullInstance.h - MMCZip.h - MMCZip.cpp - MMCStrings.h - MMCStrings.cpp - - # Basic instance manipulation tasks (derived from InstanceTask) - InstanceCreationTask.h - InstanceCreationTask.cpp - InstanceCopyTask.h - InstanceCopyTask.cpp - InstanceImportTask.h - InstanceImportTask.cpp - - # Use tracking separate from memory management - Usable.h - - # Prefix tree where node names are strings between separators - SeparatorPrefixTree.h - - # WARNING: globals live here - Env.h - Env.cpp - - # String filters - Filter.h - Filter.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 + # LOGIC - Base classes and infrastructure + BaseInstaller.h + BaseInstaller.cpp + BaseVersionList.h + BaseVersionList.cpp + InstanceList.h + InstanceList.cpp + InstanceTask.h + InstanceTask.cpp + LoggedProcess.h + LoggedProcess.cpp + MessageLevel.cpp + MessageLevel.h + BaseVersion.h + BaseInstance.h + BaseInstance.cpp + NullInstance.h + MMCZip.h + MMCZip.cpp + MMCStrings.h + MMCStrings.cpp + + # Basic instance manipulation tasks (derived from InstanceTask) + InstanceCreationTask.h + InstanceCreationTask.cpp + InstanceCopyTask.h + InstanceCopyTask.cpp + InstanceImportTask.h + InstanceImportTask.cpp + + # Use tracking separate from memory management + Usable.h + + # Prefix tree where node names are strings between separators + SeparatorPrefixTree.h + + # WARNING: globals live here + Env.h + Env.cpp + + # String filters + Filter.h + Filter.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 - ) + SOURCES FileSystem_test.cpp + LIBS MultiMC_logic + DATA testdata + ) add_unit_test(GZip - SOURCES GZip_test.cpp - LIBS MultiMC_logic - ) + SOURCES GZip_test.cpp + LIBS MultiMC_logic + ) set(PATHMATCHER_SOURCES - # Path matchers - pathmatcher/FSTreeMatcher.h - pathmatcher/IPathMatcher.h - pathmatcher/MultiMatcher.h - pathmatcher/RegexpMatcher.h + # 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 + # 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 + 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 + 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 - ) + 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 - ) + 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 + # 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 + # 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 + # Icons System and related code + icons/IIconList.h + icons/IIconList.cpp + icons/IconUtils.h + icons/IconUtils.cpp ) # Minecraft services status checker set(STATUS_SOURCES - # Status system - status/StatusChecker.h - status/StatusChecker.cpp + # 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/update/AssetUpdateTask.h - minecraft/update/AssetUpdateTask.cpp - minecraft/update/FMLLibrariesTask.cpp - minecraft/update/FMLLibrariesTask.h - minecraft/update/FoldersTask.cpp - minecraft/update/FoldersTask.h - minecraft/update/LibrariesTask.cpp - minecraft/update/LibrariesTask.h - minecraft/launch/ClaimAccount.cpp - minecraft/launch/ClaimAccount.h - minecraft/launch/CreateServerResourcePacksFolder.cpp - 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/LegacyInstance.h - minecraft/legacy/LegacyInstance.cpp - minecraft/legacy/LegacyUpgradeTask.h - minecraft/legacy/LegacyUpgradeTask.cpp - minecraft/GradleSpecifier.h - minecraft/MinecraftInstance.cpp - minecraft/MinecraftInstance.h - minecraft/LaunchProfile.cpp - minecraft/LaunchProfile.h - minecraft/Component.cpp - minecraft/Component.h - minecraft/ComponentList.cpp - minecraft/ComponentList.h - minecraft/ComponentUpdateTask.cpp - minecraft/ComponentUpdateTask.h - minecraft/MinecraftLoadAndCheck.h - minecraft/MinecraftLoadAndCheck.cpp - minecraft/MinecraftUpdate.h - minecraft/MinecraftUpdate.cpp - minecraft/MojangVersionFormat.cpp - minecraft/MojangVersionFormat.h - minecraft/Rule.cpp - minecraft/Rule.h - minecraft/OneSixVersionFormat.cpp - minecraft/OneSixVersionFormat.h - minecraft/OpSys.cpp - minecraft/OpSys.h - minecraft/ParseUtils.cpp - minecraft/ParseUtils.h - minecraft/ProfileUtils.cpp - minecraft/ProfileUtils.h - minecraft/Library.cpp - minecraft/Library.h - minecraft/MojangDownloadInfo.h - minecraft/VersionFile.cpp - minecraft/VersionFile.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 - - # Assets - minecraft/AssetsUtils.h - minecraft/AssetsUtils.cpp - - # Forge and all things forge related - minecraft/forge/ForgeXzDownload.h - minecraft/forge/ForgeXzDownload.cpp - - # Skin upload utilities - minecraft/SkinUpload.cpp - minecraft/SkinUpload.h - ) + # 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/gameoptions/GameOptions.h + minecraft/gameoptions/GameOptions.cpp + + minecraft/update/AssetUpdateTask.h + minecraft/update/AssetUpdateTask.cpp + minecraft/update/FMLLibrariesTask.cpp + minecraft/update/FMLLibrariesTask.h + minecraft/update/FoldersTask.cpp + minecraft/update/FoldersTask.h + minecraft/update/LibrariesTask.cpp + minecraft/update/LibrariesTask.h + + minecraft/launch/ClaimAccount.cpp + minecraft/launch/ClaimAccount.h + minecraft/launch/CreateServerResourcePacksFolder.cpp + 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/launch/ReconstructAssets.cpp + minecraft/launch/ReconstructAssets.h + minecraft/launch/ScanModFolders.cpp + minecraft/launch/ScanModFolders.h + + minecraft/legacy/LegacyModList.h + minecraft/legacy/LegacyModList.cpp + minecraft/legacy/LegacyInstance.h + minecraft/legacy/LegacyInstance.cpp + minecraft/legacy/LegacyUpgradeTask.h + minecraft/legacy/LegacyUpgradeTask.cpp + + minecraft/GradleSpecifier.h + minecraft/MinecraftInstance.cpp + minecraft/MinecraftInstance.h + minecraft/LaunchProfile.cpp + minecraft/LaunchProfile.h + minecraft/Component.cpp + minecraft/Component.h + minecraft/ComponentList.cpp + minecraft/ComponentList.h + minecraft/ComponentUpdateTask.cpp + minecraft/ComponentUpdateTask.h + minecraft/MinecraftLoadAndCheck.h + minecraft/MinecraftLoadAndCheck.cpp + minecraft/MinecraftUpdate.h + minecraft/MinecraftUpdate.cpp + minecraft/MojangVersionFormat.cpp + minecraft/MojangVersionFormat.h + minecraft/Rule.cpp + minecraft/Rule.h + minecraft/OneSixVersionFormat.cpp + minecraft/OneSixVersionFormat.h + minecraft/OpSys.cpp + minecraft/OpSys.h + minecraft/ParseUtils.cpp + minecraft/ParseUtils.h + minecraft/ProfileUtils.cpp + minecraft/ProfileUtils.h + minecraft/Library.cpp + minecraft/Library.h + minecraft/MojangDownloadInfo.h + minecraft/VersionFile.cpp + minecraft/VersionFile.h + minecraft/VersionFilterData.h + minecraft/VersionFilterData.cpp + minecraft/World.h + minecraft/World.cpp + minecraft/WorldList.h + minecraft/WorldList.cpp + + minecraft/mod/Mod.h + minecraft/mod/Mod.cpp + minecraft/mod/ModDetails.h + minecraft/mod/ModFolderModel.h + minecraft/mod/ModFolderModel.cpp + minecraft/mod/ModFolderLoadTask.h + minecraft/mod/ModFolderLoadTask.cpp + minecraft/mod/LocalModParseTask.h + minecraft/mod/LocalModParseTask.cpp + + # Assets + minecraft/AssetsUtils.h + minecraft/AssetsUtils.cpp + + # Forge and all things forge related + minecraft/forge/ForgeXzDownload.h + minecraft/forge/ForgeXzDownload.cpp + + # Skin upload utilities + minecraft/SkinUpload.cpp + minecraft/SkinUpload.h + ) add_unit_test(GradleSpecifier - SOURCES minecraft/GradleSpecifier_test.cpp - LIBS MultiMC_logic - ) + SOURCES minecraft/GradleSpecifier_test.cpp + LIBS MultiMC_logic + ) add_unit_test(MojangVersionFormat - SOURCES minecraft/MojangVersionFormat_test.cpp - LIBS MultiMC_logic - DATA minecraft/testdata - ) + SOURCES minecraft/MojangVersionFormat_test.cpp + LIBS MultiMC_logic + DATA minecraft/testdata + ) add_unit_test(Library - SOURCES minecraft/Library_test.cpp - LIBS MultiMC_logic - ) + 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(ModFolderModel + SOURCES minecraft/mod/ModFolderModel_test.cpp + DATA testdata + LIBS MultiMC_logic + ) add_unit_test(ParseUtils - SOURCES minecraft/ParseUtils_test.cpp - LIBS MultiMC_logic - ) + 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 + 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/SequentialTask.h - tasks/SequentialTask.cpp + # Tasks + tasks/Task.h + tasks/Task.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 + # 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 - ) + 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 + # 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 - ) + SOURCES java/JavaVersion_test.cpp + LIBS MultiMC_logic + ) set(TRANSLATIONS_SOURCES - translations/TranslationsModel.h - translations/TranslationsModel.cpp + translations/TranslationsModel.h + translations/TranslationsModel.cpp + translations/POTranslator.h + translations/POTranslator.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 + # 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(META_SOURCES - # Metadata sources - meta/JsonFormat.cpp - meta/JsonFormat.h - meta/BaseEntity.cpp - meta/BaseEntity.h - meta/VersionList.cpp - meta/VersionList.h - meta/Version.cpp - meta/Version.h - meta/Index.cpp - meta/Index.h + # Metadata sources + meta/JsonFormat.cpp + meta/JsonFormat.h + meta/BaseEntity.cpp + meta/BaseEntity.h + meta/VersionList.cpp + meta/VersionList.h + meta/Version.cpp + meta/Version.h + meta/Index.cpp + meta/Index.h ) set(FTB_SOURCES - modplatform/ftb/FtbPackFetchTask.h - modplatform/ftb/FtbPackFetchTask.cpp - modplatform/ftb/FtbPackInstallTask.h - modplatform/ftb/FtbPackInstallTask.cpp + modplatform/ftb/FtbPackFetchTask.h + modplatform/ftb/FtbPackFetchTask.cpp + modplatform/ftb/FtbPackInstallTask.h + modplatform/ftb/FtbPackInstallTask.cpp - modplatform/ftb/PackHelpers.h + modplatform/ftb/FtbPrivatePackManager.h + modplatform/ftb/FtbPrivatePackManager.cpp + + modplatform/ftb/PackHelpers.h ) set(FLAME_SOURCES - # Flame - modplatform/flame/PackManifest.h - modplatform/flame/PackManifest.cpp - modplatform/flame/FileResolvingTask.h - modplatform/flame/FileResolvingTask.cpp + # Flame + modplatform/flame/PackManifest.h + modplatform/flame/PackManifest.cpp + modplatform/flame/FileResolvingTask.h + modplatform/flame/FileResolvingTask.cpp + modplatform/flame/UrlResolvingTask.h + modplatform/flame/UrlResolvingTask.cpp ) add_unit_test(Index - SOURCES meta/Index_test.cpp - LIBS MultiMC_logic - ) + SOURCES meta/Index_test.cpp + LIBS MultiMC_logic + ) ################################ COMPILE ################################ @@ -448,25 +471,25 @@ add_unit_test(Index 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} - ${META_SOURCES} - ${ICONS_SOURCES} - ${FTB_SOURCES} - ${FLAME_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} + ${META_SOURCES} + ${ICONS_SOURCES} + ${FTB_SOURCES} + ${FLAME_SOURCES} ) add_library(MultiMC_logic SHARED ${LOGIC_SOURCES}) @@ -476,14 +499,14 @@ generate_export_header(MultiMC_logic) # Link target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES}) -qt5_use_modules(MultiMC_logic Core Xml Network Concurrent) +target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent) # Mark and export headers target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}") # Install it install( - TARGETS MultiMC_logic - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} + TARGETS MultiMC_logic + RUNTIME DESTINATION ${LIBRARY_DEST_DIR} + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} ) diff --git a/api/logic/Commandline.cpp b/api/logic/Commandline.cpp index eac9db09..59f1666b 100644 --- a/api/logic/Commandline.cpp +++ b/api/logic/Commandline.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -27,453 +27,453 @@ namespace Commandline // commandline splitter QStringList splitArgs(QString args) { - QStringList argv; - QString current; - bool escape = false; - QChar inquotes; - for (int i = 0; i < args.length(); i++) - { - QChar cchar = args.at(i); - - // \ escaped - if (escape) - { - current += cchar; - escape = false; - // in "quotes" - } - else if (!inquotes.isNull()) - { - if (cchar == '\\') - escape = true; - else if (cchar == inquotes) - inquotes = 0; - else - current += cchar; - // otherwise - } - else - { - if (cchar == ' ') - { - if (!current.isEmpty()) - { - argv << current; - current.clear(); - } - } - else if (cchar == '"' || cchar == '\'') - inquotes = cchar; - else - current += cchar; - } - } - if (!current.isEmpty()) - argv << current; - return argv; + QStringList argv; + QString current; + bool escape = false; + QChar inquotes; + for (int i = 0; i < args.length(); i++) + { + QChar cchar = args.at(i); + + // \ escaped + if (escape) + { + current += cchar; + escape = false; + // in "quotes" + } + else if (!inquotes.isNull()) + { + if (cchar == '\\') + escape = true; + else if (cchar == inquotes) + inquotes = 0; + else + current += cchar; + // otherwise + } + else + { + if (cchar == ' ') + { + if (!current.isEmpty()) + { + argv << current; + current.clear(); + } + } + else if (cchar == '"' || cchar == '\'') + inquotes = cchar; + else + current += cchar; + } + } + if (!current.isEmpty()) + argv << current; + return argv; } Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle) { - m_flagStyle = flagStyle; - m_argStyle = argStyle; + m_flagStyle = flagStyle; + m_argStyle = argStyle; } // styles setter/getter void Parser::setArgumentStyle(ArgumentStyle::Enum style) { - m_argStyle = style; + m_argStyle = style; } ArgumentStyle::Enum Parser::argumentStyle() { - return m_argStyle; + return m_argStyle; } void Parser::setFlagStyle(FlagStyle::Enum style) { - m_flagStyle = style; + m_flagStyle = style; } FlagStyle::Enum Parser::flagStyle() { - return m_flagStyle; + return m_flagStyle; } // setup methods void Parser::addSwitch(QString name, bool def) { - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otSwitch; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = otSwitch; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); } void Parser::addOption(QString name, QVariant def) { - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otOption; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = otOption; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); } void Parser::addArgument(QString name, bool required, QVariant def) { - if (m_params.contains(name)) - throw "Name not unique"; + if (m_params.contains(name)) + throw "Name not unique"; - PositionalDef *param = new PositionalDef; - param->name = name; - param->def = def; - param->required = required; - param->metavar = name; + PositionalDef *param = new PositionalDef; + param->name = name; + param->def = def; + param->required = required; + param->metavar = name; - m_positionals.append(param); - m_params[name] = (CommonDef *)param; + m_positionals.append(param); + m_params[name] = (CommonDef *)param; } void Parser::addDocumentation(QString name, QString doc, QString metavar) { - if (!m_params.contains(name)) - throw "Name does not exist"; + if (!m_params.contains(name)) + throw "Name does not exist"; - CommonDef *param = m_params[name]; - param->doc = doc; - if (!metavar.isNull()) - param->metavar = metavar; + CommonDef *param = m_params[name]; + param->doc = doc; + if (!metavar.isNull()) + param->metavar = metavar; } void Parser::addShortOpt(QString name, QChar flag) { - if (!m_params.contains(name)) - throw "Name does not exist"; - if (!m_options.contains(name)) - throw "Name is not an Option or Swtich"; - - OptionDef *param = m_options[name]; - m_flags[flag] = param; - param->flag = flag; + if (!m_params.contains(name)) + throw "Name does not exist"; + if (!m_options.contains(name)) + throw "Name is not an Option or Swtich"; + + OptionDef *param = m_options[name]; + m_flags[flag] = param; + param->flag = flag; } // help methods QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) { - QStringList help; - help << compileUsage(progName, useFlags) << "\r\n"; - - // positionals - if (!m_positionals.isEmpty()) - { - help << "\r\n"; - help << "Positional arguments:\r\n"; - QListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - help << " " << param->metavar; - help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); - help << param->doc << "\r\n"; - } - } - - // Options - if (!m_optionList.isEmpty()) - { - help << "\r\n"; - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - help << "Options & Switches:\r\n"; - QListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - help << " "; - int nameLength = optPrefix.length() + option->name.length(); - if (!option->flag.isNull()) - { - nameLength += 3 + flagPrefix.length(); - help << flagPrefix << option->flag << ", "; - } - help << optPrefix << option->name; - if (option->type == otOption) - { - QString arg = QString("%1%2").arg( - ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); - nameLength += arg.length(); - help << arg; - } - help << " " << QString(helpIndent - nameLength - 1, ' '); - help << option->doc << "\r\n"; - } - } - - return help.join(""); + QStringList help; + help << compileUsage(progName, useFlags) << "\r\n"; + + // positionals + if (!m_positionals.isEmpty()) + { + help << "\r\n"; + help << "Positional arguments:\r\n"; + QListIterator it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *param = it2.next(); + help << " " << param->metavar; + help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); + help << param->doc << "\r\n"; + } + } + + // Options + if (!m_optionList.isEmpty()) + { + help << "\r\n"; + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + help << "Options & Switches:\r\n"; + QListIterator it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + help << " "; + int nameLength = optPrefix.length() + option->name.length(); + if (!option->flag.isNull()) + { + nameLength += 3 + flagPrefix.length(); + help << flagPrefix << option->flag << ", "; + } + help << optPrefix << option->name; + if (option->type == otOption) + { + QString arg = QString("%1%2").arg( + ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); + nameLength += arg.length(); + help << arg; + } + help << " " << QString(helpIndent - nameLength - 1, ' '); + help << option->doc << "\r\n"; + } + } + + return help.join(""); } QString Parser::compileUsage(QString progName, bool useFlags) { - QStringList usage; - usage << "Usage: " << progName; - - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - // options - QListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - usage << " ["; - if (!option->flag.isNull() && useFlags) - usage << flagPrefix << option->flag; - else - usage << optPrefix << option->name; - if (option->type == otOption) - usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; - usage << "]"; - } - - // arguments - QListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - usage << " " << (param->required ? "<" : "["); - usage << param->metavar; - usage << (param->required ? ">" : "]"); - } - - return usage.join(""); + QStringList usage; + usage << "Usage: " << progName; + + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + // options + QListIterator it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + usage << " ["; + if (!option->flag.isNull() && useFlags) + usage << flagPrefix << option->flag; + else + usage << optPrefix << option->name; + if (option->type == otOption) + usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; + usage << "]"; + } + + // arguments + QListIterator it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *param = it2.next(); + usage << " " << (param->required ? "<" : "["); + usage << param->metavar; + usage << (param->required ? ">" : "]"); + } + + return usage.join(""); } // parsing QHash Parser::parse(QStringList argv) { - QHash map; + QHash map; - QStringListIterator it(argv); - QString programName = it.next(); + QStringListIterator it(argv); + QString programName = it.next(); - QString optionPrefix; - QString flagPrefix; - QListIterator positionals(m_positionals); - QStringList expecting; + QString optionPrefix; + QString flagPrefix; + QListIterator positionals(m_positionals); + QStringList expecting; - getPrefix(optionPrefix, flagPrefix); + getPrefix(optionPrefix, flagPrefix); - while (it.hasNext()) - { - QString arg = it.next(); + while (it.hasNext()) + { + QString arg = it.next(); - if (!expecting.isEmpty()) - // we were expecting an argument - { - QString name = expecting.first(); + if (!expecting.isEmpty()) + // we were expecting an argument + { + QString name = expecting.first(); /* - if (map.contains(name)) - throw ParsingError( - QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); + if (map.contains(name)) + throw ParsingError( + QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); */ - map[name] = QVariant(arg); - - expecting.removeFirst(); - continue; - } - - if (arg.startsWith(optionPrefix)) - // we have an option - { - // qDebug("Found option %s", qPrintable(arg)); - - QString name = arg.mid(optionPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - name.contains("=")) - { - int i = name.indexOf("="); - equals = name.mid(i + 1); - name = name.left(i); - } - - if (m_options.contains(name)) - { - /* - if (map.contains(name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(name, optionPrefix)); + map[name] = QVariant(arg); + + expecting.removeFirst(); + continue; + } + + if (arg.startsWith(optionPrefix)) + // we have an option + { + // qDebug("Found option %s", qPrintable(arg)); + + QString name = arg.mid(optionPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || + m_argStyle == ArgumentStyle::SpaceAndEquals) && + name.contains("=")) + { + int i = name.indexOf("="); + equals = name.mid(i + 1); + name = name.left(i); + } + + if (m_options.contains(name)) + { + /* + if (map.contains(name)) + throw ParsingError(QString("Option %2%1 was given multiple times") + .arg(name, optionPrefix)); */ - OptionDef *option = m_options[name]; - if (option->type == otSwitch) - map[name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(name); - else if (!equals.isNull()) - map[name] = equals; - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(name); - else - throw ParsingError(QString("Option %2%1 reqires an argument.") - .arg(name, optionPrefix)); - } - - continue; - } - - throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); - } - - if (arg.startsWith(flagPrefix)) - // we have (a) flag(s) - { - // qDebug("Found flags %s", qPrintable(arg)); - - QString flags = arg.mid(flagPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - flags.contains("=")) - { - int i = flags.indexOf("="); - equals = flags.mid(i + 1); - flags = flags.left(i); - } - - for (int i = 0; i < flags.length(); i++) - { - QChar flag = flags.at(i); - - if (!m_flags.contains(flag)) - throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); - - OptionDef *option = m_flags[flag]; + OptionDef *option = m_options[name]; + if (option->type == otSwitch) + map[name] = true; + else // if (option->type == otOption) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(name); + else if (!equals.isNull()) + map[name] = equals; + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(name); + else + throw ParsingError(QString("Option %2%1 reqires an argument.") + .arg(name, optionPrefix)); + } + + continue; + } + + throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); + } + + if (arg.startsWith(flagPrefix)) + // we have (a) flag(s) + { + // qDebug("Found flags %s", qPrintable(arg)); + + QString flags = arg.mid(flagPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || + m_argStyle == ArgumentStyle::SpaceAndEquals) && + flags.contains("=")) + { + int i = flags.indexOf("="); + equals = flags.mid(i + 1); + flags = flags.left(i); + } + + for (int i = 0; i < flags.length(); i++) + { + QChar flag = flags.at(i); + + if (!m_flags.contains(flag)) + throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); + + OptionDef *option = m_flags[flag]; /* - if (map.contains(option->name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(option->name, optionPrefix)); + if (map.contains(option->name)) + throw ParsingError(QString("Option %2%1 was given multiple times") + .arg(option->name, optionPrefix)); */ - if (option->type == otSwitch) - map[option->name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(option->name); - else if (!equals.isNull()) - if (i == flags.length() - 1) - map[option->name] = equals; - else - throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " - "%1 not last flag in %4%3") - .arg(option->name, flag, flags, flagPrefix)); - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(option->name); - else - throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") - .arg(option->name, flag, flagPrefix)); - } - } - - continue; - } - - // must be a positional argument - if (!positionals.hasNext()) - throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); - - PositionalDef *param = positionals.next(); - - map[param->name] = arg; - } - - // check if we're missing something - if (!expecting.isEmpty()) - throw ParsingError(QString("Was still expecting arguments for %2%1").arg( - expecting.join(QString(", ") + optionPrefix), optionPrefix)); - - while (positionals.hasNext()) - { - PositionalDef *param = positionals.next(); - if (param->required) - throw ParsingError( - QString("Missing required positional argument '%1'").arg(param->name)); - else - map[param->name] = param->def; - } - - // fill out gaps - QListIterator iter(m_optionList); - while (iter.hasNext()) - { - OptionDef *option = iter.next(); - if (!map.contains(option->name)) - map[option->name] = option->def; - } - - return map; + if (option->type == otSwitch) + map[option->name] = true; + else // if (option->type == otOption) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(option->name); + else if (!equals.isNull()) + if (i == flags.length() - 1) + map[option->name] = equals; + else + throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " + "%1 not last flag in %4%3") + .arg(option->name, flag, flags, flagPrefix)); + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(option->name); + else + throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") + .arg(option->name, flag, flagPrefix)); + } + } + + continue; + } + + // must be a positional argument + if (!positionals.hasNext()) + throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); + + PositionalDef *param = positionals.next(); + + map[param->name] = arg; + } + + // check if we're missing something + if (!expecting.isEmpty()) + throw ParsingError(QString("Was still expecting arguments for %2%1").arg( + expecting.join(QString(", ") + optionPrefix), optionPrefix)); + + while (positionals.hasNext()) + { + PositionalDef *param = positionals.next(); + if (param->required) + throw ParsingError( + QString("Missing required positional argument '%1'").arg(param->name)); + else + map[param->name] = param->def; + } + + // fill out gaps + QListIterator iter(m_optionList); + while (iter.hasNext()) + { + OptionDef *option = iter.next(); + if (!map.contains(option->name)) + map[option->name] = option->def; + } + + return map; } // clear defs void Parser::clear() { - m_flags.clear(); - m_params.clear(); - m_options.clear(); - - QMutableListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - it.remove(); - delete option; - } - - QMutableListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *arg = it2.next(); - it2.remove(); - delete arg; - } + m_flags.clear(); + m_params.clear(); + m_options.clear(); + + QMutableListIterator it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + it.remove(); + delete option; + } + + QMutableListIterator it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *arg = it2.next(); + it2.remove(); + delete arg; + } } // Destructor Parser::~Parser() { - clear(); + clear(); } // getPrefix void Parser::getPrefix(QString &opt, QString &flag) { - if (m_flagStyle == FlagStyle::Windows) - opt = flag = "/"; - else if (m_flagStyle == FlagStyle::Unix) - opt = flag = "-"; - // else if (m_flagStyle == FlagStyle::GNU) - else - { - opt = "--"; - flag = "-"; - } + if (m_flagStyle == FlagStyle::Windows) + opt = flag = "/"; + else if (m_flagStyle == FlagStyle::Unix) + opt = flag = "-"; + // else if (m_flagStyle == FlagStyle::GNU) + else + { + opt = "--"; + flag = "-"; + } } // ParsingError diff --git a/api/logic/Commandline.h b/api/logic/Commandline.h index c8c8be29..5f6e5510 100644 --- a/api/logic/Commandline.h +++ b/api/logic/Commandline.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -51,13 +51,13 @@ namespace FlagStyle { enum Enum { - GNU, /**< --option and -o (GNU Style) */ - Unix, /**< -option and -o (Unix Style) */ - Windows, /**< /option and /o (Windows Style) */ + GNU, /**< --option and -o (GNU Style) */ + Unix, /**< -option and -o (Unix Style) */ + Windows, /**< /option and /o (Windows Style) */ #ifdef Q_OS_WIN32 - Default = Windows + Default = Windows #else - Default = GNU + Default = GNU #endif }; } @@ -69,13 +69,13 @@ namespace ArgumentStyle { enum Enum { - Space, /**< --option=value */ - Equals, /**< --option value */ - SpaceAndEquals, /**< --option[= ]value */ + Space, /**< --option=value */ + Equals, /**< --option value */ + SpaceAndEquals, /**< --option[= ]value */ #ifdef Q_OS_WIN32 - Default = Equals + Default = Equals #else - Default = SpaceAndEquals + Default = SpaceAndEquals #endif }; } @@ -86,7 +86,7 @@ enum Enum class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error { public: - ParsingError(const QString &what); + ParsingError(const QString &what); }; /** @@ -95,158 +95,158 @@ public: class MULTIMC_LOGIC_EXPORT Parser { public: - /** - * @brief Parser constructor - * @param flagStyle the FlagStyle to use in this Parser - * @param argStyle the ArgumentStyle to use in this Parser - */ - Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, - ArgumentStyle::Enum argStyle = ArgumentStyle::Default); - - /** - * @brief set the flag style - * @param style - */ - void setFlagStyle(FlagStyle::Enum style); - - /** - * @brief get the flag style - * @return - */ - FlagStyle::Enum flagStyle(); - - /** - * @brief set the argument style - * @param style - */ - void setArgumentStyle(ArgumentStyle::Enum style); - - /** - * @brief get the argument style - * @return - */ - ArgumentStyle::Enum argumentStyle(); - - /** - * @brief define a boolean switch - * @param name the parameter name - * @param def the default value - */ - void addSwitch(QString name, bool def = false); - - /** - * @brief define an option that takes an additional argument - * @param name the parameter name - * @param def the default value - */ - void addOption(QString name, QVariant def = QVariant()); - - /** - * @brief define a positional argument - * @param name the parameter name - * @param required wether this argument is required - * @param def the default value - */ - void addArgument(QString name, bool required = true, QVariant def = QVariant()); - - /** - * @brief adds a flag to an existing parameter - * @param name the (existing) parameter name - * @param flag the flag character - * @see addSwitch addArgument addOption - * Note: any one parameter can only have one flag - */ - void addShortOpt(QString name, QChar flag); - - /** - * @brief adds documentation to a Parameter - * @param name the parameter name - * @param metavar a string to be displayed as placeholder for the value - * @param doc a QString containing the documentation - * Note: on positional arguments, metavar replaces the name as displayed. - * on options , metavar replaces the value placeholder - */ - void addDocumentation(QString name, QString doc, QString metavar = QString()); - - /** - * @brief generate a help message - * @param progName the program name to use in the help message - * @param helpIndent how much the parameter documentation should be indented - * @param flagsInUsage whether we should use flags instead of options in the usage - * @return a help message - */ - QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); - - /** - * @brief generate a short usage message - * @param progName the program name to use in the usage message - * @param useFlags whether we should use flags instead of options - * @return a usage message - */ - QString compileUsage(QString progName, bool useFlags = true); - - /** - * @brief parse - * @param argv a QStringList containing the program ARGV - * @return a QHash mapping argument names to their values - */ - QHash parse(QStringList argv); - - /** - * @brief clear all definitions - */ - void clear(); - - ~Parser(); + /** + * @brief Parser constructor + * @param flagStyle the FlagStyle to use in this Parser + * @param argStyle the ArgumentStyle to use in this Parser + */ + Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, + ArgumentStyle::Enum argStyle = ArgumentStyle::Default); + + /** + * @brief set the flag style + * @param style + */ + void setFlagStyle(FlagStyle::Enum style); + + /** + * @brief get the flag style + * @return + */ + FlagStyle::Enum flagStyle(); + + /** + * @brief set the argument style + * @param style + */ + void setArgumentStyle(ArgumentStyle::Enum style); + + /** + * @brief get the argument style + * @return + */ + ArgumentStyle::Enum argumentStyle(); + + /** + * @brief define a boolean switch + * @param name the parameter name + * @param def the default value + */ + void addSwitch(QString name, bool def = false); + + /** + * @brief define an option that takes an additional argument + * @param name the parameter name + * @param def the default value + */ + void addOption(QString name, QVariant def = QVariant()); + + /** + * @brief define a positional argument + * @param name the parameter name + * @param required wether this argument is required + * @param def the default value + */ + void addArgument(QString name, bool required = true, QVariant def = QVariant()); + + /** + * @brief adds a flag to an existing parameter + * @param name the (existing) parameter name + * @param flag the flag character + * @see addSwitch addArgument addOption + * Note: any one parameter can only have one flag + */ + void addShortOpt(QString name, QChar flag); + + /** + * @brief adds documentation to a Parameter + * @param name the parameter name + * @param metavar a string to be displayed as placeholder for the value + * @param doc a QString containing the documentation + * Note: on positional arguments, metavar replaces the name as displayed. + * on options , metavar replaces the value placeholder + */ + void addDocumentation(QString name, QString doc, QString metavar = QString()); + + /** + * @brief generate a help message + * @param progName the program name to use in the help message + * @param helpIndent how much the parameter documentation should be indented + * @param flagsInUsage whether we should use flags instead of options in the usage + * @return a help message + */ + QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); + + /** + * @brief generate a short usage message + * @param progName the program name to use in the usage message + * @param useFlags whether we should use flags instead of options + * @return a usage message + */ + QString compileUsage(QString progName, bool useFlags = true); + + /** + * @brief parse + * @param argv a QStringList containing the program ARGV + * @return a QHash mapping argument names to their values + */ + QHash parse(QStringList argv); + + /** + * @brief clear all definitions + */ + void clear(); + + ~Parser(); private: - FlagStyle::Enum m_flagStyle; - ArgumentStyle::Enum m_argStyle; - - enum OptionType - { - otSwitch, - otOption - }; - - // Important: the common part MUST BE COMMON ON ALL THREE structs - struct CommonDef - { - QString name; - QString doc; - QString metavar; - QVariant def; - }; - - struct OptionDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // option - OptionType type; - QChar flag; - }; - - struct PositionalDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // positional - bool required; - }; - - QHash m_options; - QHash m_flags; - QHash m_params; - QList m_positionals; - QList m_optionList; - - void getPrefix(QString &opt, QString &flag); + FlagStyle::Enum m_flagStyle; + ArgumentStyle::Enum m_argStyle; + + enum OptionType + { + otSwitch, + otOption + }; + + // Important: the common part MUST BE COMMON ON ALL THREE structs + struct CommonDef + { + QString name; + QString doc; + QString metavar; + QVariant def; + }; + + struct OptionDef + { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // option + OptionType type; + QChar flag; + }; + + struct PositionalDef + { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // positional + bool required; + }; + + QHash m_options; + QHash m_flags; + QHash m_params; + QList m_positionals; + QList m_optionList; + + void getPrefix(QString &opt, QString &flag); }; } diff --git a/api/logic/DefaultVariable.h b/api/logic/DefaultVariable.h index 38d7ecc2..5c069bd3 100644 --- a/api/logic/DefaultVariable.h +++ b/api/logic/DefaultVariable.h @@ -4,32 +4,32 @@ template class DefaultVariable { public: - DefaultVariable(const T & value) - { - defaultValue = value; - } - DefaultVariable & operator =(const T & value) - { - currentValue = value; - is_default = currentValue == defaultValue; - is_explicit = true; - return *this; - } - operator const T &() const - { - return is_default ? defaultValue : currentValue; - } - bool isDefault() const - { - return is_default; - } - bool isExplicit() const - { - return is_explicit; - } + DefaultVariable(const T & value) + { + defaultValue = value; + } + DefaultVariable & operator =(const T & value) + { + currentValue = value; + is_default = currentValue == defaultValue; + is_explicit = true; + return *this; + } + operator const T &() const + { + return is_default ? defaultValue : currentValue; + } + bool isDefault() const + { + return is_default; + } + bool isExplicit() const + { + return is_explicit; + } private: - T currentValue; - T defaultValue; - bool is_default = true; - bool is_explicit = false; + T currentValue; + T defaultValue; + bool is_default = true; + bool is_explicit = false; }; diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp index 04a5ab23..77546bbc 100644 --- a/api/logic/Env.cpp +++ b/api/logic/Env.cpp @@ -15,11 +15,12 @@ struct Env::Private { - QNetworkAccessManager m_qnam; - shared_qobject_ptr m_metacache; - std::shared_ptr m_iconlist; - shared_qobject_ptr m_metadataIndex; - QString m_jarsPath; + QNetworkAccessManager m_qnam; + shared_qobject_ptr m_metacache; + std::shared_ptr m_iconlist; + shared_qobject_ptr m_metadataIndex; + QString m_jarsPath; + QSet m_features; }; static Env * instance; @@ -30,152 +31,179 @@ static Env * instance; Env::Env() { - d = new Private(); + d = new Private(); } Env::~Env() { - delete d; + delete d; } Env& Env::Env::getInstance() { - if(!instance) - { - instance = new Env(); - } - return *instance; + if(!instance) + { + instance = new Env(); + } + return *instance; } void Env::dispose() { - delete instance; - instance = nullptr; + delete instance; + instance = nullptr; } shared_qobject_ptr< HttpMetaCache > Env::metacache() { - return d->m_metacache; + return d->m_metacache; } QNetworkAccessManager& Env::qnam() const { - return d->m_qnam; + return d->m_qnam; } std::shared_ptr Env::icons() { - return d->m_iconlist; + return d->m_iconlist; } void Env::registerIconList(std::shared_ptr iconlist) { - d->m_iconlist = iconlist; + d->m_iconlist = iconlist; } shared_qobject_ptr Env::metadataIndex() { - if (!d->m_metadataIndex) - { - d->m_metadataIndex.reset(new Meta::Index()); - } - return d->m_metadataIndex; + if (!d->m_metadataIndex) + { + d->m_metadataIndex.reset(new Meta::Index()); + } + return d->m_metadataIndex; } 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()); - m_metacache->addBase("versions", QDir("versions").absolutePath()); - m_metacache->addBase("libraries", QDir("libraries").absolutePath()); - m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); - m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); - m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); - m_metacache->addBase("general", QDir("cache").absolutePath()); - m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); - m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); - m_metacache->addBase("root", QDir::currentPath()); - m_metacache->addBase("translations", QDir("translations").absolutePath()); - m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); - m_metacache->addBase("meta", QDir("meta").absolutePath()); - m_metacache->Load(); + 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()); + m_metacache->addBase("versions", QDir("versions").absolutePath()); + m_metacache->addBase("libraries", QDir("libraries").absolutePath()); + m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); + m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); + m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); + m_metacache->addBase("general", QDir("cache").absolutePath()); + m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); + m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); + m_metacache->addBase("root", QDir::currentPath()); + m_metacache->addBase("translations", QDir("translations").absolutePath()); + m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); + m_metacache->addBase("meta", QDir("meta").absolutePath()); + m_metacache->Load(); } void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password) { - // Set the application proxy settings. - if (proxyTypeStr == "SOCKS5") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); - } - else if (proxyTypeStr == "HTTP") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); - } - else if (proxyTypeStr == "None") - { - // If we have no proxy set, set no proxy and return. - QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy)); - } - else - { - // If we have "Default" selected, set Qt to use the system proxy settings. - QNetworkProxyFactory::setUseSystemConfiguration(true); - } - - qDebug() << "Detecting proxy settings..."; - QNetworkProxy proxy = QNetworkProxy::applicationProxy(); - d->m_qnam.setProxy(proxy); - QString proxyDesc; - if (proxy.type() == QNetworkProxy::NoProxy) - { - qDebug() << "Using no proxy is an option!"; - return; - } - switch (proxy.type()) - { - case QNetworkProxy::DefaultProxy: - proxyDesc = "Default proxy: "; - break; - case QNetworkProxy::Socks5Proxy: - proxyDesc = "Socks5 proxy: "; - break; - case QNetworkProxy::HttpProxy: - proxyDesc = "HTTP proxy: "; - break; - case QNetworkProxy::HttpCachingProxy: - proxyDesc = "HTTP caching: "; - break; - case QNetworkProxy::FtpCachingProxy: - proxyDesc = "FTP caching: "; - break; - default: - proxyDesc = "DERP proxy: "; - break; - } - proxyDesc += QString("%3@%1:%2 pass %4") - .arg(proxy.hostName()) - .arg(proxy.port()) - .arg(proxy.user()) - .arg(proxy.password()); - qDebug() << proxyDesc; + // Set the application proxy settings. + if (proxyTypeStr == "SOCKS5") + { + QNetworkProxy::setApplicationProxy( + QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); + } + else if (proxyTypeStr == "HTTP") + { + QNetworkProxy::setApplicationProxy( + QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); + } + else if (proxyTypeStr == "None") + { + // If we have no proxy set, set no proxy and return. + QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy)); + } + else + { + // If we have "Default" selected, set Qt to use the system proxy settings. + QNetworkProxyFactory::setUseSystemConfiguration(true); + } + + qDebug() << "Detecting proxy settings..."; + QNetworkProxy proxy = QNetworkProxy::applicationProxy(); + d->m_qnam.setProxy(proxy); + QString proxyDesc; + if (proxy.type() == QNetworkProxy::NoProxy) + { + qDebug() << "Using no proxy is an option!"; + return; + } + switch (proxy.type()) + { + case QNetworkProxy::DefaultProxy: + proxyDesc = "Default proxy: "; + break; + case QNetworkProxy::Socks5Proxy: + proxyDesc = "Socks5 proxy: "; + break; + case QNetworkProxy::HttpProxy: + proxyDesc = "HTTP proxy: "; + break; + case QNetworkProxy::HttpCachingProxy: + proxyDesc = "HTTP caching: "; + break; + case QNetworkProxy::FtpCachingProxy: + proxyDesc = "FTP caching: "; + break; + default: + proxyDesc = "DERP proxy: "; + break; + } + proxyDesc += QString("%3@%1:%2 pass %4") + .arg(proxy.hostName()) + .arg(proxy.port()) + .arg(proxy.user()) + .arg(proxy.password()); + qDebug() << proxyDesc; } QString Env::getJarsPath() { - if(d->m_jarsPath.isEmpty()) - { - return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); - } - return d->m_jarsPath; + if(d->m_jarsPath.isEmpty()) + { + return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); + } + return d->m_jarsPath; } void Env::setJarsPath(const QString& path) { - d->m_jarsPath = path; + d->m_jarsPath = path; +} + +void Env::enableFeature(const QString& featureName, bool state) +{ + if(state) + { + d->m_features.insert(featureName); + } + else + { + d->m_features.remove(featureName); + } +} + +bool Env::isFeatureEnabled(const QString& featureName) const +{ + return d->m_features.contains(featureName); +} + +void Env::getEnabledFeatures(QSet& features) const +{ + features = d->m_features; +} + +void Env::setEnabledFeatures(const QSet& features) const +{ + d->m_features = features; } diff --git a/api/logic/Env.h b/api/logic/Env.h index 276d762d..8b9b827e 100644 --- a/api/logic/Env.h +++ b/api/logic/Env.h @@ -20,40 +20,46 @@ class Index; } #if defined(ENV) - #undef ENV + #undef ENV #endif #define ENV (Env::getInstance()) class MULTIMC_LOGIC_EXPORT Env { - friend class MultiMC; + friend class MultiMC; private: - struct Private; - Env(); - ~Env(); - static void dispose(); + struct Private; + Env(); + ~Env(); + static void dispose(); public: - static Env& getInstance(); + static Env& getInstance(); - QNetworkAccessManager &qnam() const; + QNetworkAccessManager &qnam() const; - shared_qobject_ptr metacache(); + shared_qobject_ptr metacache(); - std::shared_ptr icons(); + std::shared_ptr icons(); - /// init the cache. FIXME: possible future hook point - void initHttpMetaCache(); + /// init the cache. FIXME: possible future hook point + void initHttpMetaCache(); - /// Updates the application proxy settings from the settings object. - void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); + /// Updates the application proxy settings from the settings object. + void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); - void registerIconList(std::shared_ptr iconlist); + void registerIconList(std::shared_ptr iconlist); - shared_qobject_ptr metadataIndex(); + shared_qobject_ptr metadataIndex(); + + QString getJarsPath(); + void setJarsPath(const QString & path); + + bool isFeatureEnabled(const QString & featureName) const; + void enableFeature(const QString & featureName, bool state = true); + void getEnabledFeatures(QSet & features) const; + void setEnabledFeatures(const QSet & features) const; - QString getJarsPath(); - void setJarsPath(const QString & path); protected: - Private * d; + Private * d; }; diff --git a/api/logic/Exception.h b/api/logic/Exception.h index 30c7aa45..9400b3f8 100644 --- a/api/logic/Exception.h +++ b/api/logic/Exception.h @@ -11,24 +11,24 @@ class MULTIMC_LOGIC_EXPORT Exception : public std::exception { public: - Exception(const QString &message) : std::exception(), m_message(message) - { - qCritical() << "Exception:" << message; - } - Exception(const Exception &other) - : std::exception(), m_message(other.cause()) - { - } - virtual ~Exception() noexcept {} - const char *what() const noexcept - { - return m_message.toLatin1().constData(); - } - QString cause() const - { - return m_message; - } + Exception(const QString &message) : std::exception(), m_message(message) + { + qCritical() << "Exception:" << message; + } + Exception(const Exception &other) + : std::exception(), m_message(other.cause()) + { + } + virtual ~Exception() noexcept {} + const char *what() const noexcept + { + return m_message.toLatin1().constData(); + } + QString cause() const + { + return m_message; + } private: - QString m_message; + QString m_message; }; diff --git a/api/logic/ExponentialSeries.h b/api/logic/ExponentialSeries.h new file mode 100644 index 00000000..a9487f0a --- /dev/null +++ b/api/logic/ExponentialSeries.h @@ -0,0 +1,43 @@ + +#pragma once + +template +inline void clamp(T& current, T min, T max) +{ + if (current < min) + { + current = min; + } + else if(current > max) + { + current = max; + } +} + +// List of numbers from min to max. Next is exponent times bigger than previous. + +class ExponentialSeries +{ +public: + ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2) + { + m_current = m_min = min; + m_max = max; + m_exponent = exponent; + } + void reset() + { + m_current = m_min; + } + unsigned operator()() + { + unsigned retval = m_current; + m_current *= m_exponent; + clamp(m_current, m_min, m_max); + return retval; + } + unsigned m_current; + unsigned m_min; + unsigned m_max; + unsigned m_exponent; +}; diff --git a/api/logic/FileSystem.cpp b/api/logic/FileSystem.cpp index 4b47f415..49af2927 100644 --- a/api/logic/FileSystem.cpp +++ b/api/logic/FileSystem.cpp @@ -12,262 +12,267 @@ #include #if defined Q_OS_WIN32 - #include - #include - #include - #include - #include - #include - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include + #include + #include #else - #include + #include #endif namespace FS { void ensureExists(const QDir &dir) { - if (!QDir().mkpath(dir.absolutePath())) - { - throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + - dir.absolutePath() + ")"); - } + if (!QDir().mkpath(dir.absolutePath())) + { + throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + + dir.absolutePath() + ")"); + } } void write(const QString &filename, const QByteArray &data) { - ensureExists(QFileInfo(filename).dir()); - QSaveFile file(filename); - if (!file.open(QSaveFile::WriteOnly)) - { - throw FileSystemException("Couldn't open " + filename + " for writing: " + - file.errorString()); - } - if (data.size() != file.write(data)) - { - throw FileSystemException("Error writing data to " + filename + ": " + - file.errorString()); - } - if (!file.commit()) - { - throw FileSystemException("Error while committing data to " + filename + ": " + - file.errorString()); - } + ensureExists(QFileInfo(filename).dir()); + QSaveFile file(filename); + if (!file.open(QSaveFile::WriteOnly)) + { + throw FileSystemException("Couldn't open " + filename + " for writing: " + + file.errorString()); + } + if (data.size() != file.write(data)) + { + throw FileSystemException("Error writing data to " + filename + ": " + + file.errorString()); + } + if (!file.commit()) + { + throw FileSystemException("Error while committing data to " + filename + ": " + + file.errorString()); + } } QByteArray read(const QString &filename) { - QFile file(filename); - if (!file.open(QFile::ReadOnly)) - { - throw FileSystemException("Unable to open " + filename + " for reading: " + - file.errorString()); - } - const qint64 size = file.size(); - QByteArray data(int(size), 0); - const qint64 ret = file.read(data.data(), size); - if (ret == -1 || ret != size) - { - throw FileSystemException("Error reading data from " + filename + ": " + - file.errorString()); - } - return data; + QFile file(filename); + if (!file.open(QFile::ReadOnly)) + { + throw FileSystemException("Unable to open " + filename + " for reading: " + + file.errorString()); + } + const qint64 size = file.size(); + QByteArray data(int(size), 0); + const qint64 ret = file.read(data.data(), size); + if (ret == -1 || ret != size) + { + throw FileSystemException("Error reading data from " + filename + ": " + + file.errorString()); + } + return data; } bool updateTimestamp(const QString& filename) { #ifdef Q_OS_WIN32 - std::wstring filename_utf_16 = filename.toStdWString(); - return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); + std::wstring filename_utf_16 = filename.toStdWString(); + return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); #else - QByteArray filenameBA = QFile::encodeName(filename); - return (utime(filenameBA.data(), nullptr) == 0); + QByteArray filenameBA = QFile::encodeName(filename); + return (utime(filenameBA.data(), nullptr) == 0); #endif } bool ensureFilePathExists(QString filenamepath) { - QFileInfo a(filenamepath); - QDir dir; - QString ensuredPath = a.path(); - bool success = dir.mkpath(ensuredPath); - return success; + QFileInfo a(filenamepath); + QDir dir; + QString ensuredPath = a.path(); + bool success = dir.mkpath(ensuredPath); + return success; } bool ensureFolderPathExists(QString foldernamepath) { - QFileInfo a(foldernamepath); - QDir dir; - QString ensuredPath = a.filePath(); - bool success = dir.mkpath(ensuredPath); - return success; + QFileInfo a(foldernamepath); + QDir dir; + QString ensuredPath = a.filePath(); + bool success = dir.mkpath(ensuredPath); + return success; } bool copy::operator()(const QString &offset) { - //NOTE always deep copy on windows. the alternatives are too messy. - #if defined Q_OS_WIN32 - m_followSymlinks = true; - #endif - - auto src = PathCombine(m_src.absolutePath(), offset); - auto dst = PathCombine(m_dst.absolutePath(), offset); - - QFileInfo currentSrc(src); - if (!currentSrc.exists()) - return false; - - if(!m_followSymlinks && currentSrc.isSymLink()) - { - qDebug() << "creating symlink" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::link(currentSrc.symLinkTarget(), dst); - } - else if(currentSrc.isFile()) - { - qDebug() << "copying file" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::copy(src, dst); - } - else if(currentSrc.isDir()) - { - qDebug() << "recursing" << offset; - if (!ensureFolderPathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - QDir currentDir(src); - for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) - { - auto inner_offset = PathCombine(offset, f); - // ignore and skip stuff that matches the blacklist. - if(m_blacklist && m_blacklist->matches(inner_offset)) - { - continue; - } - if(!operator()(inner_offset)) - { - qWarning() << "Failed to copy" << inner_offset; - return false; - } - } - } - else - { - qCritical() << "Copy ERROR: Unknown filesystem object:" << src; - return false; - } - return true; + //NOTE always deep copy on windows. the alternatives are too messy. + #if defined Q_OS_WIN32 + m_followSymlinks = true; + #endif + + auto src = PathCombine(m_src.absolutePath(), offset); + auto dst = PathCombine(m_dst.absolutePath(), offset); + + QFileInfo currentSrc(src); + if (!currentSrc.exists()) + return false; + + if(!m_followSymlinks && currentSrc.isSymLink()) + { + qDebug() << "creating symlink" << src << " - " << dst; + if (!ensureFilePathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + return QFile::link(currentSrc.symLinkTarget(), dst); + } + else if(currentSrc.isFile()) + { + qDebug() << "copying file" << src << " - " << dst; + if (!ensureFilePathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + return QFile::copy(src, dst); + } + else if(currentSrc.isDir()) + { + qDebug() << "recursing" << offset; + if (!ensureFolderPathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + QDir currentDir(src); + for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) + { + auto inner_offset = PathCombine(offset, f); + // ignore and skip stuff that matches the blacklist. + if(m_blacklist && m_blacklist->matches(inner_offset)) + { + continue; + } + if(!operator()(inner_offset)) + { + qWarning() << "Failed to copy" << inner_offset; + return false; + } + } + } + else + { + qCritical() << "Copy ERROR: Unknown filesystem object:" << src; + return false; + } + return true; } bool deletePath(QString path) { - bool OK = true; - QDir dir(path); - - if (!dir.exists()) - { - return OK; - } - auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | - QDir::AllDirs | QDir::Files, - QDir::DirsFirst); - - for(auto & info: allEntries) - { + bool OK = true; + QFileInfo finfo(path); + if(finfo.isFile()) { + return QFile::remove(path); + } + + QDir dir(path); + + if (!dir.exists()) + { + return OK; + } + auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, + QDir::DirsFirst); + + for(auto & info: allEntries) + { #if defined Q_OS_WIN32 - QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); - auto wString = nativePath.toStdWString(); - DWORD dwAttrs = GetFileAttributesW(wString.c_str()); - // Windows: check for junctions, reparse points and other nasty things of that sort - if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) - { - if (info.isFile()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } - else if (info.isDir()) - { - OK &= dir.rmdir(info.absoluteFilePath()); - } - } + QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); + auto wString = nativePath.toStdWString(); + DWORD dwAttrs = GetFileAttributesW(wString.c_str()); + // Windows: check for junctions, reparse points and other nasty things of that sort + if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) + { + if (info.isFile()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } + else if (info.isDir()) + { + OK &= dir.rmdir(info.absoluteFilePath()); + } + } #else - // We do not trust Qt with reparse points, but do trust it with unix symlinks. - if(info.isSymLink()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } + // We do not trust Qt with reparse points, but do trust it with unix symlinks. + if(info.isSymLink()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } #endif - else if (info.isDir()) - { - OK &= deletePath(info.absoluteFilePath()); - } - else if (info.isFile()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } - else - { - OK = false; - qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); - } - } - OK &= dir.rmdir(dir.absolutePath()); - return OK; + else if (info.isDir()) + { + OK &= deletePath(info.absoluteFilePath()); + } + else if (info.isFile()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } + else + { + OK = false; + qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); + } + } + OK &= dir.rmdir(dir.absolutePath()); + return OK; } QString PathCombine(const QString & path1, const QString & path2) { - if(!path1.size()) - return path2; - if(!path2.size()) - return path1; + if(!path1.size()) + return path2; + if(!path2.size()) + return path1; return QDir::cleanPath(path1 + QDir::separator() + path2); } QString PathCombine(const QString & path1, const QString & path2, const QString & path3) { - return PathCombine(PathCombine(path1, path2), path3); + return PathCombine(PathCombine(path1, path2), path3); } QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4) { - return PathCombine(PathCombine(path1, path2, path3), path4); + return PathCombine(PathCombine(path1, path2, path3), path4); } QString AbsolutePath(QString path) { - return QFileInfo(path).absolutePath(); + return QFileInfo(path).absolutePath(); } QString ResolveExecutable(QString path) { - if (path.isEmpty()) - { - return QString(); - } - if(!path.contains('/')) - { - path = QStandardPaths::findExecutable(path); - } - QFileInfo pathInfo(path); - if(!pathInfo.exists() || !pathInfo.isExecutable()) - { - return QString(); - } - return pathInfo.absoluteFilePath(); + if (path.isEmpty()) + { + return QString(); + } + if(!path.contains('/')) + { + path = QStandardPaths::findExecutable(path); + } + QFileInfo pathInfo(path); + if(!pathInfo.exists() || !pathInfo.isExecutable()) + { + return QString(); + } + return pathInfo.absoluteFilePath(); } /** @@ -278,66 +283,66 @@ QString ResolveExecutable(QString path) */ QString NormalizePath(QString path) { - QDir a = QDir::currentPath(); - QString currentAbsolute = a.absolutePath(); - - QDir b(path); - QString newAbsolute = b.absolutePath(); - - if (newAbsolute.startsWith(currentAbsolute)) - { - return a.relativeFilePath(newAbsolute); - } - else - { - return newAbsolute; - } + QDir a = QDir::currentPath(); + QString currentAbsolute = a.absolutePath(); + + QDir b(path); + QString newAbsolute = b.absolutePath(); + + if (newAbsolute.startsWith(currentAbsolute)) + { + return a.relativeFilePath(newAbsolute); + } + else + { + return newAbsolute; + } } -QString badFilenameChars = "\"\\/?<>:*|!"; +QString badFilenameChars = "\"\\/?<>:*|!+\r\n"; QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) { - for (int i = 0; i < string.length(); i++) - { - if (badFilenameChars.contains(string[i])) - { - string[i] = replaceWith; - } - } - return string; + for (int i = 0; i < string.length(); i++) + { + if (badFilenameChars.contains(string[i])) + { + string[i] = replaceWith; + } + } + return string; } QString DirNameFromString(QString string, QString inDir) { - int num = 0; - QString baseName = RemoveInvalidFilenameChars(string, '-'); - QString dirName; - do - { - if(num == 0) - { - dirName = baseName; - } - else - { - dirName = baseName + QString::number(num);; - } - - // If it's over 9000 - if (num > 9000) - return ""; - num++; - } while (QFileInfo(PathCombine(inDir, dirName)).exists()); - return dirName; + int num = 0; + QString baseName = RemoveInvalidFilenameChars(string, '-'); + QString dirName; + do + { + if(num == 0) + { + dirName = baseName; + } + else + { + dirName = baseName + QString::number(num);; + } + + // If it's over 9000 + if (num > 9000) + return ""; + num++; + } while (QFileInfo(PathCombine(inDir, dirName)).exists()); + return dirName; } // Does the folder path contain any '!'? If yes, return true, otherwise false. // (This is a problem for Java) bool checkProblemticPathJava(QDir folder) { - QString pathfoldername = folder.absolutePath(); - return pathfoldername.contains("!", Qt::CaseInsensitive); + QString pathfoldername = folder.absolutePath(); + return pathfoldername.contains("!", Qt::CaseInsensitive); } // Win32 crap @@ -347,106 +352,106 @@ bool called_coinit = false; HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) { - HRESULT hres; - - if (!called_coinit) - { - hres = CoInitialize(NULL); - called_coinit = true; - - if (!SUCCEEDED(hres)) - { - qWarning("Failed to initialize COM. Error 0x%08X", hres); - return hres; - } - } - - IShellLink *link; - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, - (LPVOID *)&link); - - if (SUCCEEDED(hres)) - { - IPersistFile *persistFile; - - link->SetPath(targetPath); - link->SetArguments(args); - - hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); - if (SUCCEEDED(hres)) - { - WCHAR wstr[MAX_PATH]; - - MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); - - hres = persistFile->Save(wstr, TRUE); - persistFile->Release(); - } - link->Release(); - } - return hres; + HRESULT hres; + + if (!called_coinit) + { + hres = CoInitialize(NULL); + called_coinit = true; + + if (!SUCCEEDED(hres)) + { + qWarning("Failed to initialize COM. Error 0x%08lX", hres); + return hres; + } + } + + IShellLink *link; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, + (LPVOID *)&link); + + if (SUCCEEDED(hres)) + { + IPersistFile *persistFile; + + link->SetPath(targetPath); + link->SetArguments(args); + + hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); + if (SUCCEEDED(hres)) + { + WCHAR wstr[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); + + hres = persistFile->Save(wstr, TRUE); + persistFile->Release(); + } + link->Release(); + } + return hres; } #endif QString getDesktopDir() { - return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); } // Cross-platform Shortcut creation bool createShortCut(QString location, QString dest, QStringList args, QString name, - QString icon) + QString icon) { #if defined Q_OS_LINUX - location = PathCombine(location, name + ".desktop"); + location = PathCombine(location, name + ".desktop"); - QFile f(location); - f.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream stream(&f); + QFile f(location); + f.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream stream(&f); - QString argstring; - if (!args.empty()) - argstring = " '" + args.join("' '") + "'"; + QString argstring; + if (!args.empty()) + argstring = " '" + args.join("' '") + "'"; - stream << "[Desktop Entry]" - << "\n"; - stream << "Type=Application" - << "\n"; - stream << "TryExec=" << dest.toLocal8Bit() << "\n"; - stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; - stream << "Name=" << name.toLocal8Bit() << "\n"; - stream << "Icon=" << icon.toLocal8Bit() << "\n"; + stream << "[Desktop Entry]" + << "\n"; + stream << "Type=Application" + << "\n"; + stream << "TryExec=" << dest.toLocal8Bit() << "\n"; + stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; + stream << "Name=" << name.toLocal8Bit() << "\n"; + stream << "Icon=" << icon.toLocal8Bit() << "\n"; - stream.flush(); - f.close(); + stream.flush(); + f.close(); - f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | - QFileDevice::ExeOther); + f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | + QFileDevice::ExeOther); - return true; + return true; #elif defined Q_OS_WIN - // TODO: Fix - // QFile file(PathCombine(location, name + ".lnk")); - // WCHAR *file_w; - // WCHAR *dest_w; - // WCHAR *args_w; - // file.fileName().toWCharArray(file_w); - // dest.toWCharArray(dest_w); - - // QString argStr; - // for (int i = 0; i < args.count(); i++) - // { - // argStr.append(args[i]); - // argStr.append(" "); - // } - // argStr.toWCharArray(args_w); - - // return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); - return false; + // TODO: Fix + // QFile file(PathCombine(location, name + ".lnk")); + // WCHAR *file_w; + // WCHAR *dest_w; + // WCHAR *args_w; + // file.fileName().toWCharArray(file_w); + // dest.toWCharArray(dest_w); + + // QString argStr; + // for (int i = 0; i < args.count(); i++) + // { + // argStr.append(args[i]); + // argStr.append(" "); + // } + // argStr.toWCharArray(args_w); + + // return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); + return false; #else - qWarning("Desktop Shortcuts not supported on your platform!"); - return false; + qWarning("Desktop Shortcuts not supported on your platform!"); + return false; #endif } } diff --git a/api/logic/FileSystem.h b/api/logic/FileSystem.h index de8774ff..55ec6a58 100644 --- a/api/logic/FileSystem.h +++ b/api/logic/FileSystem.h @@ -15,7 +15,7 @@ namespace FS class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception { public: - FileSystemException(const QString &message) : Exception(message) {} + FileSystemException(const QString &message) : Exception(message) {} }; /** @@ -48,34 +48,34 @@ MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath); class MULTIMC_LOGIC_EXPORT copy { public: - copy(const QString & src, const QString & dst) - { - m_src = src; - m_dst = dst; - } - copy & followSymlinks(const bool follow) - { - m_followSymlinks = follow; - return *this; - } - copy & blacklist(const IPathMatcher * filter) - { - m_blacklist = filter; - return *this; - } - bool operator()() - { - return operator()(QString()); - } + copy(const QString & src, const QString & dst) + { + m_src = src; + m_dst = dst; + } + copy & followSymlinks(const bool follow) + { + m_followSymlinks = follow; + return *this; + } + copy & blacklist(const IPathMatcher * filter) + { + m_blacklist = filter; + return *this; + } + bool operator()() + { + return operator()(QString()); + } private: - bool operator()(const QString &offset); + bool operator()(const QString &offset); private: - bool m_followSymlinks = true; - const IPathMatcher * m_blacklist = nullptr; - QDir m_src; - QDir m_dst; + bool m_followSymlinks = true; + const IPathMatcher * m_blacklist = nullptr; + QDir m_src; + QDir m_dst; }; /** diff --git a/api/logic/FileSystem_test.cpp b/api/logic/FileSystem_test.cpp index d5e1eedb..df653ea1 100644 --- a/api/logic/FileSystem_test.cpp +++ b/api/logic/FileSystem_test.cpp @@ -7,155 +7,155 @@ class FileSystemTest : public QObject { - Q_OBJECT + Q_OBJECT - const QString bothSlash = "/foo/"; - const QString trailingSlash = "foo/"; - const QString leadingSlash = "/foo"; + const QString bothSlash = "/foo/"; + const QString trailingSlash = "foo/"; + const QString leadingSlash = "/foo"; private slots: - void test_pathCombine() - { - QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash)); - QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash)); - QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash)); - - QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash)); - QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash)); - QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash)); - } - - void test_PathCombine1_data() - { - QTest::addColumn("result"); - QTest::addColumn("path1"); - QTest::addColumn("path2"); - - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; + void test_pathCombine() + { + QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash)); + QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash)); + QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash)); + + QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash)); + QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash)); + QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash)); + } + + void test_PathCombine1_data() + { + QTest::addColumn("result"); + QTest::addColumn("path1"); + QTest::addColumn("path2"); + + QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; + QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; #if defined(Q_OS_WIN) - QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc"; - QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl"; - QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; + QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc"; + QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl"; + QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; #endif - } - - void test_PathCombine1() - { - QFETCH(QString, result); - QFETCH(QString, path1); - QFETCH(QString, path2); - - QCOMPARE(FS::PathCombine(path1, path2), result); - } - - void test_PathCombine2_data() - { - QTest::addColumn("result"); - QTest::addColumn("path1"); - QTest::addColumn("path2"); - QTest::addColumn("path3"); - - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; - QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; - QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; + } + + void test_PathCombine1() + { + QFETCH(QString, result); + QFETCH(QString, path1); + QFETCH(QString, path2); + + QCOMPARE(FS::PathCombine(path1, path2), result); + } + + void test_PathCombine2_data() + { + QTest::addColumn("result"); + QTest::addColumn("path1"); + QTest::addColumn("path2"); + QTest::addColumn("path3"); + + QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; + QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; + QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; + QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; #if defined(Q_OS_WIN) - QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl"; - QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; - QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; - QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; + QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl"; + QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; + QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; + QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; #endif - } - - void test_PathCombine2() - { - QFETCH(QString, result); - QFETCH(QString, path1); - QFETCH(QString, path2); - QFETCH(QString, path3); - - QCOMPARE(FS::PathCombine(path1, path2, path3), result); - } - - void test_copy() - { - QString folder = QFINDTESTDATA("data/test_folder"); - auto f = [&folder]() - { - QTemporaryDir tempDir; - tempDir.setAutoRemove(true); - qDebug() << "From:" << folder << "To:" << tempDir.path(); - - QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); - qDebug() << tempDir.path(); - qDebug() << target_dir.path(); - FS::copy c(folder, target_dir.path()); - c(); - - for(auto entry: target_dir.entryList()) - { - qDebug() << entry; - } - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // first try variant without trailing / - QVERIFY(!folder.endsWith('/')); - f(); - - // then variant with trailing / - folder.append('/'); - QVERIFY(folder.endsWith('/')); - f(); - } - - void test_getDesktop() - { - QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); - } + } + + void test_PathCombine2() + { + QFETCH(QString, result); + QFETCH(QString, path1); + QFETCH(QString, path2); + QFETCH(QString, path3); + + QCOMPARE(FS::PathCombine(path1, path2, path3), result); + } + + void test_copy() + { + QString folder = QFINDTESTDATA("data/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::copy c(folder, target_dir.path()); + c(); + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + } + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_getDesktop() + { + QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + } // this is only valid on linux // FIXME: implement on windows, OSX, then test. #if defined(Q_OS_LINUX) - void test_createShortcut_data() - { - QTest::addColumn("location"); - QTest::addColumn("dest"); - QTest::addColumn("args"); - QTest::addColumn("name"); - QTest::addColumn("iconLocation"); - QTest::addColumn("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); - } + void test_createShortcut_data() + { + QTest::addColumn("location"); + QTest::addColumn("dest"); + QTest::addColumn("args"); + QTest::addColumn("name"); + QTest::addColumn("iconLocation"); + QTest::addColumn("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 }; diff --git a/api/logic/Filter.cpp b/api/logic/Filter.cpp index 7f6667ae..c65ca0ce 100644 --- a/api/logic/Filter.cpp +++ b/api/logic/Filter.cpp @@ -6,26 +6,26 @@ ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){} ContainsFilter::~ContainsFilter(){} bool ContainsFilter::accepts(const QString& value) { - return value.contains(pattern); + return value.contains(pattern); } ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){} ExactFilter::~ExactFilter(){} bool ExactFilter::accepts(const QString& value) { - return value.contains(pattern); + return value == pattern; } RegexpFilter::RegexpFilter(const QString& regexp, bool invert) - :invert(invert) + :invert(invert) { - pattern.setPattern(regexp); - pattern.optimize(); + pattern.setPattern(regexp); + pattern.optimize(); } RegexpFilter::~RegexpFilter(){} bool RegexpFilter::accepts(const QString& value) { - auto match = pattern.match(value); - bool matched = match.hasMatch(); - return invert ? (!matched) : (matched); + auto match = pattern.match(value); + bool matched = match.hasMatch(); + return invert ? (!matched) : (matched); } diff --git a/api/logic/Filter.h b/api/logic/Filter.h index 8de7d8f9..1ba48e48 100644 --- a/api/logic/Filter.h +++ b/api/logic/Filter.h @@ -8,37 +8,37 @@ class MULTIMC_LOGIC_EXPORT Filter { public: - virtual ~Filter(); - virtual bool accepts(const QString & value) = 0; + virtual ~Filter(); + virtual bool accepts(const QString & value) = 0; }; class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter { public: - ContainsFilter(const QString &pattern); - virtual ~ContainsFilter(); - bool accepts(const QString & value) override; + ContainsFilter(const QString &pattern); + virtual ~ContainsFilter(); + bool accepts(const QString & value) override; private: - QString pattern; + QString pattern; }; class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter { public: - ExactFilter(const QString &pattern); - virtual ~ExactFilter(); - bool accepts(const QString & value) override; + ExactFilter(const QString &pattern); + virtual ~ExactFilter(); + bool accepts(const QString & value) override; private: - QString pattern; + QString pattern; }; class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter { public: - RegexpFilter(const QString ®exp, bool invert); - virtual ~RegexpFilter(); - bool accepts(const QString & value) override; + RegexpFilter(const QString ®exp, bool invert); + virtual ~RegexpFilter(); + bool accepts(const QString & value) override; private: - QRegularExpression pattern; - bool invert = false; + QRegularExpression pattern; + bool invert = false; }; diff --git a/api/logic/FolderInstanceProvider.cpp b/api/logic/FolderInstanceProvider.cpp deleted file mode 100644 index 69ba6c82..00000000 --- a/api/logic/FolderInstanceProvider.cpp +++ /dev/null @@ -1,462 +0,0 @@ -#include "FolderInstanceProvider.h" -#include "settings/INISettingsObject.h" -#include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/legacy/LegacyInstance.h" -#include "NullInstance.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -const static int GROUP_FILE_FORMAT_VERSION = 1; - -struct WatchLock -{ - WatchLock(QFileSystemWatcher * watcher, const QString& instDir) - : m_watcher(watcher), m_instDir(instDir) - { - m_watcher->removePath(m_instDir); - } - ~WatchLock() - { - m_watcher->addPath(m_instDir); - } - QFileSystemWatcher * m_watcher; - QString m_instDir; -}; - -FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir) - : BaseInstanceProvider(settings) -{ - // Create aand normalize path - if (!QDir::current().exists(instDir)) - { - QDir::current().mkpath(instDir); - } - // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! - m_instDir = QDir(instDir).canonicalPath(); - m_watcher = new QFileSystemWatcher(this); - connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged); - m_watcher->addPath(m_instDir); -} - -QList< InstanceId > FolderInstanceProvider::discoverInstances() -{ - QList out; - QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); - while (iter.hasNext()) - { - QString subDir = iter.next(); - QFileInfo dirInfo(subDir); - if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists()) - continue; - // if it is a symlink, ignore it if it goes to the instance folder - if(dirInfo.isSymLink()) - { - QFileInfo targetInfo(dirInfo.symLinkTarget()); - QFileInfo instDirInfo(m_instDir); - if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) - { - qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder"; - continue; - } - } - auto id = dirInfo.fileName(); - out.append(id); - qDebug() << "Found instance ID" << id; - } - return out; -} - -InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id) -{ - if(!m_groupsLoaded) - { - loadGroupList(); - } - - auto instanceRoot = FS::PathCombine(m_instDir, id); - auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); - InstancePtr inst; - - instanceSettings->registerSetting("InstanceType", "Legacy"); - - QString inst_type = instanceSettings->get("InstanceType").toString(); - - if (inst_type == "OneSix" || inst_type == "Nostalgia") - { - inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - else if (inst_type == "Legacy") - { - inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - else - { - inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - inst->init(); - inst->setProvider(this); - auto iter = groupMap.find(id); - if (iter != groupMap.end()) - { - inst->setGroupInitial((*iter)); - } - connect(inst.get(), &BaseInstance::groupChanged, this, &FolderInstanceProvider::groupChanged); - qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); - return inst; -} - -void FolderInstanceProvider::saveGroupList() -{ - WatchLock foo(m_watcher, m_instDir); - QString groupFileName = m_instDir + "/instgroups.json"; - QMap> 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 set; - set.insert(id); - reverseGroupMap[group] = set; - } - else - { - QSet &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 groupSet; - - QString groupFileName = m_instDir + "/instgroups.json"; - - // if there's no group file, fail - if (!QFileInfo(groupFileName).exists()) - return; - - QByteArray jsonData; - try - { - jsonData = FS::read(groupFileName); - } - catch (FS::FileSystemException & e) - { - qCritical() << "Failed to read instance group file :" << e.cause(); - return; - } - - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); - - // if the json was bad, fail - if (error.error != QJsonParseError::NoError) - { - qCritical() << QString("Failed to parse instance group file: %1 at offset %2") - .arg(error.errorString(), QString::number(error.offset)) - .toUtf8(); - return; - } - - // if the root of the json wasn't an object, fail - if (!jsonDoc.isObject()) - { - qWarning() << "Invalid group file. Root entry should be an object."; - return; - } - - QJsonObject rootObj = jsonDoc.object(); - - // Make sure the format version matches, otherwise fail. - if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) - return; - - // Get the groups. if it's not an object, fail - if (!rootObj.value("groups").isObject()) - { - qWarning() << "Invalid group list JSON: 'groups' should be an object."; - return; - } - - groupMap.clear(); - - // Iterate through all the groups. - QJsonObject groupMapping = rootObj.value("groups").toObject(); - for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) - { - QString groupName = iter.key(); - - // If not an object, complain and skip to the next one. - if (!iter.value().isObject()) - { - qWarning() << QString("Group '%1' in the group list should " - "be an object.") - .arg(groupName) - .toUtf8(); - continue; - } - - QJsonObject groupObj = iter.value().toObject(); - if (!groupObj.value("instances").isArray()) - { - qWarning() << QString("Group '%1' in the group list is invalid. " - "It should contain an array " - "called 'instances'.") - .arg(groupName) - .toUtf8(); - continue; - } - - // keep a list/set of groups for choosing - groupSet.insert(groupName); - - // Iterate through the list of instances in the group. - QJsonArray instancesArray = groupObj.value("instances").toArray(); - - for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); - iter2++) - { - groupMap[(*iter2).toString()] = groupName; - } - } - m_groupsLoaded = true; - emit groupsChanged(groupSet); -} - -void FolderInstanceProvider::groupChanged() -{ - // save the groups. save all of them. - auto instance = (BaseInstance *) QObject::sender(); - auto id = instance->id(); - groupMap[id] = instance->group(); - emit groupsChanged({instance->group()}); - saveGroupList(); -} - - -void FolderInstanceProvider::instanceDirContentsChanged(const QString& path) -{ - Q_UNUSED(path); - emit instancesChanged(); -} - -void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value) -{ - QString newInstDir = QDir(value.toString()).canonicalPath(); - if(newInstDir != m_instDir) - { - if(m_groupsLoaded) - { - saveGroupList(); - } - m_instDir = newInstDir; - m_groupsLoaded = false; - emit instancesChanged(); - } -} - -template -static void clamp(T& current, T min, T max) -{ - if (current < min) - { - current = min; - } - else if(current > max) - { - current = max; - } -} - -// List of numbers from min to max. Next is exponent times bigger than previous. -class ExponentialSeries -{ -public: - ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2) - { - m_current = m_min = min; - m_max = max; - m_exponent = exponent; - } - void reset() - { - m_current = m_min; - } - unsigned operator()() - { - unsigned retval = m_current; - m_current *= m_exponent; - clamp(m_current, m_min, m_max); - return retval; - } - unsigned m_current; - unsigned m_min; - unsigned m_max; - unsigned m_exponent; -}; - -/* - * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. - * Basically, it starts messing things up while MultiMC is extracting/creating instances - * and causes that horrible failure that is NTFS to lock files in place because they are open. - */ -class FolderInstanceStaging : public Task -{ -Q_OBJECT - const unsigned minBackoff = 1; - const unsigned maxBackoff = 16; -public: - FolderInstanceStaging ( - FolderInstanceProvider * parent, - Task * child, - const QString & stagingPath, - const QString& instanceName, - const QString& groupName ) - : backoff(minBackoff, maxBackoff) - { - m_parent = parent; - m_child.reset(child); - connect(child, &Task::succeeded, this, &FolderInstanceStaging::childSucceded); - connect(child, &Task::failed, this, &FolderInstanceStaging::childFailed); - connect(child, &Task::status, this, &FolderInstanceStaging::setStatus); - connect(child, &Task::progress, this, &FolderInstanceStaging::setProgress); - m_instanceName = instanceName; - m_groupName = groupName; - m_stagingPath = stagingPath; - m_backoffTimer.setSingleShot(true); - connect(&m_backoffTimer, &QTimer::timeout, this, &FolderInstanceStaging::childSucceded); - } - -protected: - virtual void executeTask() override - { - m_child->start(); - } - QStringList warnings() const override - { - return m_child->warnings(); - } - -private slots: - void childSucceded() - { - unsigned sleepTime = backoff(); - if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) - { - emitSucceeded(); - return; - } - // we actually failed, retry? - if(sleepTime == maxBackoff) - { - emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); - return; - } - qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; - m_backoffTimer.start(sleepTime * 500); - } - void childFailed(const QString & reason) - { - m_parent->destroyStagingPath(m_stagingPath); - emitFailed(reason); - } - -private: - ExponentialSeries backoff; - QString m_stagingPath; - FolderInstanceProvider * m_parent; - unique_qobject_ptr m_child; - QString m_instanceName; - QString m_groupName; - QTimer m_backoffTimer; -}; - -#include "InstanceTask.h" -Task * FolderInstanceProvider::wrapInstanceTask(InstanceTask * task) -{ - auto stagingPath = getStagedInstancePath(); - task->setStagingPath(stagingPath); - task->setParentSettings(m_globalSettings); - return new FolderInstanceStaging(this, task, stagingPath, task->name(), task->group()); -} - -QString FolderInstanceProvider::getStagedInstancePath() -{ - QString key = QUuid::createUuid().toString(); - QString relPath = FS::PathCombine("_MMC_TEMP/" , key); - QDir rootPath(m_instDir); - auto path = FS::PathCombine(m_instDir, relPath); - if(!rootPath.mkpath(relPath)) - { - return QString(); - } - return path; -} - -bool FolderInstanceProvider::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) -{ - QDir dir; - QString instID = FS::DirNameFromString(instanceName, m_instDir); - { - WatchLock lock(m_watcher, m_instDir); - QString destination = FS::PathCombine(m_instDir, instID); - if(!dir.rename(path, destination)) - { - qWarning() << "Failed to move" << path << "to" << destination; - return false; - } - groupMap[instID] = groupName; - emit groupsChanged({groupName}); - emit instancesChanged(); - } - saveGroupList(); - return true; -} - -bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath) -{ - return FS::deletePath(keyPath); -} - -#include "FolderInstanceProvider.moc" diff --git a/api/logic/FolderInstanceProvider.h b/api/logic/FolderInstanceProvider.h deleted file mode 100644 index e13dcfe9..00000000 --- a/api/logic/FolderInstanceProvider.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "BaseInstanceProvider.h" -#include - -class QFileSystemWatcher; -class InstanceTask; - -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 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 FtbInstance - Task * ftbCreationTask(FtbPackDownloader *downloader, const QString &instName, const QString &instGroup, const QString &instIcon); - - // migrate an instance to the current format - Task * legacyUpgradeTask(const InstancePtr& oldInstance); -*/ - - // Wrap an instance creation task in some more task machinery and make it ready to be used - Task * wrapInstanceTask(InstanceTask * task); - - /** - * Create a new empty staging area for instance creation and @return a path/key top commit it later. - * Used by instance manipulation tasks. - */ - QString getStagedInstancePath() override; - /** - * Commit the staging area given by @keyPath to the provider - used when creation succeeds. - * Used by instance manipulation tasks. - */ - bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName) override; - /** - * Destroy a previously created staging area given by @keyPath - used when creation fails. - * Used by instance manipulation tasks. - */ - bool destroyStagingPath(const QString & keyPath) override; - -public slots: - void on_InstFolderChanged(const Setting &setting, QVariant value); - -private slots: - void instanceDirContentsChanged(const QString &path); - void groupChanged(); - -private: /* methods */ - void loadGroupList() override; - void saveGroupList() override; - -private: /* data */ - QString m_instDir; - QFileSystemWatcher * m_watcher; - QMap groupMap; - bool m_groupsLoaded = false; -}; diff --git a/api/logic/GZip.cpp b/api/logic/GZip.cpp index 38605df6..0368c32d 100644 --- a/api/logic/GZip.cpp +++ b/api/logic/GZip.cpp @@ -4,112 +4,112 @@ bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes) { - if (compressedBytes.size() == 0) - { - uncompressedBytes = compressedBytes; - return true; - } - - unsigned uncompLength = compressedBytes.size(); - uncompressedBytes.clear(); - uncompressedBytes.resize(uncompLength); - - z_stream strm; - memset(&strm, 0, sizeof(strm)); - strm.next_in = (Bytef *)compressedBytes.data(); - strm.avail_in = compressedBytes.size(); - - bool done = false; - - if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) - { - return false; - } - - int err = Z_OK; - - while (!done) - { - // If our output buffer is too small - if (strm.total_out >= uncompLength) - { - uncompressedBytes.resize(uncompLength * 2); - uncompLength *= 2; - } - - strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out); - strm.avail_out = uncompLength - strm.total_out; - - // Inflate another chunk. - err = inflate(&strm, Z_SYNC_FLUSH); - if (err == Z_STREAM_END) - done = true; - else if (err != Z_OK) - { - break; - } - } - - if (inflateEnd(&strm) != Z_OK || !done) - { - return false; - } - - uncompressedBytes.resize(strm.total_out); - return true; + if (compressedBytes.size() == 0) + { + uncompressedBytes = compressedBytes; + return true; + } + + unsigned uncompLength = compressedBytes.size(); + uncompressedBytes.clear(); + uncompressedBytes.resize(uncompLength); + + z_stream strm; + memset(&strm, 0, sizeof(strm)); + strm.next_in = (Bytef *)compressedBytes.data(); + strm.avail_in = compressedBytes.size(); + + bool done = false; + + if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) + { + return false; + } + + int err = Z_OK; + + while (!done) + { + // If our output buffer is too small + if (strm.total_out >= uncompLength) + { + uncompressedBytes.resize(uncompLength * 2); + uncompLength *= 2; + } + + strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out); + strm.avail_out = uncompLength - strm.total_out; + + // Inflate another chunk. + err = inflate(&strm, Z_SYNC_FLUSH); + if (err == Z_STREAM_END) + done = true; + else if (err != Z_OK) + { + break; + } + } + + if (inflateEnd(&strm) != Z_OK || !done) + { + return false; + } + + uncompressedBytes.resize(strm.total_out); + return true; } bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes) { - if (uncompressedBytes.size() == 0) - { - compressedBytes = uncompressedBytes; - return true; - } - - unsigned compLength = std::min(uncompressedBytes.size(), 16); - compressedBytes.clear(); - compressedBytes.resize(compLength); - - z_stream zs; - memset(&zs, 0, sizeof(zs)); - - if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) - { - return false; - } - - zs.next_in = (Bytef*)uncompressedBytes.data(); - zs.avail_in = uncompressedBytes.size(); - - int ret; - compressedBytes.resize(uncompressedBytes.size()); - - unsigned offset = 0; - unsigned temp = 0; - do - { - auto remaining = compressedBytes.size() - offset; - if(remaining < 1) - { - compressedBytes.resize(compressedBytes.size() * 2); - } - zs.next_out = (Bytef *) (compressedBytes.data() + offset); - temp = zs.avail_out = compressedBytes.size() - offset; - ret = deflate(&zs, Z_FINISH); - offset += temp - zs.avail_out; - } while (ret == Z_OK); - - compressedBytes.resize(offset); - - if (deflateEnd(&zs) != Z_OK) - { - return false; - } - - if (ret != Z_STREAM_END) - { - return false; - } - return true; + if (uncompressedBytes.size() == 0) + { + compressedBytes = uncompressedBytes; + return true; + } + + unsigned compLength = std::min(uncompressedBytes.size(), 16); + compressedBytes.clear(); + compressedBytes.resize(compLength); + + z_stream zs; + memset(&zs, 0, sizeof(zs)); + + if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) + { + return false; + } + + zs.next_in = (Bytef*)uncompressedBytes.data(); + zs.avail_in = uncompressedBytes.size(); + + int ret; + compressedBytes.resize(uncompressedBytes.size()); + + unsigned offset = 0; + unsigned temp = 0; + do + { + auto remaining = compressedBytes.size() - offset; + if(remaining < 1) + { + compressedBytes.resize(compressedBytes.size() * 2); + } + zs.next_out = (Bytef *) (compressedBytes.data() + offset); + temp = zs.avail_out = compressedBytes.size() - offset; + ret = deflate(&zs, Z_FINISH); + offset += temp - zs.avail_out; + } while (ret == Z_OK); + + compressedBytes.resize(offset); + + if (deflateEnd(&zs) != Z_OK) + { + return false; + } + + if (ret != Z_STREAM_END) + { + return false; + } + return true; } \ No newline at end of file diff --git a/api/logic/GZip.h b/api/logic/GZip.h index 6993a222..c7eddbb3 100644 --- a/api/logic/GZip.h +++ b/api/logic/GZip.h @@ -6,7 +6,7 @@ class MULTIMC_LOGIC_EXPORT GZip { public: - static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes); - static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes); + static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes); + static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes); }; diff --git a/api/logic/GZip_test.cpp b/api/logic/GZip_test.cpp index f4c9214c..3f4d181c 100644 --- a/api/logic/GZip_test.cpp +++ b/api/logic/GZip_test.cpp @@ -6,50 +6,50 @@ void fib(int &prev, int &cur) { - auto ret = prev + cur; - prev = cur; - cur = ret; + auto ret = prev + cur; + prev = cur; + cur = ret; } class GZipTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_Through() - { - // test up to 10 MB - static const int size = 10 * 1024 * 1024; - QByteArray random; - QByteArray compressed; - QByteArray decompressed; - std::default_random_engine eng((std::random_device())()); - std::uniform_int_distribution idis(0, std::numeric_limits::max()); - - // initialize random buffer - for(int i = 0; i < size; i++) - { - random.append((char)idis(eng)); - } - - // initialize fibonacci - int prev = 1; - int cur = 1; - - // test if fibonacci long random buffers pass through GZip - do - { - QByteArray copy = random; - copy.resize(cur); - compressed.clear(); - decompressed.clear(); - QVERIFY(GZip::zip(copy, compressed)); - QVERIFY(GZip::unzip(compressed, decompressed)); - QCOMPARE(decompressed, copy); - fib(prev, cur); - } while (cur < size); - } + void test_Through() + { + // test up to 10 MB + static const int size = 10 * 1024 * 1024; + QByteArray random; + QByteArray compressed; + QByteArray decompressed; + std::default_random_engine eng((std::random_device())()); + std::uniform_int_distribution idis(0, std::numeric_limits::max()); + + // initialize random buffer + for(int i = 0; i < size; i++) + { + random.append((char)idis(eng)); + } + + // initialize fibonacci + int prev = 1; + int cur = 1; + + // test if fibonacci long random buffers pass through GZip + do + { + QByteArray copy = random; + copy.resize(cur); + compressed.clear(); + decompressed.clear(); + QVERIFY(GZip::zip(copy, compressed)); + QVERIFY(GZip::unzip(compressed, decompressed)); + QCOMPARE(decompressed, copy); + fib(prev, cur); + } while (cur < size); + } }; QTEST_GUILESS_MAIN(GZipTest) diff --git a/api/logic/InstanceCopyTask.cpp b/api/logic/InstanceCopyTask.cpp index 62c22362..a576a0fd 100644 --- a/api/logic/InstanceCopyTask.cpp +++ b/api/logic/InstanceCopyTask.cpp @@ -1,5 +1,4 @@ #include "InstanceCopyTask.h" -#include "BaseInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" #include "NullInstance.h" @@ -8,50 +7,50 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves) { - m_origInstance = origInstance; - - if(!copySaves) - { - // FIXME: get this from the original instance type... - auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); - matcherReal->caseSensitive(false); - m_matcher.reset(matcherReal); - } + m_origInstance = origInstance; + + 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())); + setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).blacklist(m_matcher.get()); + 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::finished, this, &InstanceCopyTask::copyFinished); - connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); - m_copyFutureWatcher.setFuture(m_copyFuture); + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); + connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); + connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); + m_copyFutureWatcher.setFuture(m_copyFuture); } void InstanceCopyTask::copyFinished() { - auto successful = m_copyFuture.result(); - if(!successful) - { - emitFailed(tr("Instance folder copy failed.")); - return; - } - // FIXME: shouldn't this be able to report errors? - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - - InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); - inst->setName(m_instName); - inst->setIconKey(m_instIcon); - emitSucceeded(); + auto successful = m_copyFuture.result(); + if(!successful) + { + emitFailed(tr("Instance folder copy failed.")); + return; + } + // FIXME: shouldn't this be able to report errors? + auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->registerSetting("InstanceType", "Legacy"); + + InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); + inst->setName(m_instName); + inst->setIconKey(m_instIcon); + emitSucceeded(); } void InstanceCopyTask::copyAborted() { - emitFailed(tr("Instance folder copy has been aborted.")); - return; + emitFailed(tr("Instance folder copy has been aborted.")); + return; } diff --git a/api/logic/InstanceCopyTask.h b/api/logic/InstanceCopyTask.h index a8dc9783..8dd55b40 100644 --- a/api/logic/InstanceCopyTask.h +++ b/api/logic/InstanceCopyTask.h @@ -11,23 +11,21 @@ #include "BaseInstance.h" #include "InstanceTask.h" -class BaseInstanceProvider; - class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves); + explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves); protected: - //! Entry point for tasks. - virtual void executeTask() override; - void copyFinished(); - void copyAborted(); + //! Entry point for tasks. + virtual void executeTask() override; + void copyFinished(); + void copyAborted(); private: /* data */ - InstancePtr m_origInstance; - QFuture m_copyFuture; - QFutureWatcher m_copyFutureWatcher; - std::unique_ptr m_matcher; + InstancePtr m_origInstance; + QFuture m_copyFuture; + QFutureWatcher m_copyFutureWatcher; + std::unique_ptr m_matcher; }; diff --git a/api/logic/InstanceCreationTask.cpp b/api/logic/InstanceCreationTask.cpp index 6dc2496c..978d4687 100644 --- a/api/logic/InstanceCreationTask.cpp +++ b/api/logic/InstanceCreationTask.cpp @@ -1,5 +1,4 @@ #include "InstanceCreationTask.h" -#include "BaseInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" @@ -9,25 +8,24 @@ InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version) { - m_version = version; + m_version = version; } void InstanceCreationTask::executeTask() { - setStatus(tr("Creating instance from version %1").arg(m_version->name())); - { - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->suspendSave(); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - auto components = inst.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - inst.setName(m_instName); - inst.setIconKey(m_instIcon); - inst.init(); - instanceSettings->resumeSave(); - } - emitSucceeded(); + setStatus(tr("Creating instance from version %1").arg(m_version->name())); + { + auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->suspendSave(); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); + auto components = inst.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_version->descriptor(), true); + inst.setName(m_instName); + inst.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + } + emitSucceeded(); } diff --git a/api/logic/InstanceCreationTask.h b/api/logic/InstanceCreationTask.h index e06eacbb..154a854f 100644 --- a/api/logic/InstanceCreationTask.h +++ b/api/logic/InstanceCreationTask.h @@ -10,14 +10,14 @@ class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit InstanceCreationTask(BaseVersionPtr version); + explicit InstanceCreationTask(BaseVersionPtr version); protected: - //! Entry point for tasks. - virtual void executeTask() override; + //! Entry point for tasks. + virtual void executeTask() override; private: /* data */ - BaseVersionPtr m_version; + BaseVersionPtr m_version; }; diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp index b5375cbc..1e93174c 100644 --- a/api/logic/InstanceImportTask.cpp +++ b/api/logic/InstanceImportTask.cpp @@ -1,12 +1,12 @@ #include "InstanceImportTask.h" #include "BaseInstance.h" -#include "BaseInstanceProvider.h" #include "FileSystem.h" #include "Env.h" #include "MMCZip.h" #include "NullInstance.h" #include "settings/INISettingsObject.h" #include "icons/IIconList.h" +#include "icons/IconUtils.h" #include // FIXME: this does not belong here, it's Minecraft/Flame specific @@ -18,394 +18,394 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl) { - m_sourceUrl = sourceUrl; + m_sourceUrl = sourceUrl; } void InstanceImportTask::executeTask() { - InstancePtr newInstance; + InstancePtr newInstance; - if (m_sourceUrl.isLocalFile()) - { - m_archivePath = m_sourceUrl.toLocalFile(); - processZipPack(); - } - else - { - setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); - m_downloadRequired = true; + if (m_sourceUrl.isLocalFile()) + { + m_archivePath = m_sourceUrl.toLocalFile(); + processZipPack(); + } + 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(); - } + 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() { - processZipPack(); - m_filesNetJob.reset(); + processZipPack(); + m_filesNetJob.reset(); } void InstanceImportTask::downloadFailed(QString reason) { - emitFailed(reason); - m_filesNetJob.reset(); + emitFailed(reason); + m_filesNetJob.reset(); } void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) { - setProgress(current / 2, total); + setProgress(current / 2, total); } void InstanceImportTask::processZipPack() { - setStatus(tr("Extracting modpack")); - QDir extractDir(m_stagingPath); - qDebug() << "Attempting to create instance from" << m_archivePath; + setStatus(tr("Extracting modpack")); + QDir extractDir(m_stagingPath); + qDebug() << "Attempting to create instance from" << m_archivePath; - // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) - { - emitFailed(tr("Unable to open supplied modpack zip file.")); - return; - } + // open the zip and find relevant files in it + m_packZip.reset(new QuaZip(m_archivePath)); + if (!m_packZip->open(QuaZip::mdUnzip)) + { + emitFailed(tr("Unable to open supplied modpack zip file.")); + return; + } - QStringList blacklist = {"instance.cfg", "manifest.json"}; - QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); - QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); - QString root; - if(!mmcFound.isNull()) - { - // process as MultiMC instance/pack - qDebug() << "MultiMC:" << mmcFound; - root = mmcFound; - m_modpackType = ModpackType::MultiMC; - } - else if(!flameFound.isNull()) - { - // process as Flame pack - qDebug() << "Flame:" << flameFound; - root = flameFound; - m_modpackType = ModpackType::Flame; - } + QStringList blacklist = {"instance.cfg", "manifest.json"}; + QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); + QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); + QString root; + if(!mmcFound.isNull()) + { + // process as MultiMC instance/pack + qDebug() << "MultiMC:" << mmcFound; + root = mmcFound; + m_modpackType = ModpackType::MultiMC; + } + else if(!flameFound.isNull()) + { + // process as Flame pack + qDebug() << "Flame:" << flameFound; + root = flameFound; + m_modpackType = ModpackType::Flame; + } - if(m_modpackType == ModpackType::Unknown) - { - emitFailed(tr("Archive does not contain a recognized modpack type.")); - return; - } + if(m_modpackType == ModpackType::Unknown) + { + emitFailed(tr("Archive does not contain a recognized modpack type.")); + return; + } - // make sure we extract just the pack - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); - connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &InstanceImportTask::extractAborted); - m_extractFutureWatcher.setFuture(m_extractFuture); + // make sure we extract just the pack + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); + connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); + connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &InstanceImportTask::extractAborted); + m_extractFutureWatcher.setFuture(m_extractFuture); } void InstanceImportTask::extractFinished() { - m_packZip.reset(); - if (m_extractFuture.result().isEmpty()) - { - emitFailed(tr("Failed to extract modpack")); - return; - } - QDir extractDir(m_stagingPath); + m_packZip.reset(); + if (m_extractFuture.result().isEmpty()) + { + emitFailed(tr("Failed to extract modpack")); + return; + } + QDir extractDir(m_stagingPath); - qDebug() << "Fixing permissions for extracted pack files..."; - QDirIterator it(extractDir, QDirIterator::Subdirectories); - while (it.hasNext()) - { - auto filepath = it.next(); - QFileInfo file(filepath); - auto permissions = QFile::permissions(filepath); - auto origPermissions = permissions; - if(file.isDir()) - { - // Folder +rwx for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; - } - else - { - // File +rw for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - } - if(origPermissions != permissions) - { - if(!QFile::setPermissions(filepath, permissions)) - { - logWarning(tr("Could not fix permissions for %1").arg(filepath)); - } - else - { - qDebug() << "Fixed" << filepath; - } - } - } + qDebug() << "Fixing permissions for extracted pack files..."; + QDirIterator it(extractDir, QDirIterator::Subdirectories); + while (it.hasNext()) + { + auto filepath = it.next(); + QFileInfo file(filepath); + auto permissions = QFile::permissions(filepath); + auto origPermissions = permissions; + if(file.isDir()) + { + // Folder +rwx for current user + permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; + } + else + { + // File +rw for current user + permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; + } + if(origPermissions != permissions) + { + if(!QFile::setPermissions(filepath, permissions)) + { + logWarning(tr("Could not fix permissions for %1").arg(filepath)); + } + else + { + qDebug() << "Fixed" << filepath; + } + } + } - switch(m_modpackType) - { - case ModpackType::Flame: - processFlame(); - return; - case ModpackType::MultiMC: - processMultiMC(); - return; - case ModpackType::Unknown: - emitFailed(tr("Archive does not contain a recognized modpack type.")); - return; - } + switch(m_modpackType) + { + case ModpackType::Flame: + processFlame(); + return; + case ModpackType::MultiMC: + processMultiMC(); + return; + case ModpackType::Unknown: + emitFailed(tr("Archive does not contain a recognized modpack type.")); + return; + } } void InstanceImportTask::extractAborted() { - emitFailed(tr("Instance import has been aborted.")); - return; + emitFailed(tr("Instance import has been aborted.")); + return; } void InstanceImportTask::processFlame() { - const static QMap forgemap = { - {"1.2.5", "3.4.9.171"}, - {"1.4.2", "6.0.1.355"}, - {"1.4.7", "6.6.2.534"}, - {"1.5.2", "7.8.1.737"} - }; - Flame::Manifest pack; - try - { - QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); - Flame::loadManifest(pack, configPath); - QFile::remove(configPath); - } - catch (JSONValidationError & e) - { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - if(!pack.overrides.isEmpty()) - { - QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); - if (QFile::exists(overridePath)) - { - QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) - { - emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); - return; - } - } - else - { - logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); - } - } + const static QMap forgemap = { + {"1.2.5", "3.4.9.171"}, + {"1.4.2", "6.0.1.355"}, + {"1.4.7", "6.6.2.534"}, + {"1.5.2", "7.8.1.737"} + }; + Flame::Manifest pack; + try + { + QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); + Flame::loadManifest(pack, configPath); + QFile::remove(configPath); + } + catch (const JSONValidationError &e) + { + emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); + return; + } + if(!pack.overrides.isEmpty()) + { + QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); + if (QFile::exists(overridePath)) + { + QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); + if (!QFile::rename(overridePath, mcPath)) + { + emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); + return; + } + } + else + { + logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); + } + } - QString forgeVersion; - for(auto &loader: pack.minecraft.modLoaders) - { - auto id = loader.id; - if(id.startsWith("forge-")) - { - id.remove("forge-"); - forgeVersion = id; - continue; - } - logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); - } + QString forgeVersion; + for(auto &loader: pack.minecraft.modLoaders) + { + auto id = loader.id; + if(id.startsWith("forge-")) + { + id.remove("forge-"); + forgeVersion = id; + continue; + } + logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); + } - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto mcVersion = pack.minecraft.version; - // Hack to correct some 'special sauce'... - if(mcVersion.endsWith('.')) - { - mcVersion.remove(QRegExp("[.]+$")); - logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); - } - auto components = instance.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", mcVersion, true); - if(!forgeVersion.isEmpty()) - { - // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. - if(forgeVersion == "recommended") - { - if(forgemap.contains(mcVersion)) - { - forgeVersion = forgemap[mcVersion]; - } - else - { - logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion)); - } - } - components->setComponentVersion("net.minecraftforge", forgeVersion); - } - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - if(pack.name.contains("Direwolf20")) - { - instance.setIconKey("steve"); - } - else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) - { - instance.setIconKey("ftb_logo"); - } - else - { - // default to something other than the MultiMC default to distinguish these - instance.setIconKey("flame"); - } - } - instance.init(); - QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); - QFileInfo jarmodsInfo(jarmodsPath); - if(jarmodsInfo.isDir()) - { - // install all the jar mods - qDebug() << "Found jarmods:"; - QDir jarmodsDir(jarmodsPath); - QStringList jarMods; - for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << info.fileName(); - jarMods.push_back(info.absoluteFilePath()); - } - auto profile = instance.getComponentList(); - profile->installJarMods(jarMods); - // nuke the original files - FS::deletePath(jarmodsPath); - } - instance.setName(m_instName); - m_modIdResolver.reset(new Flame::FileResolvingTask(pack)); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() - { - auto results = m_modIdResolver->getResults(); - m_filesNetJob.reset(new NetJob(tr("Mod download"))); - for(auto result: results.files) - { - QString filename = result.fileName; - if(!result.required) - { - filename += ".disabled"; - } + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto mcVersion = pack.minecraft.version; + // Hack to correct some 'special sauce'... + if(mcVersion.endsWith('.')) + { + mcVersion.remove(QRegExp("[.]+$")); + logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); + } + auto components = instance.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", mcVersion, true); + if(!forgeVersion.isEmpty()) + { + // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. + if(forgeVersion == "recommended") + { + if(forgemap.contains(mcVersion)) + { + forgeVersion = forgemap[mcVersion]; + } + else + { + logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion)); + } + } + components->setComponentVersion("net.minecraftforge", forgeVersion); + } + if (m_instIcon != "default") + { + instance.setIconKey(m_instIcon); + } + else + { + if(pack.name.contains("Direwolf20")) + { + instance.setIconKey("steve"); + } + else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) + { + instance.setIconKey("ftb_logo"); + } + else + { + // default to something other than the MultiMC default to distinguish these + instance.setIconKey("flame"); + } + } + QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); + QFileInfo jarmodsInfo(jarmodsPath); + if(jarmodsInfo.isDir()) + { + // install all the jar mods + qDebug() << "Found jarmods:"; + QDir jarmodsDir(jarmodsPath); + QStringList jarMods; + for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) + { + qDebug() << info.fileName(); + jarMods.push_back(info.absoluteFilePath()); + } + auto profile = instance.getComponentList(); + profile->installJarMods(jarMods); + // nuke the original files + FS::deletePath(jarmodsPath); + } + instance.setName(m_instName); + m_modIdResolver.reset(new Flame::FileResolvingTask(pack)); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() + { + auto results = m_modIdResolver->getResults(); + m_filesNetJob.reset(new NetJob(tr("Mod download"))); + for(auto result: results.files) + { + QString filename = result.fileName; + if(!result.required) + { + filename += ".disabled"; + } - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath , relpath); + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath , relpath); - switch(result.type) - { - case Flame::File::Type::Folder: - { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: - { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() - { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) - { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); - } - ); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) - { - m_modIdResolver.reset(); - emitFailed(tr("Unable to resolve mod IDs:\n") + reason); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status) - { - setStatus(status); - }); - m_modIdResolver->start(); + switch(result.type) + { + case Flame::File::Type::Folder: + { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: + { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_filesNetJob->addNetAction(dl); + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + m_modIdResolver.reset(); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() + { + m_filesNetJob.reset(); + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) + { + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) + { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); + } + ); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) + { + m_modIdResolver.reset(); + emitFailed(tr("Unable to resolve mod IDs:\n") + reason); + }); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total) + { + setProgress(current, total); + }); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status) + { + setStatus(status); + }); + m_modIdResolver->start(); } void InstanceImportTask::processMultiMC() { - // FIXME: copy from FolderInstanceProvider!!! FIX IT!!! - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); + // FIXME: copy from FolderInstanceProvider!!! FIX IT!!! + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + instanceSettings->registerSetting("InstanceType", "Legacy"); - NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - // reset time played on import... because packs. - instance.resetTimePlayed(); + // reset time played on import... because packs. + instance.resetTimePlayed(); - // set a new nice name - instance.setName(m_instName); + // 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}); - } - } - emitSucceeded(); + // if the icon was specified by user, use that. otherwise pull icon from the pack + if (m_instIcon != "default") + { + instance.setIconKey(m_instIcon); + } + else + { + m_instIcon = instance.iconKey(); + + auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); + if (!importIconPath.isNull() && QFile::exists(importIconPath)) + { + // import icon + auto iconList = ENV.icons(); + if (iconList->iconFileExists(m_instIcon)) + { + iconList->deleteIcon(m_instIcon); + } + iconList->installIcons({importIconPath}); + } + } + emitSucceeded(); } diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h index 06778dfe..d326391b 100644 --- a/api/logic/InstanceImportTask.h +++ b/api/logic/InstanceImportTask.h @@ -10,46 +10,45 @@ #include "QObjectPtr.h" class QuaZip; -class BaseInstanceProvider; namespace Flame { - class FileResolvingTask; + class FileResolvingTask; } class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit InstanceImportTask(const QUrl sourceUrl); + explicit InstanceImportTask(const QUrl sourceUrl); protected: - //! Entry point for tasks. - virtual void executeTask() override; + //! Entry point for tasks. + virtual void executeTask() override; private: - void processZipPack(); - void processMultiMC(); - void processFlame(); + void processZipPack(); + void processMultiMC(); + void processFlame(); private slots: - void downloadSucceeded(); - void downloadFailed(QString reason); - void downloadProgressChanged(qint64 current, qint64 total); - void extractFinished(); - void extractAborted(); + void downloadSucceeded(); + void downloadFailed(QString reason); + void downloadProgressChanged(qint64 current, qint64 total); + void extractFinished(); + void extractAborted(); private: /* data */ - NetJobPtr m_filesNetJob; - shared_qobject_ptr m_modIdResolver; - QUrl m_sourceUrl; - QString m_archivePath; - bool m_downloadRequired = false; - std::unique_ptr m_packZip; - QFuture m_extractFuture; - QFutureWatcher m_extractFutureWatcher; - enum class ModpackType{ - Unknown, - MultiMC, - Flame - } m_modpackType = ModpackType::Unknown; + NetJobPtr m_filesNetJob; + shared_qobject_ptr m_modIdResolver; + QUrl m_sourceUrl; + QString m_archivePath; + bool m_downloadRequired = false; + std::unique_ptr m_packZip; + QFuture m_extractFuture; + QFutureWatcher m_extractFutureWatcher; + enum class ModpackType{ + Unknown, + MultiMC, + Flame + } m_modpackType = ModpackType::Unknown; }; diff --git a/api/logic/InstanceList.cpp b/api/logic/InstanceList.cpp index 75b523e4..58e2ee0c 100644 --- a/api/logic/InstanceList.cpp +++ b/api/logic/InstanceList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,49 @@ */ #include +#include #include #include #include #include #include +#include #include +#include +#include +#include +#include #include "InstanceList.h" #include "BaseInstance.h" +#include "InstanceTask.h" +#include "settings/INISettingsObject.h" +#include "minecraft/legacy/LegacyInstance.h" +#include "NullInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "FileSystem.h" +#include "ExponentialSeries.h" +#include "WatchLock.h" -#include "FolderInstanceProvider.h" +const static int GROUP_FILE_FORMAT_VERSION = 1; -InstanceList::InstanceList(QObject *parent) - : QAbstractListModel(parent) +InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent) + : QAbstractListModel(parent), m_globalSettings(settings) { - resumeWatch(); + resumeWatch(); + // Create aand normalize path + if (!QDir::current().exists(instDir)) + { + QDir::current().mkpath(instDir); + } + + connect(this, &InstanceList::instancesChanged, this, &InstanceList::providerUpdated); + + // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! + m_instDir = QDir(instDir).canonicalPath(); + m_watcher = new QFileSystemWatcher(this); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &InstanceList::instanceDirContentsChanged); + m_watcher->addPath(m_instDir); } InstanceList::~InstanceList() @@ -38,310 +65,773 @@ InstanceList::~InstanceList() int InstanceList::rowCount(const QModelIndex &parent) const { - Q_UNUSED(parent); - return m_instances.count(); + 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()); + 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(index.internalPointer()); - switch (role) - { - case InstancePointerRole: - { - QVariant v = qVariantFromValue((void *)pdata); - return v; - } - case InstanceIDRole: + if (!index.isValid()) + { + return QVariant(); + } + BaseInstance *pdata = static_cast(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(); + case Qt::EditRole: + case Qt::DisplayRole: + { + return pdata->name(); + } + case Qt::AccessibleTextRole: + { + return tr("%1 Instance").arg(pdata->name()); + } + case Qt::ToolTipRole: + { + return pdata->instanceRoot(); + } + case Qt::DecorationRole: + { + return pdata->iconKey(); + } + // HACK: see GroupView.h in gui! + case GroupRole: + { + return getInstanceGroup(pdata->id()); + } + default: + break; + } + return QVariant(); +} + +bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid()) + { + return false; + } + if(role != Qt::EditRole) + { + return false; + } + BaseInstance *pdata = static_cast(index.internalPointer()); + auto newName = value.toString(); + if(pdata->name() == newName) + { + return true; + } + pdata->setName(newName); + return true; } Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const { - Qt::ItemFlags f; - if (index.isValid()) - { - f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable); - } - return f; + Qt::ItemFlags f; + if (index.isValid()) + { + f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + } + return f; +} + +GroupId InstanceList::getInstanceGroup(const InstanceId& id) const +{ + auto inst = getInstanceById(id); + if(!inst) + { + return GroupId(); + } + auto iter = m_groupMap.find(inst->id()); + if(iter != m_groupMap.end()) + { + return *iter; + } + return GroupId(); +} + +void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name) +{ + auto inst = getInstanceById(id); + if(!inst) + { + qDebug() << "Attempt to set a null instance's group"; + return; + } + + bool changed = false; + auto iter = m_groupMap.find(inst->id()); + if(iter != m_groupMap.end()) + { + if(*iter != name) + { + *iter = name; + changed = true; + } + } + else + { + changed = true; + m_groupMap[id] = name; + } + + if(changed) + { + m_groups.insert(name); + auto idx = getInstIndex(inst.get()); + emit dataChanged(index(idx), index(idx), {GroupRole}); + saveGroupList(); + } } QStringList InstanceList::getGroups() { - return m_groups.toList(); + 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()); - } - } + bool removed = false; + qDebug() << "Delete group" << name; + for(auto & instance: m_instances) + { + const auto & instID = instance->id(); + auto instGroupName = getInstanceGroup(instID); + if(instGroupName == name) + { + m_groupMap.remove(instID); + qDebug() << "Remove" << instID << "from group" << name; + removed = true; + auto idx = getInstIndex(instance.get()); + if(idx > 0) + { + emit dataChanged(index(idx), index(idx), {GroupRole}); + } + } + } + if(removed) + { + saveGroupList(); + } +} + +void InstanceList::deleteInstance(const InstanceId& id) +{ + auto inst = getInstanceById(id); + if(!inst) + { + qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?)."; + return; + } + + if(m_groupMap.remove(id)) + { + saveGroupList(); + } + + qDebug() << "Will delete instance" << id; + if(!FS::deletePath(inst->instanceRoot())) + { + qWarning() << "Deletion of instance" << id << "has not been completely successful ..."; + return; + } + + qDebug() << "Instance" << id << "has been deleted by MultiMC."; } static QMap getIdMapping(const QList &list) { - QMap 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 newList; - - auto processIds = [&](BaseInstanceProvider * provider, QList 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; + QMap 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; +} + +QList< InstanceId > InstanceList::discoverInstances() +{ + qDebug() << "Discovering instances in" << m_instDir; + QList out; + QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); + while (iter.hasNext()) + { + QString subDir = iter.next(); + QFileInfo dirInfo(subDir); + if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists()) + continue; + // if it is a symlink, ignore it if it goes to the instance folder + if(dirInfo.isSymLink()) + { + QFileInfo targetInfo(dirInfo.symLinkTarget()); + QFileInfo instDirInfo(m_instDir); + if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) + { + qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder"; + continue; + } + } + auto id = dirInfo.fileName(); + out.append(id); + qDebug() << "Found instance ID" << id; + } + instanceSet = out.toSet(); + m_instancesProbed = true; + return out; +} + +InstanceList::InstListError InstanceList::loadList() +{ + auto existingIds = getIdMapping(m_instances); + + QList newList; + + for(auto & id: discoverInstances()) + { + if(existingIds.contains(id)) + { + auto instPair = existingIds[id]; + existingIds.remove(id); + qDebug() << "Should keep and soft-reload" << id; + } + else + { + InstancePtr instPtr = loadInstance(id); + if(instPtr) + { + newList.append(instPtr); + } + } + } + + // 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; + 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_dirty = false; + return NoError; } void InstanceList::saveNow() { - for(auto & item: m_instances) - { - item->saveNow(); - } + for(auto & item: m_instances) + { + item->saveNow(); + } } void InstanceList::add(const QList &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(); + 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(); - } + if(m_watchLevel > 0) + { + qWarning() << "Bad suspend level resume in instance list"; + return; + } + m_watchLevel++; + if(m_watchLevel > 0 && m_dirty) + { + loadList(); + } } void InstanceList::suspendWatch() { - m_watchLevel --; + m_watchLevel --; } void InstanceList::providerUpdated() { - auto provider = dynamic_cast(QObject::sender()); - if(!provider) - { - qWarning() << "InstanceList::providerUpdated triggered by a non-provider"; - return; - } - m_updatedProviders.insert(provider); - if(m_watchLevel == 1) - { - loadList(); - } + m_dirty = true; + if(m_watchLevel == 1) + { + loadList(); + } } -void InstanceList::groupsPublished(QSet newGroups) +InstancePtr InstanceList::getInstanceById(QString instId) const { - m_groups.unite(newGroups); + if(instId.isEmpty()) + return InstancePtr(); + for(auto & inst: m_instances) + { + if (inst->id() == instId) + { + return inst; + } + } + return InstancePtr(); } -void InstanceList::addInstanceProvider(BaseInstanceProvider* provider) +QModelIndex InstanceList::getInstanceIndexById(const QString &id) const { - connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated); - connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished); - m_providers.append(provider); + return index(getInstIndex(getInstanceById(id).get())); } -InstancePtr InstanceList::getInstanceById(QString instId) const +int InstanceList::getInstIndex(BaseInstance *inst) const { - if(instId.isEmpty()) - return InstancePtr(); - for(auto & inst: m_instances) - { - if (inst->id() == instId) - { - return inst; - } - } - return InstancePtr(); + int count = m_instances.count(); + for (int i = 0; i < count; i++) + { + if (inst == m_instances[i].get()) + { + return i; + } + } + return -1; } -QModelIndex InstanceList::getInstanceIndexById(const QString &id) const +void InstanceList::propertiesChanged(BaseInstance *inst) { - return index(getInstIndex(getInstanceById(id).get())); + int i = getInstIndex(inst); + if (i != -1) + { + emit dataChanged(index(i), index(i)); + } } -int InstanceList::getInstIndex(BaseInstance *inst) const +InstancePtr InstanceList::loadInstance(const InstanceId& id) { - int count = m_instances.count(); - for (int i = 0; i < count; i++) - { - if (inst == m_instances[i].get()) - { - return i; - } - } - return -1; + if(!m_groupsLoaded) + { + loadGroupList(); + } + + auto instanceRoot = FS::PathCombine(m_instDir, id); + auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); + InstancePtr inst; + + instanceSettings->registerSetting("InstanceType", "Legacy"); + + QString inst_type = instanceSettings->get("InstanceType").toString(); + + if (inst_type == "OneSix" || inst_type == "Nostalgia") + { + inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); + } + else if (inst_type == "Legacy") + { + inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot)); + } + else + { + inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); + } + qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); + return inst; } -void InstanceList::propertiesChanged(BaseInstance *inst) +void InstanceList::saveGroupList() { - int i = getInstIndex(inst); - if (i != -1) - { - emit dataChanged(index(i), index(i)); - } + qDebug() << "Will save group list now."; + if(!m_instancesProbed) + { + qDebug() << "Group saving prevented because we don't know the full list of instances yet."; + return; + } + WatchLock foo(m_watcher, m_instDir); + QString groupFileName = m_instDir + "/instgroups.json"; + QMap> reverseGroupMap; + for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); iter++) + { + QString id = iter.key(); + QString group = iter.value(); + if (group.isEmpty()) + continue; + if(!instanceSet.contains(id)) + { + qDebug() << "Skipping saving missing instance" << id << "to groups list."; + continue; + } + + if (!reverseGroupMap.count(group)) + { + QSet set; + set.insert(id); + reverseGroupMap[group] = set; + } + else + { + QSet &set = reverseGroupMap[group]; + set.insert(id); + } + } + QJsonObject toplevel; + toplevel.insert("formatVersion", QJsonValue(QString("1"))); + QJsonObject groupsArr; + for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) + { + auto list = iter.value(); + auto name = iter.key(); + QJsonObject groupObj; + QJsonArray instanceArr; + groupObj.insert("hidden", QJsonValue(QString("false"))); + for (auto item : list) + { + instanceArr.append(QJsonValue(item)); + } + groupObj.insert("instances", instanceArr); + groupsArr.insert(name, groupObj); + } + toplevel.insert("groups", groupsArr); + QJsonDocument doc(toplevel); + try + { + FS::write(groupFileName, doc.toJson()); + qDebug() << "Group list saved."; + } + catch (const FS::FileSystemException &e) + { + qCritical() << "Failed to write instance group file :" << e.cause(); + } } + +void InstanceList::loadGroupList() +{ + qDebug() << "Will load group list now."; + QSet groupSet; + + QString groupFileName = m_instDir + "/instgroups.json"; + + // if there's no group file, fail + if (!QFileInfo(groupFileName).exists()) + return; + + QByteArray jsonData; + try + { + jsonData = FS::read(groupFileName); + } + catch (const FS::FileSystemException &e) + { + qCritical() << "Failed to read instance group file :" << e.cause(); + return; + } + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); + + // if the json was bad, fail + if (error.error != QJsonParseError::NoError) + { + qCritical() << QString("Failed to parse instance group file: %1 at offset %2") + .arg(error.errorString(), QString::number(error.offset)) + .toUtf8(); + return; + } + + // if the root of the json wasn't an object, fail + if (!jsonDoc.isObject()) + { + qWarning() << "Invalid group file. Root entry should be an object."; + return; + } + + QJsonObject rootObj = jsonDoc.object(); + + // Make sure the format version matches, otherwise fail. + if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) + return; + + // Get the groups. if it's not an object, fail + if (!rootObj.value("groups").isObject()) + { + qWarning() << "Invalid group list JSON: 'groups' should be an object."; + return; + } + + m_groupMap.clear(); + + // Iterate through all the groups. + QJsonObject groupMapping = rootObj.value("groups").toObject(); + for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) + { + QString groupName = iter.key(); + + // If not an object, complain and skip to the next one. + if (!iter.value().isObject()) + { + qWarning() << QString("Group '%1' in the group list should " + "be an object.") + .arg(groupName) + .toUtf8(); + continue; + } + + QJsonObject groupObj = iter.value().toObject(); + if (!groupObj.value("instances").isArray()) + { + qWarning() << QString("Group '%1' in the group list is invalid. " + "It should contain an array " + "called 'instances'.") + .arg(groupName) + .toUtf8(); + continue; + } + + // keep a list/set of groups for choosing + groupSet.insert(groupName); + + // Iterate through the list of instances in the group. + QJsonArray instancesArray = groupObj.value("instances").toArray(); + + for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) + { + m_groupMap[(*iter2).toString()] = groupName; + } + } + m_groupsLoaded = true; + m_groups.unite(groupSet); + qDebug() << "Group list loaded."; +} + +void InstanceList::instanceDirContentsChanged(const QString& path) +{ + Q_UNUSED(path); + emit instancesChanged(); +} + +void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) +{ + QString newInstDir = QDir(value.toString()).canonicalPath(); + if(newInstDir != m_instDir) + { + if(m_groupsLoaded) + { + saveGroupList(); + } + m_instDir = newInstDir; + m_groupsLoaded = false; + emit instancesChanged(); + } +} + +class InstanceStaging : public Task +{ +Q_OBJECT + const unsigned minBackoff = 1; + const unsigned maxBackoff = 16; +public: + InstanceStaging ( + InstanceList * parent, + Task * child, + const QString & stagingPath, + const QString& instanceName, + const QString& groupName ) + : backoff(minBackoff, maxBackoff) + { + m_parent = parent; + m_child.reset(child); + connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); + connect(child, &Task::failed, this, &InstanceStaging::childFailed); + connect(child, &Task::status, this, &InstanceStaging::setStatus); + connect(child, &Task::progress, this, &InstanceStaging::setProgress); + m_instanceName = instanceName; + m_groupName = groupName; + m_stagingPath = stagingPath; + m_backoffTimer.setSingleShot(true); + connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); + } + + virtual ~InstanceStaging() {}; + + + // FIXME/TODO: add ability to abort during instance commit retries + bool abort() override + { + if(m_child && m_child->canAbort()) + { + return m_child->abort(); + } + return false; + } + bool canAbort() const override + { + if(m_child && m_child->canAbort()) + { + return true; + } + return false; + } + +protected: + virtual void executeTask() override + { + m_child->start(); + } + QStringList warnings() const override + { + return m_child->warnings(); + } + +private slots: + void childSucceded() + { + unsigned sleepTime = backoff(); + if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) + { + emitSucceeded(); + return; + } + // we actually failed, retry? + if(sleepTime == maxBackoff) + { + emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); + return; + } + qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; + m_backoffTimer.start(sleepTime * 500); + } + void childFailed(const QString & reason) + { + m_parent->destroyStagingPath(m_stagingPath); + emitFailed(reason); + } + +private: + /* + * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. + * Basically, it starts messing things up while MultiMC is extracting/creating instances + * and causes that horrible failure that is NTFS to lock files in place because they are open. + */ + ExponentialSeries backoff; + QString m_stagingPath; + InstanceList * m_parent; + unique_qobject_ptr m_child; + QString m_instanceName; + QString m_groupName; + QTimer m_backoffTimer; +}; + +Task * InstanceList::wrapInstanceTask(InstanceTask * task) +{ + auto stagingPath = getStagedInstancePath(); + task->setStagingPath(stagingPath); + task->setParentSettings(m_globalSettings); + return new InstanceStaging(this, task, stagingPath, task->name(), task->group()); +} + +QString InstanceList::getStagedInstancePath() +{ + QString key = QUuid::createUuid().toString(); + QString relPath = FS::PathCombine("_MMC_TEMP/" , key); + QDir rootPath(m_instDir); + auto path = FS::PathCombine(m_instDir, relPath); + if(!rootPath.mkpath(relPath)) + { + return QString(); + } + return path; +} + +bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) +{ + QDir dir; + QString instID = FS::DirNameFromString(instanceName, m_instDir); + { + WatchLock lock(m_watcher, m_instDir); + QString destination = FS::PathCombine(m_instDir, instID); + if(!dir.rename(path, destination)) + { + qWarning() << "Failed to move" << path << "to" << destination; + return false; + } + m_groupMap[instID] = groupName; + instanceSet.insert(instID); + m_groups.insert(groupName); + emit instancesChanged(); + emit instanceSelectRequest(instID); + } + saveGroupList(); + return true; +} + +bool InstanceList::destroyStagingPath(const QString& keyPath) +{ + return FS::deletePath(keyPath); +} + +#include "InstanceList.moc" \ No newline at end of file diff --git a/api/logic/InstanceList.h b/api/logic/InstanceList.h index bb879c83..e0abd890 100644 --- a/api/logic/InstanceList.h +++ b/api/logic/InstanceList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,85 +21,146 @@ #include #include "BaseInstance.h" -#include "BaseInstanceProvider.h" #include "multimc_logic_export.h" #include "QObjectPtr.h" -class BaseInstance; +class QFileSystemWatcher; +class InstanceTask; +using InstanceId = QString; +using GroupId = QString; +using InstanceLocator = std::pair; + +enum class InstCreateError +{ + NoCreateError = 0, + NoSuchVersion, + UnknownCreateError, + InstExists, + CantCreateDir +}; + +enum class GroupsState +{ + NotLoaded, + Steady, + Dirty +}; + class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - explicit InstanceList(QObject *parent = 0); - virtual ~InstanceList(); + explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0); + virtual ~InstanceList(); public: - QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - - 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); - void saveNow(); - - /// Add an instance provider. Takes ownership of it. Should only be done before the first load. - void addInstanceProvider(BaseInstanceProvider * provider); - - InstancePtr getInstanceById(QString id) const; - QModelIndex getInstanceIndexById(const QString &id) const; - QStringList getGroups(); - - void deleteGroup(const QString & name); + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + bool setData(const QModelIndex & index, const QVariant & value, int role) override; + + enum AdditionalRoles + { + 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(); + void saveNow(); + + + InstancePtr getInstanceById(QString id) const; + QModelIndex getInstanceIndexById(const QString &id) const; + QStringList getGroups(); + GroupId getInstanceGroup(const InstanceId & id) const; + void setInstanceGroup(const InstanceId & id, const GroupId& name); + + void deleteGroup(const GroupId & name); + void deleteInstance(const InstanceId & id); + + // Wrap an instance creation task in some more task machinery and make it ready to be used + Task * wrapInstanceTask(InstanceTask * task); + + /** + * Create a new empty staging area for instance creation and @return a path/key top commit it later. + * Used by instance manipulation tasks. + */ + QString getStagedInstancePath(); + + /** + * Commit the staging area given by @keyPath to the provider - used when creation succeeds. + * Used by instance manipulation tasks. + */ + bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName); + + /** + * Destroy a previously created staging area given by @keyPath - used when creation fails. + * Used by instance manipulation tasks. + */ + bool destroyStagingPath(const QString & keyPath); signals: - void dataIsInvalid(); + void dataIsInvalid(); + void instancesChanged(); + void instanceSelectRequest(QString instanceId); + void groupsChanged(QSet groups); + +public slots: + void on_InstFolderChanged(const Setting &setting, QVariant value); private slots: - void propertiesChanged(BaseInstance *inst); - void groupsPublished(QSet); - void providerUpdated(); + void propertiesChanged(BaseInstance *inst); + void providerUpdated(); + void instanceDirContentsChanged(const QString &path); + +private: + int getInstIndex(BaseInstance *inst) const; + void suspendWatch(); + void resumeWatch(); + void add(const QList &list); + void loadGroupList(); + void saveGroupList(); + QList discoverInstances(); + InstancePtr loadInstance(const InstanceId& id); private: - int getInstIndex(BaseInstance *inst) const; - void suspendWatch(); - void resumeWatch(); - void add(const QList &list); - -protected: - int m_watchLevel = 0; - QSet m_updatedProviders; - QList m_instances; - QSet m_groups; - QVector> m_providers; + int m_watchLevel = 0; + bool m_dirty = false; + QList m_instances; + QSet m_groups; + + SettingsObjectPtr m_globalSettings; + QString m_instDir; + QFileSystemWatcher * m_watcher; + QMap m_groupMap; + QSet instanceSet; + bool m_groupsLoaded = false; + bool m_instancesProbed = false; }; diff --git a/api/logic/InstanceTask.h b/api/logic/InstanceTask.h index 8fc98eb7..c5f6c7fd 100644 --- a/api/logic/InstanceTask.h +++ b/api/logic/InstanceTask.h @@ -4,52 +4,50 @@ #include "multimc_logic_export.h" #include "settings/SettingsObject.h" -class BaseInstanceProvider; - class MULTIMC_LOGIC_EXPORT InstanceTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit InstanceTask(); - virtual ~InstanceTask(); - - void setParentSettings(SettingsObjectPtr settings) - { - m_globalSettings = settings; - } - - void setStagingPath(const QString &stagingPath) - { - m_stagingPath = stagingPath; - } - - void setName(const QString &name) - { - m_instName = name; - } - QString name() const - { - return m_instName; - } - - void setIcon(const QString &icon) - { - m_instIcon = icon; - } - - void setGroup(const QString &group) - { - m_instGroup = group; - } - QString group() const - { - return m_instGroup; - } + explicit InstanceTask(); + virtual ~InstanceTask(); + + void setParentSettings(SettingsObjectPtr settings) + { + m_globalSettings = settings; + } + + void setStagingPath(const QString &stagingPath) + { + m_stagingPath = stagingPath; + } + + void setName(const QString &name) + { + m_instName = name; + } + QString name() const + { + return m_instName; + } + + void setIcon(const QString &icon) + { + m_instIcon = icon; + } + + void setGroup(const QString &group) + { + m_instGroup = group; + } + QString group() const + { + return m_instGroup; + } protected: /* data */ - SettingsObjectPtr m_globalSettings; - QString m_instName; - QString m_instIcon; - QString m_instGroup; - QString m_stagingPath; + SettingsObjectPtr m_globalSettings; + QString m_instName; + QString m_instIcon; + QString m_instGroup; + QString m_stagingPath; }; diff --git a/api/logic/Json.cpp b/api/logic/Json.cpp index f2cbc8a3..37ada1aa 100644 --- a/api/logic/Json.cpp +++ b/api/logic/Json.cpp @@ -11,262 +11,262 @@ namespace Json { void write(const QJsonDocument &doc, const QString &filename) { - FS::write(filename, doc.toJson()); + FS::write(filename, doc.toJson()); } void write(const QJsonObject &object, const QString &filename) { - write(QJsonDocument(object), filename); + write(QJsonDocument(object), filename); } void write(const QJsonArray &array, const QString &filename) { - write(QJsonDocument(array), filename); + write(QJsonDocument(array), filename); } QByteArray toBinary(const QJsonObject &obj) { - return QJsonDocument(obj).toBinaryData(); + return QJsonDocument(obj).toBinaryData(); } QByteArray toBinary(const QJsonArray &array) { - return QJsonDocument(array).toBinaryData(); + return QJsonDocument(array).toBinaryData(); } QByteArray toText(const QJsonObject &obj) { - return QJsonDocument(obj).toJson(QJsonDocument::Compact); + return QJsonDocument(obj).toJson(QJsonDocument::Compact); } QByteArray toText(const QJsonArray &array) { - return QJsonDocument(array).toJson(QJsonDocument::Compact); + return QJsonDocument(array).toJson(QJsonDocument::Compact); } static bool isBinaryJson(const QByteArray &data) { - decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; - return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; + decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; + return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; } QJsonDocument requireDocument(const QByteArray &data, const QString &what) { - if (isBinaryJson(data)) - { - QJsonDocument doc = QJsonDocument::fromBinaryData(data); - if (doc.isNull()) - { - throw JsonException(what + ": Invalid JSON (binary JSON detected)"); - } - return doc; - } - else - { - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) - { - throw JsonException(what + ": Error parsing JSON: " + error.errorString()); - } - return doc; - } + if (isBinaryJson(data)) + { + QJsonDocument doc = QJsonDocument::fromBinaryData(data); + if (doc.isNull()) + { + throw JsonException(what + ": Invalid JSON (binary JSON detected)"); + } + return doc; + } + else + { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) + { + throw JsonException(what + ": Error parsing JSON: " + error.errorString()); + } + return doc; + } } QJsonDocument requireDocument(const QString &filename, const QString &what) { - return requireDocument(FS::read(filename), what); + return requireDocument(FS::read(filename), what); } QJsonObject requireObject(const QJsonDocument &doc, const QString &what) { - if (!doc.isObject()) - { - throw JsonException(what + " is not an object"); - } - return doc.object(); + if (!doc.isObject()) + { + throw JsonException(what + " is not an object"); + } + return doc.object(); } QJsonArray requireArray(const QJsonDocument &doc, const QString &what) { - if (!doc.isArray()) - { - throw JsonException(what + " is not an array"); - } - return doc.array(); + if (!doc.isArray()) + { + throw JsonException(what + " is not an array"); + } + return doc.array(); } void writeString(QJsonObject &to, const QString &key, const QString &value) { - if (!value.isEmpty()) - { - to.insert(key, value); - } + if (!value.isEmpty()) + { + to.insert(key, value); + } } void writeStringList(QJsonObject &to, const QString &key, const QStringList &values) { - if (!values.isEmpty()) - { - QJsonArray array; - for(auto value: values) - { - array.append(value); - } - to.insert(key, array); - } + if (!values.isEmpty()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value); + } + to.insert(key, array); + } } template<> QJsonValue toJson(const QUrl &url) { - return QJsonValue(url.toString(QUrl::FullyEncoded)); + return QJsonValue(url.toString(QUrl::FullyEncoded)); } template<> QJsonValue toJson(const QByteArray &data) { - return QJsonValue(QString::fromLatin1(data.toHex())); + return QJsonValue(QString::fromLatin1(data.toHex())); } template<> QJsonValue toJson(const QDateTime &datetime) { - return QJsonValue(datetime.toString(Qt::ISODate)); + return QJsonValue(datetime.toString(Qt::ISODate)); } template<> QJsonValue toJson(const QDir &dir) { - return QDir::current().relativeFilePath(dir.absolutePath()); + return QDir::current().relativeFilePath(dir.absolutePath()); } template<> QJsonValue toJson(const QUuid &uuid) { - return uuid.toString(); + return uuid.toString(); } template<> QJsonValue toJson(const QVariant &variant) { - return QJsonValue::fromVariant(variant); + return QJsonValue::fromVariant(variant); } template<> QByteArray requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, what); - // ensure that the string can be safely cast to Latin1 - if (string != QString::fromLatin1(string.toLatin1())) - { - throw JsonException(what + " is not encodable as Latin1"); - } - return QByteArray::fromHex(string.toLatin1()); + const QString string = ensureIsType(value, what); + // ensure that the string can be safely cast to Latin1 + if (string != QString::fromLatin1(string.toLatin1())) + { + throw JsonException(what + " is not encodable as Latin1"); + } + return QByteArray::fromHex(string.toLatin1()); } template<> QJsonArray requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isArray()) - { - throw JsonException(what + " is not an array"); - } - return value.toArray(); + if (!value.isArray()) + { + throw JsonException(what + " is not an array"); + } + return value.toArray(); } template<> QString requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isString()) - { - throw JsonException(what + " is not a string"); - } - return value.toString(); + if (!value.isString()) + { + throw JsonException(what + " is not a string"); + } + return value.toString(); } template<> bool requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isBool()) - { - throw JsonException(what + " is not a bool"); - } - return value.toBool(); + if (!value.isBool()) + { + throw JsonException(what + " is not a bool"); + } + return value.toBool(); } template<> double requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isDouble()) - { - throw JsonException(what + " is not a double"); - } - return value.toDouble(); + if (!value.isDouble()) + { + throw JsonException(what + " is not a double"); + } + return value.toDouble(); } template<> int requireIsType(const QJsonValue &value, const QString &what) { - const double doubl = requireIsType(value, what); - if (fmod(doubl, 1) != 0) - { - throw JsonException(what + " is not an integer"); - } - return int(doubl); + const double doubl = requireIsType(value, what); + if (fmod(doubl, 1) != 0) + { + throw JsonException(what + " is not an integer"); + } + return int(doubl); } template<> QDateTime requireIsType(const QJsonValue &value, const QString &what) { - const QString string = requireIsType(value, what); - const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); - if (!datetime.isValid()) - { - throw JsonException(what + " is not a ISO formatted date/time value"); - } - return datetime; + const QString string = requireIsType(value, what); + const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); + if (!datetime.isValid()) + { + throw JsonException(what + " is not a ISO formatted date/time value"); + } + return datetime; } template<> QUrl requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, what); - if (string.isEmpty()) - { - return QUrl(); - } - const QUrl url = QUrl(string, QUrl::StrictMode); - if (!url.isValid()) - { - throw JsonException(what + " is not a correctly formatted URL"); - } - return url; + const QString string = ensureIsType(value, what); + if (string.isEmpty()) + { + return QUrl(); + } + const QUrl url = QUrl(string, QUrl::StrictMode); + if (!url.isValid()) + { + throw JsonException(what + " is not a correctly formatted URL"); + } + return url; } template<> QDir requireIsType(const QJsonValue &value, const QString &what) { - const QString string = requireIsType(value, what); - // FIXME: does not handle invalid characters! - return QDir::current().absoluteFilePath(string); + const QString string = requireIsType(value, what); + // FIXME: does not handle invalid characters! + return QDir::current().absoluteFilePath(string); } template<> QUuid requireIsType(const QJsonValue &value, const QString &what) { - const QString string = requireIsType(value, what); - const QUuid uuid = QUuid(string); - if (uuid.toString() != string) // converts back => valid - { - throw JsonException(what + " is not a valid UUID"); - } - return uuid; + const QString string = requireIsType(value, what); + const QUuid uuid = QUuid(string); + if (uuid.toString() != string) // converts back => valid + { + throw JsonException(what + " is not a valid UUID"); + } + return uuid; } template<> QJsonObject requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isObject()) - { - throw JsonException(what + " is not an object"); - } - return value.toObject(); + if (!value.isObject()) + { + throw JsonException(what + " is not an object"); + } + return value.toObject(); } template<> QVariant requireIsType(const QJsonValue &value, const QString &what) { - if (value.isNull() || value.isUndefined()) - { - throw JsonException(what + " is null or undefined"); - } - return value.toVariant(); + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value.toVariant(); } template<> QJsonValue requireIsType(const QJsonValue &value, const QString &what) { - if (value.isNull() || value.isUndefined()) - { - throw JsonException(what + " is null or undefined"); - } - return value; + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value; } } diff --git a/api/logic/Json.h b/api/logic/Json.h index e59b25de..34ff6fe2 100644 --- a/api/logic/Json.h +++ b/api/logic/Json.h @@ -19,7 +19,7 @@ namespace Json class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception { public: - JsonException(const QString &message) : Exception(message) {} + JsonException(const QString &message) : Exception(message) {} }; /// @throw FileSystemException @@ -51,7 +51,7 @@ void writeStringList(QJsonObject & to, const QString &key, const QStringList &va template QJsonValue toJson(const T &t) { - return QJsonValue(t); + return QJsonValue(t); } template<> QJsonValue toJson(const QUrl &url); @@ -69,12 +69,12 @@ QJsonValue toJson(const QVariant &variant); template QJsonArray toJsonArray(const QList &container) { - QJsonArray array; - for (const T item : container) - { - array.append(toJson(item)); - } - return array; + QJsonArray array; + for (const T item : container) + { + array.append(toJson(item)); + } + return array; } ////////////////// READING //////////////////// @@ -115,119 +115,119 @@ template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType(const QJsonValue &value template T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value") { - if (value.isUndefined() || value.isNull()) - { - return default_; - } - try - { - return requireIsType(value, what); - } - catch (JsonException &) - { - return default_; - } + if (value.isUndefined() || value.isNull()) + { + return default_; + } + try + { + return requireIsType(value, what); + } + catch (const JsonException &) + { + return default_; + } } /// @throw JsonException template T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - throw JsonException(localWhat + "s parent does not contain " + localWhat); - } - return requireIsType(parent.value(key), localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return requireIsType(parent.value(key), localWhat); } template T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - return default_; - } - return ensureIsType(parent.value(key), default_, localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsType(parent.value(key), default_, localWhat); } template QVector requireIsArrayOf(const QJsonDocument &doc) { - const QJsonArray array = requireArray(doc); - QVector out; - for (const QJsonValue val : array) - { - out.append(requireIsType(val, "Document")); - } - return out; + const QJsonArray array = requireArray(doc); + QVector out; + for (const QJsonValue val : array) + { + out.append(requireIsType(val, "Document")); + } + return out; } template QVector ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value") { - const QJsonArray array = ensureIsType(value, QJsonArray(), what); - QVector out; - for (const QJsonValue val : array) - { - out.append(requireIsType(val, what)); - } - return out; + const QJsonArray array = ensureIsType(value, QJsonArray(), what); + QVector out; + for (const QJsonValue val : array) + { + out.append(requireIsType(val, what)); + } + return out; } template QVector ensureIsArrayOf(const QJsonValue &value, const QVector default_, const QString &what = "Value") { - if (value.isUndefined()) - { - return default_; - } - return ensureIsArrayOf(value, what); + if (value.isUndefined()) + { + return default_; + } + return ensureIsArrayOf(value, what); } /// @throw JsonException template QVector requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - throw JsonException(localWhat + "s parent does not contain " + localWhat); - } - return ensureIsArrayOf(parent.value(key), localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return ensureIsArrayOf(parent.value(key), localWhat); } template QVector ensureIsArrayOf(const QJsonObject &parent, const QString &key, - const QVector &default_ = QVector(), const QString &what = "__placeholder__") + const QVector &default_ = QVector(), const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - return default_; - } - return ensureIsArrayOf(parent.value(key), default_, localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsArrayOf(parent.value(key), default_, localWhat); } // this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers #define JSON_HELPERFUNCTIONS(NAME, TYPE) \ - inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \ - { \ - return requireIsType(value, what); \ - } \ - inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \ - { \ - return ensureIsType(value, default_, what); \ - } \ - inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ - { \ - return requireIsType(parent, key, what); \ - } \ - inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \ - { \ - return ensureIsType(parent, key, default_, what); \ - } + inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \ + { \ + return requireIsType(value, what); \ + } \ + inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \ + { \ + return ensureIsType(value, default_, what); \ + } \ + inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ + { \ + return requireIsType(parent, key, what); \ + } \ + inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \ + { \ + return ensureIsType(parent, key, default_, what); \ + } JSON_HELPERFUNCTIONS(Array, QJsonArray) JSON_HELPERFUNCTIONS(Object, QJsonObject) diff --git a/api/logic/LoggedProcess.cpp b/api/logic/LoggedProcess.cpp index f89b4acc..822c0f04 100644 --- a/api/logic/LoggedProcess.cpp +++ b/api/logic/LoggedProcess.cpp @@ -4,157 +4,157 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) { - // QProcess has a strange interface... let's map a lot of those into a few. - connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); - connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); - connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); - connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); - connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); + // QProcess has a strange interface... let's map a lot of those into a few. + connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); + connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); + connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); + connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); + connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); } LoggedProcess::~LoggedProcess() { - if(m_is_detachable) - { - setProcessState(QProcess::NotRunning); - } + if(m_is_detachable) + { + setProcessState(QProcess::NotRunning); + } } QStringList reprocess(const QByteArray & data, QString & leftover) { - QString str = leftover + QString::fromLocal8Bit(data); + QString str = leftover + QString::fromLocal8Bit(data); - str.remove('\r'); - QStringList lines = str.split("\n"); - leftover = lines.takeLast(); - return lines; + str.remove('\r'); + QStringList lines = str.split("\n"); + leftover = lines.takeLast(); + return lines; } void LoggedProcess::on_stdErr() { - auto lines = reprocess(readAllStandardError(), m_err_leftover); - emit log(lines, MessageLevel::StdErr); + auto lines = reprocess(readAllStandardError(), m_err_leftover); + emit log(lines, MessageLevel::StdErr); } void LoggedProcess::on_stdOut() { - auto lines = reprocess(readAllStandardOutput(), m_out_leftover); - emit log(lines, MessageLevel::StdOut); + auto lines = reprocess(readAllStandardOutput(), m_out_leftover); + emit log(lines, MessageLevel::StdOut); } void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status) { - // save the exit code - m_exit_code = exit_code; - - // Flush console window - if (!m_err_leftover.isEmpty()) - { - emit log({m_err_leftover}, MessageLevel::StdErr); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - emit log({m_err_leftover}, MessageLevel::StdOut); - m_out_leftover.clear(); - } - - // based on state, send signals - if (!m_is_aborting) - { - if (status == QProcess::NormalExit) - { - //: Message displayed on instance exit - emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC); - changeState(LoggedProcess::Finished); - } - else - { - //: Message displayed on instance crashed - if(exit_code == -1) - emit log({tr("Process crashed.")}, MessageLevel::MultiMC); - else - emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC); - changeState(LoggedProcess::Crashed); - } - } - else - { - //: Message displayed after the instance exits due to kill request - emit log({tr("Process was killed by user.")}, MessageLevel::Error); - changeState(LoggedProcess::Aborted); - } + // save the exit code + m_exit_code = exit_code; + + // Flush console window + if (!m_err_leftover.isEmpty()) + { + emit log({m_err_leftover}, MessageLevel::StdErr); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + emit log({m_err_leftover}, MessageLevel::StdOut); + m_out_leftover.clear(); + } + + // based on state, send signals + if (!m_is_aborting) + { + if (status == QProcess::NormalExit) + { + //: Message displayed on instance exit + emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC); + changeState(LoggedProcess::Finished); + } + else + { + //: Message displayed on instance crashed + if(exit_code == -1) + emit log({tr("Process crashed.")}, MessageLevel::MultiMC); + else + emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC); + changeState(LoggedProcess::Crashed); + } + } + else + { + //: Message displayed after the instance exits due to kill request + emit log({tr("Process was killed by user.")}, MessageLevel::Error); + changeState(LoggedProcess::Aborted); + } } void LoggedProcess::on_error(QProcess::ProcessError error) { - switch(error) - { - case QProcess::FailedToStart: - { - emit log({tr("The process failed to start.")}, MessageLevel::Fatal); - changeState(LoggedProcess::FailedToStart); - break; - } - // we'll just ignore those... never needed them - case QProcess::Crashed: - case QProcess::ReadError: - case QProcess::Timedout: - case QProcess::UnknownError: - case QProcess::WriteError: - break; - } + switch(error) + { + case QProcess::FailedToStart: + { + emit log({tr("The process failed to start.")}, MessageLevel::Fatal); + changeState(LoggedProcess::FailedToStart); + break; + } + // we'll just ignore those... never needed them + case QProcess::Crashed: + case QProcess::ReadError: + case QProcess::Timedout: + case QProcess::UnknownError: + case QProcess::WriteError: + break; + } } void LoggedProcess::kill() { - m_is_aborting = true; - QProcess::kill(); + m_is_aborting = true; + QProcess::kill(); } int LoggedProcess::exitCode() const { - return m_exit_code; + return m_exit_code; } void LoggedProcess::changeState(LoggedProcess::State state) { - if(state == m_state) - return; - m_state = state; - emit stateChanged(m_state); + if(state == m_state) + return; + m_state = state; + emit stateChanged(m_state); } LoggedProcess::State LoggedProcess::state() const { - return m_state; + return m_state; } void LoggedProcess::on_stateChange(QProcess::ProcessState state) { - switch(state) - { - case QProcess::NotRunning: - break; // let's not - there are too many that handle this already. - case QProcess::Starting: - { - if(m_state != LoggedProcess::NotRunning) - { - qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting; - } - changeState(LoggedProcess::Starting); - return; - } - case QProcess::Running: - { - if(m_state != LoggedProcess::Starting) - { - qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running; - } - changeState(LoggedProcess::Running); - return; - } - } + switch(state) + { + case QProcess::NotRunning: + break; // let's not - there are too many that handle this already. + case QProcess::Starting: + { + if(m_state != LoggedProcess::NotRunning) + { + qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting; + } + changeState(LoggedProcess::Starting); + return; + } + case QProcess::Running: + { + if(m_state != LoggedProcess::Starting) + { + qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running; + } + changeState(LoggedProcess::Running); + return; + } + } } #if defined Q_OS_WIN32 @@ -172,5 +172,5 @@ qint64 LoggedProcess::processId() const void LoggedProcess::setDetachable(bool detachable) { - m_is_detachable = detachable; + m_is_detachable = detachable; } diff --git a/api/logic/LoggedProcess.h b/api/logic/LoggedProcess.h index 6a80365f..256c0c45 100644 --- a/api/logic/LoggedProcess.h +++ b/api/logic/LoggedProcess.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,54 +27,54 @@ class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess { Q_OBJECT public: - enum State - { - NotRunning, - Starting, - FailedToStart, - Running, - Finished, - Crashed, - Aborted - }; + enum State + { + NotRunning, + Starting, + FailedToStart, + Running, + Finished, + Crashed, + Aborted + }; public: - explicit LoggedProcess(QObject* parent = 0); - virtual ~LoggedProcess(); + explicit LoggedProcess(QObject* parent = 0); + virtual ~LoggedProcess(); - State state() const; - int exitCode() const; - qint64 processId() const; + State state() const; + int exitCode() const; + qint64 processId() const; - void setDetachable(bool detachable); + void setDetachable(bool detachable); signals: - void log(QStringList lines, MessageLevel::Enum level); - void stateChanged(LoggedProcess::State state); + void log(QStringList lines, MessageLevel::Enum level); + void stateChanged(LoggedProcess::State state); public slots: - /** - * @brief kill the process - equivalent to kill -9 - */ - void kill(); + /** + * @brief kill the process - equivalent to kill -9 + */ + void kill(); private slots: - void on_stdErr(); - void on_stdOut(); - void on_exit(int exit_code, QProcess::ExitStatus status); - void on_error(QProcess::ProcessError error); - void on_stateChange(QProcess::ProcessState); + void on_stdErr(); + void on_stdOut(); + void on_exit(int exit_code, QProcess::ExitStatus status); + void on_error(QProcess::ProcessError error); + void on_stateChange(QProcess::ProcessState); private: - void changeState(LoggedProcess::State state); + void changeState(LoggedProcess::State state); private: - QString m_err_leftover; - QString m_out_leftover; - bool m_killed = false; - State m_state = NotRunning; - int m_exit_code = 0; - bool m_is_aborting = false; - bool m_is_detachable = false; + QString m_err_leftover; + QString m_out_leftover; + bool m_killed = false; + State m_state = NotRunning; + int m_exit_code = 0; + bool m_is_aborting = false; + bool m_is_detachable = false; }; diff --git a/api/logic/MMCStrings.cpp b/api/logic/MMCStrings.cpp index c50d596e..dc91c8d6 100644 --- a/api/logic/MMCStrings.cpp +++ b/api/logic/MMCStrings.cpp @@ -3,74 +3,74 @@ /// TAKEN FROM Qt, because it doesn't expose it intelligently static inline QChar getNextChar(const QString &s, int location) { - return (location < s.length()) ? s.at(location) : QChar(); + return (location < s.length()) ? s.at(location) : QChar(); } /// TAKEN FROM Qt, because it doesn't expose it intelligently int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) { - for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) - { - // skip spaces, tabs and 0's - QChar c1 = getNextChar(s1, l1); - while (c1.isSpace()) - c1 = getNextChar(s1, ++l1); - QChar c2 = getNextChar(s2, l2); - while (c2.isSpace()) - c2 = getNextChar(s2, ++l2); + for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) + { + // skip spaces, tabs and 0's + QChar c1 = getNextChar(s1, l1); + while (c1.isSpace()) + c1 = getNextChar(s1, ++l1); + QChar c2 = getNextChar(s2, l2); + while (c2.isSpace()) + c2 = getNextChar(s2, ++l2); - if (c1.isDigit() && c2.isDigit()) - { - while (c1.digitValue() == 0) - c1 = getNextChar(s1, ++l1); - while (c2.digitValue() == 0) - c2 = getNextChar(s2, ++l2); + if (c1.isDigit() && c2.isDigit()) + { + while (c1.digitValue() == 0) + c1 = getNextChar(s1, ++l1); + while (c2.digitValue() == 0) + c2 = getNextChar(s2, ++l2); - int lookAheadLocation1 = l1; - int lookAheadLocation2 = l2; - int currentReturnValue = 0; - // find the last digit, setting currentReturnValue as we go if it isn't equal - for (QChar lookAhead1 = c1, lookAhead2 = c2; - (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); - lookAhead1 = getNextChar(s1, ++lookAheadLocation1), - lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) - { - bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); - bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); - if (!is1ADigit && !is2ADigit) - break; - if (!is1ADigit) - return -1; - if (!is2ADigit) - return 1; - if (currentReturnValue == 0) - { - if (lookAhead1 < lookAhead2) - { - currentReturnValue = -1; - } - else if (lookAhead1 > lookAhead2) - { - currentReturnValue = 1; - } - } - } - if (currentReturnValue != 0) - return currentReturnValue; - } - if (cs == Qt::CaseInsensitive) - { - if (!c1.isLower()) - c1 = c1.toLower(); - if (!c2.isLower()) - c2 = c2.toLower(); - } - int r = QString::localeAwareCompare(c1, c2); - if (r < 0) - return -1; - if (r > 0) - return 1; - } - // The two strings are the same (02 == 2) so fall back to the normal sort - return QString::compare(s1, s2, cs); + int lookAheadLocation1 = l1; + int lookAheadLocation2 = l2; + int currentReturnValue = 0; + // find the last digit, setting currentReturnValue as we go if it isn't equal + for (QChar lookAhead1 = c1, lookAhead2 = c2; + (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); + lookAhead1 = getNextChar(s1, ++lookAheadLocation1), + lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) + { + bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); + bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); + if (!is1ADigit && !is2ADigit) + break; + if (!is1ADigit) + return -1; + if (!is2ADigit) + return 1; + if (currentReturnValue == 0) + { + if (lookAhead1 < lookAhead2) + { + currentReturnValue = -1; + } + else if (lookAhead1 > lookAhead2) + { + currentReturnValue = 1; + } + } + } + if (currentReturnValue != 0) + return currentReturnValue; + } + if (cs == Qt::CaseInsensitive) + { + if (!c1.isLower()) + c1 = c1.toLower(); + if (!c2.isLower()) + c2 = c2.toLower(); + } + int r = QString::localeAwareCompare(c1, c2); + if (r < 0) + return -1; + if (r > 0) + return 1; + } + // The two strings are the same (02 == 2) so fall back to the normal sort + return QString::compare(s1, s2, cs); } diff --git a/api/logic/MMCStrings.h b/api/logic/MMCStrings.h index 5606b909..493ba3d2 100644 --- a/api/logic/MMCStrings.h +++ b/api/logic/MMCStrings.h @@ -6,5 +6,5 @@ namespace Strings { - int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); + int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); } diff --git a/api/logic/MMCZip.cpp b/api/logic/MMCZip.cpp index 1e7ad51d..3afdbf5e 100644 --- a/api/logic/MMCZip.cpp +++ b/api/logic/MMCZip.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,231 +25,231 @@ // ours bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, const JlCompress::FilterFunction filter) { - QuaZip modZip(from.filePath()); - modZip.open(QuaZip::mdUnzip); - - QuaZipFile fileInsideMod(&modZip); - QuaZipFile zipOutFile(into); - for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) - { - QString filename = modZip.getCurrentFileName(); - if (filter && !filter(filename)) - { - qDebug() << "Skipping file " << filename << " from " - << from.fileName() << " - filtered"; - continue; - } - if (contained.contains(filename)) - { - qDebug() << "Skipping already contained file " << filename << " from " - << from.fileName(); - continue; - } - contained.insert(filename); - - if (!fileInsideMod.open(QIODevice::ReadOnly)) - { - qCritical() << "Failed to open " << filename << " from " << from.fileName(); - return false; - } - - QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); - - if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) - { - qCritical() << "Failed to open " << filename << " in the jar"; - fileInsideMod.close(); - return false; - } - if (!JlCompress::copyData(fileInsideMod, zipOutFile)) - { - zipOutFile.close(); - fileInsideMod.close(); - qCritical() << "Failed to copy data of " << filename << " into the jar"; - return false; - } - zipOutFile.close(); - fileInsideMod.close(); - } - return true; + QuaZip modZip(from.filePath()); + modZip.open(QuaZip::mdUnzip); + + QuaZipFile fileInsideMod(&modZip); + QuaZipFile zipOutFile(into); + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) + { + QString filename = modZip.getCurrentFileName(); + if (filter && !filter(filename)) + { + qDebug() << "Skipping file " << filename << " from " + << from.fileName() << " - filtered"; + continue; + } + if (contained.contains(filename)) + { + qDebug() << "Skipping already contained file " << filename << " from " + << from.fileName(); + continue; + } + contained.insert(filename); + + if (!fileInsideMod.open(QIODevice::ReadOnly)) + { + qCritical() << "Failed to open " << filename << " from " << from.fileName(); + return false; + } + + QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); + + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) + { + qCritical() << "Failed to open " << filename << " in the jar"; + fileInsideMod.close(); + return false; + } + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) + { + zipOutFile.close(); + fileInsideMod.close(); + qCritical() << "Failed to copy data of " << filename << " into the jar"; + return false; + } + zipOutFile.close(); + fileInsideMod.close(); + } + return true; } // ours bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods) { - QuaZip zipOut(targetJarPath); - if (!zipOut.open(QuaZip::mdCreate)) - { - QFile::remove(targetJarPath); - qCritical() << "Failed to open the minecraft.jar for modding"; - return false; - } - // Files already added to the jar. - // These files will be skipped. - QSet addedFiles; - - // Modify the jar - QListIterator i(mods); + QuaZip zipOut(targetJarPath); + if (!zipOut.open(QuaZip::mdCreate)) + { + QFile::remove(targetJarPath); + qCritical() << "Failed to open the minecraft.jar for modding"; + return false; + } + // Files already added to the jar. + // These files will be skipped. + QSet addedFiles; + + // Modify the jar + QListIterator i(mods); i.toBack(); while (i.hasPrevious()) - { - const Mod &mod = i.previous(); - // do not merge disabled mods. - if (!mod.enabled()) - continue; - if (mod.type() == Mod::MOD_ZIPFILE) - { - if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - } - else if (mod.type() == Mod::MOD_SINGLEFILE) - { - // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); - if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - addedFiles.insert(filename.fileName()); - } - else if (mod.type() == Mod::MOD_FOLDER) - { - // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); - QString what_to_zip = filename.absoluteFilePath(); - QDir dir(what_to_zip); - dir.cdUp(); - QString parent_dir = dir.absolutePath(); - if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - qDebug() << "Adding folder " << filename.fileName() << " from " - << filename.absoluteFilePath(); - } - else - { - // Make sure we do not continue launching when something is missing or undefined... - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar."; - return false; - } - } - - if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");})) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to insert minecraft.jar contents."; - return false; - } - - // Recompress the jar - zipOut.close(); - if (zipOut.getZipError() != 0) - { - QFile::remove(targetJarPath); - qCritical() << "Failed to finalize minecraft.jar!"; - return false; - } - return true; + { + const Mod &mod = i.previous(); + // do not merge disabled mods. + if (!mod.enabled()) + continue; + if (mod.type() == Mod::MOD_ZIPFILE) + { + if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + return false; + } + } + else if (mod.type() == Mod::MOD_SINGLEFILE) + { + // FIXME: buggy - does not work with addedFiles + auto filename = mod.filename(); + if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + return false; + } + addedFiles.insert(filename.fileName()); + } + else if (mod.type() == Mod::MOD_FOLDER) + { + // FIXME: buggy - does not work with addedFiles + auto filename = mod.filename(); + QString what_to_zip = filename.absoluteFilePath(); + QDir dir(what_to_zip); + dir.cdUp(); + QString parent_dir = dir.absolutePath(); + if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + return false; + } + qDebug() << "Adding folder " << filename.fileName() << " from " + << filename.absoluteFilePath(); + } + else + { + // Make sure we do not continue launching when something is missing or undefined... + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar."; + return false; + } + } + + if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");})) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to insert minecraft.jar contents."; + return false; + } + + // Recompress the jar + zipOut.close(); + if (zipOut.getZipError() != 0) + { + QFile::remove(targetJarPath); + qCritical() << "Failed to finalize minecraft.jar!"; + return false; + } + return true; } // ours QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root) { - QuaZipDir rootDir(zip, root); - for(auto fileName: rootDir.entryList(QDir::Files)) - { - if(fileName == what) - return root; - } - for(auto fileName: rootDir.entryList(QDir::Dirs)) - { - QString result = findFolderOfFileInZip(zip, what, root + fileName); - if(!result.isEmpty()) - { - return result; - } - } - return QString(); + QuaZipDir rootDir(zip, root); + for(auto fileName: rootDir.entryList(QDir::Files)) + { + if(fileName == what) + return root; + } + for(auto fileName: rootDir.entryList(QDir::Dirs)) + { + QString result = findFolderOfFileInZip(zip, what, root + fileName); + if(!result.isEmpty()) + { + return result; + } + } + return QString(); } // ours bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) { - QuaZipDir rootDir(zip, root); - for(auto fileName: rootDir.entryList(QDir::Files)) - { - if(fileName == what) - { - result.append(root); - return true; - } - } - for(auto fileName: rootDir.entryList(QDir::Dirs)) - { - findFilesInZip(zip, what, result, root + fileName); - } - return !result.isEmpty(); + QuaZipDir rootDir(zip, root); + for(auto fileName: rootDir.entryList(QDir::Files)) + { + if(fileName == what) + { + result.append(root); + return true; + } + } + for(auto fileName: rootDir.entryList(QDir::Dirs)) + { + findFilesInZip(zip, what, result, root + fileName); + } + return !result.isEmpty(); } // ours QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) { - QDir directory(target); - QStringList extracted; - qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; - if (!zip->goToFirstFile()) - { - qWarning() << "Failed to seek to first file in zip"; - return QStringList(); - } - do - { - QString name = zip->getCurrentFileName(); - if(!name.startsWith(subdir)) - { - continue; - } - name.remove(0, subdir.size()); - QString absFilePath = directory.absoluteFilePath(name); - if(name.isEmpty()) - { - absFilePath += "/"; - } - if (!JlCompress::extractFile(zip, "", absFilePath)) - { - qWarning() << "Failed to extract file" << name << "to" << absFilePath; - JlCompress::removeFile(extracted); - return QStringList(); - } - extracted.append(absFilePath); - qDebug() << "Extracted file" << name; - } while (zip->goToNextFile()); - return extracted; + QDir directory(target); + QStringList extracted; + qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; + if (!zip->goToFirstFile()) + { + qWarning() << "Failed to seek to first file in zip"; + return QStringList(); + } + do + { + QString name = zip->getCurrentFileName(); + if(!name.startsWith(subdir)) + { + continue; + } + name.remove(0, subdir.size()); + QString absFilePath = directory.absoluteFilePath(name); + if(name.isEmpty()) + { + absFilePath += "/"; + } + if (!JlCompress::extractFile(zip, "", absFilePath)) + { + qWarning() << "Failed to extract file" << name << "to" << absFilePath; + JlCompress::removeFile(extracted); + return QStringList(); + } + extracted.append(absFilePath); + qDebug() << "Extracted file" << name; + } while (zip->goToNextFile()); + return extracted; } // ours QStringList MMCZip::extractDir(QString fileCompressed, QString dir) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) - { - return {}; - } - return MMCZip::extractSubDir(&zip, "", dir); + QuaZip zip(fileCompressed); + if (!zip.open(QuaZip::mdUnzip)) + { + return {}; + } + return MMCZip::extractSubDir(&zip, "", dir); } diff --git a/api/logic/MMCZip.h b/api/logic/MMCZip.h index 68094b2c..85ac7802 100644 --- a/api/logic/MMCZip.h +++ b/api/logic/MMCZip.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ #include #include #include -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" #include #include "multimc_logic_export.h" @@ -28,44 +28,44 @@ namespace MMCZip { - /** - * Merge two zip files, using a filter function - */ - bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, - const JlCompress::FilterFunction filter = nullptr); + /** + * Merge two zip files, using a filter function + */ + bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, + const JlCompress::FilterFunction filter = nullptr); - /** - * take a source jar, add mods to it, resulting in target jar - */ - bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); + /** + * take a source jar, add mods to it, resulting in target jar + */ + bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); - /** - * Find a single file in archive by file name (not path) - * - * \return the path prefix where the file is - */ - QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); + /** + * Find a single file in archive by file name (not path) + * + * \return the path prefix where the file is + */ + QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); - /** - * Find a multiple files of the same name in archive by file name - * If a file is found in a path, no deeper paths are searched - * - * \return true if anything was found - */ - bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); + /** + * Find a multiple files of the same name in archive by file name + * If a file is found in a path, no deeper paths are searched + * + * \return true if anything was found + */ + bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); - /** - * Extract a subdirectory from an archive - */ - QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); + /** + * Extract a subdirectory from an archive + */ + QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); - /** - * Extract a whole archive. - * - * \param fileCompressed The name of the archive. - * \param dir The directory to extract to, the current directory if left empty. - * \return The list of the full paths of the files extracted, empty on failure. - */ - QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir); + /** + * Extract a whole archive. + * + * \param fileCompressed The name of the archive. + * \param dir The directory to extract to, the current directory if left empty. + * \return The list of the full paths of the files extracted, empty on failure. + */ + QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir); } diff --git a/api/logic/MessageLevel.cpp b/api/logic/MessageLevel.cpp index a5191290..0a2cd23d 100644 --- a/api/logic/MessageLevel.cpp +++ b/api/logic/MessageLevel.cpp @@ -2,35 +2,35 @@ MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) { - if (levelName == "MultiMC") - return MessageLevel::MultiMC; - else if (levelName == "Debug") - return MessageLevel::Debug; - else if (levelName == "Info") - return MessageLevel::Info; - else if (levelName == "Message") - return MessageLevel::Message; - else if (levelName == "Warning") - return MessageLevel::Warning; - else if (levelName == "Error") - return MessageLevel::Error; - else if (levelName == "Fatal") - return MessageLevel::Fatal; - // Skip PrePost, it's not exposed to !![]! - // Also skip StdErr and StdOut - else - return MessageLevel::Unknown; + if (levelName == "MultiMC") + return MessageLevel::MultiMC; + else if (levelName == "Debug") + return MessageLevel::Debug; + else if (levelName == "Info") + return MessageLevel::Info; + else if (levelName == "Message") + return MessageLevel::Message; + else if (levelName == "Warning") + return MessageLevel::Warning; + else if (levelName == "Error") + return MessageLevel::Error; + else if (levelName == "Fatal") + return MessageLevel::Fatal; + // Skip PrePost, it's not exposed to !![]! + // Also skip StdErr and StdOut + else + return MessageLevel::Unknown; } MessageLevel::Enum MessageLevel::fromLine(QString &line) { - // Level prefix - int endmark = line.indexOf("]!"); - if (line.startsWith("!![") && endmark != -1) - { - auto level = MessageLevel::getLevel(line.left(endmark).mid(3)); - line = line.mid(endmark + 2); - return level; - } - return MessageLevel::Unknown; + // Level prefix + int endmark = line.indexOf("]!"); + if (line.startsWith("!![") && endmark != -1) + { + auto level = MessageLevel::getLevel(line.left(endmark).mid(3)); + line = line.mid(endmark + 2); + return level; + } + return MessageLevel::Unknown; } diff --git a/api/logic/MessageLevel.h b/api/logic/MessageLevel.h index 0128148d..f19048e3 100644 --- a/api/logic/MessageLevel.h +++ b/api/logic/MessageLevel.h @@ -10,16 +10,16 @@ namespace MessageLevel { enum Enum { - Unknown, /**< No idea what this is or where it came from */ - StdOut, /**< Undetermined stderr messages */ - StdErr, /**< Undetermined stdout messages */ - MultiMC, /**< MultiMC Messages */ - Debug, /**< Debug Messages */ - Info, /**< Info Messages */ - Message, /**< Standard Messages */ - Warning, /**< Warnings */ - Error, /**< Errors */ - Fatal, /**< Fatal Errors */ + Unknown, /**< No idea what this is or where it came from */ + StdOut, /**< Undetermined stderr messages */ + StdErr, /**< Undetermined stdout messages */ + MultiMC, /**< MultiMC Messages */ + Debug, /**< Debug Messages */ + Info, /**< Info Messages */ + Message, /**< Standard Messages */ + Warning, /**< Warnings */ + Error, /**< Errors */ + Fatal, /**< Fatal Errors */ }; MessageLevel::Enum getLevel(const QString &levelName); diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h index 64965277..e9ba1a13 100644 --- a/api/logic/NullInstance.h +++ b/api/logic/NullInstance.h @@ -1,77 +1,76 @@ #pragma once #include "BaseInstance.h" +#include "launch/LaunchTask.h" class NullInstance: public BaseInstance { + Q_OBJECT public: - NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) - :BaseInstance(globalSettings, settings, rootDir) - { - setVersionBroken(true); - } - virtual ~NullInstance() {}; - virtual void init() override - { - } - virtual void saveNow() override - { - } - virtual QString getStatusbarDescription() override - { - return tr("Unknown instance type"); - }; - virtual QSet< QString > traits() const override - { - return {}; - }; - virtual QString instanceConfigFolder() const override - { - return instanceRoot(); - }; - virtual std::shared_ptr createLaunchTask(AuthSessionPtr) override - { - return nullptr; - } - virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override - { - return nullptr; - } - virtual QProcessEnvironment createEnvironment() override - { - return QProcessEnvironment(); - } - virtual QMap getVariables() const override - { - return QMap(); - } - virtual IPathMatcher::Ptr getLogFileMatcher() override - { - return nullptr; - } - virtual QString getLogFileRoot() override - { - return instanceRoot(); - } - virtual QString typeName() const override - { - return "Null"; - } - bool canExport() const override - { - return false; - } - bool canEdit() const override - { - return false; - } - bool canLaunch() const override - { - return false; - } - QStringList verboseDescription(AuthSessionPtr session) override - { - QStringList out; - out << "Null instance - placeholder."; - return out; - } + NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) + :BaseInstance(globalSettings, settings, rootDir) + { + setVersionBroken(true); + } + virtual ~NullInstance() {}; + void saveNow() override + { + } + QString getStatusbarDescription() override + { + return tr("Unknown instance type"); + }; + QSet< QString > traits() const override + { + return {}; + }; + QString instanceConfigFolder() const override + { + return instanceRoot(); + }; + shared_qobject_ptr createLaunchTask(AuthSessionPtr) override + { + return nullptr; + } + shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override + { + return nullptr; + } + QProcessEnvironment createEnvironment() override + { + return QProcessEnvironment(); + } + QMap getVariables() const override + { + return QMap(); + } + IPathMatcher::Ptr getLogFileMatcher() override + { + return nullptr; + } + QString getLogFileRoot() override + { + return instanceRoot(); + } + QString typeName() const override + { + return "Null"; + } + bool canExport() const override + { + return false; + } + bool canEdit() const override + { + return false; + } + bool canLaunch() const override + { + return false; + } + QStringList verboseDescription(AuthSessionPtr session) override + { + QStringList out; + out << "Null instance - placeholder."; + return out; + } }; diff --git a/api/logic/ProblemProvider.h b/api/logic/ProblemProvider.h index 978710f0..33c9d364 100644 --- a/api/logic/ProblemProvider.h +++ b/api/logic/ProblemProvider.h @@ -4,46 +4,46 @@ enum class ProblemSeverity { - None, - Warning, - Error + None, + Warning, + Error }; struct PatchProblem { - ProblemSeverity m_severity; - QString m_description; + ProblemSeverity m_severity; + QString m_description; }; class MULTIMC_LOGIC_EXPORT ProblemProvider { public: - virtual ~ProblemProvider() {}; - virtual const QList getProblems() const = 0; - virtual ProblemSeverity getProblemSeverity() const = 0; + virtual ~ProblemProvider() {}; + virtual const QList getProblems() const = 0; + virtual ProblemSeverity getProblemSeverity() const = 0; }; class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider { public: - const QList getProblems() const override - { - return m_problems; - } - ProblemSeverity getProblemSeverity() const override - { - return m_problemSeverity; - } - virtual void addProblem(ProblemSeverity severity, const QString &description) - { - if(severity > m_problemSeverity) - { - m_problemSeverity = severity; - } - m_problems.append({severity, description}); - } + const QList getProblems() const override + { + return m_problems; + } + ProblemSeverity getProblemSeverity() const override + { + return m_problemSeverity; + } + virtual void addProblem(ProblemSeverity severity, const QString &description) + { + if(severity > m_problemSeverity) + { + m_problemSeverity = severity; + } + m_problems.append({severity, description}); + } private: - QList m_problems; - ProblemSeverity m_problemSeverity = ProblemSeverity::None; + QList m_problems; + ProblemSeverity m_problemSeverity = ProblemSeverity::None; }; diff --git a/api/logic/QObjectPtr.h b/api/logic/QObjectPtr.h index 4a42f728..0ff51136 100644 --- a/api/logic/QObjectPtr.h +++ b/api/logic/QObjectPtr.h @@ -8,10 +8,10 @@ namespace details { struct DeleteQObjectLater { - void operator()(QObject *obj) const - { - obj->deleteLater(); - } + void operator()(QObject *obj) const + { + obj->deleteLater(); + } }; } /** @@ -28,56 +28,56 @@ template class shared_qobject_ptr { public: - shared_qobject_ptr(){} - shared_qobject_ptr(T * wrap) - { - reset(wrap); - } - shared_qobject_ptr(const shared_qobject_ptr& other) - { - m_ptr = other.m_ptr; - } - template - shared_qobject_ptr(const shared_qobject_ptr &other) - { - m_ptr = other.unwrap(); - } + shared_qobject_ptr(){} + shared_qobject_ptr(T * wrap) + { + reset(wrap); + } + shared_qobject_ptr(const shared_qobject_ptr& other) + { + m_ptr = other.m_ptr; + } + template + shared_qobject_ptr(const shared_qobject_ptr &other) + { + m_ptr = other.unwrap(); + } public: - void reset(T * wrap) - { - using namespace std::placeholders; - m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); - } - void reset(const shared_qobject_ptr &other) - { - m_ptr = other.m_ptr; - } - void reset() - { - m_ptr.reset(); - } - T * get() const - { - return m_ptr.get(); - } - T * operator->() const - { - return m_ptr.get(); - } - T & operator*() const - { - return *m_ptr.get(); - } - operator bool() const - { - return m_ptr.get() != nullptr; - } - const std::shared_ptr unwrap() const - { - return m_ptr; - } + void reset(T * wrap) + { + using namespace std::placeholders; + m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); + } + void reset(const shared_qobject_ptr &other) + { + m_ptr = other.m_ptr; + } + void reset() + { + m_ptr.reset(); + } + T * get() const + { + return m_ptr.get(); + } + T * operator->() const + { + return m_ptr.get(); + } + T & operator*() const + { + return *m_ptr.get(); + } + operator bool() const + { + return m_ptr.get() != nullptr; + } + const std::shared_ptr unwrap() const + { + return m_ptr; + } private: - std::shared_ptr m_ptr; + std::shared_ptr m_ptr; }; diff --git a/api/logic/RWStorage.h b/api/logic/RWStorage.h index 4afbd96c..5d792367 100644 --- a/api/logic/RWStorage.h +++ b/api/logic/RWStorage.h @@ -5,59 +5,59 @@ template class RWStorage { public: - void add(K key, V value) - { - QWriteLocker l(&lock); - cache[key] = value; - stale_entries.remove(key); - } - V get(K key) - { - QReadLocker l(&lock); - if(cache.contains(key)) - { - return cache[key]; - } - else return V(); - } - bool get(K key, V& value) - { - QReadLocker l(&lock); - if(cache.contains(key)) - { - value = cache[key]; - return true; - } - else return false; - } - bool has(K key) - { - QReadLocker l(&lock); - return cache.contains(key); - } - bool stale(K key) - { - QReadLocker l(&lock); - if(!cache.contains(key)) - return true; - return stale_entries.contains(key); - } - void setStale(K key) - { - QWriteLocker l(&lock); - if(cache.contains(key)) - { - stale_entries.insert(key); - } - } - void clear() - { - QWriteLocker l(&lock); - cache.clear(); - stale_entries.clear(); - } + void add(K key, V value) + { + QWriteLocker l(&lock); + cache[key] = value; + stale_entries.remove(key); + } + V get(K key) + { + QReadLocker l(&lock); + if(cache.contains(key)) + { + return cache[key]; + } + else return V(); + } + bool get(K key, V& value) + { + QReadLocker l(&lock); + if(cache.contains(key)) + { + value = cache[key]; + return true; + } + else return false; + } + bool has(K key) + { + QReadLocker l(&lock); + return cache.contains(key); + } + bool stale(K key) + { + QReadLocker l(&lock); + if(!cache.contains(key)) + return true; + return stale_entries.contains(key); + } + void setStale(K key) + { + QWriteLocker l(&lock); + if(cache.contains(key)) + { + stale_entries.insert(key); + } + } + void clear() + { + QWriteLocker l(&lock); + cache.clear(); + stale_entries.clear(); + } private: - QReadWriteLock lock; - QMap cache; - QSet stale_entries; + QReadWriteLock lock; + QMap cache; + QSet stale_entries; }; diff --git a/api/logic/RecursiveFileSystemWatcher.cpp b/api/logic/RecursiveFileSystemWatcher.cpp index 59c3f0f0..b7417cdf 100644 --- a/api/logic/RecursiveFileSystemWatcher.cpp +++ b/api/logic/RecursiveFileSystemWatcher.cpp @@ -4,108 +4,108 @@ #include RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent) - : QObject(parent), m_watcher(new QFileSystemWatcher(this)) + : QObject(parent), m_watcher(new QFileSystemWatcher(this)) { - connect(m_watcher, &QFileSystemWatcher::fileChanged, this, - &RecursiveFileSystemWatcher::fileChange); - connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, - &RecursiveFileSystemWatcher::directoryChange); + connect(m_watcher, &QFileSystemWatcher::fileChanged, this, + &RecursiveFileSystemWatcher::fileChange); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, + &RecursiveFileSystemWatcher::directoryChange); } void RecursiveFileSystemWatcher::setRootDir(const QDir &root) { - bool wasEnabled = m_isEnabled; - disable(); - m_root = root; - setFiles(scanRecursive(m_root)); - if (wasEnabled) - { - enable(); - } + bool wasEnabled = m_isEnabled; + disable(); + m_root = root; + setFiles(scanRecursive(m_root)); + if (wasEnabled) + { + enable(); + } } void RecursiveFileSystemWatcher::setWatchFiles(const bool watchFiles) { - bool wasEnabled = m_isEnabled; - disable(); - m_watchFiles = watchFiles; - if (wasEnabled) - { - enable(); - } + bool wasEnabled = m_isEnabled; + disable(); + m_watchFiles = watchFiles; + if (wasEnabled) + { + enable(); + } } void RecursiveFileSystemWatcher::enable() { - if (m_isEnabled) - { - return; - } - Q_ASSERT(m_root != QDir::root()); - addFilesToWatcherRecursive(m_root); - m_isEnabled = true; + if (m_isEnabled) + { + return; + } + Q_ASSERT(m_root != QDir::root()); + addFilesToWatcherRecursive(m_root); + m_isEnabled = true; } void RecursiveFileSystemWatcher::disable() { - if (!m_isEnabled) - { - return; - } - m_isEnabled = false; - m_watcher->removePaths(m_watcher->files()); - m_watcher->removePaths(m_watcher->directories()); + if (!m_isEnabled) + { + return; + } + m_isEnabled = false; + m_watcher->removePaths(m_watcher->files()); + m_watcher->removePaths(m_watcher->directories()); } void RecursiveFileSystemWatcher::setFiles(const QStringList &files) { - if (files != m_files) - { - m_files = files; - emit filesChanged(); - } + if (files != m_files) + { + m_files = files; + emit filesChanged(); + } } void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir) { - m_watcher->addPath(dir.absolutePath()); - for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) - { - addFilesToWatcherRecursive(dir.absoluteFilePath(directory)); - } - if (m_watchFiles) - { - for (const QFileInfo &info : dir.entryInfoList(QDir::Files)) - { - m_watcher->addPath(info.absoluteFilePath()); - } - } + m_watcher->addPath(dir.absolutePath()); + for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + addFilesToWatcherRecursive(dir.absoluteFilePath(directory)); + } + if (m_watchFiles) + { + for (const QFileInfo &info : dir.entryInfoList(QDir::Files)) + { + m_watcher->addPath(info.absoluteFilePath()); + } + } } QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory) { - QStringList ret; - if(!m_matcher) - { - return {}; - } - for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden)) - { - ret.append(scanRecursive(directory.absoluteFilePath(dir))); - } - for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden)) - { - auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file)); - if (m_matcher->matches(relPath)) - { - ret.append(relPath); - } - } - return ret; + QStringList ret; + if(!m_matcher) + { + return {}; + } + for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden)) + { + ret.append(scanRecursive(directory.absoluteFilePath(dir))); + } + for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden)) + { + auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file)); + if (m_matcher->matches(relPath)) + { + ret.append(relPath); + } + } + return ret; } void RecursiveFileSystemWatcher::fileChange(const QString &path) { - emit fileChanged(path); + emit fileChanged(path); } void RecursiveFileSystemWatcher::directoryChange(const QString &path) { - setFiles(scanRecursive(m_root)); + setFiles(scanRecursive(m_root)); } diff --git a/api/logic/RecursiveFileSystemWatcher.h b/api/logic/RecursiveFileSystemWatcher.h index 07bce0b9..c9c39f49 100644 --- a/api/logic/RecursiveFileSystemWatcher.h +++ b/api/logic/RecursiveFileSystemWatcher.h @@ -8,56 +8,56 @@ class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject { - Q_OBJECT + Q_OBJECT public: - RecursiveFileSystemWatcher(QObject *parent); - - void setRootDir(const QDir &root); - QDir rootDir() const - { - return m_root; - } - - // WARNING: setting this to true may be bad for performance - void setWatchFiles(const bool watchFiles); - bool watchFiles() const - { - return m_watchFiles; - } - - void setMatcher(IPathMatcher::Ptr matcher) - { - m_matcher = matcher; - } - - QStringList files() const - { - return m_files; - } + RecursiveFileSystemWatcher(QObject *parent); + + void setRootDir(const QDir &root); + QDir rootDir() const + { + return m_root; + } + + // WARNING: setting this to true may be bad for performance + void setWatchFiles(const bool watchFiles); + bool watchFiles() const + { + return m_watchFiles; + } + + void setMatcher(IPathMatcher::Ptr matcher) + { + m_matcher = matcher; + } + + QStringList files() const + { + return m_files; + } signals: - void filesChanged(); - void fileChanged(const QString &path); + void filesChanged(); + void fileChanged(const QString &path); public slots: - void enable(); - void disable(); + void enable(); + void disable(); private: - QDir m_root; - bool m_watchFiles = false; - bool m_isEnabled = false; - IPathMatcher::Ptr m_matcher; + QDir m_root; + bool m_watchFiles = false; + bool m_isEnabled = false; + IPathMatcher::Ptr m_matcher; - QFileSystemWatcher *m_watcher; + QFileSystemWatcher *m_watcher; - QStringList m_files; - void setFiles(const QStringList &files); + QStringList m_files; + void setFiles(const QStringList &files); - void addFilesToWatcherRecursive(const QDir &dir); - QStringList scanRecursive(const QDir &dir); + void addFilesToWatcherRecursive(const QDir &dir); + QStringList scanRecursive(const QDir &dir); private slots: - void fileChange(const QString &path); - void directoryChange(const QString &path); + void fileChange(const QString &path); + void directoryChange(const QString &path); }; diff --git a/api/logic/SeparatorPrefixTree.h b/api/logic/SeparatorPrefixTree.h index fd149af0..7a841cb7 100644 --- a/api/logic/SeparatorPrefixTree.h +++ b/api/logic/SeparatorPrefixTree.h @@ -7,292 +7,292 @@ template class SeparatorPrefixTree { public: - SeparatorPrefixTree(QStringList paths) - { - insert(paths); - } + SeparatorPrefixTree(QStringList paths) + { + insert(paths); + } - SeparatorPrefixTree(bool contained = false) - { - m_contained = contained; - } + SeparatorPrefixTree(bool contained = false) + { + m_contained = contained; + } - void insert(QStringList paths) - { - for(auto &path: paths) - { - insert(path); - } - } + void insert(QStringList paths) + { + for(auto &path: paths) + { + insert(path); + } + } - /// insert an exact path into the tree - SeparatorPrefixTree & insert(QString path) - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - children[path] = SeparatorPrefixTree(true); - return children[path]; - } - else - { - auto prefix = path.left(sepIndex); - if(!children.contains(prefix)) - { - children[prefix] = SeparatorPrefixTree(false); - } - return children[prefix].insert(path.mid(sepIndex + 1)); - } - } + /// insert an exact path into the tree + SeparatorPrefixTree & insert(QString path) + { + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + children[path] = SeparatorPrefixTree(true); + return children[path]; + } + else + { + auto prefix = path.left(sepIndex); + if(!children.contains(prefix)) + { + children[prefix] = SeparatorPrefixTree(false); + } + return children[prefix].insert(path.mid(sepIndex + 1)); + } + } - /// is the path fully contained in the tree? - bool contains(QString path) const - { - auto node = find(path); - return node != nullptr; - } + /// is the path fully contained in the tree? + bool contains(QString path) const + { + auto node = find(path); + return node != nullptr; + } - /// does the tree cover a path? That means the prefix of the path is contained in the tree - bool covers(QString path) const - { - // if we found some valid node, it's good enough. the tree covers the path - if(m_contained) - { - return true; - } - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return false; - } - return (*found).covers(QString()); - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return false; - } - return (*found).covers(path.mid(sepIndex + 1)); - } - } + /// does the tree cover a path? That means the prefix of the path is contained in the tree + bool covers(QString path) const + { + // if we found some valid node, it's good enough. the tree covers the path + if(m_contained) + { + return true; + } + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return false; + } + return (*found).covers(QString()); + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return false; + } + return (*found).covers(path.mid(sepIndex + 1)); + } + } - /// return the contained path that covers the path specified - QString cover(QString path) const - { - // if we found some valid node, it's good enough. the tree covers the path - if(m_contained) - { - return QString(""); - } - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return QString(); - } - auto nested = (*found).cover(QString()); - if(nested.isNull()) - { - return nested; - } - if(nested.isEmpty()) - return path; - return path + Tseparator + nested; - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return QString(); - } - auto nested = (*found).cover(path.mid(sepIndex + 1)); - if(nested.isNull()) - { - return nested; - } - if(nested.isEmpty()) - return prefix; - return prefix + Tseparator + nested; - } - } + /// return the contained path that covers the path specified + QString cover(QString path) const + { + // if we found some valid node, it's good enough. the tree covers the path + if(m_contained) + { + return QString(""); + } + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return QString(); + } + auto nested = (*found).cover(QString()); + if(nested.isNull()) + { + return nested; + } + if(nested.isEmpty()) + return path; + return path + Tseparator + nested; + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return QString(); + } + auto nested = (*found).cover(path.mid(sepIndex + 1)); + if(nested.isNull()) + { + return nested; + } + if(nested.isEmpty()) + return prefix; + return prefix + Tseparator + nested; + } + } - /// Does the path-specified node exist in the tree? It does not have to be contained. - bool exists(QString path) const - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return false; - } - return true; - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return false; - } - return (*found).exists(path.mid(sepIndex + 1)); - } - } + /// Does the path-specified node exist in the tree? It does not have to be contained. + bool exists(QString path) const + { + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return false; + } + return true; + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return false; + } + return (*found).exists(path.mid(sepIndex + 1)); + } + } - /// find a node in the tree by name - const SeparatorPrefixTree * find(QString path) const - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return nullptr; - } - return &(*found); - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return nullptr; - } - return (*found).find(path.mid(sepIndex + 1)); - } - } + /// find a node in the tree by name + const SeparatorPrefixTree * find(QString path) const + { + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return nullptr; + } + return &(*found); + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return nullptr; + } + return (*found).find(path.mid(sepIndex + 1)); + } + } - /// is this a leaf node? - bool leaf() const - { - return children.isEmpty(); - } + /// is this a leaf node? + bool leaf() const + { + return children.isEmpty(); + } - /// is this node actually contained in the tree, or is it purely structural? - bool contained() const - { - return m_contained; - } + /// is this node actually contained in the tree, or is it purely structural? + bool contained() const + { + return m_contained; + } - /// Remove a path from the tree - bool remove(QString path) - { - return removeInternal(path) != Failed; - } + /// Remove a path from the tree + bool remove(QString path) + { + return removeInternal(path) != Failed; + } - /// Clear all children of this node tree node - void clear() - { - children.clear(); - } + /// Clear all children of this node tree node + void clear() + { + children.clear(); + } - QStringList toStringList() const - { - QStringList collected; - // collecting these is more expensive. - auto iter = children.begin(); - while(iter != children.end()) - { - QStringList list = iter.value().toStringList(); - for(int i = 0; i < list.size(); i++) - { - list[i] = iter.key() + Tseparator + list[i]; - } - collected.append(list); - if((*iter).m_contained) - { - collected.append(iter.key()); - } - iter++; - } - return collected; - } + QStringList toStringList() const + { + QStringList collected; + // collecting these is more expensive. + auto iter = children.begin(); + while(iter != children.end()) + { + QStringList list = iter.value().toStringList(); + for(int i = 0; i < list.size(); i++) + { + list[i] = iter.key() + Tseparator + list[i]; + } + collected.append(list); + if((*iter).m_contained) + { + collected.append(iter.key()); + } + iter++; + } + return collected; + } private: - enum Removal - { - Failed, - Succeeded, - HasChildren - }; - Removal removeInternal(QString path = QString()) - { - if(path.isEmpty()) - { - if(!m_contained) - { - // remove all children - we are removing a prefix - clear(); - return Succeeded; - } - m_contained = false; - if(children.size()) - { - return HasChildren; - } - return Succeeded; - } - Removal remStatus = Failed; - QString childToRemove; - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - childToRemove = path; - auto found = children.find(childToRemove); - if(found == children.end()) - { - return Failed; - } - remStatus = (*found).removeInternal(); - } - else - { - childToRemove = path.left(sepIndex); - auto found = children.find(childToRemove); - if(found == children.end()) - { - return Failed; - } - remStatus = (*found).removeInternal(path.mid(sepIndex + 1)); - } - switch (remStatus) - { - case Failed: - case HasChildren: - { - return remStatus; - } - case Succeeded: - { - children.remove(childToRemove); - if(m_contained) - { - return HasChildren; - } - if(children.size()) - { - return HasChildren; - } - return Succeeded; - } - } - return Failed; - } + enum Removal + { + Failed, + Succeeded, + HasChildren + }; + Removal removeInternal(QString path = QString()) + { + if(path.isEmpty()) + { + if(!m_contained) + { + // remove all children - we are removing a prefix + clear(); + return Succeeded; + } + m_contained = false; + if(children.size()) + { + return HasChildren; + } + return Succeeded; + } + Removal remStatus = Failed; + QString childToRemove; + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + childToRemove = path; + auto found = children.find(childToRemove); + if(found == children.end()) + { + return Failed; + } + remStatus = (*found).removeInternal(); + } + else + { + childToRemove = path.left(sepIndex); + auto found = children.find(childToRemove); + if(found == children.end()) + { + return Failed; + } + remStatus = (*found).removeInternal(path.mid(sepIndex + 1)); + } + switch (remStatus) + { + case Failed: + case HasChildren: + { + return remStatus; + } + case Succeeded: + { + children.remove(childToRemove); + if(m_contained) + { + return HasChildren; + } + if(children.size()) + { + return HasChildren; + } + return Succeeded; + } + } + return Failed; + } private: - QMap> children; - bool m_contained = false; + QMap> children; + bool m_contained = false; }; diff --git a/api/logic/Usable.h b/api/logic/Usable.h index 1168be4d..83dd083d 100644 --- a/api/logic/Usable.h +++ b/api/logic/Usable.h @@ -12,27 +12,27 @@ class Usable; */ class Usable { - friend class UseLock; + friend class UseLock; public: - std::size_t useCount() - { - return m_useCount; - } - bool isInUse() - { - return m_useCount > 0; - } + std::size_t useCount() + { + return m_useCount; + } + bool isInUse() + { + return m_useCount > 0; + } protected: - virtual void decrementUses() - { - m_useCount--; - } - virtual void incrementUses() - { - m_useCount++; - } + virtual void decrementUses() + { + m_useCount--; + } + virtual void incrementUses() + { + m_useCount++; + } private: - std::size_t m_useCount = 0; + std::size_t m_useCount = 0; }; /** @@ -43,16 +43,16 @@ private: class UseLock { public: - UseLock(std::shared_ptr 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(); - } + UseLock(std::shared_ptr 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 m_usable; + std::shared_ptr m_usable; }; diff --git a/api/logic/Version.cpp b/api/logic/Version.cpp index 2c83374f..42eac669 100644 --- a/api/logic/Version.cpp +++ b/api/logic/Version.cpp @@ -7,79 +7,79 @@ Version::Version(const QString &str) : m_string(str) { - parse(); + parse(); } bool Version::operator<(const Version &other) const { - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 < sec2; - } - } + const int size = qMax(m_sections.size(), other.m_sections.size()); + for (int i = 0; i < size; ++i) + { + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); + const Section sec2 = + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); + if (sec1 != sec2) + { + return sec1 < sec2; + } + } - return false; + return false; } bool Version::operator<=(const Version &other) const { - return *this < other || *this == other; + return *this < other || *this == other; } bool Version::operator>(const Version &other) const { - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 > sec2; - } - } + const int size = qMax(m_sections.size(), other.m_sections.size()); + for (int i = 0; i < size; ++i) + { + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); + const Section sec2 = + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); + if (sec1 != sec2) + { + return sec1 > sec2; + } + } - return false; + return false; } bool Version::operator>=(const Version &other) const { - return *this > other || *this == other; + return *this > other || *this == other; } bool Version::operator==(const Version &other) const { - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return false; - } - } + const int size = qMax(m_sections.size(), other.m_sections.size()); + for (int i = 0; i < size; ++i) + { + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); + const Section sec2 = + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); + if (sec1 != sec2) + { + return false; + } + } - return true; + return true; } bool Version::operator!=(const Version &other) const { - return !operator==(other); + return !operator==(other); } void Version::parse() { - m_sections.clear(); + m_sections.clear(); - // FIXME: this is bad. versions can contain a lot more separators... - QStringList parts = m_string.split('.'); + // FIXME: this is bad. versions can contain a lot more separators... + QStringList parts = m_string.split('.'); - for (const auto part : parts) - { - m_sections.append(Section(part)); - } + for (const auto part : parts) + { + m_sections.append(Section(part)); + } } diff --git a/api/logic/Version.h b/api/logic/Version.h index 08dfb6e9..c5d93081 100644 --- a/api/logic/Version.h +++ b/api/logic/Version.h @@ -7,100 +7,101 @@ class QUrl; -struct MULTIMC_LOGIC_EXPORT Version +class MULTIMC_LOGIC_EXPORT Version { - Version(const QString &str); - Version() {} +public: + Version(const QString &str); + Version() {} - bool operator<(const Version &other) const; - bool operator<=(const Version &other) const; - bool operator>(const Version &other) const; - bool operator>=(const Version &other) const; - bool operator==(const Version &other) const; - bool operator!=(const Version &other) const; + bool operator<(const Version &other) const; + bool operator<=(const Version &other) const; + bool operator>(const Version &other) const; + bool operator>=(const Version &other) const; + bool operator==(const Version &other) const; + bool operator!=(const Version &other) const; - QString toString() const - { - return m_string; - } + QString toString() const + { + return m_string; + } private: - QString m_string; - struct Section - { - explicit Section(const QString &fullString) - { - m_fullString = fullString; - int cutoff = m_fullString.size(); - for(int i = 0; i < m_fullString.size(); i++) - { - if(!m_fullString[i].isDigit()) - { - cutoff = i; - break; - } - } - auto numPart = m_fullString.leftRef(cutoff); - if(numPart.size()) - { - numValid = true; - m_numPart = numPart.toInt(); - } - auto stringPart = m_fullString.midRef(cutoff); - if(stringPart.size()) - { - m_stringPart = stringPart.toString(); - } - } - explicit Section() {} - bool numValid = false; - int m_numPart = 0; - QString m_stringPart; - QString m_fullString; + QString m_string; + struct Section + { + explicit Section(const QString &fullString) + { + m_fullString = fullString; + int cutoff = m_fullString.size(); + for(int i = 0; i < m_fullString.size(); i++) + { + if(!m_fullString[i].isDigit()) + { + cutoff = i; + break; + } + } + auto numPart = m_fullString.leftRef(cutoff); + if(numPart.size()) + { + numValid = true; + m_numPart = numPart.toInt(); + } + auto stringPart = m_fullString.midRef(cutoff); + if(stringPart.size()) + { + m_stringPart = stringPart.toString(); + } + } + explicit Section() {} + bool numValid = false; + int m_numPart = 0; + QString m_stringPart; + QString m_fullString; - inline bool operator!=(const Section &other) const - { - if(numValid && other.numValid) - { - return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; - } - else - { - return m_fullString != other.m_fullString; - } - } - inline bool operator<(const Section &other) const - { - if(numValid && other.numValid) - { - if(m_numPart < other.m_numPart) - return true; - if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) - return true; - return false; - } - else - { - return m_fullString < other.m_fullString; - } - } - inline bool operator>(const Section &other) const - { - if(numValid && other.numValid) - { - if(m_numPart > other.m_numPart) - return true; - if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) - return true; - return false; - } - else - { - return m_fullString > other.m_fullString; - } - } - }; - QList
m_sections; + inline bool operator!=(const Section &other) const + { + if(numValid && other.numValid) + { + return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; + } + else + { + return m_fullString != other.m_fullString; + } + } + inline bool operator<(const Section &other) const + { + if(numValid && other.numValid) + { + if(m_numPart < other.m_numPart) + return true; + if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) + return true; + return false; + } + else + { + return m_fullString < other.m_fullString; + } + } + inline bool operator>(const Section &other) const + { + if(numValid && other.numValid) + { + if(m_numPart > other.m_numPart) + return true; + if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) + return true; + return false; + } + else + { + return m_fullString > other.m_fullString; + } + } + }; + QList
m_sections; - void parse(); + void parse(); }; diff --git a/api/logic/Version_test.cpp b/api/logic/Version_test.cpp index b8e05768..9c222a70 100644 --- a/api/logic/Version_test.cpp +++ b/api/logic/Version_test.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,64 +20,64 @@ class ModUtilsTest : public QObject { - Q_OBJECT - void setupVersions() - { - QTest::addColumn("first"); - QTest::addColumn("second"); - QTest::addColumn("lessThan"); - QTest::addColumn("equal"); + Q_OBJECT + void setupVersions() + { + QTest::addColumn("first"); + QTest::addColumn("second"); + QTest::addColumn("lessThan"); + QTest::addColumn("equal"); - QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true; - QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true; + QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true; + QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true; + QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true; + QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true; - QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false; - QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false; - QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false; - QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false; - QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false; - QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false; - QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false; + QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false; + QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false; + QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false; + QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false; + QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false; + QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false; + QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false; - QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false; - QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false; - QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false; - QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false; - QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false; - } + QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false; + QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false; + QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false; + QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false; + QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false; + } private slots: - void initTestCase() - { + void initTestCase() + { - } - void cleanupTestCase() - { + } + void cleanupTestCase() + { - } + } - void test_versionCompare_data() - { - setupVersions(); - } - void test_versionCompare() - { - QFETCH(QString, first); - QFETCH(QString, second); - QFETCH(bool, lessThan); - QFETCH(bool, equal); + void test_versionCompare_data() + { + setupVersions(); + } + void test_versionCompare() + { + QFETCH(QString, first); + QFETCH(QString, second); + QFETCH(bool, lessThan); + QFETCH(bool, equal); - const auto v1 = Version(first); - const auto v2 = Version(second); + const auto v1 = Version(first); + const auto v2 = Version(second); - QCOMPARE(v1 < v2, lessThan); - QCOMPARE(v1 > v2, !lessThan && !equal); - QCOMPARE(v1 == v2, equal); - } + QCOMPARE(v1 < v2, lessThan); + QCOMPARE(v1 > v2, !lessThan && !equal); + QCOMPARE(v1 == v2, equal); + } }; QTEST_GUILESS_MAIN(ModUtilsTest) diff --git a/api/logic/WatchLock.h b/api/logic/WatchLock.h new file mode 100644 index 00000000..3e08b413 --- /dev/null +++ b/api/logic/WatchLock.h @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include + +struct WatchLock +{ + WatchLock(QFileSystemWatcher * watcher, const QString& directory) + : m_watcher(watcher), m_directory(directory) + { + m_watcher->removePath(m_directory); + } + ~WatchLock() + { + m_watcher->addPath(m_directory); + } + QFileSystemWatcher * m_watcher; + QString m_directory; +}; diff --git a/api/logic/icons/IIconList.h b/api/logic/icons/IIconList.h index e6c16d50..9a3fe022 100644 --- a/api/logic/icons/IIconList.h +++ b/api/logic/icons/IIconList.h @@ -6,21 +6,21 @@ enum IconType : unsigned { - Builtin, - Transient, - FileBased, - ICONS_TOTAL, - ToBeDeleted + 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; - virtual void installIcon(const QString &file, const QString &name) = 0; + 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; + virtual void installIcon(const QString &file, const QString &name) = 0; }; diff --git a/api/logic/icons/IconUtils.cpp b/api/logic/icons/IconUtils.cpp new file mode 100644 index 00000000..bf530c16 --- /dev/null +++ b/api/logic/icons/IconUtils.cpp @@ -0,0 +1,62 @@ +#include "IconUtils.h" + +#include "FileSystem.h" +#include + +#include + +namespace { +std::array validIconExtensions = {{ + "svg", + "png", + "ico", + "gif", + "jpg", + "jpeg" +}}; +} + +namespace IconUtils{ + +QString findBestIconIn(const QString &folder, const QString & iconKey) { + int best_found = validIconExtensions.size(); + QString best_filename; + + QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags); + while (it.hasNext()) { + it.next(); + auto fileInfo = it.fileInfo(); + + if(fileInfo.completeBaseName() != iconKey) + continue; + + auto extension = fileInfo.suffix(); + + for(int i = 0; i < best_found; i++) { + if(extension == validIconExtensions[i]) { + best_found = i; + qDebug() << i << " : " << fileInfo.fileName(); + best_filename = fileInfo.fileName(); + } + } + } + return FS::PathCombine(folder, best_filename); +} + +QString getIconFilter() { + QString out; + QTextStream stream(&out); + stream << '('; + for(size_t i = 0; i < validIconExtensions.size() - 1; i++) { + if(i > 0) { + stream << " "; + } + stream << "*." << validIconExtensions[i]; + } + stream << " *." << validIconExtensions[validIconExtensions.size() - 1]; + stream << ')'; + return out; +} + +} + diff --git a/api/logic/icons/IconUtils.h b/api/logic/icons/IconUtils.h new file mode 100644 index 00000000..ce236946 --- /dev/null +++ b/api/logic/icons/IconUtils.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "multimc_logic_export.h" + +namespace IconUtils { + +// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path +MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey); + +// Get icon file type filter for file browser dialogs +MULTIMC_LOGIC_EXPORT QString getIconFilter(); + +} diff --git a/api/logic/java/JavaChecker.cpp b/api/logic/java/JavaChecker.cpp index f0b71e48..ca0f4bde 100644 --- a/api/logic/java/JavaChecker.cpp +++ b/api/logic/java/JavaChecker.cpp @@ -16,149 +16,149 @@ JavaChecker::JavaChecker(QObject *parent) : QObject(parent) void JavaChecker::performCheck() { - QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar"); - - QStringList args; - - process.reset(new QProcess()); - if(m_args.size()) - { - auto extraArgs = Commandline::splitArgs(m_args); - args.append(extraArgs); - } - if(m_minMem != 0) - { - args << QString("-Xms%1m").arg(m_minMem); - } - if(m_maxMem != 0) - { - args << QString("-Xmx%1m").arg(m_maxMem); - } - if(m_permGen != 64) - { - args << QString("-XX:PermSize=%1m").arg(m_permGen); - } - - args.append({"-jar", checkerJar}); - process->setArguments(args); - process->setProgram(m_path); - process->setProcessChannelMode(QProcess::SeparateChannels); - process->setProcessEnvironment(CleanEnviroment()); - qDebug() << "Running java checker: " + m_path + args.join(" ");; - - connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); - connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); - connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); - connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); - connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); - killTimer.setSingleShot(true); - killTimer.start(15000); - process->start(); + QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar"); + + QStringList args; + + process.reset(new QProcess()); + if(m_args.size()) + { + auto extraArgs = Commandline::splitArgs(m_args); + args.append(extraArgs); + } + if(m_minMem != 0) + { + args << QString("-Xms%1m").arg(m_minMem); + } + if(m_maxMem != 0) + { + args << QString("-Xmx%1m").arg(m_maxMem); + } + if(m_permGen != 64) + { + args << QString("-XX:PermSize=%1m").arg(m_permGen); + } + + args.append({"-jar", checkerJar}); + process->setArguments(args); + process->setProgram(m_path); + process->setProcessChannelMode(QProcess::SeparateChannels); + process->setProcessEnvironment(CleanEnviroment()); + qDebug() << "Running java checker: " + m_path + args.join(" ");; + + connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); + connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); + connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); + connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); + connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); + killTimer.setSingleShot(true); + killTimer.start(15000); + process->start(); } void JavaChecker::stdoutReady() { - QByteArray data = process->readAllStandardOutput(); - QString added = QString::fromLocal8Bit(data); - added.remove('\r'); - m_stdout += added; + QByteArray data = process->readAllStandardOutput(); + QString added = QString::fromLocal8Bit(data); + added.remove('\r'); + m_stdout += added; } void JavaChecker::stderrReady() { - QByteArray data = process->readAllStandardError(); - QString added = QString::fromLocal8Bit(data); - added.remove('\r'); - m_stderr += added; + QByteArray data = process->readAllStandardError(); + QString added = QString::fromLocal8Bit(data); + added.remove('\r'); + m_stderr += added; } void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) { - killTimer.stop(); - QProcessPtr _process; - _process.swap(process); - - JavaCheckResult result; - { - result.path = m_path; - 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) - { - result.validity = JavaCheckResult::Validity::Errored; - emit checkFinished(result); - return; - } - - bool success = true; - - QMap results; - QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); - for(QString line : lines) - { - line = line.trimmed(); - - auto parts = line.split('=', QString::SkipEmptyParts); - if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) - { - success = false; - } - else - { - results.insert(parts[0], parts[1]); - } - } - - if(!results.contains("os.arch") || !results.contains("java.version") || !success) - { - result.validity = JavaCheckResult::Validity::ReturnedInvalidData; - emit checkFinished(result); - return; - } - - auto os_arch = results["os.arch"]; - auto java_version = results["java.version"]; - bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; - - - result.validity = JavaCheckResult::Validity::Valid; - result.is_64bit = is_64; - result.mojangPlatform = is_64 ? "64" : "32"; - result.realPlatform = os_arch; - result.javaVersion = java_version; - qDebug() << "Java checker succeeded."; - emit checkFinished(result); + killTimer.stop(); + QProcessPtr _process = process; + process.reset(); + + JavaCheckResult result; + { + result.path = m_path; + 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) + { + result.validity = JavaCheckResult::Validity::Errored; + emit checkFinished(result); + return; + } + + bool success = true; + + QMap results; + QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); + for(QString line : lines) + { + line = line.trimmed(); + + auto parts = line.split('=', QString::SkipEmptyParts); + if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) + { + success = false; + } + else + { + results.insert(parts[0], parts[1]); + } + } + + if(!results.contains("os.arch") || !results.contains("java.version") || !success) + { + result.validity = JavaCheckResult::Validity::ReturnedInvalidData; + emit checkFinished(result); + return; + } + + auto os_arch = results["os.arch"]; + auto java_version = results["java.version"]; + bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; + + + result.validity = JavaCheckResult::Validity::Valid; + result.is_64bit = is_64; + result.mojangPlatform = is_64 ? "64" : "32"; + result.realPlatform = os_arch; + result.javaVersion = java_version; + qDebug() << "Java checker succeeded."; + emit checkFinished(result); } void JavaChecker::error(QProcess::ProcessError err) { - if(err == QProcess::FailedToStart) - { - killTimer.stop(); - qDebug() << "Java checker has failed to start."; - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } - - emit checkFinished(result); - return; - } + if(err == QProcess::FailedToStart) + { + killTimer.stop(); + qDebug() << "Java checker has failed to start."; + JavaCheckResult result; + { + result.path = m_path; + result.id = m_id; + } + + emit checkFinished(result); + return; + } } void JavaChecker::timeout() { - // NO MERCY. NO ABUSE. - if(process) - { - qDebug() << "Java checker has been killed by timeout."; - process->kill(); - } + // NO MERCY. NO ABUSE. + if(process) + { + qDebug() << "Java checker has been killed by timeout."; + process->kill(); + } } diff --git a/api/logic/java/JavaChecker.h b/api/logic/java/JavaChecker.h index c6bd697c..af0dcb90 100644 --- a/api/logic/java/JavaChecker.h +++ b/api/logic/java/JavaChecker.h @@ -3,6 +3,8 @@ #include #include +#include "QObjectPtr.h" + #include "multimc_logic_export.h" #include "JavaVersion.h" @@ -11,50 +13,50 @@ class JavaChecker; struct MULTIMC_LOGIC_EXPORT JavaCheckResult { - QString path; - QString mojangPlatform; - QString realPlatform; - JavaVersion javaVersion; - QString outLog; - QString errorLog; - bool is_64bit = false; - int id; - enum class Validity - { - Errored, - ReturnedInvalidData, - Valid - } validity = Validity::Errored; + QString path; + QString mojangPlatform; + QString realPlatform; + JavaVersion javaVersion; + QString outLog; + QString errorLog; + bool is_64bit = false; + int id; + enum class Validity + { + Errored, + ReturnedInvalidData, + Valid + } validity = Validity::Errored; }; -typedef std::shared_ptr QProcessPtr; -typedef std::shared_ptr JavaCheckerPtr; +typedef shared_qobject_ptr QProcessPtr; +typedef shared_qobject_ptr JavaCheckerPtr; class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit JavaChecker(QObject *parent = 0); - void performCheck(); + explicit JavaChecker(QObject *parent = 0); + void performCheck(); - QString m_path; - QString m_args; - int m_id = 0; - int m_minMem = 0; - int m_maxMem = 0; - int m_permGen = 64; + QString m_path; + QString m_args; + int m_id = 0; + int m_minMem = 0; + int m_maxMem = 0; + int m_permGen = 64; signals: - void checkFinished(JavaCheckResult result); + void checkFinished(JavaCheckResult result); private: - QProcessPtr process; - QTimer killTimer; - QString m_stdout; - QString m_stderr; + QProcessPtr process; + QTimer killTimer; + QString m_stdout; + QString m_stderr; public slots: - void timeout(); - void finished(int exitcode, QProcess::ExitStatus); - void error(QProcess::ProcessError); - void stdoutReady(); - void stderrReady(); + void timeout(); + void finished(int exitcode, QProcess::ExitStatus); + void error(QProcess::ProcessError); + void stdoutReady(); + void stderrReady(); }; diff --git a/api/logic/java/JavaCheckerJob.cpp b/api/logic/java/JavaCheckerJob.cpp index fabb5aaa..223d8f55 100644 --- a/api/logic/java/JavaCheckerJob.cpp +++ b/api/logic/java/JavaCheckerJob.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,26 +19,26 @@ void JavaCheckerJob::partFinished(JavaCheckResult result) { - num_finished++; - qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" - << javacheckers.size(); - setProgress(num_finished, javacheckers.size()); + num_finished++; + qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" + << javacheckers.size(); + setProgress(num_finished, javacheckers.size()); - javaresults.replace(result.id, result); + javaresults.replace(result.id, result); - if (num_finished == javacheckers.size()) - { - emitSucceeded(); - } + if (num_finished == javacheckers.size()) + { + emitSucceeded(); + } } void JavaCheckerJob::executeTask() { - qDebug() << m_job_name.toLocal8Bit() << " started."; - for (auto iter : javacheckers) - { - javaresults.append(JavaCheckResult()); - connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); - iter->performCheck(); - } + qDebug() << m_job_name.toLocal8Bit() << " started."; + for (auto iter : javacheckers) + { + javaresults.append(JavaCheckResult()); + connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); + iter->performCheck(); + } } diff --git a/api/logic/java/JavaCheckerJob.h b/api/logic/java/JavaCheckerJob.h index cac2b638..24d0d1b8 100644 --- a/api/logic/java/JavaCheckerJob.h +++ b/api/logic/java/JavaCheckerJob.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,41 +20,42 @@ #include "tasks/Task.h" class JavaCheckerJob; -typedef std::shared_ptr JavaCheckerJobPtr; +typedef shared_qobject_ptr JavaCheckerJobPtr; // FIXME: this just seems horribly redundant class JavaCheckerJob : public Task { - Q_OBJECT + Q_OBJECT public: - explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {}; - - bool addJavaCheckerAction(JavaCheckerPtr base) - { - javacheckers.append(base); - // if this is already running, the action needs to be started right away! - if (isRunning()) - { - setProgress(num_finished, javacheckers.size()); - connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); - base->performCheck(); - } - return true; - } - QList getResults() - { - return javaresults; - } + explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {}; + virtual ~JavaCheckerJob() {}; + + bool addJavaCheckerAction(JavaCheckerPtr base) + { + javacheckers.append(base); + // if this is already running, the action needs to be started right away! + if (isRunning()) + { + setProgress(num_finished, javacheckers.size()); + connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); + base->performCheck(); + } + return true; + } + QList getResults() + { + return javaresults; + } private slots: - void partFinished(JavaCheckResult result); + void partFinished(JavaCheckResult result); protected: - virtual void executeTask() override; + virtual void executeTask() override; private: - QString m_job_name; - QList javacheckers; - QList javaresults; - int num_finished = 0; + QString m_job_name; + QList javacheckers; + QList javaresults; + int num_finished = 0; }; diff --git a/api/logic/java/JavaInstall.cpp b/api/logic/java/JavaInstall.cpp index bb262b6e..5bcf7bcb 100644 --- a/api/logic/java/JavaInstall.cpp +++ b/api/logic/java/JavaInstall.cpp @@ -3,26 +3,26 @@ bool JavaInstall::operator<(const JavaInstall &rhs) { - auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); - if(archCompare != 0) - return archCompare < 0; - if(id < rhs.id) - { - return true; - } - if(id > rhs.id) - { - return false; - } - return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; + auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); + if(archCompare != 0) + return archCompare < 0; + if(id < rhs.id) + { + return true; + } + if(id > rhs.id) + { + return false; + } + return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; } bool JavaInstall::operator==(const JavaInstall &rhs) { - return arch == rhs.arch && id == rhs.id && path == rhs.path; + return arch == rhs.arch && id == rhs.id && path == rhs.path; } bool JavaInstall::operator>(const JavaInstall &rhs) { - return (!operator<(rhs)) && (!operator==(rhs)); + return (!operator<(rhs)) && (!operator==(rhs)); } diff --git a/api/logic/java/JavaInstall.h b/api/logic/java/JavaInstall.h index 882c7386..64be40d1 100644 --- a/api/logic/java/JavaInstall.h +++ b/api/logic/java/JavaInstall.h @@ -5,34 +5,34 @@ struct JavaInstall : public BaseVersion { - JavaInstall(){} - JavaInstall(QString id, QString arch, QString path) - : id(id), arch(arch), path(path) - { - } - virtual QString descriptor() - { - return id.toString(); - } + JavaInstall(){} + JavaInstall(QString id, QString arch, QString path) + : id(id), arch(arch), path(path) + { + } + virtual QString descriptor() + { + return id.toString(); + } - virtual QString name() - { - return id.toString(); - } + virtual QString name() + { + return id.toString(); + } - virtual QString typeString() const - { - return arch; - } + virtual QString typeString() const + { + return arch; + } - bool operator<(const JavaInstall & rhs); - bool operator==(const JavaInstall & rhs); - bool operator>(const JavaInstall & rhs); + bool operator<(const JavaInstall & rhs); + bool operator==(const JavaInstall & rhs); + bool operator>(const JavaInstall & rhs); - JavaVersion id; - QString arch; - QString path; - bool recommended = false; + JavaVersion id; + QString arch; + QString path; + bool recommended = false; }; typedef std::shared_ptr JavaInstallPtr; diff --git a/api/logic/java/JavaInstallList.cpp b/api/logic/java/JavaInstallList.cpp index 9d2e2f8b..a71a7dbe 100644 --- a/api/logic/java/JavaInstallList.cpp +++ b/api/logic/java/JavaInstallList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,111 +31,111 @@ JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent) shared_qobject_ptr JavaInstallList::getLoadTask() { - load(); - return getCurrentTask(); + load(); + return getCurrentTask(); } shared_qobject_ptr JavaInstallList::getCurrentTask() { - if(m_status == Status::InProgress) - { - return m_loadTask; - } - return nullptr; + if(m_status == Status::InProgress) + { + return m_loadTask; + } + return nullptr; } void JavaInstallList::load() { - if(m_status != Status::InProgress) - { - m_status = Status::InProgress; - m_loadTask = new JavaListLoadTask(this); - m_loadTask->start(); - } + if(m_status != Status::InProgress) + { + m_status = Status::InProgress; + m_loadTask = new JavaListLoadTask(this); + m_loadTask->start(); + } } const BaseVersionPtr JavaInstallList::at(int i) const { - return m_vlist.at(i); + return m_vlist.at(i); } bool JavaInstallList::isLoaded() { - return m_status == JavaInstallList::Status::Done; + return m_status == JavaInstallList::Status::Done; } int JavaInstallList::count() const { - return m_vlist.count(); + return m_vlist.count(); } QVariant JavaInstallList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - auto version = std::dynamic_pointer_cast(m_vlist[index.row()]); - switch (role) - { - case VersionPointerRole: - return qVariantFromValue(m_vlist[index.row()]); - case VersionIdRole: - return version->descriptor(); - case VersionRole: - return version->id.toString(); - case RecommendedRole: - return version->recommended; - case PathRole: - return version->path; - case ArchitectureRole: - return version->arch; - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = std::dynamic_pointer_cast(m_vlist[index.row()]); + switch (role) + { + case VersionPointerRole: + return qVariantFromValue(m_vlist[index.row()]); + case VersionIdRole: + return version->descriptor(); + case VersionRole: + return version->id.toString(); + case RecommendedRole: + return version->recommended; + case PathRole: + return version->path; + case ArchitectureRole: + return version->arch; + default: + return QVariant(); + } } BaseVersionList::RoleList JavaInstallList::providesRoles() const { - return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole}; + return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole}; } void JavaInstallList::updateListData(QList versions) { - beginResetModel(); - m_vlist = versions; - sortVersions(); - if(m_vlist.size()) - { - auto best = std::dynamic_pointer_cast(m_vlist[0]); - best->recommended = true; - } - endResetModel(); - m_status = Status::Done; - m_loadTask.reset(); + beginResetModel(); + m_vlist = versions; + sortVersions(); + if(m_vlist.size()) + { + auto best = std::dynamic_pointer_cast(m_vlist[0]); + best->recommended = true; + } + endResetModel(); + m_status = Status::Done; + m_loadTask.reset(); } bool sortJavas(BaseVersionPtr left, BaseVersionPtr right) { - auto rleft = std::dynamic_pointer_cast(left); - auto rright = std::dynamic_pointer_cast(right); - return (*rleft) > (*rright); + auto rleft = std::dynamic_pointer_cast(left); + auto rright = std::dynamic_pointer_cast(right); + return (*rleft) > (*rright); } void JavaInstallList::sortVersions() { - beginResetModel(); - std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); - endResetModel(); + beginResetModel(); + std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); + endResetModel(); } JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task() { - m_list = vlist; - m_currentRecommended = NULL; + m_list = vlist; + m_currentRecommended = NULL; } JavaListLoadTask::~JavaListLoadTask() @@ -144,65 +144,65 @@ JavaListLoadTask::~JavaListLoadTask() void JavaListLoadTask::executeTask() { - setStatus(tr("Detecting Java installations...")); + setStatus(tr("Detecting Java installations...")); - JavaUtils ju; - QList candidate_paths = ju.FindJavaPaths(); + JavaUtils ju; + QList candidate_paths = ju.FindJavaPaths(); - m_job = std::shared_ptr(new JavaCheckerJob("Java detection")); - connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); - connect(m_job.get(), &Task::progress, this, &Task::setProgress); + m_job = new JavaCheckerJob("Java detection"); + connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); + connect(m_job.get(), &Task::progress, this, &Task::setProgress); - qDebug() << "Probing the following Java paths: "; - int id = 0; - for(QString candidate : candidate_paths) - { - qDebug() << " " << candidate; + qDebug() << "Probing the following Java paths: "; + int id = 0; + for(QString candidate : candidate_paths) + { + qDebug() << " " << candidate; - auto candidate_checker = new JavaChecker(); - candidate_checker->m_path = candidate; - candidate_checker->m_id = id; - m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); + auto candidate_checker = new JavaChecker(); + candidate_checker->m_path = candidate; + candidate_checker->m_id = id; + m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); - id++; - } + id++; + } - m_job->start(); + m_job->start(); } void JavaListLoadTask::javaCheckerFinished() { - QList candidates; - auto results = m_job->getResults(); - - qDebug() << "Found the following valid Java installations:"; - for(JavaCheckResult result : results) - { - if(result.validity == JavaCheckResult::Validity::Valid) - { - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = result.javaVersion; - javaVersion->arch = result.mojangPlatform; - javaVersion->path = result.path; - candidates.append(javaVersion); - - qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; - } - } - - QList javas_bvp; - for (auto java : candidates) - { - //qDebug() << java->id << java->arch << " at " << java->path; - BaseVersionPtr bp_java = std::dynamic_pointer_cast(java); - - if (bp_java) - { - javas_bvp.append(java); - } - } - - m_list->updateListData(javas_bvp); - emitSucceeded(); + QList candidates; + auto results = m_job->getResults(); + + qDebug() << "Found the following valid Java installations:"; + for(JavaCheckResult result : results) + { + if(result.validity == JavaCheckResult::Validity::Valid) + { + JavaInstallPtr javaVersion(new JavaInstall()); + + javaVersion->id = result.javaVersion; + javaVersion->arch = result.mojangPlatform; + javaVersion->path = result.path; + candidates.append(javaVersion); + + qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; + } + } + + QList javas_bvp; + for (auto java : candidates) + { + //qDebug() << java->id << java->arch << " at " << java->path; + BaseVersionPtr bp_java = std::dynamic_pointer_cast(java); + + if (bp_java) + { + javas_bvp.append(java); + } + } + + m_list->updateListData(javas_bvp); + emitSucceeded(); } diff --git a/api/logic/java/JavaInstallList.h b/api/logic/java/JavaInstallList.h index 39f37b80..b98908f3 100644 --- a/api/logic/java/JavaInstallList.h +++ b/api/logic/java/JavaInstallList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,58 +24,60 @@ #include "JavaCheckerJob.h" #include "JavaInstall.h" +#include "QObjectPtr.h" + #include "multimc_logic_export.h" class JavaListLoadTask; class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList { - Q_OBJECT - enum class Status - { - NotDone, - InProgress, - Done - }; + Q_OBJECT + enum class Status + { + NotDone, + InProgress, + Done + }; public: - explicit JavaInstallList(QObject *parent = 0); + explicit JavaInstallList(QObject *parent = 0); - shared_qobject_ptr getLoadTask() override; - bool isLoaded() override; - const BaseVersionPtr at(int i) const override; - int count() const override; - void sortVersions() override; + shared_qobject_ptr getLoadTask() override; + bool isLoaded() override; + const BaseVersionPtr at(int i) const override; + int count() const override; + void sortVersions() override; - QVariant data(const QModelIndex &index, int role) const override; - RoleList providesRoles() const override; + QVariant data(const QModelIndex &index, int role) const override; + RoleList providesRoles() const override; public slots: - void updateListData(QList versions) override; + void updateListData(QList versions) override; protected: - void load(); - shared_qobject_ptr getCurrentTask(); + void load(); + shared_qobject_ptr getCurrentTask(); protected: - Status m_status = Status::NotDone; - shared_qobject_ptr m_loadTask; - QList m_vlist; + Status m_status = Status::NotDone; + shared_qobject_ptr m_loadTask; + QList m_vlist; }; class JavaListLoadTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit JavaListLoadTask(JavaInstallList *vlist); - ~JavaListLoadTask(); + explicit JavaListLoadTask(JavaInstallList *vlist); + virtual ~JavaListLoadTask(); - void executeTask() override; + void executeTask() override; public slots: - void javaCheckerFinished(); + void javaCheckerFinished(); protected: - std::shared_ptr m_job; - JavaInstallList *m_list; - JavaInstall *m_currentRecommended; + shared_qobject_ptr m_job; + JavaInstallList *m_list; + JavaInstall *m_currentRecommended; }; diff --git a/api/logic/java/JavaUtils.cpp b/api/logic/java/JavaUtils.cpp index 4a77bc7e..1f9719a8 100644 --- a/api/logic/java/JavaUtils.cpp +++ b/api/logic/java/JavaUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,313 +31,315 @@ JavaUtils::JavaUtils() { } +#ifdef Q_OS_LINUX static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) { - QDir mmcBin(QCoreApplication::applicationDirPath()); - auto items = LD_LIBRARY_PATH.split(':'); - QStringList final; - for(auto & item: items) - { - QDir test(item); - if(test == mmcBin) - { - qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; - continue; - } - final.append(item); - } - return final.join(':'); + QDir mmcBin(QCoreApplication::applicationDirPath()); + auto items = LD_LIBRARY_PATH.split(':'); + QStringList final; + for(auto & item: items) + { + QDir test(item); + if(test == mmcBin) + { + qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; + continue; + } + final.append(item); + } + return final.join(':'); } +#endif QProcessEnvironment CleanEnviroment() { - // prepare the process environment - QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); - QProcessEnvironment env; - - QStringList ignored = - { - "JAVA_ARGS", - "CLASSPATH", - "CONFIGPATH", - "JAVA_HOME", - "JRE_HOME", - "_JAVA_OPTIONS", - "JAVA_OPTIONS", - "JAVA_TOOL_OPTIONS" - }; - for(auto key: rawenv.keys()) - { - auto value = rawenv.value(key); - // filter out dangerous java crap - if(ignored.contains(key)) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // filter MultiMC-related things - if(key.startsWith("QT_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } + // prepare the process environment + QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); + QProcessEnvironment env; + + QStringList ignored = + { + "JAVA_ARGS", + "CLASSPATH", + "CONFIGPATH", + "JAVA_HOME", + "JRE_HOME", + "_JAVA_OPTIONS", + "JAVA_OPTIONS", + "JAVA_TOOL_OPTIONS" + }; + for(auto key: rawenv.keys()) + { + auto value = rawenv.value(key); + // filter out dangerous java crap + if(ignored.contains(key)) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // filter MultiMC-related things + if(key.startsWith("QT_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } #ifdef Q_OS_LINUX - // Do not pass LD_* variables to java. They were intended for MultiMC - if(key.startsWith("LD_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // Strip IBus - // IBus is a Linux IME framework. For some reason, it breaks MC? - if (key == "XMODIFIERS" && value.contains(IBUS)) - { - QString save = value; - value.replace(IBUS, ""); - qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; - } - if(key == "GAME_PRELOAD") - { - env.insert("LD_PRELOAD", value); - continue; - } - if(key == "GAME_LIBRARY_PATH") - { - env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); - continue; - } + // Do not pass LD_* variables to java. They were intended for MultiMC + if(key.startsWith("LD_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // Strip IBus + // IBus is a Linux IME framework. For some reason, it breaks MC? + if (key == "XMODIFIERS" && value.contains(IBUS)) + { + QString save = value; + value.replace(IBUS, ""); + qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; + } + if(key == "GAME_PRELOAD") + { + env.insert("LD_PRELOAD", value); + continue; + } + if(key == "GAME_LIBRARY_PATH") + { + env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); + continue; + } #endif - // qDebug() << "Env: " << key << value; - env.insert(key, value); - } + // qDebug() << "Env: " << key << value; + env.insert(key, value); + } #ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG42500 - if(!env.contains("LD_LIBRARY_PATH")) - { - env.insert("LD_LIBRARY_PATH", ""); - } + // HACK: Workaround for QTBUG42500 + if(!env.contains("LD_LIBRARY_PATH")) + { + env.insert("LD_LIBRARY_PATH", ""); + } #endif - return env; + return env; } JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) { - JavaInstallPtr javaVersion(new JavaInstall()); + JavaInstallPtr javaVersion(new JavaInstall()); - javaVersion->id = id; - javaVersion->arch = arch; - javaVersion->path = path; + javaVersion->id = id; + javaVersion->arch = arch; + javaVersion->path = path; - return javaVersion; + return javaVersion; } JavaInstallPtr JavaUtils::GetDefaultJava() { - JavaInstallPtr javaVersion(new JavaInstall()); + JavaInstallPtr javaVersion(new JavaInstall()); - javaVersion->id = "java"; - javaVersion->arch = "unknown"; + javaVersion->id = "java"; + javaVersion->arch = "unknown"; #if defined(Q_OS_WIN32) - javaVersion->path = "javaw"; + javaVersion->path = "javaw"; #else - javaVersion->path = "java"; + javaVersion->path = "java"; #endif - return javaVersion; + return javaVersion; } #if defined(Q_OS_WIN32) QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName) { - QList javas; - - QString archType = "unknown"; - if (keyType == KEY_WOW64_64KEY) - archType = "64"; - else if (keyType == KEY_WOW64_32KEY) - archType = "32"; - - HKEY jreKey; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, - KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) - { - // Read the current type version from the registry. - // This will be used to find any key that contains the JavaHome value. - char *value = new char[0]; - DWORD valueSz = 0; - if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == - ERROR_MORE_DATA) - { - value = new char[valueSz]; - RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); - } - - QString recommended = value; - - TCHAR subKeyName[255]; - DWORD subKeyNameSize, numSubKeys, retCode; - - // Get the number of subkeys - RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, - NULL, NULL); - - // Iterate until RegEnumKeyEx fails - if (numSubKeys > 0) - { - for (DWORD i = 0; i < numSubKeys; i++) - { - subKeyNameSize = 255; - retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, - NULL); - if (retCode == ERROR_SUCCESS) - { - // Now open the registry key for the version that we just got. - QString newKeyName = keyName + "\\" + subKeyName; - - HKEY newKey; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, - KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) - { - // Read the JavaHome value to find where Java is installed. - value = new char[0]; - valueSz = 0; - if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, - &valueSz) == ERROR_MORE_DATA) - { - value = new char[valueSz]; - RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, - &valueSz); - - // Now, we construct the version object and add it to the list. - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = subKeyName; - javaVersion->arch = archType; - javaVersion->path = - QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); - javas.append(javaVersion); - } - - RegCloseKey(newKey); - } - } - } - } - - RegCloseKey(jreKey); - } - - return javas; + QList javas; + + QString archType = "unknown"; + if (keyType == KEY_WOW64_64KEY) + archType = "64"; + else if (keyType == KEY_WOW64_32KEY) + archType = "32"; + + HKEY jreKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, + KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) + { + // Read the current type version from the registry. + // This will be used to find any key that contains the JavaHome value. + char *value = new char[0]; + DWORD valueSz = 0; + if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == + ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); + } + + QString recommended = value; + + TCHAR subKeyName[255]; + DWORD subKeyNameSize, numSubKeys, retCode; + + // Get the number of subkeys + RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, + NULL, NULL); + + // Iterate until RegEnumKeyEx fails + if (numSubKeys > 0) + { + for (DWORD i = 0; i < numSubKeys; i++) + { + subKeyNameSize = 255; + retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, + NULL); + if (retCode == ERROR_SUCCESS) + { + // Now open the registry key for the version that we just got. + QString newKeyName = keyName + "\\" + subKeyName; + + HKEY newKey; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, + KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) + { + // Read the JavaHome value to find where Java is installed. + value = new char[0]; + valueSz = 0; + if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, + &valueSz) == ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, + &valueSz); + + // Now, we construct the version object and add it to the list. + JavaInstallPtr javaVersion(new JavaInstall()); + + javaVersion->id = subKeyName; + javaVersion->arch = archType; + javaVersion->path = + QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); + javas.append(javaVersion); + } + + RegCloseKey(newKey); + } + } + } + } + + RegCloseKey(jreKey); + } + + return javas; } QList JavaUtils::FindJavaPaths() { - QList java_candidates; - - QList JRE64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); - QList JDK64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); - QList JRE32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); - QList JDK32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); - - java_candidates.append(JRE64s); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); - java_candidates.append(JDK64s); - java_candidates.append(JRE32s); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); - java_candidates.append(JDK32s); - java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); - - QList candidates; - for(JavaInstallPtr java_candidate : java_candidates) - { - if(!candidates.contains(java_candidate->path)) - { - candidates.append(java_candidate->path); - } - } - - return candidates; + QList java_candidates; + + QList JRE64s = this->FindJavaFromRegistryKey( + KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList JDK64s = this->FindJavaFromRegistryKey( + KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + QList JRE32s = this->FindJavaFromRegistryKey( + KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList JDK32s = this->FindJavaFromRegistryKey( + KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + + java_candidates.append(JRE64s); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); + java_candidates.append(JDK64s); + java_candidates.append(JRE32s); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); + java_candidates.append(JDK32s); + java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); + + QList candidates; + for(JavaInstallPtr java_candidate : java_candidates) + { + if(!candidates.contains(java_candidate->path)) + { + candidates.append(java_candidate->path); + } + } + + return candidates; } #elif defined(Q_OS_MAC) QList JavaUtils::FindJavaPaths() { - QList javas; - javas.append(this->GetDefaultJava()->path); - javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"); - javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"); - javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); - QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); - QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString &java, libraryJVMJavas) { - javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); - javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); - } - QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); - QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString &java, systemLibraryJVMJavas) { - javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); - javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); - } - return javas; + QList javas; + javas.append(this->GetDefaultJava()->path); + javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"); + javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"); + javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); + QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); + QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &java, libraryJVMJavas) { + javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); + javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); + } + QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); + QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &java, systemLibraryJVMJavas) { + javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); + javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); + } + return javas; } #elif defined(Q_OS_LINUX) QList JavaUtils::FindJavaPaths() { - qDebug() << "Linux Java detection incomplete - defaulting to \"java\""; - - QList javas; - javas.append(this->GetDefaultJava()->path); - 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; + qDebug() << "Linux Java detection incomplete - defaulting to \"java\""; + + QList javas; + javas.append(this->GetDefaultJava()->path); + 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 QList JavaUtils::FindJavaPaths() { - qDebug() << "Unknown operating system build - defaulting to \"java\""; + qDebug() << "Unknown operating system build - defaulting to \"java\""; - QList javas; - javas.append(this->GetDefaultJava()->path); + QList javas; + javas.append(this->GetDefaultJava()->path); - return javas; + return javas; } #endif diff --git a/api/logic/java/JavaUtils.h b/api/logic/java/JavaUtils.h index b43e93cf..100d942f 100644 --- a/api/logic/java/JavaUtils.h +++ b/api/logic/java/JavaUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,15 +30,15 @@ QProcessEnvironment CleanEnviroment(); class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject { - Q_OBJECT + Q_OBJECT public: - JavaUtils(); + JavaUtils(); - JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); - QList FindJavaPaths(); - JavaInstallPtr GetDefaultJava(); + JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); + QList FindJavaPaths(); + JavaInstallPtr GetDefaultJava(); #ifdef Q_OS_WIN - QList FindJavaFromRegistryKey(DWORD keyType, QString keyName); + QList FindJavaFromRegistryKey(DWORD keyType, QString keyName); #endif }; diff --git a/api/logic/java/JavaVersion.cpp b/api/logic/java/JavaVersion.cpp index 27050da3..179ccd8d 100644 --- a/api/logic/java/JavaVersion.cpp +++ b/api/logic/java/JavaVersion.cpp @@ -6,116 +6,116 @@ JavaVersion & JavaVersion::operator=(const QString & javaVersionString) { - m_string = javaVersionString; + m_string = javaVersionString; - auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int - { - auto str = match.captured(what); - if(str.isEmpty()) - { - return 0; - } - return str.toInt(); - }; + auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int + { + auto str = match.captured(what); + if(str.isEmpty()) + { + return 0; + } + return str.toInt(); + }; - QRegularExpression pattern; - if(javaVersionString.startsWith("1.")) - { - pattern = QRegularExpression ("1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); - } - else - { - pattern = QRegularExpression("(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); - } + QRegularExpression pattern; + if(javaVersionString.startsWith("1.")) + { + pattern = QRegularExpression ("1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); + } + else + { + pattern = QRegularExpression("(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); + } - auto match = pattern.match(m_string); - m_parseable = match.hasMatch(); - m_major = getCapturedInteger(match, "major"); - m_minor = getCapturedInteger(match, "minor"); - m_security = getCapturedInteger(match, "security"); - m_prerelease = match.captured("prerelease"); - return *this; + auto match = pattern.match(m_string); + m_parseable = match.hasMatch(); + m_major = getCapturedInteger(match, "major"); + m_minor = getCapturedInteger(match, "minor"); + m_security = getCapturedInteger(match, "security"); + m_prerelease = match.captured("prerelease"); + return *this; } JavaVersion::JavaVersion(const QString &rhs) { - operator=(rhs); + operator=(rhs); } QString JavaVersion::toString() { - return m_string; + return m_string; } bool JavaVersion::requiresPermGen() { - if(m_parseable) - { - return m_major < 8; - } - return true; + if(m_parseable) + { + return m_major < 8; + } + return true; } bool JavaVersion::operator<(const JavaVersion &rhs) { - if(m_parseable && rhs.m_parseable) - { - auto major = m_major; - auto rmajor = rhs.m_major; + if(m_parseable && rhs.m_parseable) + { + auto major = m_major; + auto rmajor = rhs.m_major; - // HACK: discourage using java 9 - if(major > 8) - major = -major; - if(rmajor > 8) - rmajor = -rmajor; + // HACK: discourage using java 9 + if(major > 8) + major = -major; + if(rmajor > 8) + rmajor = -rmajor; - if(major < rmajor) - return true; - if(major > rmajor) - return false; - if(m_minor < rhs.m_minor) - return true; - if(m_minor > rhs.m_minor) - return false; - if(m_security < rhs.m_security) - return true; - if(m_security > rhs.m_security) - return false; + if(major < rmajor) + return true; + if(major > rmajor) + return false; + if(m_minor < rhs.m_minor) + return true; + if(m_minor > rhs.m_minor) + return false; + if(m_security < rhs.m_security) + return true; + if(m_security > rhs.m_security) + return false; - // everything else being equal, consider prerelease status - bool thisPre = !m_prerelease.isEmpty(); - bool rhsPre = !rhs.m_prerelease.isEmpty(); - if(thisPre && !rhsPre) - { - // this is a prerelease and the other one isn't -> lesser - return true; - } - else if(!thisPre && rhsPre) - { - // this isn't a prerelease and the other one is -> greater - return false; - } - else if(thisPre && rhsPre) - { - // both are prereleases - use natural compare... - return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; - } - // neither is prerelease, so they are the same -> this cannot be less than rhs - return false; - } - else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; + // everything else being equal, consider prerelease status + bool thisPre = !m_prerelease.isEmpty(); + bool rhsPre = !rhs.m_prerelease.isEmpty(); + if(thisPre && !rhsPre) + { + // this is a prerelease and the other one isn't -> lesser + return true; + } + else if(!thisPre && rhsPre) + { + // this isn't a prerelease and the other one is -> greater + return false; + } + else if(thisPre && rhsPre) + { + // both are prereleases - use natural compare... + return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; + } + // neither is prerelease, so they are the same -> this cannot be less than rhs + return false; + } + else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; } bool JavaVersion::operator==(const JavaVersion &rhs) { - if(m_parseable && rhs.m_parseable) - { - return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease; - } - return m_string == rhs.m_string; + if(m_parseable && rhs.m_parseable) + { + return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease; + } + return m_string == rhs.m_string; } bool JavaVersion::operator>(const JavaVersion &rhs) { - return (!operator<(rhs)) && (!operator==(rhs)); + return (!operator<(rhs)) && (!operator==(rhs)); } diff --git a/api/logic/java/JavaVersion.h b/api/logic/java/JavaVersion.h index de13998c..8589c21a 100644 --- a/api/logic/java/JavaVersion.h +++ b/api/logic/java/JavaVersion.h @@ -5,46 +5,46 @@ // NOTE: apparently the GNU C library pollutes the global namespace with these... undef them. #ifdef major - #undef major + #undef major #endif #ifdef minor - #undef minor + #undef minor #endif class MULTIMC_LOGIC_EXPORT JavaVersion { - friend class JavaVersionTest; + friend class JavaVersionTest; public: - JavaVersion() {}; - JavaVersion(const QString & rhs); - - JavaVersion & operator=(const QString & rhs); - - bool operator<(const JavaVersion & rhs); - bool operator==(const JavaVersion & rhs); - bool operator>(const JavaVersion & rhs); - - bool requiresPermGen(); - - QString toString(); - - int major() - { - return m_major; - } - int minor() - { - return m_minor; - } - int security() - { - return m_security; - } + JavaVersion() {}; + JavaVersion(const QString & rhs); + + JavaVersion & operator=(const QString & rhs); + + bool operator<(const JavaVersion & rhs); + bool operator==(const JavaVersion & rhs); + bool operator>(const JavaVersion & rhs); + + bool requiresPermGen(); + + QString toString(); + + int major() + { + return m_major; + } + int minor() + { + return m_minor; + } + int security() + { + return m_security; + } private: - QString m_string; - int m_major = 0; - int m_minor = 0; - int m_security = 0; - bool m_parseable = false; - QString m_prerelease; + QString m_string; + int m_major = 0; + int m_minor = 0; + int m_security = 0; + bool m_parseable = false; + QString m_prerelease; }; diff --git a/api/logic/java/JavaVersion_test.cpp b/api/logic/java/JavaVersion_test.cpp index e719ffc8..10ae13a7 100644 --- a/api/logic/java/JavaVersion_test.cpp +++ b/api/logic/java/JavaVersion_test.cpp @@ -5,110 +5,110 @@ class JavaVersionTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_Parse_data() - { - QTest::addColumn("string"); - QTest::addColumn("major"); - QTest::addColumn("minor"); - QTest::addColumn("security"); - QTest::addColumn("prerelease"); + void test_Parse_data() + { + QTest::addColumn("string"); + QTest::addColumn("major"); + QTest::addColumn("minor"); + QTest::addColumn("security"); + QTest::addColumn("prerelease"); - QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString(); - QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea"; + QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString(); + QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea"; - QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString(); - QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString(); - QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString(); - QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea"; - QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea"; - } - void test_Parse() - { - QFETCH(QString, string); - QFETCH(int, major); - QFETCH(int, minor); - QFETCH(int, security); - QFETCH(QString, prerelease); + QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString(); + QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString(); + QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString(); + QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea"; + QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea"; + } + void test_Parse() + { + QFETCH(QString, string); + QFETCH(int, major); + QFETCH(int, minor); + QFETCH(int, security); + QFETCH(QString, prerelease); - JavaVersion test(string); - QCOMPARE(test.m_string, string); - QCOMPARE(test.toString(), string); - QCOMPARE(test.m_major, major); - QCOMPARE(test.m_minor, minor); - QCOMPARE(test.m_security, security); - QCOMPARE(test.m_prerelease, prerelease); - } + JavaVersion test(string); + QCOMPARE(test.m_string, string); + QCOMPARE(test.toString(), string); + QCOMPARE(test.m_major, major); + QCOMPARE(test.m_minor, minor); + QCOMPARE(test.m_security, security); + QCOMPARE(test.m_prerelease, prerelease); + } - void test_Sort_data() - { - QTest::addColumn("lhs"); - QTest::addColumn("rhs"); - QTest::addColumn("smaller"); - QTest::addColumn("equal"); - QTest::addColumn("bigger"); + void test_Sort_data() + { + QTest::addColumn("lhs"); + QTest::addColumn("rhs"); + QTest::addColumn("smaller"); + QTest::addColumn("equal"); + QTest::addColumn("bigger"); - // old format and new format equivalence - QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false; - // old format major version - QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false; - // new format - first release vs first security patch - QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false; - QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true; - // new format - first minor vs first release/security patch - QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true; - QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false; - QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true; - QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false; - // new format - omitted numbers - QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false; - QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false; - QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false; - // early access and prereleases compared to final release - QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false; - QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false; - QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true; - // prerelease difference only testing - QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false; - QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false; - QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false; - QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false; - QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false; - QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false; - QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false; - } - void test_Sort() - { - QFETCH(QString, lhs); - QFETCH(QString, rhs); - QFETCH(bool, smaller); - QFETCH(bool, equal); - QFETCH(bool, bigger); - JavaVersion lver(lhs); - JavaVersion rver(rhs); - QCOMPARE(lver < rver, smaller); - QCOMPARE(lver == rver, equal); - QCOMPARE(lver > rver, bigger); - } - void test_PermGen_data() - { - QTest::addColumn("version"); - QTest::addColumn("needs_permgen"); - QTest::newRow("1.6.0_33") << "1.6.0_33" << true; - QTest::newRow("1.7.0_60") << "1.7.0_60" << true; - QTest::newRow("1.8.0_22") << "1.8.0_22" << false; - QTest::newRow("9-ea") << "9-ea" << false; - QTest::newRow("9.2.4") << "9.2.4" << false; - } - void test_PermGen() - { - QFETCH(QString, version); - QFETCH(bool, needs_permgen); - JavaVersion v(version); - QCOMPARE(needs_permgen, v.requiresPermGen()); - } + // old format and new format equivalence + QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false; + // old format major version + QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false; + // new format - first release vs first security patch + QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false; + QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true; + // new format - first minor vs first release/security patch + QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true; + QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false; + QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true; + QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false; + // new format - omitted numbers + QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false; + QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false; + QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false; + // early access and prereleases compared to final release + QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false; + QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false; + QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true; + // prerelease difference only testing + QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false; + QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false; + QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false; + QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false; + QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false; + QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false; + QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false; + } + void test_Sort() + { + QFETCH(QString, lhs); + QFETCH(QString, rhs); + QFETCH(bool, smaller); + QFETCH(bool, equal); + QFETCH(bool, bigger); + JavaVersion lver(lhs); + JavaVersion rver(rhs); + QCOMPARE(lver < rver, smaller); + QCOMPARE(lver == rver, equal); + QCOMPARE(lver > rver, bigger); + } + void test_PermGen_data() + { + QTest::addColumn("version"); + QTest::addColumn("needs_permgen"); + QTest::newRow("1.6.0_33") << "1.6.0_33" << true; + QTest::newRow("1.7.0_60") << "1.7.0_60" << true; + QTest::newRow("1.8.0_22") << "1.8.0_22" << false; + QTest::newRow("9-ea") << "9-ea" << false; + QTest::newRow("9.2.4") << "9.2.4" << false; + } + void test_PermGen() + { + QFETCH(QString, version); + QFETCH(bool, needs_permgen); + JavaVersion v(version); + QCOMPARE(needs_permgen, v.requiresPermGen()); + } }; QTEST_GUILESS_MAIN(JavaVersionTest) diff --git a/api/logic/java/launch/CheckJava.cpp b/api/logic/java/launch/CheckJava.cpp index 24f26682..b75c6dc6 100644 --- a/api/logic/java/launch/CheckJava.cpp +++ b/api/logic/java/launch/CheckJava.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,115 +22,115 @@ 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 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); - } + 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(); - 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(); + 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 = new 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; - } - } + 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"); + 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); - } - } + 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); + } + } } diff --git a/api/logic/java/launch/CheckJava.h b/api/logic/java/launch/CheckJava.h index 82508cd4..f0dd2308 100644 --- a/api/logic/java/launch/CheckJava.h +++ b/api/logic/java/launch/CheckJava.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,25 +21,25 @@ class CheckJava: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){}; - virtual ~CheckJava() {}; + explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){}; + virtual ~CheckJava() {}; - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } private slots: - void checkJavaFinished(JavaCheckResult result); + void checkJavaFinished(JavaCheckResult result); private: - void printJavaInfo(const QString & version, const QString & architecture); - void printSystemInfo(bool javaIsKnown, bool javaIs64bit); + void printJavaInfo(const QString & version, const QString & architecture); + void printSystemInfo(bool javaIsKnown, bool javaIs64bit); private: - QString m_javaPath; - qlonglong m_javaUnixTime; - JavaCheckerPtr m_JavaChecker; + QString m_javaPath; + qlonglong m_javaUnixTime; + JavaCheckerPtr m_JavaChecker; }; diff --git a/api/logic/launch/LaunchStep.cpp b/api/logic/launch/LaunchStep.cpp index 01f72a0a..bcaca406 100644 --- a/api/logic/launch/LaunchStep.cpp +++ b/api/logic/launch/LaunchStep.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,10 @@ void LaunchStep::bind(LaunchTask *parent) { - m_parent = parent; - connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); - connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); - connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); - connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished); - connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested); + m_parent = parent; + connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); + connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); + connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); + connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished); + connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested); } diff --git a/api/logic/launch/LaunchStep.h b/api/logic/launch/LaunchStep.h index 80778e34..dd380e43 100644 --- a/api/logic/launch/LaunchStep.h +++ b/api/logic/launch/LaunchStep.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,28 +23,28 @@ class LaunchTask; class LaunchStep: public Task { - Q_OBJECT + Q_OBJECT public: /* methods */ - explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent) - { - bind(parent); - }; - virtual ~LaunchStep() {}; + explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent) + { + bind(parent); + }; + virtual ~LaunchStep() {}; -protected: /* methods */ - virtual void bind(LaunchTask *parent); +private: /* methods */ + void bind(LaunchTask *parent); signals: - void logLines(QStringList lines, MessageLevel::Enum level); - void logLine(QString line, MessageLevel::Enum level); - void readyForLaunch(); - void progressReportingRequest(); + void logLines(QStringList lines, MessageLevel::Enum level); + void logLine(QString line, MessageLevel::Enum level); + void readyForLaunch(); + void progressReportingRequest(); public slots: - virtual void proceed() {}; - // called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends - virtual void finalize() {}; + 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; -}; \ No newline at end of file + LaunchTask *m_parent; +}; diff --git a/api/logic/launch/LaunchTask.cpp b/api/logic/launch/LaunchTask.cpp index 99c16721..841b8363 100644 --- a/api/logic/launch/LaunchTask.cpp +++ b/api/logic/launch/LaunchTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -30,251 +30,251 @@ void LaunchTask::init() { - m_instance->setRunning(true); + m_instance->setRunning(true); } -std::shared_ptr LaunchTask::create(InstancePtr inst) +shared_qobject_ptr LaunchTask::create(InstancePtr inst) { - std::shared_ptr proc(new LaunchTask(inst)); - proc->init(); - return proc; + shared_qobject_ptr proc(new LaunchTask(inst)); + proc->init(); + return proc; } LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance) { } -void LaunchTask::appendStep(std::shared_ptr step) +void LaunchTask::appendStep(shared_qobject_ptr step) { - m_steps.append(step); + m_steps.append(step); } -void LaunchTask::prependStep(std::shared_ptr step) +void LaunchTask::prependStep(shared_qobject_ptr step) { - m_steps.prepend(step); + m_steps.prepend(step); } void LaunchTask::executeTask() { - m_instance->setCrashed(false); - if(!m_steps.size()) - { - state = LaunchTask::Finished; - emitSucceeded(); - } - state = LaunchTask::Running; - onStepFinished(); + m_instance->setCrashed(false); + if(!m_steps.size()) + { + state = LaunchTask::Finished; + emitSucceeded(); + } + state = LaunchTask::Running; + onStepFinished(); } void LaunchTask::onReadyForLaunch() { - state = LaunchTask::Waiting; - emit readyForLaunch(); + state = LaunchTask::Waiting; + emit readyForLaunch(); } void LaunchTask::onStepFinished() { - // initial -> just start the first step - if(currentStep == -1) - { - currentStep ++; - m_steps[currentStep]->start(); - return; - } + // initial -> just start the first step + if(currentStep == -1) + { + currentStep ++; + m_steps[currentStep]->start(); + return; + } - auto step = m_steps[currentStep]; - if(step->wasSuccessful()) - { - // end? - if(currentStep == m_steps.size() - 1) - { - finalizeSteps(true, QString()); - } - else - { - currentStep ++; - step = m_steps[currentStep]; - step->start(); - } - } - else - { - finalizeSteps(false, step->failReason()); - } + auto step = m_steps[currentStep]; + if(step->wasSuccessful()) + { + // end? + if(currentStep == m_steps.size() - 1) + { + finalizeSteps(true, QString()); + } + else + { + currentStep ++; + step = m_steps[currentStep]; + step->start(); + } + } + else + { + finalizeSteps(false, step->failReason()); + } } void LaunchTask::finalizeSteps(bool successful, const QString& error) { - for(auto step = currentStep; step >= 0; step--) - { - m_steps[step]->finalize(); - } - if(successful) - { - emitSucceeded(); - } - else - { - emitFailed(error); - } + for(auto step = currentStep; step >= 0; step--) + { + m_steps[step]->finalize(); + } + if(successful) + { + emitSucceeded(); + } + else + { + emitFailed(error); + } } void LaunchTask::onProgressReportingRequested() { - state = LaunchTask::Waiting; - emit requestProgress(m_steps[currentStep].get()); + state = LaunchTask::Waiting; + emit requestProgress(m_steps[currentStep].get()); } void LaunchTask::setCensorFilter(QMap filter) { - m_censorFilter = filter; + m_censorFilter = filter; } QString LaunchTask::censorPrivateInfo(QString in) { - auto iter = m_censorFilter.begin(); - while (iter != m_censorFilter.end()) - { - in.replace(iter.key(), iter.value()); - iter++; - } - return in; + auto iter = m_censorFilter.begin(); + while (iter != m_censorFilter.end()) + { + in.replace(iter.key(), iter.value()); + iter++; + } + return in; } void LaunchTask::proceed() { - if(state != LaunchTask::Waiting) - { - return; - } - m_steps[currentStep]->proceed(); + if(state != LaunchTask::Waiting) + { + return; + } + m_steps[currentStep]->proceed(); } bool LaunchTask::canAbort() const { - switch(state) - { - case LaunchTask::Aborted: - case LaunchTask::Failed: - case LaunchTask::Finished: - return false; - case LaunchTask::NotStarted: - return true; - case LaunchTask::Running: - case LaunchTask::Waiting: - { - auto step = m_steps[currentStep]; - return step->canAbort(); - } - } - return false; + switch(state) + { + case LaunchTask::Aborted: + case LaunchTask::Failed: + case LaunchTask::Finished: + return false; + case LaunchTask::NotStarted: + return true; + case LaunchTask::Running: + case LaunchTask::Waiting: + { + auto step = m_steps[currentStep]; + return step->canAbort(); + } + } + return false; } bool LaunchTask::abort() { - switch(state) - { - case LaunchTask::Aborted: - case LaunchTask::Failed: - case LaunchTask::Finished: - return true; - case LaunchTask::NotStarted: - { - state = LaunchTask::Aborted; - emitFailed("Aborted"); - return true; - } - case LaunchTask::Running: - case LaunchTask::Waiting: - { - auto step = m_steps[currentStep]; - if(!step->canAbort()) - { - return false; - } - if(step->abort()) - { - state = LaunchTask::Aborted; - return true; - } - } - default: - break; - } - return false; + switch(state) + { + case LaunchTask::Aborted: + case LaunchTask::Failed: + case LaunchTask::Finished: + return true; + case LaunchTask::NotStarted: + { + state = LaunchTask::Aborted; + emitFailed("Aborted"); + return true; + } + case LaunchTask::Running: + case LaunchTask::Waiting: + { + auto step = m_steps[currentStep]; + if(!step->canAbort()) + { + return false; + } + if(step->abort()) + { + state = LaunchTask::Aborted; + return true; + } + } + default: + break; + } + return false; } shared_qobject_ptr LaunchTask::getLogModel() { - if(!m_logModel) - { - m_logModel.reset(new LogModel()); - m_logModel->setMaxLines(m_instance->getConsoleMaxLines()); - m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); - // FIXME: should this really be here? - m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" - "You may have to fix your mods because the game is still logging to files and" - " likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines())); - } - return m_logModel; + if(!m_logModel) + { + m_logModel.reset(new LogModel()); + m_logModel->setMaxLines(m_instance->getConsoleMaxLines()); + m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); + // FIXME: should this really be here? + m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" + "You may have to fix your mods because the game is still logging to files and" + " likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines())); + } + return m_logModel; } void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel) { - for (auto & line: lines) - { - onLogLine(line, defaultLevel); - } + for (auto & line: lines) + { + onLogLine(line, defaultLevel); + } } void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) { - // if the launcher part set a log level, use it - auto innerLevel = MessageLevel::fromLine(line); - if(innerLevel != MessageLevel::Unknown) - { - level = innerLevel; - } + // if the launcher part set a log level, use it + auto innerLevel = MessageLevel::fromLine(line); + if(innerLevel != MessageLevel::Unknown) + { + level = innerLevel; + } - // If the level is still undetermined, guess level - if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) - { - level = m_instance->guessLevel(line, level); - } + // If the level is still undetermined, guess level + if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) + { + level = m_instance->guessLevel(line, level); + } - // censor private user info - line = censorPrivateInfo(line); + // censor private user info + line = censorPrivateInfo(line); - auto &model = *getLogModel(); - model.append(level, line); + auto &model = *getLogModel(); + model.append(level, line); } void LaunchTask::emitSucceeded() { - m_instance->setRunning(false); - Task::emitSucceeded(); + m_instance->setRunning(false); + Task::emitSucceeded(); } void LaunchTask::emitFailed(QString reason) { - m_instance->setRunning(false); - m_instance->setCrashed(true); - Task::emitFailed(reason); + m_instance->setRunning(false); + m_instance->setCrashed(true); + Task::emitFailed(reason); } QString LaunchTask::substituteVariables(const QString &cmd) const { - QString out = cmd; - auto variables = m_instance->getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - out.replace("$" + it.key(), it.value()); - } - auto env = QProcessEnvironment::systemEnvironment(); - for (auto var : env.keys()) - { - out.replace("$" + var, env.value(var)); - } - return out; + QString out = cmd; + auto variables = m_instance->getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + out.replace("$" + it.key(), it.value()); + } + auto env = QProcessEnvironment::systemEnvironment(); + for (auto var : env.keys()) + { + out.replace("$" + var, env.value(var)); + } + return out; } diff --git a/api/logic/launch/LaunchTask.h b/api/logic/launch/LaunchTask.h index 746d6d19..ee04bd9a 100644 --- a/api/logic/launch/LaunchTask.h +++ b/api/logic/launch/LaunchTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -6,7 +6,7 @@ * 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 + * 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, @@ -28,98 +28,98 @@ class MULTIMC_LOGIC_EXPORT LaunchTask: public Task { - Q_OBJECT + Q_OBJECT protected: - explicit LaunchTask(InstancePtr instance); - void init(); + explicit LaunchTask(InstancePtr instance); + void init(); public: - enum State - { - NotStarted, - Running, - Waiting, - Failed, - Aborted, - Finished - }; + enum State + { + NotStarted, + Running, + Waiting, + Failed, + Aborted, + Finished + }; public: /* methods */ - static std::shared_ptr create(InstancePtr inst); - virtual ~LaunchTask() {}; + static shared_qobject_ptr create(InstancePtr inst); + virtual ~LaunchTask() {}; - void appendStep(std::shared_ptr step); - void prependStep(std::shared_ptr step); - void setCensorFilter(QMap filter); + void appendStep(shared_qobject_ptr step); + void prependStep(shared_qobject_ptr step); + void setCensorFilter(QMap filter); - InstancePtr instance() - { - return m_instance; - } + InstancePtr instance() + { + return m_instance; + } - void setPid(qint64 pid) - { - m_pid = pid; - } + void setPid(qint64 pid) + { + m_pid = pid; + } - qint64 pid() - { - return m_pid; - } + qint64 pid() + { + return m_pid; + } - /** - * @brief prepare the process for launch (for multi-stage launch) - */ - virtual void executeTask() override; + /** + * @brief prepare the process for launch (for multi-stage launch) + */ + virtual void executeTask() override; - /** - * @brief launch the armed instance - */ - void proceed(); + /** + * @brief launch the armed instance + */ + void proceed(); - /** - * @brief abort launch - */ - bool abort() override; + /** + * @brief abort launch + */ + bool abort() override; - bool canAbort() const override; + bool canAbort() const override; - shared_qobject_ptr getLogModel(); + shared_qobject_ptr getLogModel(); public: - QString substituteVariables(const QString &cmd) const; - QString censorPrivateInfo(QString in); + QString substituteVariables(const QString &cmd) const; + QString censorPrivateInfo(QString in); protected: /* methods */ - virtual void emitFailed(QString reason) override; - virtual void emitSucceeded() override; + virtual void emitFailed(QString reason) override; + virtual void emitSucceeded() override; signals: - /** - * @brief emitted when the launch preparations are done - */ - void readyForLaunch(); + /** + * @brief emitted when the launch preparations are done + */ + void readyForLaunch(); - void requestProgress(Task *task); + void requestProgress(Task *task); - void requestLogging(); + void requestLogging(); public slots: - void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); - void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); - void onReadyForLaunch(); - void onStepFinished(); - void onProgressReportingRequested(); + void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); + void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); + void onReadyForLaunch(); + void onStepFinished(); + void onProgressReportingRequested(); private: /*methods */ - void finalizeSteps(bool successful, const QString & error); + void finalizeSteps(bool successful, const QString & error); protected: /* data */ - InstancePtr m_instance; - shared_qobject_ptr m_logModel; - QList > m_steps; - QMap m_censorFilter; - int currentStep = -1; - State state = NotStarted; - qint64 m_pid = -1; + InstancePtr m_instance; + shared_qobject_ptr m_logModel; + QList > m_steps; + QMap m_censorFilter; + int currentStep = -1; + State state = NotStarted; + qint64 m_pid = -1; }; diff --git a/api/logic/launch/LogModel.cpp b/api/logic/launch/LogModel.cpp index 72b076e9..92f9487a 100644 --- a/api/logic/launch/LogModel.cpp +++ b/api/logic/launch/LogModel.cpp @@ -2,166 +2,166 @@ LogModel::LogModel(QObject *parent):QAbstractListModel(parent) { - m_content.resize(m_maxLines); + m_content.resize(m_maxLines); } int LogModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) - return 0; + if (parent.isValid()) + return 0; - return m_numLines; + 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(); + 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(); + 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; + m_suspended = suspend; } bool LogModel::suspended() { - return m_suspended; + return m_suspended; } void LogModel::clear() { - beginResetModel(); - m_firstLine = 0; - m_numLines = 0; - endResetModel(); + 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; + QString out; + out.reserve(m_numLines * 80); + for(int i = 0; i < m_numLines; i++) + { + QString & line = m_content[(m_firstLine + i) % m_maxLines].line; + out.append(line + '\n'); + } + out.squeeze(); + return out; } void LogModel::setMaxLines(int maxLines) { - // no-op - if(maxLines == m_maxLines) - { - return; - } - // if it all still fits in the buffer, just resize it - if(m_firstLine + m_numLines < m_maxLines) - { - m_maxLines = maxLines; - m_content.resize(maxLines); - return; - } - // otherwise, we need to reorganize the data because it crosses the wrap boundary - QVector 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; + // no-op + if(maxLines == m_maxLines) + { + return; + } + // if it all still fits in the buffer, just resize it + if(m_firstLine + m_numLines < m_maxLines) + { + m_maxLines = maxLines; + m_content.resize(maxLines); + return; + } + // otherwise, we need to reorganize the data because it crosses the wrap boundary + QVector newContent; + newContent.resize(maxLines); + if(m_numLines <= maxLines) + { + // if it all fits in the new buffer, just copy it over + for(int i = 0; i < m_numLines; i++) + { + newContent[i] = m_content[(m_firstLine + i) % m_maxLines]; + } + m_content.swap(newContent); + } + else + { + // if it doesn't fit, part of the data needs to be thrown away (the oldest log messages) + int lead = m_numLines - maxLines; + beginRemoveRows(QModelIndex(), 0, lead - 1); + for(int i = 0; i < maxLines; i++) + { + newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines]; + } + m_numLines = m_maxLines; + m_content.swap(newContent); + endRemoveRows(); + } + m_firstLine = 0; + m_maxLines = maxLines; } int LogModel::getMaxLines() { - return m_maxLines; + return m_maxLines; } void LogModel::setStopOnOverflow(bool stop) { - m_stopOnOverflow = stop; + m_stopOnOverflow = stop; } void LogModel::setOverflowMessage(const QString& overflowMessage) { - m_overflowMessage = overflowMessage; + m_overflowMessage = overflowMessage; } void LogModel::setLineWrap(bool state) { - if(m_lineWrap != state) - { - m_lineWrap = state; - } + if(m_lineWrap != state) + { + m_lineWrap = state; + } } bool LogModel::wrapLines() const { - return m_lineWrap; + return m_lineWrap; } diff --git a/api/logic/launch/LogModel.h b/api/logic/launch/LogModel.h index e6deac89..bccceaef 100644 --- a/api/logic/launch/LogModel.h +++ b/api/logic/launch/LogModel.h @@ -8,53 +8,53 @@ class MULTIMC_LOGIC_EXPORT LogModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - explicit LogModel(QObject *parent = 0); + explicit LogModel(QObject *parent = 0); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; - void append(MessageLevel::Enum, QString line); - void clear(); + void append(MessageLevel::Enum, QString line); + void clear(); - void suspend(bool suspend); - bool suspended(); + void suspend(bool suspend); + bool suspended(); - QString toPlainText(); + QString toPlainText(); - int getMaxLines(); - void setMaxLines(int maxLines); - void setStopOnOverflow(bool stop); - void setOverflowMessage(const QString & overflowMessage); + int getMaxLines(); + void setMaxLines(int maxLines); + void setStopOnOverflow(bool stop); + void setOverflowMessage(const QString & overflowMessage); - void setLineWrap(bool state); - bool wrapLines() const; + void setLineWrap(bool state); + bool wrapLines() const; - enum Roles - { - LevelRole = Qt::UserRole - }; + enum Roles + { + LevelRole = Qt::UserRole + }; private /* types */: - struct entry - { - MessageLevel::Enum level; - QString line; - }; + struct entry + { + MessageLevel::Enum level; + QString line; + }; private: /* data */ - QVector 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; - bool m_lineWrap = true; + QVector 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; + bool m_lineWrap = true; private: - Q_DISABLE_COPY(LogModel) + Q_DISABLE_COPY(LogModel) }; diff --git a/api/logic/launch/steps/PostLaunchCommand.cpp b/api/logic/launch/steps/PostLaunchCommand.cpp index bc3b3ffe..e0b2b6fc 100644 --- a/api/logic/launch/steps/PostLaunchCommand.cpp +++ b/api/logic/launch/steps/PostLaunchCommand.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,67 +18,67 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent) { - auto instance = m_parent->instance(); - m_command = instance->getPostExitCommand(); - m_process.setProcessEnvironment(instance->createEnvironment()); - connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state); + auto instance = m_parent->instance(); + m_command = instance->getPostExitCommand(); + m_process.setProcessEnvironment(instance->createEnvironment()); + connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state); } void PostLaunchCommand::executeTask() { - QString postlaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC); - m_process.start(postlaunch_cmd); + QString postlaunch_cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC); + m_process.start(postlaunch_cmd); } void PostLaunchCommand::on_state(LoggedProcess::State state) { - auto getError = [&]() - { - return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); - }; - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - return; - } - case LoggedProcess::Finished: - { - if(m_process.exitCode() != 0) - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - } - else - { - emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); - emitSucceeded(); - } - } - default: - break; - } + auto getError = [&]() + { + return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); + }; + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + return; + } + case LoggedProcess::Finished: + { + if(m_process.exitCode() != 0) + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + } + else + { + emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); + emitSucceeded(); + } + } + default: + break; + } } void PostLaunchCommand::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } bool PostLaunchCommand::abort() { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + return true; } diff --git a/api/logic/launch/steps/PostLaunchCommand.h b/api/logic/launch/steps/PostLaunchCommand.h index 36b3b96f..da200534 100644 --- a/api/logic/launch/steps/PostLaunchCommand.h +++ b/api/logic/launch/steps/PostLaunchCommand.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,20 +20,22 @@ class PostLaunchCommand: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit PostLaunchCommand(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); + explicit PostLaunchCommand(LaunchTask *parent); + virtual ~PostLaunchCommand() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; + LoggedProcess m_process; + QString m_command; }; diff --git a/api/logic/launch/steps/PreLaunchCommand.cpp b/api/logic/launch/steps/PreLaunchCommand.cpp index 7c37d5cb..6f1f280d 100644 --- a/api/logic/launch/steps/PreLaunchCommand.cpp +++ b/api/logic/launch/steps/PreLaunchCommand.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,68 +18,68 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask *parent) : LaunchStep(parent) { - auto instance = m_parent->instance(); - m_command = instance->getPreLaunchCommand(); - m_process.setProcessEnvironment(instance->createEnvironment()); - connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &PreLaunchCommand::on_state); + auto instance = m_parent->instance(); + m_command = instance->getPreLaunchCommand(); + m_process.setProcessEnvironment(instance->createEnvironment()); + connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &PreLaunchCommand::on_state); } void PreLaunchCommand::executeTask() { - //FIXME: where to put this? - QString prelaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::MultiMC); - m_process.start(prelaunch_cmd); + //FIXME: where to put this? + QString prelaunch_cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::MultiMC); + m_process.start(prelaunch_cmd); } void PreLaunchCommand::on_state(LoggedProcess::State state) { - auto getError = [&]() - { - return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); - }; - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - return; - } - case LoggedProcess::Finished: - { - if(m_process.exitCode() != 0) - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - } - else - { - emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); - emitSucceeded(); - } - } - default: - break; - } + auto getError = [&]() + { + return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); + }; + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + return; + } + case LoggedProcess::Finished: + { + if(m_process.exitCode() != 0) + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + } + else + { + emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); + emitSucceeded(); + } + } + default: + break; + } } void PreLaunchCommand::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } bool PreLaunchCommand::abort() { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + return true; } diff --git a/api/logic/launch/steps/PreLaunchCommand.h b/api/logic/launch/steps/PreLaunchCommand.h index 880cc076..f1e9db5d 100644 --- a/api/logic/launch/steps/PreLaunchCommand.h +++ b/api/logic/launch/steps/PreLaunchCommand.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,20 +20,22 @@ class PreLaunchCommand: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit PreLaunchCommand(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); + explicit PreLaunchCommand(LaunchTask *parent); + virtual ~PreLaunchCommand() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; + LoggedProcess m_process; + QString m_command; }; diff --git a/api/logic/launch/steps/TextPrint.cpp b/api/logic/launch/steps/TextPrint.cpp index f307b1fd..0c1f320c 100644 --- a/api/logic/launch/steps/TextPrint.cpp +++ b/api/logic/launch/steps/TextPrint.cpp @@ -2,28 +2,28 @@ TextPrint::TextPrint(LaunchTask * parent, const QStringList &lines, MessageLevel::Enum level) : LaunchStep(parent) { - m_lines = lines; - m_level = level; + m_lines = lines; + m_level = level; } TextPrint::TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level) : LaunchStep(parent) { - m_lines.append(line); - m_level = level; + m_lines.append(line); + m_level = level; } void TextPrint::executeTask() { - emit logLines(m_lines, m_level); - emitSucceeded(); + emit logLines(m_lines, m_level); + emitSucceeded(); } bool TextPrint::canAbort() const { - return true; + return true; } bool TextPrint::abort() { - emitFailed("Aborted."); - return true; + emitFailed("Aborted."); + return true; } diff --git a/api/logic/launch/steps/TextPrint.h b/api/logic/launch/steps/TextPrint.h index 3e3e06cb..099fdc13 100644 --- a/api/logic/launch/steps/TextPrint.h +++ b/api/logic/launch/steps/TextPrint.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,17 +27,17 @@ class MULTIMC_LOGIC_EXPORT TextPrint: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit TextPrint(LaunchTask *parent, const QStringList &lines, MessageLevel::Enum level); - explicit TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level); - virtual ~TextPrint(){}; + explicit TextPrint(LaunchTask *parent, const QStringList &lines, MessageLevel::Enum level); + explicit TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level); + virtual ~TextPrint(){}; - virtual void executeTask(); - virtual bool canAbort() const; - virtual bool abort(); + virtual void executeTask(); + virtual bool canAbort() const; + virtual bool abort(); private: - QStringList m_lines; - MessageLevel::Enum m_level; + QStringList m_lines; + MessageLevel::Enum m_level; }; diff --git a/api/logic/launch/steps/Update.cpp b/api/logic/launch/steps/Update.cpp index d057febb..6eb012f7 100644 --- a/api/logic/launch/steps/Update.cpp +++ b/api/logic/launch/steps/Update.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,63 +18,63 @@ void Update::executeTask() { - if(m_aborted) - { - emitFailed(tr("Task aborted.")); - return; - } - m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); - if(m_updateTask) - { - connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); - connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); - connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); - emit progressReportingRequest(); - return; - } - emitSucceeded(); + if(m_aborted) + { + emitFailed(tr("Task aborted.")); + return; + } + m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); + if(m_updateTask) + { + connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); + connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); + connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); + emit progressReportingRequest(); + return; + } + emitSucceeded(); } void Update::proceed() { - m_updateTask->start(); + m_updateTask->start(); } void Update::updateFinished() { - if(m_updateTask->wasSuccessful()) - { - 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); - } + if(m_updateTask->wasSuccessful()) + { + 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; + 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; + m_aborted = true; + if(m_updateTask) + { + if(m_updateTask->canAbort()) + { + return m_updateTask->abort(); + } + } + return true; } diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h index 7a14011a..331698bd 100644 --- a/api/logic/launch/steps/Update.h +++ b/api/logic/launch/steps/Update.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,22 +24,22 @@ // FIXME: stupid. should be defined by the instance type? or even completely abstracted away... class Update: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {}; - virtual ~Update() {}; + explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {}; + virtual ~Update() {}; - void executeTask() override; - bool canAbort() const override; - void proceed() override; + void executeTask() override; + bool canAbort() const override; + void proceed() override; public slots: - bool abort() override; + bool abort() override; private slots: - void updateFinished(); + void updateFinished(); private: - shared_qobject_ptr m_updateTask; - bool m_aborted = false; - Net::Mode m_mode = Net::Mode::Offline; + shared_qobject_ptr m_updateTask; + bool m_aborted = false; + Net::Mode m_mode = Net::Mode::Offline; }; diff --git a/api/logic/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp index 5c2339cb..ce0be859 100644 --- a/api/logic/meta/BaseEntity.cpp +++ b/api/logic/meta/BaseEntity.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,45 +27,45 @@ class ParsingValidator : public Net::Validator { public: /* con/des */ - ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity) - { - }; - virtual ~ParsingValidator() - { - }; + ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity) + { + }; + virtual ~ParsingValidator() + { + }; public: /* methods */ - bool init(QNetworkRequest &) override - { - return true; - } - bool write(QByteArray & data) override - { - this->data.append(data); - return true; - } - bool abort() override - { - return true; - } - bool validate(QNetworkReply &) override - { - auto fname = m_entity->localFilename(); - try - { - m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname)); - return true; - } - catch (Exception &e) - { - qWarning() << "Unable to parse response:" << e.cause(); - return false; - } - } + bool init(QNetworkRequest &) override + { + return true; + } + bool write(QByteArray & data) override + { + this->data.append(data); + return true; + } + bool abort() override + { + return true; + } + bool validate(QNetworkReply &) override + { + auto fname = m_entity->localFilename(); + try + { + m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname)); + return true; + } + catch (const Exception &e) + { + qWarning() << "Unable to parse response:" << e.cause(); + return false; + } + } private: /* data */ - QByteArray data; - Meta::BaseEntity *m_entity; + QByteArray data; + Meta::BaseEntity *m_entity; }; Meta::BaseEntity::~BaseEntity() @@ -74,89 +74,89 @@ Meta::BaseEntity::~BaseEntity() QUrl Meta::BaseEntity::url() const { - return QUrl("https://v1.meta.multimc.org").resolved(localFilename()); + return QUrl("https://meta.multimc.org/v1/").resolved(localFilename()); } bool Meta::BaseEntity::loadLocalFile() { - const QString fname = QDir("meta").absoluteFilePath(localFilename()); - if (!QFile::exists(fname)) - { - return false; - } - // TODO: check if the file has the expected checksum - try - { - parse(Json::requireObject(Json::requireDocument(fname, fname), fname)); - return true; - } - catch (Exception &e) - { - qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); - // just make sure it's gone and we never consider it again. - QFile::remove(fname); - return false; - } + const QString fname = QDir("meta").absoluteFilePath(localFilename()); + if (!QFile::exists(fname)) + { + return false; + } + // TODO: check if the file has the expected checksum + try + { + parse(Json::requireObject(Json::requireDocument(fname, fname), fname)); + return true; + } + catch (const Exception &e) + { + qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); + // just make sure it's gone and we never consider it again. + QFile::remove(fname); + return false; + } } void Meta::BaseEntity::load(Net::Mode loadType) { - // load local file if nothing is loaded yet - if(!isLoaded()) - { - if(loadLocalFile()) - { - m_loadStatus = LoadStatus::Local; - } - } - // if we need remote update, run the update task - if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) - { - return; - } - NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename())); - auto url = this->url(); - auto entry = ENV.metacache()->resolveEntry("meta", localFilename()); - entry->setStale(true); - auto dl = Net::Download::makeCached(url, entry); - /* - * The validator parses the file and loads it into the object. - * If that fails, the file is not written to storage. - */ - dl->addValidator(new ParsingValidator(this)); - job->addNetAction(dl); - m_updateStatus = UpdateStatus::InProgress; - m_updateTask.reset(job); - QObject::connect(job, &NetJob::succeeded, [&]() - { - m_loadStatus = LoadStatus::Remote; - m_updateStatus = UpdateStatus::Succeeded; - m_updateTask.reset(); - }); - QObject::connect(job, &NetJob::failed, [&]() - { - m_updateStatus = UpdateStatus::Failed; - m_updateTask.reset(); - }); - m_updateTask->start(); + // load local file if nothing is loaded yet + if(!isLoaded()) + { + if(loadLocalFile()) + { + m_loadStatus = LoadStatus::Local; + } + } + // if we need remote update, run the update task + if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) + { + return; + } + NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename())); + auto url = this->url(); + auto entry = ENV.metacache()->resolveEntry("meta", localFilename()); + entry->setStale(true); + auto dl = Net::Download::makeCached(url, entry); + /* + * The validator parses the file and loads it into the object. + * If that fails, the file is not written to storage. + */ + dl->addValidator(new ParsingValidator(this)); + job->addNetAction(dl); + m_updateStatus = UpdateStatus::InProgress; + m_updateTask.reset(job); + QObject::connect(job, &NetJob::succeeded, [&]() + { + m_loadStatus = LoadStatus::Remote; + m_updateStatus = UpdateStatus::Succeeded; + m_updateTask.reset(); + }); + QObject::connect(job, &NetJob::failed, [&]() + { + m_updateStatus = UpdateStatus::Failed; + m_updateTask.reset(); + }); + m_updateTask->start(); } bool Meta::BaseEntity::isLoaded() const { - return m_loadStatus > LoadStatus::NotLoaded; + return m_loadStatus > LoadStatus::NotLoaded; } bool Meta::BaseEntity::shouldStartRemoteUpdate() const { - // TODO: version-locks and offline mode? - return m_updateStatus != UpdateStatus::InProgress; + // TODO: version-locks and offline mode? + return m_updateStatus != UpdateStatus::InProgress; } shared_qobject_ptr Meta::BaseEntity::getCurrentTask() { - if(m_updateStatus == UpdateStatus::InProgress) - { - return m_updateTask; - } - return nullptr; + if(m_updateStatus == UpdateStatus::InProgress) + { + return m_updateTask; + } + return nullptr; } diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h index 418c979f..5fc5981a 100644 --- a/api/logic/meta/BaseEntity.h +++ b/api/logic/meta/BaseEntity.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,41 +28,41 @@ namespace Meta class MULTIMC_LOGIC_EXPORT BaseEntity { public: /* types */ - using Ptr = std::shared_ptr; - enum class LoadStatus - { - NotLoaded, - Local, - Remote - }; - enum class UpdateStatus - { - NotDone, - InProgress, - Failed, - Succeeded - }; + using Ptr = std::shared_ptr; + enum class LoadStatus + { + NotLoaded, + Local, + Remote + }; + enum class UpdateStatus + { + NotDone, + InProgress, + Failed, + Succeeded + }; public: - virtual ~BaseEntity(); + virtual ~BaseEntity(); - virtual void parse(const QJsonObject &obj) = 0; + virtual void parse(const QJsonObject &obj) = 0; - virtual QString localFilename() const = 0; - virtual QUrl url() const; + virtual QString localFilename() const = 0; + virtual QUrl url() const; - bool isLoaded() const; - bool shouldStartRemoteUpdate() const; + bool isLoaded() const; + bool shouldStartRemoteUpdate() const; - void load(Net::Mode loadType); - shared_qobject_ptr getCurrentTask(); + void load(Net::Mode loadType); + shared_qobject_ptr getCurrentTask(); protected: /* methods */ - bool loadLocalFile(); + bool loadLocalFile(); private: - LoadStatus m_loadStatus = LoadStatus::NotLoaded; - UpdateStatus m_updateStatus = UpdateStatus::NotDone; - shared_qobject_ptr m_updateTask; + LoadStatus m_loadStatus = LoadStatus::NotLoaded; + UpdateStatus m_updateStatus = UpdateStatus::NotDone; + shared_qobject_ptr m_updateTask; }; } diff --git a/api/logic/meta/Index.cpp b/api/logic/meta/Index.cpp index 6e1e34cd..4e0026b3 100644 --- a/api/logic/meta/Index.cpp +++ b/api/logic/meta/Index.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,128 +21,128 @@ namespace Meta { Index::Index(QObject *parent) - : QAbstractListModel(parent) + : QAbstractListModel(parent) { } Index::Index(const QVector &lists, QObject *parent) - : QAbstractListModel(parent), m_lists(lists) + : QAbstractListModel(parent), m_lists(lists) { - for (int i = 0; i < m_lists.size(); ++i) - { - m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i)); - connectVersionList(i, m_lists.at(i)); - } + for (int i = 0; i < m_lists.size(); ++i) + { + m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i)); + connectVersionList(i, m_lists.at(i)); + } } QVariant Index::data(const QModelIndex &index, int role) const { - if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size()) - { - return QVariant(); - } + if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size()) + { + return QVariant(); + } - VersionListPtr list = m_lists.at(index.row()); - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case 0: return list->humanReadable(); - default: break; - } - case UidRole: return list->uid(); - case NameRole: return list->name(); - case ListPtrRole: return QVariant::fromValue(list); - } - return QVariant(); + VersionListPtr list = m_lists.at(index.row()); + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case 0: return list->humanReadable(); + default: break; + } + case UidRole: return list->uid(); + case NameRole: return list->name(); + case ListPtrRole: return QVariant::fromValue(list); + } + return QVariant(); } int Index::rowCount(const QModelIndex &parent) const { - return m_lists.size(); + return m_lists.size(); } int Index::columnCount(const QModelIndex &parent) const { - return 1; + return 1; } QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) - { - return tr("Name"); - } - else - { - return QVariant(); - } + if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) + { + return tr("Name"); + } + else + { + return QVariant(); + } } bool Index::hasUid(const QString &uid) const { - return m_uids.contains(uid); + return m_uids.contains(uid); } VersionListPtr Index::get(const QString &uid) { - VersionListPtr out = m_uids.value(uid, nullptr); - if(!out) - { - out = std::make_shared(uid); - m_uids[uid] = out; - } - return out; + VersionListPtr out = m_uids.value(uid, nullptr); + if(!out) + { + out = std::make_shared(uid); + m_uids[uid] = out; + } + return out; } VersionPtr Index::get(const QString &uid, const QString &version) { - auto list = get(uid); - return list->getVersion(version); + auto list = get(uid); + return list->getVersion(version); } void Index::parse(const QJsonObject& obj) { - parseIndex(obj, this); + parseIndex(obj, this); } void Index::merge(const std::shared_ptr &other) { - const QVector lists = std::dynamic_pointer_cast(other)->m_lists; - // initial load, no need to merge - if (m_lists.isEmpty()) - { - beginResetModel(); - m_lists = lists; - for (int i = 0; i < lists.size(); ++i) - { - m_uids.insert(lists.at(i)->uid(), lists.at(i)); - connectVersionList(i, lists.at(i)); - } - endResetModel(); - } - else - { - for (const VersionListPtr &list : lists) - { - if (m_uids.contains(list->uid())) - { - m_uids[list->uid()]->mergeFromIndex(list); - } - else - { - beginInsertRows(QModelIndex(), m_lists.size(), m_lists.size()); - connectVersionList(m_lists.size(), list); - m_lists.append(list); - m_uids.insert(list->uid(), list); - endInsertRows(); - } - } - } + const QVector lists = std::dynamic_pointer_cast(other)->m_lists; + // initial load, no need to merge + if (m_lists.isEmpty()) + { + beginResetModel(); + m_lists = lists; + for (int i = 0; i < lists.size(); ++i) + { + m_uids.insert(lists.at(i)->uid(), lists.at(i)); + connectVersionList(i, lists.at(i)); + } + endResetModel(); + } + else + { + for (const VersionListPtr &list : lists) + { + if (m_uids.contains(list->uid())) + { + m_uids[list->uid()]->mergeFromIndex(list); + } + else + { + beginInsertRows(QModelIndex(), m_lists.size(), m_lists.size()); + connectVersionList(m_lists.size(), list); + m_lists.append(list); + m_uids.insert(list->uid(), list); + endInsertRows(); + } + } + } } void Index::connectVersionList(const int row, const VersionListPtr &list) { - connect(list.get(), &VersionList::nameChanged, this, [this, row]() - { - emit dataChanged(index(row), index(row), QVector() << Qt::DisplayRole); - }); + connect(list.get(), &VersionList::nameChanged, this, [this, row]() + { + emit dataChanged(index(row), index(row), QVector() << Qt::DisplayRole); + }); } } diff --git a/api/logic/meta/Index.h b/api/logic/meta/Index.h index 0ec43f96..7a84ff38 100644 --- a/api/logic/meta/Index.h +++ b/api/logic/meta/Index.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,41 +31,41 @@ using VersionPtr = std::shared_ptr; class MULTIMC_LOGIC_EXPORT Index : public QAbstractListModel, public BaseEntity { - Q_OBJECT + Q_OBJECT public: - explicit Index(QObject *parent = nullptr); - explicit Index(const QVector &lists, QObject *parent = nullptr); + explicit Index(QObject *parent = nullptr); + explicit Index(const QVector &lists, QObject *parent = nullptr); - enum - { - UidRole = Qt::UserRole, - NameRole, - ListPtrRole - }; + enum + { + UidRole = Qt::UserRole, + NameRole, + ListPtrRole + }; - QVariant data(const QModelIndex &index, int role) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QString localFilename() const override { return "index.json"; } + QString localFilename() const override { return "index.json"; } - // queries - VersionListPtr get(const QString &uid); - VersionPtr get(const QString &uid, const QString &version); - bool hasUid(const QString &uid) const; + // queries + VersionListPtr get(const QString &uid); + VersionPtr get(const QString &uid, const QString &version); + bool hasUid(const QString &uid) const; - QVector lists() const { return m_lists; } + QVector lists() const { return m_lists; } public: // for usage by parsers only - void merge(const std::shared_ptr &other); - void parse(const QJsonObject &obj) override; + void merge(const std::shared_ptr &other); + void parse(const QJsonObject &obj) override; private: - QVector m_lists; - QHash m_uids; + QVector m_lists; + QHash m_uids; - void connectVersionList(const int row, const VersionListPtr &list); + void connectVersionList(const int row, const VersionListPtr &list); }; } diff --git a/api/logic/meta/Index_test.cpp b/api/logic/meta/Index_test.cpp index 1c5face2..b0892070 100644 --- a/api/logic/meta/Index_test.cpp +++ b/api/logic/meta/Index_test.cpp @@ -7,36 +7,36 @@ class IndexTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_isProvidedByEnv() - { - QVERIFY(ENV.metadataIndex()); - QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex()); - } + void test_isProvidedByEnv() + { + QVERIFY(ENV.metadataIndex()); + QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex()); + } - void test_hasUid_and_getList() - { - Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); - QVERIFY(windex.hasUid("list1")); - QVERIFY(!windex.hasUid("asdf")); - QVERIFY(windex.get("list2") != nullptr); - QCOMPARE(windex.get("list2")->uid(), QString("list2")); - QVERIFY(windex.get("adsf") != nullptr); - } + void test_hasUid_and_getList() + { + Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); + QVERIFY(windex.hasUid("list1")); + QVERIFY(!windex.hasUid("asdf")); + QVERIFY(windex.get("list2") != nullptr); + QCOMPARE(windex.get("list2")->uid(), QString("list2")); + QVERIFY(windex.get("adsf") != nullptr); + } - void test_merge() - { - Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); - QCOMPARE(windex.lists().size(), 3); - windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}))); - QCOMPARE(windex.lists().size(), 3); - windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list4"), std::make_shared("list2"), std::make_shared("list5")}))); - QCOMPARE(windex.lists().size(), 5); - windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list6")}))); - QCOMPARE(windex.lists().size(), 6); - } + void test_merge() + { + Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); + QCOMPARE(windex.lists().size(), 3); + windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}))); + QCOMPARE(windex.lists().size(), 3); + windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list4"), std::make_shared("list2"), std::make_shared("list5")}))); + QCOMPARE(windex.lists().size(), 5); + windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list6")}))); + QCOMPARE(windex.lists().size(), 6); + } }; QTEST_GUILESS_MAIN(IndexTest) diff --git a/api/logic/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp index 2183e579..41abb8e9 100644 --- a/api/logic/meta/JsonFormat.cpp +++ b/api/logic/meta/JsonFormat.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,141 +30,141 @@ namespace Meta MetadataVersion currentFormatVersion() { - return MetadataVersion::InitialRelease; + return MetadataVersion::InitialRelease; } // Index static std::shared_ptr parseIndexInternal(const QJsonObject &obj) { - const QVector objects = requireIsArrayOf(obj, "packages"); - QVector lists; - lists.reserve(objects.size()); - std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj) - { - VersionListPtr list = std::make_shared(requireString(obj, "uid")); - list->setName(ensureString(obj, "name", QString())); - return list; - }); - return std::make_shared(lists); + const QVector objects = requireIsArrayOf(obj, "packages"); + QVector lists; + lists.reserve(objects.size()); + std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj) + { + VersionListPtr list = std::make_shared(requireString(obj, "uid")); + list->setName(ensureString(obj, "name", QString())); + return list; + }); + return std::make_shared(lists); } // Version static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj) { - VersionPtr version = std::make_shared(uid, requireString(obj, "version")); - version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000); - version->setType(ensureString(obj, "type", QString())); - version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); - version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); - RequireSet requires, conflicts; - parseRequires(obj, &requires, "requires"); - parseRequires(obj, &conflicts, "conflicts"); - version->setRequires(requires, conflicts); - return version; + VersionPtr version = std::make_shared(uid, requireString(obj, "version")); + version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000); + version->setType(ensureString(obj, "type", QString())); + version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); + version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); + RequireSet requires, conflicts; + parseRequires(obj, &requires, "requires"); + parseRequires(obj, &conflicts, "conflicts"); + version->setRequires(requires, conflicts); + return version; } static std::shared_ptr parseVersionInternal(const QJsonObject &obj) { - VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj); + VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj); - version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj), - QString("%1/%2.json").arg(version->uid(), version->version()), - obj.contains("order"))); - return version; + version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj), + QString("%1/%2.json").arg(version->uid(), version->version()), + obj.contains("order"))); + return version; } // Version list / package static std::shared_ptr parseVersionListInternal(const QJsonObject &obj) { - const QString uid = requireString(obj, "uid"); - - const QVector versionsRaw = requireIsArrayOf(obj, "versions"); - QVector versions; - versions.reserve(versionsRaw.size()); - std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj) - { - auto version = parseCommonVersion(uid, vObj); - version->setProvidesRecommendations(); - return version; - }); - - VersionListPtr list = std::make_shared(uid); - list->setName(ensureString(obj, "name", QString())); - list->setVersions(versions); - return list; + const QString uid = requireString(obj, "uid"); + + const QVector versionsRaw = requireIsArrayOf(obj, "versions"); + QVector versions; + versions.reserve(versionsRaw.size()); + std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj) + { + auto version = parseCommonVersion(uid, vObj); + version->setProvidesRecommendations(); + return version; + }); + + VersionListPtr list = std::make_shared(uid); + list->setName(ensureString(obj, "name", QString())); + list->setVersions(versions); + return list; } MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required) { - if (!obj.contains("formatVersion")) - { - if(required) - { - return MetadataVersion::Invalid; - } - return MetadataVersion::InitialRelease; - } - if (!obj.value("formatVersion").isDouble()) - { - return MetadataVersion::Invalid; - } - switch(obj.value("formatVersion").toInt()) - { - case 0: - case 1: - return MetadataVersion::InitialRelease; - default: - return MetadataVersion::Invalid; - } + if (!obj.contains("formatVersion")) + { + if(required) + { + return MetadataVersion::Invalid; + } + return MetadataVersion::InitialRelease; + } + if (!obj.value("formatVersion").isDouble()) + { + return MetadataVersion::Invalid; + } + switch(obj.value("formatVersion").toInt()) + { + case 0: + case 1: + return MetadataVersion::InitialRelease; + default: + return MetadataVersion::Invalid; + } } void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version) { - if(version == MetadataVersion::Invalid) - { - return; - } - obj.insert("formatVersion", int(version)); + if(version == MetadataVersion::Invalid) + { + return; + } + obj.insert("formatVersion", int(version)); } void parseIndex(const QJsonObject &obj, Index *ptr) { - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseIndexInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: + ptr->merge(parseIndexInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } } void parseVersionList(const QJsonObject &obj, VersionList *ptr) { - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseVersionListInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: + ptr->merge(parseVersionListInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } } void parseVersion(const QJsonObject &obj, Version *ptr) { - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseVersionInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: + ptr->merge(parseVersionInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } } /* @@ -174,44 +174,44 @@ void parseVersion(const QJsonObject &obj, Version *ptr) */ void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName) { - if(obj.contains(keyName)) - { - QSet requires; - auto reqArray = requireArray(obj, keyName); - auto iter = reqArray.begin(); - while(iter != reqArray.end()) - { - auto reqObject = requireObject(*iter); - auto uid = requireString(reqObject, "uid"); - auto equals = ensureString(reqObject, "equals", QString()); - auto suggests = ensureString(reqObject, "suggests", QString()); - ptr->insert({uid, equals, suggests}); - iter++; - } - } + if(obj.contains(keyName)) + { + QSet requires; + auto reqArray = requireArray(obj, keyName); + auto iter = reqArray.begin(); + while(iter != reqArray.end()) + { + auto reqObject = requireObject(*iter); + auto uid = requireString(reqObject, "uid"); + auto equals = ensureString(reqObject, "equals", QString()); + auto suggests = ensureString(reqObject, "suggests", QString()); + ptr->insert({uid, equals, suggests}); + iter++; + } + } } void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName) { - if(!ptr || ptr->empty()) - { - return; - } - QJsonArray arrOut; - for(auto &iter: *ptr) - { - QJsonObject reqOut; - reqOut.insert("uid", iter.uid); - if(!iter.equalsVersion.isEmpty()) - { - reqOut.insert("equals", iter.equalsVersion); - } - if(!iter.suggests.isEmpty()) - { - reqOut.insert("suggests", iter.suggests); - } - arrOut.append(reqOut); - } - obj.insert(keyName, arrOut); + if(!ptr || ptr->empty()) + { + return; + } + QJsonArray arrOut; + for(auto &iter: *ptr) + { + QJsonObject reqOut; + reqOut.insert("uid", iter.uid); + if(!iter.equalsVersion.isEmpty()) + { + reqOut.insert("equals", iter.equalsVersion); + } + if(!iter.suggests.isEmpty()) + { + reqOut.insert("suggests", iter.suggests); + } + arrOut.append(reqOut); + } + obj.insert(keyName, arrOut); } } diff --git a/api/logic/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h index 762a36f6..06732ffd 100644 --- a/api/logic/meta/JsonFormat.h +++ b/api/logic/meta/JsonFormat.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,39 +30,39 @@ class VersionList; enum class MetadataVersion { - Invalid = -1, - InitialRelease = 1 + Invalid = -1, + InitialRelease = 1 }; class ParseException : public Exception { public: - using Exception::Exception; + using Exception::Exception; }; struct Require { - bool operator==(const Require & rhs) const - { - return uid == rhs.uid; - } - bool operator<(const Require & rhs) const - { - return uid < rhs.uid; - } - bool deepEquals(const Require & rhs) const - { - return uid == rhs.uid - && equalsVersion == rhs.equalsVersion - && suggests == rhs.suggests; - } - QString uid; - QString equalsVersion; - QString suggests; + bool operator==(const Require & rhs) const + { + return uid == rhs.uid; + } + bool operator<(const Require & rhs) const + { + return uid < rhs.uid; + } + bool deepEquals(const Require & rhs) const + { + return uid == rhs.uid + && equalsVersion == rhs.equalsVersion + && suggests == rhs.suggests; + } + QString uid; + QString equalsVersion; + QString suggests; }; inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW { - return qHash(key.uid, seed); + return qHash(key.uid, seed); } using RequireSet = std::set; @@ -80,4 +80,4 @@ void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyNa MetadataVersion currentFormatVersion(); } -Q_DECLARE_METATYPE(std::set); \ No newline at end of file +Q_DECLARE_METATYPE(std::set) diff --git a/api/logic/meta/Version.cpp b/api/logic/meta/Version.cpp index edc70f33..ca889550 100644 --- a/api/logic/meta/Version.cpp +++ b/api/logic/meta/Version.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ #include "minecraft/ComponentList.h" Meta::Version::Version(const QString &uid, const QString &version) - : BaseVersion(), m_uid(uid), m_version(version) + : BaseVersion(), m_uid(uid), m_version(version) { } @@ -31,110 +31,110 @@ Meta::Version::~Version() QString Meta::Version::descriptor() { - return m_version; + return m_version; } QString Meta::Version::name() { - if(m_data) - return m_data->name; - return m_uid; + if(m_data) + return m_data->name; + return m_uid; } QString Meta::Version::typeString() const { - return m_type; + return m_type; } QDateTime Meta::Version::time() const { - return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC); + return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC); } void Meta::Version::parse(const QJsonObject& obj) { - parseVersion(obj, this); + parseVersion(obj, this); } void Meta::Version::mergeFromList(const Meta::VersionPtr& other) { - if(other->m_providesRecommendations) - { - if(m_recommended != other->m_recommended) - { - setRecommended(other->m_recommended); - } - } - if (m_type != other->m_type) - { - setType(other->m_type); - } - if (m_time != other->m_time) - { - setTime(other->m_time); - } - if (m_requires != other->m_requires) - { - m_requires = other->m_requires; - } - if (m_conflicts != other->m_conflicts) - { - m_conflicts = other->m_conflicts; - } - if(m_volatile != other->m_volatile) - { - setVolatile(other->m_volatile); - } + if(other->m_providesRecommendations) + { + if(m_recommended != other->m_recommended) + { + setRecommended(other->m_recommended); + } + } + if (m_type != other->m_type) + { + setType(other->m_type); + } + if (m_time != other->m_time) + { + setTime(other->m_time); + } + if (m_requires != other->m_requires) + { + m_requires = other->m_requires; + } + if (m_conflicts != other->m_conflicts) + { + m_conflicts = other->m_conflicts; + } + if(m_volatile != other->m_volatile) + { + setVolatile(other->m_volatile); + } } void Meta::Version::merge(const VersionPtr &other) { - mergeFromList(other); - if(other->m_data) - { - setData(other->m_data); - } + mergeFromList(other); + if(other->m_data) + { + setData(other->m_data); + } } QString Meta::Version::localFilename() const { - return m_uid + '/' + m_version + ".json"; + return m_uid + '/' + m_version + ".json"; } void Meta::Version::setType(const QString &type) { - m_type = type; - emit typeChanged(); + m_type = type; + emit typeChanged(); } void Meta::Version::setTime(const qint64 time) { - m_time = time; - emit timeChanged(); + m_time = time; + emit timeChanged(); } void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts) { - m_requires = requires; - m_conflicts = conflicts; - emit requiresChanged(); + m_requires = requires; + m_conflicts = conflicts; + emit requiresChanged(); } void Meta::Version::setVolatile(bool volatile_) { - m_volatile = volatile_; + m_volatile = volatile_; } void Meta::Version::setData(const VersionFilePtr &data) { - m_data = data; + m_data = data; } void Meta::Version::setProvidesRecommendations() { - m_providesRecommendations = true; + m_providesRecommendations = true; } void Meta::Version::setRecommended(bool recommended) { - m_recommended = recommended; + m_recommended = recommended; } diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h index 33bd5b35..d902049a 100644 --- a/api/logic/meta/Version.h +++ b/api/logic/meta/Version.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,82 +36,82 @@ using VersionPtr = std::shared_ptr; class MULTIMC_LOGIC_EXPORT Version : public QObject, public BaseVersion, public BaseEntity { - Q_OBJECT + Q_OBJECT public: /* con/des */ - explicit Version(const QString &uid, const QString &version); - virtual ~Version(); - - QString descriptor() override; - QString name() override; - QString typeString() const override; - - QString uid() const - { - return m_uid; - } - QString version() const - { - return m_version; - } - QString type() const - { - return m_type; - } - QDateTime time() const; - qint64 rawTime() const - { - return m_time; - } - const Meta::RequireSet &requires() const - { - return m_requires; - } - VersionFilePtr data() const - { - return m_data; - } - bool isRecommended() const - { - return m_recommended; - } - bool isLoaded() const - { - return m_data != nullptr; - } - - void merge(const VersionPtr &other); - void mergeFromList(const VersionPtr &other); - void parse(const QJsonObject &obj) override; - - QString localFilename() const override; + explicit Version(const QString &uid, const QString &version); + virtual ~Version(); + + QString descriptor() override; + QString name() override; + QString typeString() const override; + + QString uid() const + { + return m_uid; + } + QString version() const + { + return m_version; + } + QString type() const + { + return m_type; + } + QDateTime time() const; + qint64 rawTime() const + { + return m_time; + } + const Meta::RequireSet &requires() const + { + return m_requires; + } + VersionFilePtr data() const + { + return m_data; + } + bool isRecommended() const + { + return m_recommended; + } + bool isLoaded() const + { + return m_data != nullptr; + } + + void merge(const VersionPtr &other); + void mergeFromList(const VersionPtr &other); + void parse(const QJsonObject &obj) override; + + QString localFilename() const override; public: // for usage by format parsers only - void setType(const QString &type); - void setTime(const qint64 time); - void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); - void setVolatile(bool volatile_); - void setRecommended(bool recommended); - void setProvidesRecommendations(); - void setData(const VersionFilePtr &data); + void setType(const QString &type); + void setTime(const qint64 time); + void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); + void setVolatile(bool volatile_); + void setRecommended(bool recommended); + void setProvidesRecommendations(); + void setData(const VersionFilePtr &data); signals: - void typeChanged(); - void timeChanged(); - void requiresChanged(); + void typeChanged(); + void timeChanged(); + void requiresChanged(); private: - bool m_providesRecommendations = false; - bool m_recommended = false; - QString m_name; - QString m_uid; - QString m_version; - QString m_type; - qint64 m_time = 0; - Meta::RequireSet m_requires; - Meta::RequireSet m_conflicts; - bool m_volatile = false; - VersionFilePtr m_data; + bool m_providesRecommendations = false; + bool m_recommended = false; + QString m_name; + QString m_uid; + QString m_version; + QString m_type; + qint64 m_time = 0; + Meta::RequireSet m_requires; + Meta::RequireSet m_conflicts; + bool m_volatile = false; + VersionFilePtr m_data; }; } diff --git a/api/logic/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp index 9ae02301..cc15d0e4 100644 --- a/api/logic/meta/VersionList.cpp +++ b/api/logic/meta/VersionList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,222 +24,222 @@ namespace Meta { VersionList::VersionList(const QString &uid, QObject *parent) - : BaseVersionList(parent), m_uid(uid) + : BaseVersionList(parent), m_uid(uid) { - setObjectName("Version list: " + uid); + setObjectName("Version list: " + uid); } shared_qobject_ptr VersionList::getLoadTask() { - load(Net::Mode::Online); - return getCurrentTask(); + load(Net::Mode::Online); + return getCurrentTask(); } bool VersionList::isLoaded() { - return BaseEntity::isLoaded(); + return BaseEntity::isLoaded(); } const BaseVersionPtr VersionList::at(int i) const { - return m_versions.at(i); + return m_versions.at(i); } int VersionList::count() const { - return m_versions.size(); + return m_versions.size(); } void VersionList::sortVersions() { - beginResetModel(); - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) - { - return *a.get() < *b.get(); - }); - endResetModel(); + beginResetModel(); + std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) + { + return *a.get() < *b.get(); + }); + endResetModel(); } QVariant VersionList::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid()) - { - return QVariant(); - } - - VersionPtr version = m_versions.at(index.row()); - - switch (role) - { - case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast(version)); - case VersionRole: - case VersionIdRole: - return version->version(); - case ParentVersionRole: - { - // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. - auto & reqs = version->requires(); - auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) - { - return req.uid == "net.minecraft"; - }); - if (iter != reqs.end()) - { - return (*iter).equalsVersion; - } - return QVariant(); - } - case TypeRole: return version->type(); - - case UidRole: return version->uid(); - case TimeRole: return version->time(); - case RequiresRole: return QVariant::fromValue(version->requires()); - case SortRole: return version->rawTime(); - case VersionPtrRole: return QVariant::fromValue(version); - case RecommendedRole: return version->isRecommended(); - // FIXME: this should be determined in whatever view/proxy is used... - // case LatestRole: return version == getLatestStable(); - default: return QVariant(); - } + if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid()) + { + return QVariant(); + } + + VersionPtr version = m_versions.at(index.row()); + + switch (role) + { + case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast(version)); + case VersionRole: + case VersionIdRole: + return version->version(); + case ParentVersionRole: + { + // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. + auto & reqs = version->requires(); + auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) + { + return req.uid == "net.minecraft"; + }); + if (iter != reqs.end()) + { + return (*iter).equalsVersion; + } + return QVariant(); + } + case TypeRole: return version->type(); + + case UidRole: return version->uid(); + case TimeRole: return version->time(); + case RequiresRole: return QVariant::fromValue(version->requires()); + case SortRole: return version->rawTime(); + case VersionPtrRole: return QVariant::fromValue(version); + case RecommendedRole: return version->isRecommended(); + // FIXME: this should be determined in whatever view/proxy is used... + // case LatestRole: return version == getLatestStable(); + default: return QVariant(); + } } BaseVersionList::RoleList VersionList::providesRoles() const { - return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, - TypeRole, UidRole, TimeRole, RequiresRole, SortRole, - RecommendedRole, LatestRole, VersionPtrRole}; + return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, + TypeRole, UidRole, TimeRole, RequiresRole, SortRole, + RecommendedRole, LatestRole, VersionPtrRole}; } QHash VersionList::roleNames() const { - QHash roles = BaseVersionList::roleNames(); - roles.insert(UidRole, "uid"); - roles.insert(TimeRole, "time"); - roles.insert(SortRole, "sort"); - roles.insert(RequiresRole, "requires"); - return roles; + QHash roles = BaseVersionList::roleNames(); + roles.insert(UidRole, "uid"); + roles.insert(TimeRole, "time"); + roles.insert(SortRole, "sort"); + roles.insert(RequiresRole, "requires"); + return roles; } QString VersionList::localFilename() const { - return m_uid + "/index.json"; + return m_uid + "/index.json"; } QString VersionList::humanReadable() const { - return m_name.isEmpty() ? m_uid : m_name; + return m_name.isEmpty() ? m_uid : m_name; } VersionPtr VersionList::getVersion(const QString &version) { - VersionPtr out = m_lookup.value(version, nullptr); - if(!out) - { - out = std::make_shared(m_uid, version); - m_lookup[version] = out; - } - return out; + VersionPtr out = m_lookup.value(version, nullptr); + if(!out) + { + out = std::make_shared(m_uid, version); + m_lookup[version] = out; + } + return out; } void VersionList::setName(const QString &name) { - m_name = name; - emit nameChanged(name); + m_name = name; + emit nameChanged(name); } void VersionList::setVersions(const QVector &versions) { - beginResetModel(); - m_versions = versions; - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) - { - return a->rawTime() > b->rawTime(); - }); - for (int i = 0; i < m_versions.size(); ++i) - { - m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i)); - setupAddedVersion(i, m_versions.at(i)); - } + beginResetModel(); + m_versions = versions; + std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) + { + return a->rawTime() > b->rawTime(); + }); + for (int i = 0; i < m_versions.size(); ++i) + { + m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i)); + setupAddedVersion(i, m_versions.at(i)); + } - // FIXME: this is dumb, we have 'recommended' as part of the metadata already... - auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; }); - m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt; - endResetModel(); + // FIXME: this is dumb, we have 'recommended' as part of the metadata already... + auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; }); + m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt; + endResetModel(); } void VersionList::parse(const QJsonObject& obj) { - parseVersionList(obj, this); + parseVersionList(obj, this); } // FIXME: this is dumb, we have 'recommended' as part of the metadata already... static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b) { - if(!a) - return b; - if(!b) - return a; - if(a->type() == b->type()) - { - // newer of same type wins - return (a->rawTime() > b->rawTime() ? a : b); - } - // 'release' type wins - return (a->type() == "release" ? a : b); + if(!a) + return b; + if(!b) + return a; + if(a->type() == b->type()) + { + // newer of same type wins + return (a->rawTime() > b->rawTime() ? a : b); + } + // 'release' type wins + return (a->type() == "release" ? a : b); } void VersionList::mergeFromIndex(const VersionListPtr &other) { - if (m_name != other->m_name) - { - setName(other->m_name); - } + if (m_name != other->m_name) + { + setName(other->m_name); + } } void VersionList::merge(const VersionListPtr &other) { - if (m_name != other->m_name) - { - setName(other->m_name); - } - - // TODO: do not reset the whole model. maybe? - beginResetModel(); - m_versions.clear(); - if(other->m_versions.isEmpty()) - { - qWarning() << "Empty list loaded ..."; - } - for (const VersionPtr &version : other->m_versions) - { - // we already have the version. merge the contents - if (m_lookup.contains(version->version())) - { - m_lookup.value(version->version())->mergeFromList(version); - } - else - { - m_lookup.insert(version->uid(), version); - } - // connect it. - setupAddedVersion(m_versions.size(), version); - m_versions.append(version); - m_recommended = getBetterVersion(m_recommended, version); - } - endResetModel(); + if (m_name != other->m_name) + { + setName(other->m_name); + } + + // TODO: do not reset the whole model. maybe? + beginResetModel(); + m_versions.clear(); + if(other->m_versions.isEmpty()) + { + qWarning() << "Empty list loaded ..."; + } + for (const VersionPtr &version : other->m_versions) + { + // we already have the version. merge the contents + if (m_lookup.contains(version->version())) + { + m_lookup.value(version->version())->mergeFromList(version); + } + else + { + m_lookup.insert(version->uid(), version); + } + // connect it. + setupAddedVersion(m_versions.size(), version); + m_versions.append(version); + m_recommended = getBetterVersion(m_recommended, version); + } + endResetModel(); } void VersionList::setupAddedVersion(const int row, const VersionPtr &version) { - // FIXME: do not disconnect from everythin, disconnect only the lambdas here - version->disconnect(); - connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << RequiresRole); }); - connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TimeRole << SortRole); }); - connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TypeRole); }); + // FIXME: do not disconnect from everythin, disconnect only the lambdas here + version->disconnect(); + connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << RequiresRole); }); + connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TimeRole << SortRole); }); + connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TypeRole); }); } BaseVersionPtr VersionList::getRecommended() const { - return m_recommended; + return m_recommended; } } diff --git a/api/logic/meta/VersionList.h b/api/logic/meta/VersionList.h index ad8733cc..24b01d08 100644 --- a/api/logic/meta/VersionList.h +++ b/api/logic/meta/VersionList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,75 +27,75 @@ using VersionListPtr = std::shared_ptr; class MULTIMC_LOGIC_EXPORT VersionList : public BaseVersionList, public BaseEntity { - Q_OBJECT - Q_PROPERTY(QString uid READ uid CONSTANT) - Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_OBJECT + Q_PROPERTY(QString uid READ uid CONSTANT) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) public: - explicit VersionList(const QString &uid, QObject *parent = nullptr); - - enum Roles - { - UidRole = Qt::UserRole + 100, - TimeRole, - RequiresRole, - VersionPtrRole - }; - - shared_qobject_ptr getLoadTask() override; - bool isLoaded() override; - const BaseVersionPtr at(int i) const override; - int count() const override; - void sortVersions() override; - - BaseVersionPtr getRecommended() const override; - - QVariant data(const QModelIndex &index, int role) const override; - RoleList providesRoles() const override; - QHash roleNames() const override; - - QString localFilename() const override; - - QString uid() const - { - return m_uid; - } - QString name() const - { - return m_name; - } - QString humanReadable() const; - - VersionPtr getVersion(const QString &version); - - QVector versions() const - { - return m_versions; - } + explicit VersionList(const QString &uid, QObject *parent = nullptr); + + enum Roles + { + UidRole = Qt::UserRole + 100, + TimeRole, + RequiresRole, + VersionPtrRole + }; + + shared_qobject_ptr getLoadTask() override; + bool isLoaded() override; + const BaseVersionPtr at(int i) const override; + int count() const override; + void sortVersions() override; + + BaseVersionPtr getRecommended() const override; + + QVariant data(const QModelIndex &index, int role) const override; + RoleList providesRoles() const override; + QHash roleNames() const override; + + QString localFilename() const override; + + QString uid() const + { + return m_uid; + } + QString name() const + { + return m_name; + } + QString humanReadable() const; + + VersionPtr getVersion(const QString &version); + + QVector versions() const + { + return m_versions; + } public: // for usage only by parsers - void setName(const QString &name); - void setVersions(const QVector &versions); - void merge(const VersionListPtr &other); - void mergeFromIndex(const VersionListPtr &other); - void parse(const QJsonObject &obj) override; + void setName(const QString &name); + void setVersions(const QVector &versions); + void merge(const VersionListPtr &other); + void mergeFromIndex(const VersionListPtr &other); + void parse(const QJsonObject &obj) override; signals: - void nameChanged(const QString &name); + void nameChanged(const QString &name); protected slots: - void updateListData(QList) override - { - } + void updateListData(QList) override + { + } private: - QVector m_versions; - QHash m_lookup; - QString m_uid; - QString m_name; + QVector m_versions; + QHash m_lookup; + QString m_uid; + QString m_name; - VersionPtr m_recommended; + VersionPtr m_recommended; - void setupAddedVersion(const int row, const VersionPtr &version); + void setupAddedVersion(const int row, const VersionPtr &version); }; } Q_DECLARE_METATYPE(Meta::VersionListPtr) diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp index 5b25bede..f94f68b1 100644 --- a/api/logic/minecraft/AssetsUtils.cpp +++ b/api/logic/minecraft/AssetsUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,34 @@ #include "FileSystem.h" #include "net/Download.h" #include "net/ChecksumValidator.h" +#include "net/URLConstants.h" + +namespace { +QSet collectPathsFromDir(QString dirPath) +{ + QFileInfo dirInfo(dirPath); + + if (!dirInfo.exists()) + { + return {}; + } + + QSet out; + + QDirIterator iter(dirPath, QDirIterator::Subdirectories); + while (iter.hasNext()) + { + QString value = iter.next(); + QFileInfo info(value); + if(info.isFile()) + { + out.insert(value); + qDebug() << value; + } + } + return out; +} +} namespace AssetsUtils @@ -36,202 +64,270 @@ namespace AssetsUtils * Returns true on success, with index populated * index is undefined otherwise */ -bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index) +bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index) +{ + /* + { + "objects": { + "icons/icon_16x16.png": { + "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a", + "size": 3665 + }, + ... + } + } + } + */ + + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::ReadOnly)) + { + qCritical() << "Failed to read assets index file" << path; + return false; + } + index.id = assetsId; + + // Read the file and close it. + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); + + // Fail if the JSON is invalid. + if (parseError.error != QJsonParseError::NoError) + { + qCritical() << "Failed to parse assets index file:" << parseError.errorString() + << "at offset " << QString::number(parseError.offset); + return false; + } + + // Make sure the root is an object. + if (!jsonDoc.isObject()) + { + qCritical() << "Invalid assets index JSON: Root should be an array."; + return false; + } + + QJsonObject root = jsonDoc.object(); + + QJsonValue isVirtual = root.value("virtual"); + if (!isVirtual.isUndefined()) + { + index.isVirtual = isVirtual.toBool(false); + } + + QJsonValue mapToResources = root.value("map_to_resources"); + if (!mapToResources.isUndefined()) + { + index.mapToResources = mapToResources.toBool(false); + } + + QJsonValue objects = root.value("objects"); + QVariantMap map = objects.toVariant().toMap(); + + for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) + { + // qDebug() << iter.key(); + + QVariant variant = iter.value(); + QVariantMap nested_objects = variant.toMap(); + + AssetObject object; + + for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); + nested_iter != nested_objects.end(); ++nested_iter) + { + // qDebug() << nested_iter.key() << nested_iter.value().toString(); + QString key = nested_iter.key(); + QVariant value = nested_iter.value(); + + if (key == "hash") + { + object.hash = value.toString(); + } + else if (key == "size") + { + object.size = value.toDouble(); + } + } + + index.objects.insert(iter.key(), object); + } + + return true; +} + +// FIXME: ugly code duplication +QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder) { - /* - { - "objects": { - "icons/icon_16x16.png": { - "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a", - "size": 3665 - }, - ... - } - } - } - */ - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << "Failed to read assets index file" << path; - return false; - } - index->id = assetsId; - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << "Failed to parse assets index file:" << parseError.errorString() - << "at offset " << QString::number(parseError.offset); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid assets index JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - QJsonValue isVirtual = root.value("virtual"); - if (!isVirtual.isUndefined()) - { - index->isVirtual = isVirtual.toBool(false); - } - - QJsonValue objects = root.value("objects"); - QVariantMap map = objects.toVariant().toMap(); - - for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) - { - // qDebug() << iter.key(); - - QVariant variant = iter.value(); - QVariantMap nested_objects = variant.toMap(); - - AssetObject object; - - for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); - nested_iter != nested_objects.end(); ++nested_iter) - { - // qDebug() << nested_iter.key() << nested_iter.value().toString(); - QString key = nested_iter.key(); - QVariant value = nested_iter.value(); - - if (key == "hash") - { - object.hash = value.toString(); - } - else if (key == "size") - { - object.size = value.toDouble(); - } - } - - index->objects.insert(iter.key(), object); - } - - return true; + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); + + if (!indexFile.exists()) + { + qCritical() << "No assets index file" << indexPath << "; can't determine assets path!"; + return virtualRoot; + } + + AssetsIndex index; + if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) + { + qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!"; + return virtualRoot; + } + + QString targetPath; + if(index.isVirtual) + { + return virtualRoot; + } + else if(index.mapToResources) + { + return QDir(resourcesFolder); + } + return virtualRoot; } -QDir reconstructAssets(QString assetsId) +// FIXME: ugly code duplication +bool reconstructAssets(QString assetsId, QString resourcesFolder) { - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); - - if (!indexFile.exists()) - { - qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets"; - return virtualRoot; - } - - qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() - << objectDir.path() << virtualDir.path() << virtualRoot.path(); - - AssetsIndex index; - bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index); - - if (loadAssetsIndex && index.isVirtual) - { - qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path(); - - for (QString map : index.objects.keys()) - { - AssetObject asset_object = index.objects.value(map); - QString target_path = FS::PathCombine(virtualRoot.path(), map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash); - QFile original(original_path); - if (!original.exists()) - continue; - if (!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - // qDebug() << target_dir; - if (!target_dir.exists()) - QDir("").mkpath(target_dir.path()); - - bool couldCopy = original.copy(target_path); - qDebug() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); - } - } - - // TODO: Write last used time to virtualRoot/.lastused - } - - return virtualRoot; + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); + + if (!indexFile.exists()) + { + qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!"; + return false; + } + + qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path(); + + AssetsIndex index; + if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) + { + qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!"; + return false; + } + + QString targetPath; + bool removeLeftovers = false; + if(index.isVirtual) + { + targetPath = virtualRoot.path(); + removeLeftovers = true; + qDebug() << "Reconstructing virtual assets folder at" << targetPath; + } + else if(index.mapToResources) + { + targetPath = resourcesFolder; + qDebug() << "Reconstructing resources folder at" << targetPath; + } + + if (!targetPath.isNull()) + { + auto presentFiles = collectPathsFromDir(targetPath); + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = FS::PathCombine(targetPath, map); + QFile target(target_path); + + QString tlk = asset_object.hash.left(2); + + QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash); + QFile original(original_path); + if (!original.exists()) + continue; + + presentFiles.remove(target_path); + + if (!target.exists()) + { + QFileInfo info(target_path); + QDir target_dir = info.dir(); + + qDebug() << target_dir.path(); + FS::ensureFolderPathExists(target_dir.path()); + + bool couldCopy = original.copy(target_path); + qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy); + } + } + + // TODO: Write last used time to virtualRoot/.lastused + if(removeLeftovers) + { + for(auto & file: presentFiles) + { + qDebug() << "Would remove" << file; + } + } + } + return true; } } 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; + 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(); + return "assets/objects/" + getRelPath(); } QUrl AssetObject::getUrl() { - return QUrl("http://resources.download.minecraft.net/" + getRelPath()); + return URLConstants::RESOURCE_BASE + getRelPath(); } QString AssetObject::getRelPath() { - return hash.left(2) + "/" + hash; + 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; + 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; } diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h index b7ea9cc1..356b8c8a 100644 --- a/api/logic/minecraft/AssetsUtils.h +++ b/api/logic/minecraft/AssetsUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,27 +22,32 @@ struct AssetObject { - QString getRelPath(); - QUrl getUrl(); - QString getLocalPath(); - NetActionPtr getDownloadAction(); + QString getRelPath(); + QUrl getUrl(); + QString getLocalPath(); + NetActionPtr getDownloadAction(); - QString hash; - qint64 size; + QString hash; + qint64 size; }; struct AssetsIndex { - NetJobPtr getDownloadJob(); + NetJobPtr getDownloadJob(); - QString id; - QMap objects; - bool isVirtual = false; + QString id; + QMap objects; + bool isVirtual = false; + bool mapToResources = false; }; +/// FIXME: this is absolutely horrendous. REDO!!!! namespace AssetsUtils { -bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index); +bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index); + +QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder); + /// Reconstruct a virtual assets folder for the given assets ID and return the folder -QDir reconstructAssets(QString assetsId); +bool reconstructAssets(QString assetsId, QString resourcesFolder); } diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp index 50a2ae16..51957d17 100644 --- a/api/logic/minecraft/Component.cpp +++ b/api/logic/minecraft/Component.cpp @@ -13,356 +13,356 @@ Component::Component(ComponentList * parent, const QString& uid) { - assert(parent); - m_parent = parent; + assert(parent); + m_parent = parent; - m_uid = uid; + m_uid = uid; } Component::Component(ComponentList * parent, std::shared_ptr version) { - assert(parent); - m_parent = parent; + assert(parent); + m_parent = parent; - m_metaVersion = version; - m_uid = version->uid(); - m_version = m_cachedVersion = version->version(); - m_cachedName = version->name(); - m_loaded = version->isLoaded(); + m_metaVersion = version; + m_uid = version->uid(); + m_version = m_cachedVersion = version->version(); + m_cachedName = version->name(); + m_loaded = version->isLoaded(); } Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr file) { - assert(parent); - m_parent = parent; + assert(parent); + m_parent = parent; - m_file = file; - m_uid = uid; - m_cachedVersion = m_file->version; - m_cachedName = m_file->name; - m_loaded = true; + m_file = file; + m_uid = uid; + m_cachedVersion = m_file->version; + m_cachedName = m_file->name; + m_loaded = true; } std::shared_ptr Component::getMeta() { - return m_metaVersion; + return m_metaVersion; } void Component::applyTo(LaunchProfile* profile) { - // do not apply disabled components - if(!isEnabled()) - { - return; - } - auto vfile = getVersionFile(); - if(vfile) - { - vfile->applyTo(profile); - } - else - { - profile->applyProblemSeverity(getProblemSeverity()); - } + // do not apply disabled components + if(!isEnabled()) + { + return; + } + auto vfile = getVersionFile(); + if(vfile) + { + vfile->applyTo(profile); + } + else + { + profile->applyProblemSeverity(getProblemSeverity()); + } } std::shared_ptr Component::getVersionFile() const { - if(m_metaVersion) - { - if(!m_metaVersion->isLoaded()) - { - m_metaVersion->load(Net::Mode::Online); - } - return m_metaVersion->data(); - } - else - { - return m_file; - } + if(m_metaVersion) + { + if(!m_metaVersion->isLoaded()) + { + m_metaVersion->load(Net::Mode::Online); + } + return m_metaVersion->data(); + } + else + { + return m_file; + } } std::shared_ptr Component::getVersionList() const { - // FIXME: what if the metadata index isn't loaded yet? - if(ENV.metadataIndex()->hasUid(m_uid)) - { - return ENV.metadataIndex()->get(m_uid); - } - return nullptr; + // FIXME: what if the metadata index isn't loaded yet? + if(ENV.metadataIndex()->hasUid(m_uid)) + { + return ENV.metadataIndex()->get(m_uid); + } + return nullptr; } int Component::getOrder() { - if(m_orderOverride) - return m_order; + if(m_orderOverride) + return m_order; - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->order; - } - return 0; + auto vfile = getVersionFile(); + if(vfile) + { + return vfile->order; + } + return 0; } void Component::setOrder(int order) { - m_orderOverride = true; - m_order = order; + m_orderOverride = true; + m_order = order; } QString Component::getID() { - return m_uid; + return m_uid; } QString Component::getName() { - if (!m_cachedName.isEmpty()) - return m_cachedName; - return m_uid; + if (!m_cachedName.isEmpty()) + return m_cachedName; + return m_uid; } QString Component::getVersion() { - return m_cachedVersion; + return m_cachedVersion; } QString Component::getFilename() { - return m_parent->patchFilePathForUid(m_uid); + return m_parent->patchFilePathForUid(m_uid); } QDateTime Component::getReleaseDateTime() { - if(m_metaVersion) - { - return m_metaVersion->time(); - } - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->releaseTime; - } - // FIXME: fake - return QDateTime::currentDateTime(); + if(m_metaVersion) + { + return m_metaVersion->time(); + } + auto vfile = getVersionFile(); + if(vfile) + { + return vfile->releaseTime; + } + // FIXME: fake + return QDateTime::currentDateTime(); } bool Component::isEnabled() { - return !canBeDisabled() || !m_disabled; -}; + return !canBeDisabled() || !m_disabled; +} bool Component::canBeDisabled() { - return isRemovable() && !m_dependencyOnly; + return isRemovable() && !m_dependencyOnly; } bool Component::setEnabled(bool state) { - bool intendedDisabled = !state; - if (!canBeDisabled()) - { - intendedDisabled = false; - } - if(intendedDisabled != m_disabled) - { - m_disabled = intendedDisabled; - emit dataChanged(); - return true; - } - return false; + bool intendedDisabled = !state; + if (!canBeDisabled()) + { + intendedDisabled = false; + } + if(intendedDisabled != m_disabled) + { + m_disabled = intendedDisabled; + emit dataChanged(); + return true; + } + return false; } bool Component::isCustom() { - return m_file != nullptr; -}; + return m_file != nullptr; +} bool Component::isCustomizable() { - if(m_metaVersion) - { - if(getVersionFile()) - { - return true; - } - } - return false; + if(m_metaVersion) + { + if(getVersionFile()) + { + return true; + } + } + return false; } bool Component::isRemovable() { - return !m_important; + return !m_important; } bool Component::isRevertible() { - if (isCustom()) - { - if(ENV.metadataIndex()->hasUid(m_uid)) - { - return true; - } - } - return false; + if (isCustom()) + { + if(ENV.metadataIndex()->hasUid(m_uid)) + { + return true; + } + } + return false; } bool Component::isMoveable() { - // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'. - return true; + // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'. + return true; } bool Component::isVersionChangeable() { - auto list = getVersionList(); - if(list) - { - if(!list->isLoaded()) - { - list->load(Net::Mode::Online); - } - return list->count() != 0; - } - return false; + auto list = getVersionList(); + if(list) + { + if(!list->isLoaded()) + { + list->load(Net::Mode::Online); + } + return list->count() != 0; + } + return false; } void Component::setImportant(bool state) { - if(m_important != state) - { - m_important = state; - emit dataChanged(); - } + if(m_important != state) + { + m_important = state; + emit dataChanged(); + } } ProblemSeverity Component::getProblemSeverity() const { - auto file = getVersionFile(); - if(file) - { - return file->getProblemSeverity(); - } - return ProblemSeverity::Error; + auto file = getVersionFile(); + if(file) + { + return file->getProblemSeverity(); + } + return ProblemSeverity::Error; } const QList Component::getProblems() const { - auto file = getVersionFile(); - if(file) - { - return file->getProblems(); - } - return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}}; + auto file = getVersionFile(); + if(file) + { + return file->getProblems(); + } + return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}}; } void Component::setVersion(const QString& version) { - if(version == m_version) - { - return; - } - m_version = version; - if(m_loaded) - { - // we are loaded and potentially have state to invalidate - if(m_file) - { - // we have a file... explicit version has been changed and there is nothing else to do. - } - else - { - // we don't have a file, therefore we are loaded with metadata - m_cachedVersion = version; - // see if the meta version is loaded - auto metaVersion = ENV.metadataIndex()->get(m_uid, version); - if(metaVersion->isLoaded()) - { - // if yes, we can continue with that. - m_metaVersion = metaVersion; - } - else - { - // if not, we need loading - m_metaVersion.reset(); - m_loaded = false; - } - updateCachedData(); - } - } - else - { - // not loaded... assume it will be sorted out later by the update task - } - emit dataChanged(); + if(version == m_version) + { + return; + } + m_version = version; + if(m_loaded) + { + // we are loaded and potentially have state to invalidate + if(m_file) + { + // we have a file... explicit version has been changed and there is nothing else to do. + } + else + { + // we don't have a file, therefore we are loaded with metadata + m_cachedVersion = version; + // see if the meta version is loaded + auto metaVersion = ENV.metadataIndex()->get(m_uid, version); + if(metaVersion->isLoaded()) + { + // if yes, we can continue with that. + m_metaVersion = metaVersion; + } + else + { + // if not, we need loading + m_metaVersion.reset(); + m_loaded = false; + } + updateCachedData(); + } + } + else + { + // not loaded... assume it will be sorted out later by the update task + } + emit dataChanged(); } bool Component::customize() { - if(isCustom()) - { - return false; - } + if(isCustom()) + { + return false; + } - auto filename = getFilename(); - if(!FS::ensureFilePathExists(filename)) - { - return false; - } - // FIXME: get rid of this try-catch. - try - { - QSaveFile jsonFile(filename); - if(!jsonFile.open(QIODevice::WriteOnly)) - { - return false; - } - auto vfile = getVersionFile(); - if(!vfile) - { - return false; - } - auto document = OneSixVersionFormat::versionFileToJson(vfile); - jsonFile.write(document.toJson()); - if(!jsonFile.commit()) - { - return false; - } - m_file = vfile; - m_metaVersion.reset(); - emit dataChanged(); - } - catch (Exception &error) - { - qWarning() << "Version could not be loaded:" << error.cause(); - } - return true; + auto filename = getFilename(); + if(!FS::ensureFilePathExists(filename)) + { + return false; + } + // FIXME: get rid of this try-catch. + try + { + QSaveFile jsonFile(filename); + if(!jsonFile.open(QIODevice::WriteOnly)) + { + return false; + } + auto vfile = getVersionFile(); + if(!vfile) + { + return false; + } + auto document = OneSixVersionFormat::versionFileToJson(vfile); + jsonFile.write(document.toJson()); + if(!jsonFile.commit()) + { + return false; + } + m_file = vfile; + m_metaVersion.reset(); + emit dataChanged(); + } + catch (const Exception &error) + { + qWarning() << "Version could not be loaded:" << error.cause(); + } + return true; } bool Component::revert() { - if(!isCustom()) - { - // already not custom - return true; - } - auto filename = getFilename(); - bool result = true; - // just kill the file and reload - if(QFile::exists(filename)) - { - result = QFile::remove(filename); - } - if(result) - { - // file gone... - m_file.reset(); + if(!isCustom()) + { + // already not custom + return true; + } + auto filename = getFilename(); + bool result = true; + // just kill the file and reload + if(QFile::exists(filename)) + { + result = QFile::remove(filename); + } + if(result) + { + // file gone... + m_file.reset(); - // check local cache for metadata... - auto version = ENV.metadataIndex()->get(m_uid, m_version); - if(version->isLoaded()) - { - m_metaVersion = version; - } - else - { - m_metaVersion.reset(); - m_loaded = false; - } - emit dataChanged(); - } - return result; + // check local cache for metadata... + auto version = ENV.metadataIndex()->get(m_uid, m_version); + if(version->isLoaded()) + { + m_metaVersion = version; + } + else + { + m_metaVersion.reset(); + m_loaded = false; + } + emit dataChanged(); + } + return result; } /** @@ -372,68 +372,68 @@ bool Component::revert() */ static bool deepCompare(const std::set & a, const std::set & b) { - // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes - if(a.size() != b.size()) - { - return false; - } - for(const auto & reqA :a) - { - const auto &iter2 = b.find(reqA); - if(iter2 == b.cend()) - { - return false; - } - const auto & reqB = *iter2; - if(!reqA.deepEquals(reqB)) - { - return false; - } - } - return true; + // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes + if(a.size() != b.size()) + { + return false; + } + for(const auto & reqA :a) + { + const auto &iter2 = b.find(reqA); + if(iter2 == b.cend()) + { + return false; + } + const auto & reqB = *iter2; + if(!reqA.deepEquals(reqB)) + { + return false; + } + } + return true; } void Component::updateCachedData() { - auto file = getVersionFile(); - if(file) - { - bool changed = false; - if(m_cachedName != file->name) - { - m_cachedName = file->name; - changed = true; - } - if(m_cachedVersion != file->version) - { - m_cachedVersion = file->version; - changed = true; - } - if(m_cachedVolatile != file->m_volatile) - { - m_cachedVolatile = file->m_volatile; - changed = true; - } - if(!deepCompare(m_cachedRequires, file->requires)) - { - m_cachedRequires = file->requires; - changed = true; - } - if(!deepCompare(m_cachedConflicts, file->conflicts)) - { - m_cachedConflicts = file->conflicts; - changed = true; - } - if(changed) - { - emit dataChanged(); - } - } - else - { - // in case we removed all the metadata - m_cachedRequires.clear(); - m_cachedConflicts.clear(); - emit dataChanged(); - } + auto file = getVersionFile(); + if(file) + { + bool changed = false; + if(m_cachedName != file->name) + { + m_cachedName = file->name; + changed = true; + } + if(m_cachedVersion != file->version) + { + m_cachedVersion = file->version; + changed = true; + } + if(m_cachedVolatile != file->m_volatile) + { + m_cachedVolatile = file->m_volatile; + changed = true; + } + if(!deepCompare(m_cachedRequires, file->requires)) + { + m_cachedRequires = file->requires; + changed = true; + } + if(!deepCompare(m_cachedConflicts, file->conflicts)) + { + m_cachedConflicts = file->conflicts; + changed = true; + } + if(changed) + { + emit dataChanged(); + } + } + else + { + // in case we removed all the metadata + m_cachedRequires.clear(); + m_cachedConflicts.clear(); + emit dataChanged(); + } } diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h index 778fbb18..6a0f86c8 100644 --- a/api/logic/minecraft/Component.h +++ b/api/logic/minecraft/Component.h @@ -13,8 +13,8 @@ class ComponentList; class LaunchProfile; namespace Meta { - class Version; - class VersionList; + class Version; + class VersionList; } class VersionFile; @@ -22,90 +22,90 @@ class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider { Q_OBJECT public: - Component(ComponentList * parent, const QString &uid); + Component(ComponentList * parent, const QString &uid); - // DEPRECATED: remove these constructors? - Component(ComponentList * parent, std::shared_ptr version); - Component(ComponentList * parent, const QString & uid, std::shared_ptr file); + // DEPRECATED: remove these constructors? + Component(ComponentList * parent, std::shared_ptr version); + Component(ComponentList * parent, const QString & uid, std::shared_ptr file); - virtual ~Component(){}; - void applyTo(LaunchProfile *profile); + virtual ~Component(){}; + void applyTo(LaunchProfile *profile); - bool isEnabled(); - bool setEnabled (bool state); - bool canBeDisabled(); + bool isEnabled(); + bool setEnabled (bool state); + bool canBeDisabled(); - bool isMoveable(); - bool isCustomizable(); - bool isRevertible(); - bool isRemovable(); - bool isCustom(); - bool isVersionChangeable(); + bool isMoveable(); + bool isCustomizable(); + bool isRevertible(); + bool isRemovable(); + bool isCustom(); + bool isVersionChangeable(); - // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code - void setOrder(int order); - int getOrder(); + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + void setOrder(int order); + int getOrder(); - QString getID(); - QString getName(); - QString getVersion(); - std::shared_ptr getMeta(); - QDateTime getReleaseDateTime(); + QString getID(); + QString getName(); + QString getVersion(); + std::shared_ptr getMeta(); + QDateTime getReleaseDateTime(); - QString getFilename(); + QString getFilename(); - std::shared_ptr getVersionFile() const; - std::shared_ptr getVersionList() const; + std::shared_ptr getVersionFile() const; + std::shared_ptr getVersionList() const; - void setImportant (bool state); + void setImportant (bool state); - const QList getProblems() const override; - ProblemSeverity getProblemSeverity() const override; + const QList getProblems() const override; + ProblemSeverity getProblemSeverity() const override; - void setVersion(const QString & version); - bool customize(); - bool revert(); + void setVersion(const QString & version); + bool customize(); + bool revert(); - void updateCachedData(); + void updateCachedData(); signals: - void dataChanged(); + void dataChanged(); public: /* data */ - ComponentList * m_parent; - - // BEGIN: persistent component list properties - /// ID of the component - QString m_uid; - /// version of the component - when there's a custom json override, this is also the version the component reverts to - QString m_version; - /// if true, this has been added automatically to satisfy dependencies and may be automatically removed - bool m_dependencyOnly = false; - /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed. - bool m_important = false; - /// if true, the component is disabled - bool m_disabled = false; - - /// cached name for display purposes, taken from the version file (meta or local override) - QString m_cachedName; - /// cached version for display AND other purposes, taken from the version file (meta or local override) - QString m_cachedVersion; - /// cached set of requirements, taken from the version file (meta or local override) - Meta::RequireSet m_cachedRequires; - Meta::RequireSet m_cachedConflicts; - /// if true, the component is volatile and may be automatically removed when no longer needed - bool m_cachedVolatile = false; - // END: persistent component list properties - - // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code - bool m_orderOverride = false; - int m_order = 0; - - // load state - std::shared_ptr m_metaVersion; - std::shared_ptr m_file; - bool m_loaded = false; + ComponentList * m_parent; + + // BEGIN: persistent component list properties + /// ID of the component + QString m_uid; + /// version of the component - when there's a custom json override, this is also the version the component reverts to + QString m_version; + /// if true, this has been added automatically to satisfy dependencies and may be automatically removed + bool m_dependencyOnly = false; + /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed. + bool m_important = false; + /// if true, the component is disabled + bool m_disabled = false; + + /// cached name for display purposes, taken from the version file (meta or local override) + QString m_cachedName; + /// cached version for display AND other purposes, taken from the version file (meta or local override) + QString m_cachedVersion; + /// cached set of requirements, taken from the version file (meta or local override) + Meta::RequireSet m_cachedRequires; + Meta::RequireSet m_cachedConflicts; + /// if true, the component is volatile and may be automatically removed when no longer needed + bool m_cachedVolatile = false; + // END: persistent component list properties + + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + bool m_orderOverride = false; + int m_order = 0; + + // load state + std::shared_ptr m_metaVersion; + std::shared_ptr m_file; + bool m_loaded = false; }; typedef shared_qobject_ptr ComponentPtr; diff --git a/api/logic/minecraft/ComponentList.cpp b/api/logic/minecraft/ComponentList.cpp index a207e987..51fe214d 100644 --- a/api/logic/minecraft/ComponentList.cpp +++ b/api/logic/minecraft/ComponentList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,18 +37,20 @@ #include "ComponentUpdateTask.h" ComponentList::ComponentList(MinecraftInstance * instance) - : QAbstractListModel() + : QAbstractListModel() { - d.reset(new ComponentListData); - d->m_instance = instance; - d->m_saveTimer.setSingleShot(true); - d->m_saveTimer.setInterval(5000); - connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal); + d.reset(new ComponentListData); + d->m_instance = instance; + d->m_saveTimer.setSingleShot(true); + d->m_saveTimer.setInterval(5000); + d->interactionDisabled = instance->isRunning(); + connect(d->m_instance, &BaseInstance::runningStatusChanged, this, &ComponentList::disableInteraction); + connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal); } ComponentList::~ComponentList() { - saveNow(); + saveNow(); } // BEGIN: component file format @@ -57,151 +59,151 @@ static const int currentComponentsFileVersion = 1; static QJsonObject componentToJsonV1(ComponentPtr component) { - QJsonObject obj; - // critical - obj.insert("uid", component->m_uid); - if(!component->m_version.isEmpty()) - { - obj.insert("version", component->m_version); - } - if(component->m_dependencyOnly) - { - obj.insert("dependencyOnly", true); - } - if(component->m_important) - { - obj.insert("important", true); - } - if(component->m_disabled) - { - obj.insert("disabled", true); - } - - // cached - if(!component->m_cachedVersion.isEmpty()) - { - obj.insert("cachedVersion", component->m_cachedVersion); - } - if(!component->m_cachedName.isEmpty()) - { - obj.insert("cachedName", component->m_cachedName); - } - Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires"); - Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); - if(component->m_cachedVolatile) - { - obj.insert("cachedVolatile", true); - } - return obj; + QJsonObject obj; + // critical + obj.insert("uid", component->m_uid); + if(!component->m_version.isEmpty()) + { + obj.insert("version", component->m_version); + } + if(component->m_dependencyOnly) + { + obj.insert("dependencyOnly", true); + } + if(component->m_important) + { + obj.insert("important", true); + } + if(component->m_disabled) + { + obj.insert("disabled", true); + } + + // cached + if(!component->m_cachedVersion.isEmpty()) + { + obj.insert("cachedVersion", component->m_cachedVersion); + } + if(!component->m_cachedName.isEmpty()) + { + obj.insert("cachedName", component->m_cachedName); + } + Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires"); + Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); + if(component->m_cachedVolatile) + { + obj.insert("cachedVolatile", true); + } + return obj; } static ComponentPtr componentFromJsonV1(ComponentList * parent, const QString & componentJsonPattern, const QJsonObject &obj) { - // critical - auto uid = Json::requireString(obj.value("uid")); - auto filePath = componentJsonPattern.arg(uid); - auto component = new Component(parent, uid); - component->m_version = Json::ensureString(obj.value("version")); - component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); - component->m_important = Json::ensureBoolean(obj.value("important"), false); - - // cached - // TODO @RESILIENCE: ignore invalid values/structure here? - component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion")); - component->m_cachedName = Json::ensureString(obj.value("cachedName")); - Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires"); - Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); - component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false); - bool disabled = Json::ensureBoolean(obj.value("disabled"), false); - component->setEnabled(!disabled); - return component; + // critical + auto uid = Json::requireString(obj.value("uid")); + auto filePath = componentJsonPattern.arg(uid); + auto component = new Component(parent, uid); + component->m_version = Json::ensureString(obj.value("version")); + component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); + component->m_important = Json::ensureBoolean(obj.value("important"), false); + + // cached + // TODO @RESILIENCE: ignore invalid values/structure here? + component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion")); + component->m_cachedName = Json::ensureString(obj.value("cachedName")); + Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires"); + Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); + component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false); + bool disabled = Json::ensureBoolean(obj.value("disabled"), false); + component->setEnabled(!disabled); + return component; } // Save the given component container data to a file static bool saveComponentList(const QString & filename, const ComponentContainer & container) { - QJsonObject obj; - obj.insert("formatVersion", currentComponentsFileVersion); - QJsonArray orderArray; - for(auto component: container) - { - orderArray.append(componentToJsonV1(component)); - } - obj.insert("components", orderArray); - QSaveFile outFile(filename); - if (!outFile.open(QFile::WriteOnly)) - { - qCritical() << "Couldn't open" << outFile.fileName() - << "for writing:" << outFile.errorString(); - return false; - } - auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); - if(outFile.write(data) != data.size()) - { - qCritical() << "Couldn't write all the data into" << outFile.fileName() - << "because:" << outFile.errorString(); - return false; - } - if(!outFile.commit()) - { - qCritical() << "Couldn't save" << outFile.fileName() - << "because:" << outFile.errorString(); - } - return true; + QJsonObject obj; + obj.insert("formatVersion", currentComponentsFileVersion); + QJsonArray orderArray; + for(auto component: container) + { + orderArray.append(componentToJsonV1(component)); + } + obj.insert("components", orderArray); + QSaveFile outFile(filename); + if (!outFile.open(QFile::WriteOnly)) + { + qCritical() << "Couldn't open" << outFile.fileName() + << "for writing:" << outFile.errorString(); + return false; + } + auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); + if(outFile.write(data) != data.size()) + { + qCritical() << "Couldn't write all the data into" << outFile.fileName() + << "because:" << outFile.errorString(); + return false; + } + if(!outFile.commit()) + { + qCritical() << "Couldn't save" << outFile.fileName() + << "because:" << outFile.errorString(); + } + return true; } // Read the given file into component containers static bool loadComponentList(ComponentList * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container) { - QFile componentsFile(filename); - if (!componentsFile.exists()) - { - qWarning() << "Components file doesn't exist. This should never happen."; - return false; - } - if (!componentsFile.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << componentsFile.fileName() - << " for reading:" << componentsFile.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and then read it and process it if all above is true. - try - { - auto obj = Json::requireObject(doc); - // check order file version. - auto version = Json::requireInteger(obj.value("formatVersion")); - if (version != currentComponentsFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid component file version, expected %1") - .arg(currentComponentsFileVersion)); - } - auto orderArray = Json::requireArray(obj.value("components")); - for(auto item: orderArray) - { - auto obj = Json::requireObject(item, "Component must be an object."); - container.append(componentFromJsonV1(parent, componentJsonPattern, obj)); - } - } - catch (JSONValidationError &err) - { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; - container.clear(); - return false; - } - return true; + QFile componentsFile(filename); + if (!componentsFile.exists()) + { + qWarning() << "Components file doesn't exist. This should never happen."; + return false; + } + if (!componentsFile.open(QFile::ReadOnly)) + { + qCritical() << "Couldn't open" << componentsFile.fileName() + << " for reading:" << componentsFile.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } + + // and then read it and process it if all above is true. + try + { + auto obj = Json::requireObject(doc); + // check order file version. + auto version = Json::requireInteger(obj.value("formatVersion")); + if (version != currentComponentsFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid component file version, expected %1") + .arg(currentComponentsFileVersion)); + } + auto orderArray = Json::requireArray(obj.value("components")); + for(auto item: orderArray) + { + auto obj = Json::requireObject(item, "Component must be an object."); + container.append(componentFromJsonV1(parent, componentJsonPattern, obj)); + } + } + catch (const JSONValidationError &err) + { + qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; + container.clear(); + return false; + } + return true; } // END: component file format @@ -210,217 +212,217 @@ static bool loadComponentList(ComponentList * parent, const QString & filename, void ComponentList::saveNow() { - if(saveIsScheduled()) - { - d->m_saveTimer.stop(); - save_internal(); - } + if(saveIsScheduled()) + { + d->m_saveTimer.stop(); + save_internal(); + } } bool ComponentList::saveIsScheduled() const { - return d->dirty; + return d->dirty; } void ComponentList::buildingFromScratch() { - d->loaded = true; - d->dirty = true; + d->loaded = true; + d->dirty = true; } void ComponentList::scheduleSave() { - if(!d->loaded) - { - qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name(); - return; - } - if(!d->dirty) - { - d->dirty = true; - qDebug() << "Component list save is scheduled for" << d->m_instance->name(); - } - d->m_saveTimer.start(); + if(!d->loaded) + { + qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name(); + return; + } + if(!d->dirty) + { + d->dirty = true; + qDebug() << "Component list save is scheduled for" << d->m_instance->name(); + } + d->m_saveTimer.start(); } QString ComponentList::componentsFilePath() const { - return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); + return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); } QString ComponentList::patchesPattern() const { - return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); + return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); } QString ComponentList::patchFilePathForUid(const QString& uid) const { - return patchesPattern().arg(uid); + return patchesPattern().arg(uid); } void ComponentList::save_internal() { - qDebug() << "Component list save performed now for" << d->m_instance->name(); - auto filename = componentsFilePath(); - saveComponentList(filename, d->components); - d->dirty = false; + qDebug() << "Component list save performed now for" << d->m_instance->name(); + auto filename = componentsFilePath(); + saveComponentList(filename, d->components); + d->dirty = false; } bool ComponentList::load() { - auto filename = componentsFilePath(); - QFile componentsFile(filename); - - // migrate old config to new one, if needed - if(!componentsFile.exists()) - { - if(!migratePreComponentConfig()) - { - // FIXME: the user should be notified... - qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name(); - return false; - } - } - - // load the new component list and swap it with the current one... - ComponentContainer newComponents; - if(!loadComponentList(this, filename, patchesPattern(), newComponents)) - { - qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); - return false; - } - else - { - // FIXME: actually use fine-grained updates, not this... - beginResetModel(); - // disconnect all the old components - for(auto component: d->components) - { - disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); - } - d->components.clear(); - d->componentIndex.clear(); - for(auto component: newComponents) - { - if(d->componentIndex.contains(component->m_uid)) - { - qWarning() << "Ignoring duplicate component entry" << component->m_uid; - continue; - } - connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); - d->components.append(component); - d->componentIndex[component->m_uid] = component; - } - endResetModel(); - d->loaded = true; - return true; - } + auto filename = componentsFilePath(); + QFile componentsFile(filename); + + // migrate old config to new one, if needed + if(!componentsFile.exists()) + { + if(!migratePreComponentConfig()) + { + // FIXME: the user should be notified... + qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name(); + return false; + } + } + + // load the new component list and swap it with the current one... + ComponentContainer newComponents; + if(!loadComponentList(this, filename, patchesPattern(), newComponents)) + { + qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); + return false; + } + else + { + // FIXME: actually use fine-grained updates, not this... + beginResetModel(); + // disconnect all the old components + for(auto component: d->components) + { + disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + } + d->components.clear(); + d->componentIndex.clear(); + for(auto component: newComponents) + { + if(d->componentIndex.contains(component->m_uid)) + { + qWarning() << "Ignoring duplicate component entry" << component->m_uid; + continue; + } + connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + d->components.append(component); + d->componentIndex[component->m_uid] = component; + } + endResetModel(); + d->loaded = true; + return true; + } } void ComponentList::reload(Net::Mode netmode) { - // Do not reload when the update/resolve task is running. It is in control. - if(d->m_updateTask) - { - return; - } - - // flush any scheduled saves to not lose state - saveNow(); - - // FIXME: differentiate when a reapply is required by propagating state from components - invalidateLaunchProfile(); - - if(load()) - { - resolve(netmode); - } + // Do not reload when the update/resolve task is running. It is in control. + if(d->m_updateTask) + { + return; + } + + // flush any scheduled saves to not lose state + saveNow(); + + // FIXME: differentiate when a reapply is required by propagating state from components + invalidateLaunchProfile(); + + if(load()) + { + resolve(netmode); + } } shared_qobject_ptr ComponentList::getCurrentTask() { - return d->m_updateTask; + return d->m_updateTask; } void ComponentList::resolve(Net::Mode netmode) { - auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); - d->m_updateTask.reset(updateTask); - connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded); - connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed); - d->m_updateTask->start(); + auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); + d->m_updateTask.reset(updateTask); + connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded); + connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed); + d->m_updateTask->start(); } void ComponentList::updateSucceeded() { - qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); - d->m_updateTask.reset(); - invalidateLaunchProfile(); + qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); + d->m_updateTask.reset(); + invalidateLaunchProfile(); } void ComponentList::updateFailed(const QString& error) { - qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error; - d->m_updateTask.reset(); - invalidateLaunchProfile(); + qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error; + d->m_updateTask.reset(); + invalidateLaunchProfile(); } // NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig). static void upgradeDeprecatedFiles(QString root, QString instanceName) { - auto versionJsonPath = FS::PathCombine(root, "version.json"); - auto customJsonPath = FS::PathCombine(root, "custom.json"); - auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json"); - - QString sourceFile; - QString renameFile; - - // convert old crap. - if(QFile::exists(customJsonPath)) - { - sourceFile = customJsonPath; - renameFile = versionJsonPath; - } - else if(QFile::exists(versionJsonPath)) - { - sourceFile = versionJsonPath; - } - if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) - { - if(!FS::ensureFilePathExists(mcJson)) - { - qWarning() << "Couldn't create patches folder for" << instanceName; - return; - } - if(!renameFile.isEmpty() && QFile::exists(renameFile)) - { - if(!QFile::rename(renameFile, renameFile + ".old")) - { - qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName; - return; - } - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); - ProfileUtils::removeLwjglFromPatch(file); - file->uid = "net.minecraft"; - file->version = file->minecraftVersion; - file->name = "Minecraft"; - - Meta::Require needsLwjgl; - needsLwjgl.uid = "org.lwjgl"; - file->requires.insert(needsLwjgl); - - if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson)) - { - return; - } - if(!QFile::rename(sourceFile, sourceFile + ".old")) - { - qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName; - return; - } - } + auto versionJsonPath = FS::PathCombine(root, "version.json"); + auto customJsonPath = FS::PathCombine(root, "custom.json"); + auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json"); + + QString sourceFile; + QString renameFile; + + // convert old crap. + if(QFile::exists(customJsonPath)) + { + sourceFile = customJsonPath; + renameFile = versionJsonPath; + } + else if(QFile::exists(versionJsonPath)) + { + sourceFile = versionJsonPath; + } + if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) + { + if(!FS::ensureFilePathExists(mcJson)) + { + qWarning() << "Couldn't create patches folder for" << instanceName; + return; + } + if(!renameFile.isEmpty() && QFile::exists(renameFile)) + { + if(!QFile::rename(renameFile, renameFile + ".old")) + { + qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName; + return; + } + } + auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); + ProfileUtils::removeLwjglFromPatch(file); + file->uid = "net.minecraft"; + file->version = file->minecraftVersion; + file->name = "Minecraft"; + + Meta::Require needsLwjgl; + needsLwjgl.uid = "org.lwjgl"; + file->requires.insert(needsLwjgl); + + if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson)) + { + return; + } + if(!QFile::rename(sourceFile, sourceFile + ".old")) + { + qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName; + return; + } + } } /* @@ -431,774 +433,793 @@ static void upgradeDeprecatedFiles(QString root, QString instanceName) */ bool ComponentList::migratePreComponentConfig() { - // upgrade the very old files from the beginnings of MultiMC 5 - upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name()); - - QList components; - QSet loaded; - - auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict) - { - auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json"); - auto intendedVersion = d->getOldConfigVersion(uid); - // load up the base minecraft patch - ComponentPtr component; - if(QFile::exists(jsonFilePath)) - { - if(intendedVersion.isEmpty()) - { - intendedVersion = emptyVersion; - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); - // fix uid - file->uid = uid; - // if version is missing, add it from the outside. - if(file->version.isEmpty()) - { - file->version = intendedVersion; - } - // if this is a dependency (LWJGL), mark it also as volatile - if(asDependency) - { - file->m_volatile = true; - } - // insert requirements if needed - if(!req.uid.isEmpty()) - { - file->requires.insert(req); - } - // insert conflicts if needed - if(!conflict.uid.isEmpty()) - { - file->conflicts.insert(conflict); - } - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath); - component = new Component(this, uid, file); - component->m_version = intendedVersion; - } - else if(!intendedVersion.isEmpty()) - { - auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); - component = new Component(this, metaVersion); - } - else - { - return; - } - component->m_dependencyOnly = asDependency; - component->m_important = !asDependency; - components.append(component); - }; - // TODO: insert depends and conflicts here if these are customized files... - Meta::Require reqLwjgl; - reqLwjgl.uid = "org.lwjgl"; - reqLwjgl.suggests = "2.9.1"; - Meta::Require conflictLwjgl3; - conflictLwjgl3.uid = "org.lwjgl3"; - Meta::Require nullReq; - addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3); - addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq); - - // first, collect all other file-based patches and load them - QMap loadedComponents; - QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches")); - for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - // parse the file - qDebug() << "Reading" << info.fileName(); - auto file = ProfileUtils::parseJsonFile(info, true); - - // correct missing or wrong uid based on the file name - QString uid = info.completeBaseName(); - - // ignore builtins, they've been handled already - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - - // handle horrible corner cases - if(uid.isEmpty()) - { - // if you have a file named '.json', make it just go away. - // FIXME: @QUALITY do not ignore return value - QFile::remove(info.absoluteFilePath()); - continue; - } - file->uid = uid; - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath()); - - auto component = new Component(this, file->uid, file); - auto version = d->getOldConfigVersion(file->uid); - if(!version.isEmpty()) - { - component->m_version = version; - } - loadedComponents[file->uid] = component; - } - // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files - auto loadSpecial = [&](const QString & uid, int order) - { - auto patchVersion = d->getOldConfigVersion(uid); - if(!patchVersion.isEmpty() && !loadedComponents.contains(uid)) - { - auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion)); - patch->setOrder(order); - loadedComponents[uid] = patch; - } - }; - loadSpecial("net.minecraftforge", 5); - loadSpecial("com.mumfrey.liteloader", 10); - - // load the old order.json file, if present - ProfileUtils::PatchOrder userOrder; - ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder); - - // now add all the patches by user sort order - for (auto uid : userOrder) - { - // ignore builtins - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - // ordering has a patch that is gone? - if(!loadedComponents.contains(uid)) - { - continue; - } - components.append(loadedComponents.take(uid)); - } - - // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json - if(!loadedComponents.isEmpty()) - { - // inserting into multimap by order number as key sorts the patches and detects duplicates - QMultiMap files; - auto iter = loadedComponents.begin(); - while(iter != loadedComponents.end()) - { - files.insert((*iter)->getOrder(), *iter); - iter++; - } - - // then just extract the patches and put them in the list - for (auto order : files.keys()) - { - const auto &values = files.values(order); - for(auto &value: values) - { - // TODO: put back the insertion of problem messages here, so the user knows about the id duplication - components.append(value); - } - } - } - // new we have a complete list of components... - return saveComponentList(componentsFilePath(), components); + // upgrade the very old files from the beginnings of MultiMC 5 + upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name()); + + QList components; + QSet loaded; + + auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict) + { + auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json"); + auto intendedVersion = d->getOldConfigVersion(uid); + // load up the base minecraft patch + ComponentPtr component; + if(QFile::exists(jsonFilePath)) + { + if(intendedVersion.isEmpty()) + { + intendedVersion = emptyVersion; + } + auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); + // fix uid + file->uid = uid; + // if version is missing, add it from the outside. + if(file->version.isEmpty()) + { + file->version = intendedVersion; + } + // if this is a dependency (LWJGL), mark it also as volatile + if(asDependency) + { + file->m_volatile = true; + } + // insert requirements if needed + if(!req.uid.isEmpty()) + { + file->requires.insert(req); + } + // insert conflicts if needed + if(!conflict.uid.isEmpty()) + { + file->conflicts.insert(conflict); + } + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath); + component = new Component(this, uid, file); + component->m_version = intendedVersion; + } + else if(!intendedVersion.isEmpty()) + { + auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); + component = new Component(this, metaVersion); + } + else + { + return; + } + component->m_dependencyOnly = asDependency; + component->m_important = !asDependency; + components.append(component); + }; + // TODO: insert depends and conflicts here if these are customized files... + Meta::Require reqLwjgl; + reqLwjgl.uid = "org.lwjgl"; + reqLwjgl.suggests = "2.9.1"; + Meta::Require conflictLwjgl3; + conflictLwjgl3.uid = "org.lwjgl3"; + Meta::Require nullReq; + addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3); + addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq); + + // first, collect all other file-based patches and load them + QMap loadedComponents; + QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches")); + for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + // parse the file + qDebug() << "Reading" << info.fileName(); + auto file = ProfileUtils::parseJsonFile(info, true); + + // correct missing or wrong uid based on the file name + QString uid = info.completeBaseName(); + + // ignore builtins, they've been handled already + if (uid == "net.minecraft") + continue; + if (uid == "org.lwjgl") + continue; + + // handle horrible corner cases + if(uid.isEmpty()) + { + // if you have a file named '.json', make it just go away. + // FIXME: @QUALITY do not ignore return value + QFile::remove(info.absoluteFilePath()); + continue; + } + file->uid = uid; + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath()); + + auto component = new Component(this, file->uid, file); + auto version = d->getOldConfigVersion(file->uid); + if(!version.isEmpty()) + { + component->m_version = version; + } + loadedComponents[file->uid] = component; + } + // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files + auto loadSpecial = [&](const QString & uid, int order) + { + auto patchVersion = d->getOldConfigVersion(uid); + if(!patchVersion.isEmpty() && !loadedComponents.contains(uid)) + { + auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion)); + patch->setOrder(order); + loadedComponents[uid] = patch; + } + }; + loadSpecial("net.minecraftforge", 5); + loadSpecial("com.mumfrey.liteloader", 10); + + // load the old order.json file, if present + ProfileUtils::PatchOrder userOrder; + ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder); + + // now add all the patches by user sort order + for (auto uid : userOrder) + { + // ignore builtins + if (uid == "net.minecraft") + continue; + if (uid == "org.lwjgl") + continue; + // ordering has a patch that is gone? + if(!loadedComponents.contains(uid)) + { + continue; + } + components.append(loadedComponents.take(uid)); + } + + // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json + if(!loadedComponents.isEmpty()) + { + // inserting into multimap by order number as key sorts the patches and detects duplicates + QMultiMap files; + auto iter = loadedComponents.begin(); + while(iter != loadedComponents.end()) + { + files.insert((*iter)->getOrder(), *iter); + iter++; + } + + // then just extract the patches and put them in the list + for (auto order : files.keys()) + { + const auto &values = files.values(order); + for(auto &value: values) + { + // TODO: put back the insertion of problem messages here, so the user knows about the id duplication + components.append(value); + } + } + } + // new we have a complete list of components... + return saveComponentList(componentsFilePath(), components); } // END: save/load void ComponentList::appendComponent(ComponentPtr component) { - insertComponent(d->components.size(), component); + insertComponent(d->components.size(), component); } void ComponentList::insertComponent(size_t index, ComponentPtr component) { - auto id = component->getID(); - if(id.isEmpty()) - { - qWarning() << "Attempt to add a component with empty ID!"; - return; - } - if(d->componentIndex.contains(id)) - { - qWarning() << "Attempt to add a component that is already present!"; - return; - } - beginInsertRows(QModelIndex(), index, index); - d->components.insert(index, component); - d->componentIndex[id] = component; - endInsertRows(); - connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); - scheduleSave(); + auto id = component->getID(); + if(id.isEmpty()) + { + qWarning() << "Attempt to add a component with empty ID!"; + return; + } + if(d->componentIndex.contains(id)) + { + qWarning() << "Attempt to add a component that is already present!"; + return; + } + beginInsertRows(QModelIndex(), index, index); + d->components.insert(index, component); + d->componentIndex[id] = component; + endInsertRows(); + connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + scheduleSave(); } void ComponentList::componentDataChanged() { - auto objPtr = qobject_cast(sender()); - if(!objPtr) - { - qWarning() << "ComponentList got dataChenged signal from a non-Component!"; - return; - } - // figure out which one is it... in a seriously dumb way. - int index = 0; - for (auto component: d->components) - { - if(component.get() == objPtr) - { - emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); - scheduleSave(); - return; - } - index++; - } - qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!"; + auto objPtr = qobject_cast(sender()); + if(!objPtr) + { + qWarning() << "ComponentList got dataChenged signal from a non-Component!"; + return; + } + if(objPtr->getID() == "net.minecraft") { + emit minecraftChanged(); + } + // figure out which one is it... in a seriously dumb way. + int index = 0; + for (auto component: d->components) + { + if(component.get() == objPtr) + { + emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); + scheduleSave(); + return; + } + index++; + } + qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!"; } bool ComponentList::remove(const int index) { - auto patch = getComponent(index); - if (!patch->isRemovable()) - { - qWarning() << "Patch" << patch->getID() << "is non-removable"; - return false; - } - - if(!removeComponent_internal(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be removed"; - return false; - } - - beginRemoveRows(QModelIndex(), index, index); - d->components.removeAt(index); - d->componentIndex.remove(patch->getID()); - endRemoveRows(); - invalidateLaunchProfile(); - scheduleSave(); - return true; + auto patch = getComponent(index); + if (!patch->isRemovable()) + { + qWarning() << "Patch" << patch->getID() << "is non-removable"; + return false; + } + + if(!removeComponent_internal(patch)) + { + qCritical() << "Patch" << patch->getID() << "could not be removed"; + return false; + } + + beginRemoveRows(QModelIndex(), index, index); + d->components.removeAt(index); + d->componentIndex.remove(patch->getID()); + endRemoveRows(); + invalidateLaunchProfile(); + scheduleSave(); + return true; } bool ComponentList::remove(const QString id) { - int i = 0; - for (auto patch : d->components) - { - if (patch->getID() == id) - { - return remove(i); - } - i++; - } - return false; + int i = 0; + for (auto patch : d->components) + { + if (patch->getID() == id) + { + return remove(i); + } + i++; + } + return false; } bool ComponentList::customize(int index) { - auto patch = getComponent(index); - if (!patch->isCustomizable()) - { - qDebug() << "Patch" << patch->getID() << "is not customizable"; - return false; - } - if(!patch->customize()) - { - qCritical() << "Patch" << patch->getID() << "could not be customized"; - return false; - } - invalidateLaunchProfile(); - scheduleSave(); - return true; + auto patch = getComponent(index); + if (!patch->isCustomizable()) + { + qDebug() << "Patch" << patch->getID() << "is not customizable"; + return false; + } + if(!patch->customize()) + { + qCritical() << "Patch" << patch->getID() << "could not be customized"; + return false; + } + invalidateLaunchProfile(); + scheduleSave(); + return true; } bool ComponentList::revertToBase(int index) { - auto patch = getComponent(index); - if (!patch->isRevertible()) - { - qDebug() << "Patch" << patch->getID() << "is not revertible"; - return false; - } - if(!patch->revert()) - { - qCritical() << "Patch" << patch->getID() << "could not be reverted"; - return false; - } - invalidateLaunchProfile(); - scheduleSave(); - return true; + auto patch = getComponent(index); + if (!patch->isRevertible()) + { + qDebug() << "Patch" << patch->getID() << "is not revertible"; + return false; + } + if(!patch->revert()) + { + qCritical() << "Patch" << patch->getID() << "could not be reverted"; + return false; + } + invalidateLaunchProfile(); + scheduleSave(); + return true; } Component * ComponentList::getComponent(const QString &id) { - auto iter = d->componentIndex.find(id); - if (iter == d->componentIndex.end()) - { - return nullptr; - } - return (*iter).get(); + auto iter = d->componentIndex.find(id); + if (iter == d->componentIndex.end()) + { + return nullptr; + } + return (*iter).get(); } Component * ComponentList::getComponent(int index) { - if(index < 0 || index >= d->components.size()) - { - return nullptr; - } - return d->components[index].get(); + if(index < 0 || index >= d->components.size()) + { + return nullptr; + } + return d->components[index].get(); } QVariant ComponentList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= d->components.size()) - return QVariant(); - - auto patch = d->components.at(row); - - switch (role) - { - case Qt::CheckStateRole: - { - switch (column) - { - case NameColumn: - return d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - } - case Qt::DisplayRole: - { - switch (column) - { - case NameColumn: - return d->components.at(row)->getName(); - case VersionColumn: - { - if(patch->isCustom()) - { - return QString("%1 (Custom)").arg(patch->getVersion()); - } - else - { - return patch->getVersion(); - } - } - default: - return QVariant(); - } - } - case Qt::DecorationRole: - { - switch(column) - { - case NameColumn: - { - auto severity = patch->getProblemSeverity(); - switch (severity) - { - case ProblemSeverity::Warning: - return "warning"; - case ProblemSeverity::Error: - return "error"; - default: - return QVariant(); - } - } - default: - { - return QVariant(); - } - } - } - } - return QVariant(); + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= d->components.size()) + return QVariant(); + + auto patch = d->components.at(row); + + switch (role) + { + case Qt::CheckStateRole: + { + switch (column) + { + case NameColumn: { + return patch->isEnabled() ? Qt::Checked : Qt::Unchecked; + } + default: + return QVariant(); + } + } + case Qt::DisplayRole: + { + switch (column) + { + case NameColumn: + return patch->getName(); + case VersionColumn: + { + if(patch->isCustom()) + { + return QString("%1 (Custom)").arg(patch->getVersion()); + } + else + { + return patch->getVersion(); + } + } + default: + return QVariant(); + } + } + case Qt::DecorationRole: + { + switch(column) + { + case NameColumn: + { + auto severity = patch->getProblemSeverity(); + switch (severity) + { + case ProblemSeverity::Warning: + return "warning"; + case ProblemSeverity::Error: + return "error"; + default: + return QVariant(); + } + } + default: + { + return QVariant(); + } + } + } + } + return QVariant(); } bool ComponentList::setData(const QModelIndex& index, const QVariant& value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - auto component = d->components[index.row()]; - if (component->setEnabled(!component->isEnabled())) - { - return true; - } - } - return false; + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) + { + return false; + } + + if (role == Qt::CheckStateRole) + { + auto component = d->components[index.row()]; + if (component->setEnabled(!component->isEnabled())) + { + return true; + } + } + return false; } QVariant ComponentList::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case NameColumn: + return tr("Name"); + case VersionColumn: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); } + +// FIXME: zero precision mess Qt::ItemFlags ComponentList::flags(const QModelIndex &index) const { - if (!index.isValid()) - return Qt::NoItemFlags; + if (!index.isValid()) { + return Qt::NoItemFlags; + } - Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - int row = index.row(); + int row = index.row(); - if (row < 0 || row >= d->components.size()) - return Qt::NoItemFlags; + if (row < 0 || row >= d->components.size()) { + return Qt::NoItemFlags; + } - auto patch = d->components.at(row); - // TODO: this will need fine-tuning later... - if(patch->canBeDisabled()) - { - outFlags |= Qt::ItemIsUserCheckable; - } - return outFlags; + auto patch = d->components.at(row); + // TODO: this will need fine-tuning later... + if(patch->canBeDisabled() && !d->interactionDisabled) + { + outFlags |= Qt::ItemIsUserCheckable; + } + return outFlags; } int ComponentList::rowCount(const QModelIndex &parent) const { - return d->components.size(); + return d->components.size(); } int ComponentList::columnCount(const QModelIndex &parent) const { - return NUM_COLUMNS; + return NUM_COLUMNS; } void ComponentList::move(const int index, const MoveDirection direction) { - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - - if (index < 0 || index >= d->components.size()) - return; - if (theirIndex >= rowCount()) - theirIndex = rowCount() - 1; - if (theirIndex == -1) - theirIndex = rowCount() - 1; - if (index == theirIndex) - return; - int togap = theirIndex > index ? theirIndex + 1 : theirIndex; - - auto from = getComponent(index); - auto to = getComponent(theirIndex); - - if (!from || !to || !to->isMoveable() || !from->isMoveable()) - { - return; - } - beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); - d->components.swap(index, theirIndex); - endMoveRows(); - invalidateLaunchProfile(); - scheduleSave(); + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + + if (index < 0 || index >= d->components.size()) + return; + if (theirIndex >= rowCount()) + theirIndex = rowCount() - 1; + if (theirIndex == -1) + theirIndex = rowCount() - 1; + if (index == theirIndex) + return; + int togap = theirIndex > index ? theirIndex + 1 : theirIndex; + + auto from = getComponent(index); + auto to = getComponent(theirIndex); + + if (!from || !to || !to->isMoveable() || !from->isMoveable()) + { + return; + } + beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); + d->components.swap(index, theirIndex); + endMoveRows(); + invalidateLaunchProfile(); + scheduleSave(); } void ComponentList::invalidateLaunchProfile() { - d->m_profile.reset(); + d->m_profile.reset(); } void ComponentList::installJarMods(QStringList selectedFiles) { - installJarMods_internal(selectedFiles); + installJarMods_internal(selectedFiles); } void ComponentList::installCustomJar(QString selectedFile) { - installCustomJar_internal(selectedFile); + installCustomJar_internal(selectedFile); } bool ComponentList::installEmpty(const QString& uid, const QString& name) { - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - auto f = std::make_shared(); - f->name = name; - f->uid = uid; - f->version = "1"; - QString patchFileName = FS::PathCombine(patchDir, uid + ".json"); - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - scheduleSave(); - invalidateLaunchProfile(); - return true; + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + auto f = std::make_shared(); + f->name = name; + f->uid = uid; + f->version = "1"; + QString patchFileName = FS::PathCombine(patchDir, uid + ".json"); + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + scheduleSave(); + invalidateLaunchProfile(); + return true; } bool ComponentList::removeComponent_internal(ComponentPtr patch) { - bool ok = true; - // first, remove the patch file. this ensures it's not used anymore - auto fileName = patch->getFilename(); - if(fileName.size()) - { - QFile patchFile(fileName); - if(patchFile.exists() && !patchFile.remove()) - { - qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); - return false; - } - } - - // FIXME: we need a generic way of removing local resources, not just jar mods... - auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool - { - if (!jarMod->isLocal()) - { - return true; - } - QStringList jar, temp1, temp2, temp3; - jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); - QFileInfo finfo (jar[0]); - if(finfo.exists()) - { - QFile jarModFile(jar[0]); - if(!jarModFile.remove()) - { - qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); - return false; - } - return true; - } - return true; - }; - - auto vFile = patch->getVersionFile(); - if(vFile) - { - auto &jarMods = vFile->jarMods; - for(auto &jarmod: jarMods) - { - ok &= preRemoveJarMod(jarmod); - } - } - return ok; + bool ok = true; + // first, remove the patch file. this ensures it's not used anymore + auto fileName = patch->getFilename(); + if(fileName.size()) + { + QFile patchFile(fileName); + if(patchFile.exists() && !patchFile.remove()) + { + qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); + return false; + } + } + + // FIXME: we need a generic way of removing local resources, not just jar mods... + auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool + { + if (!jarMod->isLocal()) + { + return true; + } + QStringList jar, temp1, temp2, temp3; + jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); + QFileInfo finfo (jar[0]); + if(finfo.exists()) + { + QFile jarModFile(jar[0]); + if(!jarModFile.remove()) + { + qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); + return false; + } + return true; + } + return true; + }; + + auto vFile = patch->getVersionFile(); + if(vFile) + { + auto &jarMods = vFile->jarMods; + for(auto &jarmod: jarMods) + { + ok &= preRemoveJarMod(jarmod); + } + } + return ok; } bool ComponentList::installJarMods_internal(QStringList filepaths) { - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir())) - { - return false; - } - - for(auto filepath:filepaths) - { - QFileInfo sourceInfo(filepath); - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - QString target_filename = id + ".jar"; - QString target_id = "org.multimc.jarmod." + id; - QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; - QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); - - QFileInfo targetInfo(finalPath); - if(targetInfo.exists()) - { - return false; - } - - if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) - { - return false; - } - - auto f = std::make_shared(); - auto jarMod = std::make_shared(); - jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - jarMod->setFilename(target_filename); - jarMod->setDisplayName(sourceInfo.completeBaseName()); - jarMod->setHint("local"); - f->jarMods.append(jarMod); - f->name = target_name; - f->uid = target_id; - QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - } - scheduleSave(); - invalidateLaunchProfile(); - return true; + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + + if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir())) + { + return false; + } + + for(auto filepath:filepaths) + { + QFileInfo sourceInfo(filepath); + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); + QString target_filename = id + ".jar"; + QString target_id = "org.multimc.jarmod." + id; + QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; + QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); + + QFileInfo targetInfo(finalPath); + if(targetInfo.exists()) + { + return false; + } + + if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) + { + return false; + } + + auto f = std::make_shared(); + auto jarMod = std::make_shared(); + jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); + jarMod->setFilename(target_filename); + jarMod->setDisplayName(sourceInfo.completeBaseName()); + jarMod->setHint("local"); + f->jarMods.append(jarMod); + f->name = target_name; + f->uid = target_id; + QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + } + scheduleSave(); + invalidateLaunchProfile(); + return true; } bool ComponentList::installCustomJar_internal(QString filepath) { - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - QString libDir = d->m_instance->getLocalLibraryPath(); - if (!FS::ensureFolderPathExists(libDir)) - { - return false; - } - - auto specifier = GradleSpecifier("org.multimc:customjar:1"); - QFileInfo sourceInfo(filepath); - QString target_filename = specifier.getFileName(); - QString target_id = specifier.artifactId(); - QString target_name = sourceInfo.completeBaseName() + " (custom jar)"; - QString finalPath = FS::PathCombine(libDir, target_filename); - - QFileInfo jarInfo(finalPath); - if (jarInfo.exists()) - { - if(!QFile::remove(finalPath)) - { - return false; - } - } - if (!QFile::copy(filepath, finalPath)) - { - return false; - } - - auto f = std::make_shared(); - auto jarMod = std::make_shared(); - jarMod->setRawName(specifier); - jarMod->setDisplayName(sourceInfo.completeBaseName()); - jarMod->setHint("local"); - f->mainJar = jarMod; - f->name = target_name; - f->uid = target_id; - QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - - scheduleSave(); - invalidateLaunchProfile(); - return true; + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + + QString libDir = d->m_instance->getLocalLibraryPath(); + if (!FS::ensureFolderPathExists(libDir)) + { + return false; + } + + auto specifier = GradleSpecifier("org.multimc:customjar:1"); + QFileInfo sourceInfo(filepath); + QString target_filename = specifier.getFileName(); + QString target_id = specifier.artifactId(); + QString target_name = sourceInfo.completeBaseName() + " (custom jar)"; + QString finalPath = FS::PathCombine(libDir, target_filename); + + QFileInfo jarInfo(finalPath); + if (jarInfo.exists()) + { + if(!QFile::remove(finalPath)) + { + return false; + } + } + if (!QFile::copy(filepath, finalPath)) + { + return false; + } + + auto f = std::make_shared(); + auto jarMod = std::make_shared(); + jarMod->setRawName(specifier); + jarMod->setDisplayName(sourceInfo.completeBaseName()); + jarMod->setHint("local"); + f->mainJar = jarMod; + f->name = target_name; + f->uid = target_id; + QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + + scheduleSave(); + invalidateLaunchProfile(); + return true; } std::shared_ptr ComponentList::getProfile() const { - if(!d->m_profile) - { - try - { - auto profile = std::make_shared(); - for(auto file: d->components) - { - qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); - file->applyTo(profile.get()); - } - d->m_profile = profile; - } - catch (Exception & error) - { - qWarning() << "Couldn't apply profile patches because: " << error.cause(); - } - } - return d->m_profile; + if(!d->m_profile) + { + try + { + auto profile = std::make_shared(); + for(auto file: d->components) + { + qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); + file->applyTo(profile.get()); + } + d->m_profile = profile; + } + catch (const Exception &error) + { + qWarning() << "Couldn't apply profile patches because: " << error.cause(); + } + } + return d->m_profile; } void ComponentList::setOldConfigVersion(const QString& uid, const QString& version) { - if(version.isEmpty()) - { - return; - } - d->m_oldConfigVersions[uid] = version; + if(version.isEmpty()) + { + return; + } + d->m_oldConfigVersions[uid] = version; } bool ComponentList::setComponentVersion(const QString& uid, const QString& version, bool important) { - auto iter = d->componentIndex.find(uid); - if(iter != d->componentIndex.end()) - { - ComponentPtr component = *iter; - // set existing - if(component->revert()) - { - component->setVersion(version); - component->setImportant(important); - return true; - } - return false; - } - else - { - // add new - auto component = new Component(this, uid); - component->m_version = version; - component->m_important = important; - appendComponent(component); - return true; - } + auto iter = d->componentIndex.find(uid); + if(iter != d->componentIndex.end()) + { + ComponentPtr component = *iter; + // set existing + if(component->revert()) + { + component->setVersion(version); + component->setImportant(important); + return true; + } + return false; + } + else + { + // add new + auto component = new Component(this, uid); + component->m_version = version; + component->m_important = important; + appendComponent(component); + return true; + } } QString ComponentList::getComponentVersion(const QString& uid) const { - const auto iter = d->componentIndex.find(uid); - if (iter != d->componentIndex.end()) - { - return (*iter)->getVersion(); - } - return QString(); + const auto iter = d->componentIndex.find(uid); + if (iter != d->componentIndex.end()) + { + return (*iter)->getVersion(); + } + return QString(); +} + +void ComponentList::disableInteraction(bool disable) +{ + if(d->interactionDisabled != disable) { + d->interactionDisabled = disable; + auto size = d->components.size(); + if(size) { + emit dataChanged(index(0), index(size - 1)); + } + } } diff --git a/api/logic/minecraft/ComponentList.h b/api/logic/minecraft/ComponentList.h index cf4d9975..7b5e1385 100644 --- a/api/logic/minecraft/ComponentList.h +++ b/api/logic/minecraft/ComponentList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,111 +36,115 @@ class ComponentUpdateTask; class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel { - Q_OBJECT - friend ComponentUpdateTask; + Q_OBJECT + friend ComponentUpdateTask; public: - enum Columns - { - NameColumn = 0, - VersionColumn, - NUM_COLUMNS - }; + enum Columns + { + NameColumn = 0, + VersionColumn, + NUM_COLUMNS + }; - explicit ComponentList(MinecraftInstance * instance); - virtual ~ComponentList(); + explicit ComponentList(MinecraftInstance * instance); + virtual ~ComponentList(); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int columnCount(const QModelIndex &parent) const override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch. - void buildingFromScratch(); + /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch. + void buildingFromScratch(); - /// install more jar mods - void installJarMods(QStringList selectedFiles); + /// install more jar mods + void installJarMods(QStringList selectedFiles); - /// install a jar/zip as a replacement for the main jar - void installCustomJar(QString selectedFile); + /// install a jar/zip as a replacement for the main jar + void installCustomJar(QString selectedFile); - enum MoveDirection { MoveUp, MoveDown }; - /// move component file # up or down the list - void move(const int index, const MoveDirection direction); + enum MoveDirection { MoveUp, MoveDown }; + /// move component file # up or down the list + void move(const int index, const MoveDirection direction); - /// remove component file # - including files/records - bool remove(const int index); + /// remove component file # - including files/records + bool remove(const int index); - /// remove component file by id - including files/records - bool remove(const QString id); + /// remove component file by id - including files/records + bool remove(const QString id); - bool customize(int index); + bool customize(int index); - bool revertToBase(int index); + bool revertToBase(int index); - /// reload the list, reload all components, resolve dependencies - void reload(Net::Mode netmode); + /// reload the list, reload all components, resolve dependencies + void reload(Net::Mode netmode); - // reload all components, resolve dependencies - void resolve(Net::Mode netmode); + // reload all components, resolve dependencies + void resolve(Net::Mode netmode); - /// get current running task... - shared_qobject_ptr getCurrentTask(); + /// get current running task... + shared_qobject_ptr getCurrentTask(); - std::shared_ptr getProfile() const; + std::shared_ptr getProfile() const; - // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config - void setOldConfigVersion(const QString &uid, const QString &version); + // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config + void setOldConfigVersion(const QString &uid, const QString &version); - QString getComponentVersion(const QString &uid) const; + QString getComponentVersion(const QString &uid) const; - bool setComponentVersion(const QString &uid, const QString &version, bool important = false); + bool setComponentVersion(const QString &uid, const QString &version, bool important = false); - bool installEmpty(const QString &uid, const QString &name); + bool installEmpty(const QString &uid, const QString &name); - QString patchFilePathForUid(const QString &uid) const; + QString patchFilePathForUid(const QString &uid) const; - /// if there is a save scheduled, do it now. - void saveNow(); + /// if there is a save scheduled, do it now. + void saveNow(); + +signals: + void minecraftChanged(); public: - /// get the profile component by id - Component * getComponent(const QString &id); + /// get the profile component by id + Component * getComponent(const QString &id); - /// get the profile component by index - Component * getComponent(int index); + /// get the profile component by index + Component * getComponent(int index); private: - void scheduleSave(); - bool saveIsScheduled() const; + void scheduleSave(); + bool saveIsScheduled() const; - /// apply the component patches. Catches all the errors and returns true/false for success/failure - void invalidateLaunchProfile(); + /// apply the component patches. Catches all the errors and returns true/false for success/failure + void invalidateLaunchProfile(); - /// Add the component to the internal list of patches - void appendComponent(ComponentPtr component); - /// insert component so that its index is ideally the specified one (returns real index) - void insertComponent(size_t index, ComponentPtr component); + /// Add the component to the internal list of patches + void appendComponent(ComponentPtr component); + /// insert component so that its index is ideally the specified one (returns real index) + void insertComponent(size_t index, ComponentPtr component); - QString componentsFilePath() const; - QString patchesPattern() const; + QString componentsFilePath() const; + QString patchesPattern() const; private slots: - void save_internal(); - void updateSucceeded(); - void updateFailed(const QString & error); - void componentDataChanged(); + void save_internal(); + void updateSucceeded(); + void updateFailed(const QString & error); + void componentDataChanged(); + void disableInteraction(bool disable); private: - bool load(); - bool installJarMods_internal(QStringList filepaths); + bool load(); + bool installJarMods_internal(QStringList filepaths); bool installCustomJar_internal(QString filepath); - bool removeComponent_internal(ComponentPtr patch); + bool removeComponent_internal(ComponentPtr patch); - bool migratePreComponentConfig(); + bool migratePreComponentConfig(); private: /* data */ - std::unique_ptr d; + std::unique_ptr d; }; diff --git a/api/logic/minecraft/ComponentList_p.h b/api/logic/minecraft/ComponentList_p.h index 26ca5049..7a3d498b 100644 --- a/api/logic/minecraft/ComponentList_p.h +++ b/api/logic/minecraft/ComponentList_p.h @@ -13,30 +13,31 @@ using ConnectionList = QList; struct ComponentListData { - // the instance this belongs to - MinecraftInstance *m_instance; + // the instance this belongs to + MinecraftInstance *m_instance; - // the launch profile (volatile, temporary thing created on demand) - std::shared_ptr m_profile; + // the launch profile (volatile, temporary thing created on demand) + std::shared_ptr m_profile; - // version information migrated from instance.cfg file. Single use on migration! - std::map m_oldConfigVersions; - QString getOldConfigVersion(const QString& uid) const - { - const auto iter = m_oldConfigVersions.find(uid); - if(iter != m_oldConfigVersions.cend()) - { - return (*iter).second; - } - return QString(); - } + // version information migrated from instance.cfg file. Single use on migration! + std::map m_oldConfigVersions; + QString getOldConfigVersion(const QString& uid) const + { + const auto iter = m_oldConfigVersions.find(uid); + if(iter != m_oldConfigVersions.cend()) + { + return (*iter).second; + } + return QString(); + } - // persistent list of components and related machinery - ComponentContainer components; - ComponentIndex componentIndex; - bool dirty = false; - QTimer m_saveTimer; - shared_qobject_ptr m_updateTask; - bool loaded = false; + // persistent list of components and related machinery + ComponentContainer components; + ComponentIndex componentIndex; + bool dirty = false; + QTimer m_saveTimer; + shared_qobject_ptr m_updateTask; + bool loaded = false; + bool interactionDisabled = true; }; diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp index 2d6ceb91..15003160 100644 --- a/api/logic/minecraft/ComponentUpdateTask.cpp +++ b/api/logic/minecraft/ComponentUpdateTask.cpp @@ -32,12 +32,12 @@ */ ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent) - : Task(parent) + : Task(parent) { - d.reset(new ComponentUpdateTaskData); - d->m_list = list; - d->mode = mode; - d->netmode = netmode; + d.reset(new ComponentUpdateTaskData); + d->m_list = list; + d->mode = mode; + d->netmode = netmode; } ComponentUpdateTask::~ComponentUpdateTask() @@ -46,356 +46,356 @@ ComponentUpdateTask::~ComponentUpdateTask() void ComponentUpdateTask::executeTask() { - qDebug() << "Loading components"; - loadComponents(); + qDebug() << "Loading components"; + loadComponents(); } namespace { enum class LoadResult { - LoadedLocal, - RequiresRemote, - Failed + LoadedLocal, + RequiresRemote, + Failed }; LoadResult composeLoadResult(LoadResult a, LoadResult b) { - if (a < b) - { - return b; - } - return a; + if (a < b) + { + return b; + } + return a; } static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr& loadTask, Net::Mode netmode) { - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto customPatchFilename = component->getFilename(); - if(QFile::exists(customPatchFilename)) - { - // if local file exists... - - // check for uid problems inside... - bool fileChanged = false; - auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); - if(file->uid != component->m_uid) - { - file->uid = component->m_uid; - fileChanged = true; - } - if(fileChanged) - { - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); - } - - component->m_file = file; - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); - component->m_metaVersion = metaVersion; - if(metaVersion->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaVersion->load(netmode); - loadTask = metaVersion->getCurrentTask(); - if(loadTask) - result = LoadResult::RequiresRemote; - else if (metaVersion->isLoaded()) - result = LoadResult::LoadedLocal; - else - result = LoadResult::Failed; - } - } - return result; + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto customPatchFilename = component->getFilename(); + if(QFile::exists(customPatchFilename)) + { + // if local file exists... + + // check for uid problems inside... + bool fileChanged = false; + auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); + if(file->uid != component->m_uid) + { + file->uid = component->m_uid; + fileChanged = true; + } + if(fileChanged) + { + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); + } + + component->m_file = file; + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); + component->m_metaVersion = metaVersion; + if(metaVersion->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaVersion->load(netmode); + loadTask = metaVersion->getCurrentTask(); + if(loadTask) + result = LoadResult::RequiresRemote; + else if (metaVersion->isLoaded()) + result = LoadResult::LoadedLocal; + else + result = LoadResult::Failed; + } + } + return result; } // FIXME: dead code. determine if this can still be useful? /* static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr& loadTask, Net::Mode netmode) { - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto metaList = ENV.metadataIndex()->get(component->m_uid); - if(metaList->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaList->load(netmode); - loadTask = metaList->getCurrentTask(); - result = LoadResult::RequiresRemote; - } - return result; + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto metaList = ENV.metadataIndex()->get(component->m_uid); + if(metaList->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaList->load(netmode); + loadTask = metaList->getCurrentTask(); + result = LoadResult::RequiresRemote; + } + return result; } */ static LoadResult loadIndex(shared_qobject_ptr& loadTask, Net::Mode netmode) { - // FIXME: DECIDE. do we want to run the update task anyway? - if(ENV.metadataIndex()->isLoaded()) - { - qDebug() << "Index is already loaded"; - return LoadResult::LoadedLocal; - } - ENV.metadataIndex()->load(netmode); - loadTask = ENV.metadataIndex()->getCurrentTask(); - if(loadTask) - { - return LoadResult::RequiresRemote; - } - // FIXME: this is assuming the load succeeded... did it really? - return LoadResult::LoadedLocal; + // FIXME: DECIDE. do we want to run the update task anyway? + if(ENV.metadataIndex()->isLoaded()) + { + qDebug() << "Index is already loaded"; + return LoadResult::LoadedLocal; + } + ENV.metadataIndex()->load(netmode); + loadTask = ENV.metadataIndex()->getCurrentTask(); + if(loadTask) + { + return LoadResult::RequiresRemote; + } + // FIXME: this is assuming the load succeeded... did it really? + return LoadResult::LoadedLocal; } } void ComponentUpdateTask::loadComponents() { - LoadResult result = LoadResult::LoadedLocal; - size_t taskIndex = 0; - size_t componentIndex = 0; - d->remoteLoadSuccessful = true; - // load the main index (it is needed to determine if components can revert) - { - // FIXME: tear out as a method? or lambda? - shared_qobject_ptr indexLoadTask; - auto singleResult = loadIndex(indexLoadTask, d->netmode); - result = composeLoadResult(result, singleResult); - if(indexLoadTask) - { - qDebug() << "Remote loading is being run for metadata index"; - RemoteLoadStatus status; - status.type = RemoteLoadStatus::Type::Index; - d->remoteLoadStatusList.append(status); - connect(indexLoadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - taskIndex++; - } - } - // load all the components OR their lists... - for (auto component: d->m_list->d->components) - { - shared_qobject_ptr loadTask; - LoadResult singleResult; - RemoteLoadStatus::Type loadType; - // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that... + LoadResult result = LoadResult::LoadedLocal; + size_t taskIndex = 0; + size_t componentIndex = 0; + d->remoteLoadSuccessful = true; + // load the main index (it is needed to determine if components can revert) + { + // FIXME: tear out as a method? or lambda? + shared_qobject_ptr indexLoadTask; + auto singleResult = loadIndex(indexLoadTask, d->netmode); + result = composeLoadResult(result, singleResult); + if(indexLoadTask) + { + qDebug() << "Remote loading is being run for metadata index"; + RemoteLoadStatus status; + status.type = RemoteLoadStatus::Type::Index; + d->remoteLoadStatusList.append(status); + connect(indexLoadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + taskIndex++; + } + } + // load all the components OR their lists... + for (auto component: d->m_list->d->components) + { + shared_qobject_ptr loadTask; + LoadResult singleResult; + RemoteLoadStatus::Type loadType; + // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that... #if 0 - switch(d->mode) - { - case Mode::Launch: - { - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; - break; - } - case Mode::Resolution: - { - singleResult = loadComponentList(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::List; - break; - } - } + switch(d->mode) + { + case Mode::Launch: + { + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; + break; + } + case Mode::Resolution: + { + singleResult = loadComponentList(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::List; + break; + } + } #else - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; #endif - if(singleResult == LoadResult::LoadedLocal) - { - component->updateCachedData(); - } - result = composeLoadResult(result, singleResult); - if (loadTask) - { - qDebug() << "Remote loading is being run for" << component->getName(); - connect(loadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(loadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - RemoteLoadStatus status; - status.type = loadType; - status.componentListIndex = componentIndex; - d->remoteLoadStatusList.append(status); - taskIndex++; - } - componentIndex++; - } - d->remoteTasksInProgress = taskIndex; - switch(result) - { - case LoadResult::LoadedLocal: - { - // Everything got loaded. Advance to dependency resolution. - resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); - break; - } - case LoadResult::RequiresRemote: - { - // we wait for signals. - break; - } - case LoadResult::Failed: - { - emitFailed(tr("Some component metadata load tasks failed.")); - break; - } - } + if(singleResult == LoadResult::LoadedLocal) + { + component->updateCachedData(); + } + result = composeLoadResult(result, singleResult); + if (loadTask) + { + qDebug() << "Remote loading is being run for" << component->getName(); + connect(loadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(loadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + RemoteLoadStatus status; + status.type = loadType; + status.componentListIndex = componentIndex; + d->remoteLoadStatusList.append(status); + taskIndex++; + } + componentIndex++; + } + d->remoteTasksInProgress = taskIndex; + switch(result) + { + case LoadResult::LoadedLocal: + { + // Everything got loaded. Advance to dependency resolution. + resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); + break; + } + case LoadResult::RequiresRemote: + { + // we wait for signals. + break; + } + case LoadResult::Failed: + { + emitFailed(tr("Some component metadata load tasks failed.")); + break; + } + } } namespace { - struct RequireEx : public Meta::Require - { - size_t indexOfFirstDependee = 0; - }; - struct RequireCompositionResult - { - bool ok; - RequireEx outcome; - }; - using RequireExSet = std::set; + struct RequireEx : public Meta::Require + { + size_t indexOfFirstDependee = 0; + }; + struct RequireCompositionResult + { + bool ok; + RequireEx outcome; + }; + using RequireExSet = std::set; } static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b) { - assert(a.uid == b.uid); - RequireEx out; - out.uid = a.uid; - out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); - if(a.equalsVersion.isEmpty()) - { - out.equalsVersion = b.equalsVersion; - } - else if (b.equalsVersion.isEmpty()) - { - out.equalsVersion = a.equalsVersion; - } - else if (a.equalsVersion == b.equalsVersion) - { - out.equalsVersion = a.equalsVersion; - } - else - { - // FIXME: mark error as explicit version conflict - return {false, out}; - } - - if(a.suggests.isEmpty()) - { - out.suggests = b.suggests; - } - else if (b.suggests.isEmpty()) - { - out.suggests = a.suggests; - } - else - { - Version aVer(a.suggests); - Version bVer(b.suggests); - out.suggests = (aVer < bVer ? b.suggests : a.suggests); - } - return {true, out}; + assert(a.uid == b.uid); + RequireEx out; + out.uid = a.uid; + out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); + if(a.equalsVersion.isEmpty()) + { + out.equalsVersion = b.equalsVersion; + } + else if (b.equalsVersion.isEmpty()) + { + out.equalsVersion = a.equalsVersion; + } + else if (a.equalsVersion == b.equalsVersion) + { + out.equalsVersion = a.equalsVersion; + } + else + { + // FIXME: mark error as explicit version conflict + return {false, out}; + } + + if(a.suggests.isEmpty()) + { + out.suggests = b.suggests; + } + else if (b.suggests.isEmpty()) + { + out.suggests = a.suggests; + } + else + { + Version aVer(a.suggests); + Version bVer(b.suggests); + out.suggests = (aVer < bVer ? b.suggests : a.suggests); + } + return {true, out}; } // gather the requirements from all components, finding any obvious conflicts static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output) { - bool succeeded = true; - size_t componentNum = 0; - for(auto component: input) - { - auto &componentRequires = component->m_cachedRequires; - for(const auto & componentRequire: componentRequires) - { - auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ - return req.uid == componentRequire.uid; - }); - - RequireEx componenRequireEx; - componenRequireEx.uid = componentRequire.uid; - componenRequireEx.suggests = componentRequire.suggests; - componenRequireEx.equalsVersion = componentRequire.equalsVersion; - componenRequireEx.indexOfFirstDependee = componentNum; - - if(found != output.cend()) - { - // found... process it further - auto result = composeRequirement(componenRequireEx, *found); - if(result.ok) - { - output.erase(componenRequireEx); - output.insert(result.outcome); - } - else - { - qCritical() - << "Conflicting requirements:" - << componentRequire.uid - << "versions:" - << componentRequire.equalsVersion - << ";" - << (*found).equalsVersion; - } - succeeded &= result.ok; - } - else - { - // not found, accumulate - output.insert(componenRequireEx); - } - } - componentNum++; - } - return succeeded; + bool succeeded = true; + size_t componentNum = 0; + for(auto component: input) + { + auto &componentRequires = component->m_cachedRequires; + for(const auto & componentRequire: componentRequires) + { + auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ + return req.uid == componentRequire.uid; + }); + + RequireEx componenRequireEx; + componenRequireEx.uid = componentRequire.uid; + componenRequireEx.suggests = componentRequire.suggests; + componenRequireEx.equalsVersion = componentRequire.equalsVersion; + componenRequireEx.indexOfFirstDependee = componentNum; + + if(found != output.cend()) + { + // found... process it further + auto result = composeRequirement(componenRequireEx, *found); + if(result.ok) + { + output.erase(componenRequireEx); + output.insert(result.outcome); + } + else + { + qCritical() + << "Conflicting requirements:" + << componentRequire.uid + << "versions:" + << componentRequire.equalsVersion + << ";" + << (*found).equalsVersion; + } + succeeded &= result.ok; + } + else + { + // not found, accumulate + output.insert(componenRequireEx); + } + } + componentNum++; + } + return succeeded; } /// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps) static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove) { - for(const auto & component: components) - { - if(!component->m_dependencyOnly) - continue; - if(!component->m_cachedVolatile) - continue; - RequireEx reqNeedle; - reqNeedle.uid = component->m_uid; - const auto iter = reqs.find(reqNeedle); - if(iter == reqs.cend()) - { - toRemove.append(component->m_uid); - } - } + for(const auto & component: components) + { + if(!component->m_dependencyOnly) + continue; + if(!component->m_cachedVolatile) + continue; + RequireEx reqNeedle; + reqNeedle.uid = component->m_uid; + const auto iter = reqs.find(reqNeedle); + if(iter == reqs.cend()) + { + toRemove.append(component->m_uid); + } + } } /** @@ -408,86 +408,86 @@ static void getTrivialRemovals(const ComponentContainer & components, const Requ */ static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange) { - enum class Decision - { - Undetermined, - Met, - Missing, - VersionNotSame, - LockedVersionNotSame - } decision = Decision::Undetermined; - - QString reqStr; - bool succeeded = true; - // list the composed requirements and say if they are met or unmet - for(auto & req: input) - { - do - { - if(req.equalsVersion.isEmpty()) - { - reqStr = QString("Req: %1").arg(req.uid); - if(index.contains(req.uid)) - { - decision = Decision::Met; - } - else - { - toAdd.insert(req); - decision = Decision::Missing; - } - break; - } - else - { - reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); - const auto & compIter = index.find(req.uid); - if(compIter == index.cend()) - { - toAdd.insert(req); - decision = Decision::Missing; - break; - } - auto & comp = (*compIter); - if(comp->getVersion() != req.equalsVersion) - { - if(comp->m_dependencyOnly) - { - decision = Decision::VersionNotSame; - } - else - { - decision = Decision::LockedVersionNotSame; - } - break; - } - decision = Decision::Met; - } - } while(false); - switch(decision) - { - case Decision::Undetermined: - qCritical() << "No decision for" << reqStr; - succeeded = false; - break; - case Decision::Met: - qDebug() << reqStr << "Is met."; - break; - case Decision::Missing: - qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; - toAdd.insert(req); - break; - case Decision::VersionNotSame: - qDebug() << reqStr << "already has different version that can be changed."; - toChange.insert(req); - break; - case Decision::LockedVersionNotSame: - qDebug() << reqStr << "already has different version that cannot be changed."; - succeeded = false; - break; - } - } - return succeeded; + enum class Decision + { + Undetermined, + Met, + Missing, + VersionNotSame, + LockedVersionNotSame + } decision = Decision::Undetermined; + + QString reqStr; + bool succeeded = true; + // list the composed requirements and say if they are met or unmet + for(auto & req: input) + { + do + { + if(req.equalsVersion.isEmpty()) + { + reqStr = QString("Req: %1").arg(req.uid); + if(index.contains(req.uid)) + { + decision = Decision::Met; + } + else + { + toAdd.insert(req); + decision = Decision::Missing; + } + break; + } + else + { + reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); + const auto & compIter = index.find(req.uid); + if(compIter == index.cend()) + { + toAdd.insert(req); + decision = Decision::Missing; + break; + } + auto & comp = (*compIter); + if(comp->getVersion() != req.equalsVersion) + { + if(comp->m_dependencyOnly) + { + decision = Decision::VersionNotSame; + } + else + { + decision = Decision::LockedVersionNotSame; + } + break; + } + decision = Decision::Met; + } + } while(false); + switch(decision) + { + case Decision::Undetermined: + qCritical() << "No decision for" << reqStr; + succeeded = false; + break; + case Decision::Met: + qDebug() << reqStr << "Is met."; + break; + case Decision::Missing: + qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; + toAdd.insert(req); + break; + case Decision::VersionNotSame: + qDebug() << reqStr << "already has different version that can be changed."; + toChange.insert(req); + break; + case Decision::LockedVersionNotSame: + qDebug() << reqStr << "already has different version that cannot be changed."; + succeeded = false; + break; + } + } + return succeeded; } // FIXME, TODO: decouple dependency resolution from loading @@ -495,197 +495,206 @@ static bool getTrivialComponentChanges(const ComponentIndex & index, const Requi // FIXME: throw all this away and use a graph void ComponentUpdateTask::resolveDependencies(bool checkOnly) { - qDebug() << "Resolving dependencies"; - /* - * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: - * 1. There are conflicting dependencies on the same uid with different exact version numbers - * -> hard error - * 2. A dependency has non-matching exact version number - * -> hard error - * 3. A dependency is entirely missing and needs to be injected before the dependee(s) - * -> requirements are injected - * - * NOTE: this is a placeholder and should eventually be replaced with something 'serious' - */ - auto & components = d->m_list->d->components; - auto & componentIndex = d->m_list->d->componentIndex; - - RequireExSet allRequires; - QStringList toRemove; - do - { - allRequires.clear(); - toRemove.clear(); - if(!gatherRequirementsFromComponents(components, allRequires)) - { - emitFailed(tr("Conflicting requirements detected during dependency checking!")); - return; - } - getTrivialRemovals(components, allRequires, toRemove); - if(!toRemove.isEmpty()) - { - qDebug() << "Removing obsolete components..."; - for(auto & remove : toRemove) - { - qDebug() << "Removing" << remove; - d->m_list->remove(remove); - } - } - } while (!toRemove.isEmpty()); - RequireExSet toAdd; - RequireExSet toChange; - bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); - if(!succeeded) - { - emitFailed(tr("Instance has conflicting dependencies.")); - return; - } - if(checkOnly) - { - if(toAdd.size() || toChange.size()) - { - emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); - } - else - { - emitSucceeded(); - } - return; - } - - bool recursionNeeded = false; - if(toAdd.size()) - { - // add stuff... - for(auto &add: toAdd) - { - ComponentPtr component = new Component(d->m_list, add.uid); - if(!add.equalsVersion.isEmpty()) - { - // exact version - qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; - component->m_version = add.equalsVersion; - } - else - { - // version needs to be decided - qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; + qDebug() << "Resolving dependencies"; + /* + * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: + * 1. There are conflicting dependencies on the same uid with different exact version numbers + * -> hard error + * 2. A dependency has non-matching exact version number + * -> hard error + * 3. A dependency is entirely missing and needs to be injected before the dependee(s) + * -> requirements are injected + * + * NOTE: this is a placeholder and should eventually be replaced with something 'serious' + */ + auto & components = d->m_list->d->components; + auto & componentIndex = d->m_list->d->componentIndex; + + RequireExSet allRequires; + QStringList toRemove; + do + { + allRequires.clear(); + toRemove.clear(); + if(!gatherRequirementsFromComponents(components, allRequires)) + { + emitFailed(tr("Conflicting requirements detected during dependency checking!")); + return; + } + getTrivialRemovals(components, allRequires, toRemove); + if(!toRemove.isEmpty()) + { + qDebug() << "Removing obsolete components..."; + for(auto & remove : toRemove) + { + qDebug() << "Removing" << remove; + d->m_list->remove(remove); + } + } + } while (!toRemove.isEmpty()); + RequireExSet toAdd; + RequireExSet toChange; + bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); + if(!succeeded) + { + emitFailed(tr("Instance has conflicting dependencies.")); + return; + } + if(checkOnly) + { + if(toAdd.size() || toChange.size()) + { + emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); + } + else + { + emitSucceeded(); + } + return; + } + + bool recursionNeeded = false; + if(toAdd.size()) + { + // add stuff... + for(auto &add: toAdd) + { + ComponentPtr component = new Component(d->m_list, add.uid); + if(!add.equalsVersion.isEmpty()) + { + // exact version + qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; + component->m_version = add.equalsVersion; + } + else + { + // version needs to be decided + qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; // ############################################################################################################ // HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. - if(!add.suggests.isEmpty()) - { - component->m_version = add.suggests; - } - else - { - if(add.uid == "org.lwjgl") - { - component->m_version = "2.9.1"; - } - else if (add.uid == "org.lwjgl3") - { - component->m_version = "3.1.2"; - } - } + if(!add.suggests.isEmpty()) + { + component->m_version = add.suggests; + } + else + { + if(add.uid == "org.lwjgl") + { + component->m_version = "2.9.1"; + } + else if (add.uid == "org.lwjgl3") + { + component->m_version = "3.1.2"; + } + else if (add.uid == "net.fabricmc.intermediary") + { + auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){ + return cmp->getID() == "net.minecraft"; + }); + if(minecraft != components.end()) { + component->m_version = (*minecraft)->getVersion(); + } + } + } // HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. // ############################################################################################################ - } - component->m_dependencyOnly = true; - // FIXME: this should not work directly with the component list - d->m_list->insertComponent(add.indexOfFirstDependee, component); - componentIndex[add.uid] = component; - } - recursionNeeded = true; - } - if(toChange.size()) - { - // change a version of something that exists - for(auto &change: toChange) - { - // FIXME: this should not work directly with the component list - qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; - auto component = componentIndex[change.uid]; - component->setVersion(change.equalsVersion); - } - recursionNeeded = true; - } - - if(recursionNeeded) - { - loadComponents(); - } - else - { - emitSucceeded(); - } + } + component->m_dependencyOnly = true; + // FIXME: this should not work directly with the component list + d->m_list->insertComponent(add.indexOfFirstDependee, component); + componentIndex[add.uid] = component; + } + recursionNeeded = true; + } + if(toChange.size()) + { + // change a version of something that exists + for(auto &change: toChange) + { + // FIXME: this should not work directly with the component list + qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; + auto component = componentIndex[change.uid]; + component->setVersion(change.equalsVersion); + } + recursionNeeded = true; + } + + if(recursionNeeded) + { + loadComponents(); + } + else + { + emitSucceeded(); + } } void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) { - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "succeeded"; - taskSlot.succeeded = false; - taskSlot.finished = true; - d->remoteTasksInProgress --; - // update the cached data of the component from the downloaded version file. - if (taskSlot.type == RemoteLoadStatus::Type::Version) - { - auto component = d->m_list->getComponent(taskSlot.componentListIndex); - component->m_loaded = true; - component->updateCachedData(); - } - checkIfAllFinished(); + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "Remote task" << taskIndex << "succeeded"; + taskSlot.succeeded = false; + taskSlot.finished = true; + d->remoteTasksInProgress --; + // update the cached data of the component from the downloaded version file. + if (taskSlot.type == RemoteLoadStatus::Type::Version) + { + auto component = d->m_list->getComponent(taskSlot.componentListIndex); + component->m_loaded = true; + component->updateCachedData(); + } + checkIfAllFinished(); } void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) { - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "failed: " << msg; - d->remoteLoadSuccessful = false; - taskSlot.succeeded = false; - taskSlot.finished = true; - taskSlot.error = msg; - d->remoteTasksInProgress --; - checkIfAllFinished(); + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "Remote task" << taskIndex << "failed: " << msg; + d->remoteLoadSuccessful = false; + taskSlot.succeeded = false; + taskSlot.finished = true; + taskSlot.error = msg; + d->remoteTasksInProgress --; + checkIfAllFinished(); } void ComponentUpdateTask::checkIfAllFinished() { - if(d->remoteTasksInProgress) - { - // not yet... - return; - } - if(d->remoteLoadSuccessful) - { - // nothing bad happened... clear the temp load status and proceed with looking at dependencies - d->remoteLoadStatusList.clear(); - resolveDependencies(d->mode == Mode::Launch); - } - else - { - // remote load failed... report error and bail - QStringList allErrorsList; - for(auto & item: d->remoteLoadStatusList) - { - if(!item.succeeded) - { - allErrorsList.append(item.error); - } - } - auto allErrors = allErrorsList.join("\n"); - emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); - d->remoteLoadStatusList.clear(); - } + if(d->remoteTasksInProgress) + { + // not yet... + return; + } + if(d->remoteLoadSuccessful) + { + // nothing bad happened... clear the temp load status and proceed with looking at dependencies + d->remoteLoadStatusList.clear(); + resolveDependencies(d->mode == Mode::Launch); + } + else + { + // remote load failed... report error and bail + QStringList allErrorsList; + for(auto & item: d->remoteLoadStatusList) + { + if(!item.succeeded) + { + allErrorsList.append(item.error); + } + } + auto allErrors = allErrorsList.join("\n"); + emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); + d->remoteLoadStatusList.clear(); + } } diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h index 11d122b6..4cb29a89 100644 --- a/api/logic/minecraft/ComponentUpdateTask.h +++ b/api/logic/minecraft/ComponentUpdateTask.h @@ -9,29 +9,29 @@ struct ComponentUpdateTaskData; class ComponentUpdateTask : public Task { - Q_OBJECT + Q_OBJECT public: - enum class Mode - { - Launch, - Resolution - }; + enum class Mode + { + Launch, + Resolution + }; public: - explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0); - virtual ~ComponentUpdateTask(); + explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0); + virtual ~ComponentUpdateTask(); protected: - void executeTask(); + void executeTask(); private: - void loadComponents(); - void resolveDependencies(bool checkOnly); + void loadComponents(); + void resolveDependencies(bool checkOnly); - void remoteLoadSucceeded(size_t index); - void remoteLoadFailed(size_t index, const QString &msg); - void checkIfAllFinished(); + void remoteLoadSucceeded(size_t index); + void remoteLoadFailed(size_t index, const QString &msg); + void checkIfAllFinished(); private: - std::unique_ptr d; + std::unique_ptr d; }; diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h index a5216506..5989cf07 100644 --- a/api/logic/minecraft/ComponentUpdateTask_p.h +++ b/api/logic/minecraft/ComponentUpdateTask_p.h @@ -9,24 +9,24 @@ class ComponentList; struct RemoteLoadStatus { - enum class Type - { - Index, - List, - Version - } type = Type::Version; - size_t componentListIndex = 0; - bool finished = false; - bool succeeded = false; - QString error; + enum class Type + { + Index, + List, + Version + } type = Type::Version; + size_t componentListIndex = 0; + bool finished = false; + bool succeeded = false; + QString error; }; struct ComponentUpdateTaskData { - ComponentList * m_list = nullptr; - QList remoteLoadStatusList; - bool remoteLoadSuccessful = true; - size_t remoteTasksInProgress = 0; - ComponentUpdateTask::Mode mode; - Net::Mode netmode; + ComponentList * m_list = nullptr; + QList remoteLoadStatusList; + bool remoteLoadSuccessful = true; + size_t remoteTasksInProgress = 0; + ComponentUpdateTask::Mode mode; + Net::Mode netmode; }; diff --git a/api/logic/minecraft/GradleSpecifier.h b/api/logic/minecraft/GradleSpecifier.h index f842008c..959325c6 100644 --- a/api/logic/minecraft/GradleSpecifier.h +++ b/api/logic/minecraft/GradleSpecifier.h @@ -6,138 +6,138 @@ struct GradleSpecifier { - GradleSpecifier() - { - m_valid = false; - } - GradleSpecifier(QString value) - { - operator=(value); - } - GradleSpecifier & operator =(const QString & value) - { - /* - org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar - DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" - DEBUG 1 "org.gradle.test.classifiers" - DEBUG 2 "service" - DEBUG 3 "1.0" - DEBUG 4 ":jdk15" - DEBUG 5 "jdk15" - DEBUG 6 "@jar" - DEBUG 7 "jar" - */ - QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?"); - m_valid = matcher.exactMatch(value); - auto elements = matcher.capturedTexts(); - m_groupId = elements[1]; - m_artifactId = elements[2]; - m_version = elements[3]; - m_classifier = elements[5]; - if(!elements[7].isEmpty()) - { - m_extension = elements[7]; - } - return *this; - } - operator QString() const - { - if(!m_valid) - return "INVALID"; - QString retval = m_groupId + ":" + m_artifactId + ":" + m_version; - if(!m_classifier.isEmpty()) - { - retval += ":" + m_classifier; - } - if(m_extension.isExplicit()) - { - retval += "@" + m_extension; - } - return retval; - } - QString getFileName() const - { - QString filename = m_artifactId + '-' + m_version; - if(!m_classifier.isEmpty()) - { - filename += "-" + m_classifier; - } - filename += "." + m_extension; - return filename; - } - QString toPath(const QString & filenameOverride = QString()) const - { - if(!m_valid) - return "INVALID"; - QString filename; - if(filenameOverride.isEmpty()) - { - filename = getFileName(); - } - else - { - filename = filenameOverride; - } - QString path = m_groupId; - path.replace('.', '/'); - path += '/' + m_artifactId + '/' + m_version + '/' + filename; - return path; - } - inline bool valid() const - { - return m_valid; - } - inline QString version() const - { - return m_version; - } - inline QString groupId() const - { - return m_groupId; - } - inline QString artifactId() const - { - return m_artifactId; - } - inline void setClassifier(const QString & classifier) - { - m_classifier = classifier; - } - inline QString classifier() const - { - return m_classifier; - } - inline QString extension() const - { - return m_extension; - } - inline QString artifactPrefix() const - { - return m_groupId + ":" + m_artifactId; - } - bool matchName(const GradleSpecifier & other) const - { - return other.artifactId() == artifactId() && other.groupId() == groupId(); - } - bool operator==(const GradleSpecifier & other) const - { - if(m_groupId != other.m_groupId) - return false; - if(m_artifactId != other.m_artifactId) - return false; - if(m_version != other.m_version) - return false; - if(m_classifier != other.m_classifier) - return false; - if(m_extension != other.m_extension) - return false; - return true; - } + GradleSpecifier() + { + m_valid = false; + } + GradleSpecifier(QString value) + { + operator=(value); + } + GradleSpecifier & operator =(const QString & value) + { + /* + org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar + DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" + DEBUG 1 "org.gradle.test.classifiers" + DEBUG 2 "service" + DEBUG 3 "1.0" + DEBUG 4 ":jdk15" + DEBUG 5 "jdk15" + DEBUG 6 "@jar" + DEBUG 7 "jar" + */ + QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?"); + m_valid = matcher.exactMatch(value); + auto elements = matcher.capturedTexts(); + m_groupId = elements[1]; + m_artifactId = elements[2]; + m_version = elements[3]; + m_classifier = elements[5]; + if(!elements[7].isEmpty()) + { + m_extension = elements[7]; + } + return *this; + } + operator QString() const + { + if(!m_valid) + return "INVALID"; + QString retval = m_groupId + ":" + m_artifactId + ":" + m_version; + if(!m_classifier.isEmpty()) + { + retval += ":" + m_classifier; + } + if(m_extension.isExplicit()) + { + retval += "@" + m_extension; + } + return retval; + } + QString getFileName() const + { + QString filename = m_artifactId + '-' + m_version; + if(!m_classifier.isEmpty()) + { + filename += "-" + m_classifier; + } + filename += "." + m_extension; + return filename; + } + QString toPath(const QString & filenameOverride = QString()) const + { + if(!m_valid) + return "INVALID"; + QString filename; + if(filenameOverride.isEmpty()) + { + filename = getFileName(); + } + else + { + filename = filenameOverride; + } + QString path = m_groupId; + path.replace('.', '/'); + path += '/' + m_artifactId + '/' + m_version + '/' + filename; + return path; + } + inline bool valid() const + { + return m_valid; + } + inline QString version() const + { + return m_version; + } + inline QString groupId() const + { + return m_groupId; + } + inline QString artifactId() const + { + return m_artifactId; + } + inline void setClassifier(const QString & classifier) + { + m_classifier = classifier; + } + inline QString classifier() const + { + return m_classifier; + } + inline QString extension() const + { + return m_extension; + } + inline QString artifactPrefix() const + { + return m_groupId + ":" + m_artifactId; + } + bool matchName(const GradleSpecifier & other) const + { + return other.artifactId() == artifactId() && other.groupId() == groupId(); + } + bool operator==(const GradleSpecifier & other) const + { + if(m_groupId != other.m_groupId) + return false; + if(m_artifactId != other.m_artifactId) + return false; + if(m_version != other.m_version) + return false; + if(m_classifier != other.m_classifier) + return false; + if(m_extension != other.m_extension) + return false; + return true; + } private: - QString m_groupId; - QString m_artifactId; - QString m_version; - QString m_classifier; - DefaultVariable m_extension = DefaultVariable("jar"); - bool m_valid = false; + QString m_groupId; + QString m_artifactId; + QString m_version; + QString m_classifier; + DefaultVariable m_extension = DefaultVariable("jar"); + bool m_valid = false; }; diff --git a/api/logic/minecraft/GradleSpecifier_test.cpp b/api/logic/minecraft/GradleSpecifier_test.cpp index 155522cd..f49ec718 100644 --- a/api/logic/minecraft/GradleSpecifier_test.cpp +++ b/api/logic/minecraft/GradleSpecifier_test.cpp @@ -5,71 +5,71 @@ class GradleSpecifierTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_Positive_data() - { - QTest::addColumn("through"); - - QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0"; - QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15"; - QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar"; - QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar"; - QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz"; - } - void test_Positive() - { - QFETCH(QString, through); - - QString converted = GradleSpecifier(through); - - QCOMPARE(converted, through); - } - - void test_Path_data() - { - QTest::addColumn("spec"); - QTest::addColumn("expected"); - - QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar"; - QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad"; - } - void test_Path() - { - QFETCH(QString, spec); - QFETCH(QString, expected); - - QString converted = GradleSpecifier(spec).toPath(); - - QCOMPARE(converted, expected); - } - void test_Negative_data() - { - QTest::addColumn("input"); - - QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::"; - QTest::newRow("nonsense") << "I like turtles"; - QTest::newRow("empty string") << ""; - QTest::newRow("missing version") << "herp.derp:artifact"; - } - void test_Negative() - { - QFETCH(QString, input); - - GradleSpecifier spec(input); - QVERIFY(!spec.valid()); - QCOMPARE(spec.operator QString(), QString("INVALID")); - } + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void test_Positive_data() + { + QTest::addColumn("through"); + + QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0"; + QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15"; + QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar"; + QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar"; + QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz"; + } + void test_Positive() + { + QFETCH(QString, through); + + QString converted = GradleSpecifier(through); + + QCOMPARE(converted, through); + } + + void test_Path_data() + { + QTest::addColumn("spec"); + QTest::addColumn("expected"); + + QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar"; + QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad"; + } + void test_Path() + { + QFETCH(QString, spec); + QFETCH(QString, expected); + + QString converted = GradleSpecifier(spec).toPath(); + + QCOMPARE(converted, expected); + } + void test_Negative_data() + { + QTest::addColumn("input"); + + QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::"; + QTest::newRow("nonsense") << "I like turtles"; + QTest::newRow("empty string") << ""; + QTest::newRow("missing version") << "herp.derp:artifact"; + } + void test_Negative() + { + QFETCH(QString, input); + + GradleSpecifier spec(input); + QVERIFY(!spec.valid()); + QCOMPARE(spec.operator QString(), QString("INVALID")); + } }; QTEST_GUILESS_MAIN(GradleSpecifierTest) diff --git a/api/logic/minecraft/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp index 436a39d9..c39bdf04 100644 --- a/api/logic/minecraft/LaunchProfile.cpp +++ b/api/logic/minecraft/LaunchProfile.cpp @@ -3,295 +3,295 @@ void LaunchProfile::clear() { - m_minecraftVersion.clear(); - m_minecraftVersionType.clear(); - m_minecraftAssets.reset(); - m_minecraftArguments.clear(); - m_tweakers.clear(); - m_mainClass.clear(); - m_appletClass.clear(); - m_libraries.clear(); - m_traits.clear(); - m_jarMods.clear(); - m_mainJar.reset(); - m_problemSeverity = ProblemSeverity::None; + m_minecraftVersion.clear(); + m_minecraftVersionType.clear(); + m_minecraftAssets.reset(); + m_minecraftArguments.clear(); + m_tweakers.clear(); + m_mainClass.clear(); + m_appletClass.clear(); + m_libraries.clear(); + m_traits.clear(); + m_jarMods.clear(); + m_mainJar.reset(); + m_problemSeverity = ProblemSeverity::None; } static void applyString(const QString & from, QString & to) { - if(from.isEmpty()) - return; - to = from; + if(from.isEmpty()) + return; + to = from; } void LaunchProfile::applyMinecraftVersion(const QString& id) { - applyString(id, this->m_minecraftVersion); + applyString(id, this->m_minecraftVersion); } void LaunchProfile::applyAppletClass(const QString& appletClass) { - applyString(appletClass, this->m_appletClass); + applyString(appletClass, this->m_appletClass); } void LaunchProfile::applyMainClass(const QString& mainClass) { - applyString(mainClass, this->m_mainClass); + applyString(mainClass, this->m_mainClass); } void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments) { - applyString(minecraftArguments, this->m_minecraftArguments); + applyString(minecraftArguments, this->m_minecraftArguments); } void LaunchProfile::applyMinecraftVersionType(const QString& type) { - applyString(type, this->m_minecraftVersionType); + applyString(type, this->m_minecraftVersionType); } void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) { - if(assets) - { - m_minecraftAssets = assets; - } + if(assets) + { + m_minecraftAssets = assets; + } } void LaunchProfile::applyTraits(const QSet& traits) { - this->m_traits.unite(traits); + this->m_traits.unite(traits); } void LaunchProfile::applyTweakers(const QStringList& tweakers) { - // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence - QStringList newTweakers; - for(auto & tweaker: m_tweakers) - { - if (tweakers.contains(tweaker)) - { - continue; - } - newTweakers.append(tweaker); - } - // then just append the new tweakers (or moved original ones) - newTweakers += tweakers; - m_tweakers = newTweakers; + // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence + QStringList newTweakers; + for(auto & tweaker: m_tweakers) + { + if (tweakers.contains(tweaker)) + { + continue; + } + newTweakers.append(tweaker); + } + // then just append the new tweakers (or moved original ones) + newTweakers += tweakers; + m_tweakers = newTweakers; } void LaunchProfile::applyJarMods(const QList& jarMods) { - this->m_jarMods.append(jarMods); + this->m_jarMods.append(jarMods); } static int findLibraryByName(QList *haystack, const GradleSpecifier &needle) { - int retval = -1; - for (int i = 0; i < haystack->size(); ++i) - { - if (haystack->at(i)->rawName().matchName(needle)) - { - // only one is allowed. - if (retval != -1) - return -1; - retval = i; - } - } - return retval; + int retval = -1; + for (int i = 0; i < haystack->size(); ++i) + { + if (haystack->at(i)->rawName().matchName(needle)) + { + // only one is allowed. + if (retval != -1) + return -1; + retval = i; + } + } + return retval; } void LaunchProfile::applyMods(const QList& mods) { - QList * list = &m_mods; - for(auto & mod: mods) - { - auto modCopy = Library::limitedCopy(mod); - - // find the mod by name. - const int index = findLibraryByName(list, mod->rawName()); - // mod not found? just add it. - if (index < 0) - { - list->append(modCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(mod->version()) > Version(existingLibrary->version())) - { - list->replace(index, modCopy); - } - } + QList * list = &m_mods; + for(auto & mod: mods) + { + auto modCopy = Library::limitedCopy(mod); + + // find the mod by name. + const int index = findLibraryByName(list, mod->rawName()); + // mod not found? just add it. + if (index < 0) + { + list->append(modCopy); + return; + } + + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(mod->version()) > Version(existingLibrary->version())) + { + list->replace(index, modCopy); + } + } } void LaunchProfile::applyLibrary(LibraryPtr library) { - if(!library->isActive()) - { - return; - } + if(!library->isActive()) + { + return; + } - QList * list = &m_libraries; - if(library->isNative()) - { - list = &m_nativeLibraries; - } + QList * list = &m_libraries; + if(library->isNative()) + { + list = &m_nativeLibraries; + } - auto libraryCopy = Library::limitedCopy(library); + auto libraryCopy = Library::limitedCopy(library); - // find the library by name. - const int index = findLibraryByName(list, library->rawName()); - // library not found? just add it. - if (index < 0) - { - list->append(libraryCopy); - return; - } + // find the library by name. + const int index = findLibraryByName(list, library->rawName()); + // library not found? just add it. + if (index < 0) + { + list->append(libraryCopy); + return; + } - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(library->version()) > Version(existingLibrary->version())) - { - list->replace(index, libraryCopy); - } + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(library->version()) > Version(existingLibrary->version())) + { + list->replace(index, libraryCopy); + } } const LibraryPtr LaunchProfile::getMainJar() const { - return m_mainJar; + return m_mainJar; } void LaunchProfile::applyMainJar(LibraryPtr jar) { - if(jar) - { - m_mainJar = jar; - } + if(jar) + { + m_mainJar = jar; + } } void LaunchProfile::applyProblemSeverity(ProblemSeverity severity) { - if (m_problemSeverity < severity) - { - m_problemSeverity = severity; - } + if (m_problemSeverity < severity) + { + m_problemSeverity = severity; + } } const QList LaunchProfile::getProblems() const { - // FIXME: implement something that actually makes sense here - return {}; + // FIXME: implement something that actually makes sense here + return {}; } QString LaunchProfile::getMinecraftVersion() const { - return m_minecraftVersion; + return m_minecraftVersion; } QString LaunchProfile::getAppletClass() const { - return m_appletClass; + return m_appletClass; } QString LaunchProfile::getMainClass() const { - return m_mainClass; + return m_mainClass; } const QSet &LaunchProfile::getTraits() const { - return m_traits; + return m_traits; } const QStringList & LaunchProfile::getTweakers() const { - return m_tweakers; + return m_tweakers; } bool LaunchProfile::hasTrait(const QString& trait) const { - return m_traits.contains(trait); + return m_traits.contains(trait); } ProblemSeverity LaunchProfile::getProblemSeverity() const { - return m_problemSeverity; + return m_problemSeverity; } QString LaunchProfile::getMinecraftVersionType() const { - return m_minecraftVersionType; + return m_minecraftVersionType; } std::shared_ptr LaunchProfile::getMinecraftAssets() const { - if(!m_minecraftAssets) - { - return std::make_shared("legacy"); - } - return m_minecraftAssets; + if(!m_minecraftAssets) + { + return std::make_shared("legacy"); + } + return m_minecraftAssets; } QString LaunchProfile::getMinecraftArguments() const { - return m_minecraftArguments; + return m_minecraftArguments; } const QList & LaunchProfile::getJarMods() const { - return m_jarMods; + return m_jarMods; } const QList & LaunchProfile::getLibraries() const { - return m_libraries; + return m_libraries; } const QList & LaunchProfile::getNativeLibraries() const { - return m_nativeLibraries; + return m_nativeLibraries; } void LaunchProfile::getLibraryFiles( - const QString& architecture, - QStringList& jars, - QStringList& nativeJars, - const QString& overridePath, - const QString& tempPath + const QString& architecture, + QStringList& jars, + QStringList& nativeJars, + const QString& overridePath, + const QString& tempPath ) const { - QStringList native32, native64; - jars.clear(); - nativeJars.clear(); - for (auto lib : getLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - // NOTE: order is important here, add main jar last to the lists - if(m_mainJar) - { - // FIXME: HACK!! jar modding is weird and unsystematic! - if(m_jarMods.size()) - { - QDir tempDir(tempPath); - jars.append(tempDir.absoluteFilePath("minecraft.jar")); - } - else - { - m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - } - for (auto lib : getNativeLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - if(architecture == "32") - { - nativeJars.append(native32); - } - else if(architecture == "64") - { - nativeJars.append(native64); - } + QStringList native32, native64; + jars.clear(); + nativeJars.clear(); + for (auto lib : getLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + // NOTE: order is important here, add main jar last to the lists + if(m_mainJar) + { + // FIXME: HACK!! jar modding is weird and unsystematic! + if(m_jarMods.size()) + { + QDir tempDir(tempPath); + jars.append(tempDir.absoluteFilePath("minecraft.jar")); + } + else + { + m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + } + for (auto lib : getNativeLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + if(architecture == "32") + { + nativeJars.append(native32); + } + else if(architecture == "64") + { + nativeJars.append(native64); + } } diff --git a/api/logic/minecraft/LaunchProfile.h b/api/logic/minecraft/LaunchProfile.h index e7f5f4af..77174079 100644 --- a/api/logic/minecraft/LaunchProfile.h +++ b/api/logic/minecraft/LaunchProfile.h @@ -6,94 +6,94 @@ class LaunchProfile: public ProblemProvider { public: - virtual ~LaunchProfile() {}; + virtual ~LaunchProfile() {}; public: /* application of profile variables from patches */ - void applyMinecraftVersion(const QString& id); - void applyMainClass(const QString& mainClass); - void applyAppletClass(const QString& appletClass); - void applyMinecraftArguments(const QString& minecraftArguments); - void applyMinecraftVersionType(const QString& type); - void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); - void applyTraits(const QSet &traits); - void applyTweakers(const QStringList &tweakers); - void applyJarMods(const QList &jarMods); - void applyMods(const QList &jarMods); - void applyLibrary(LibraryPtr library); - void applyMainJar(LibraryPtr jar); - void applyProblemSeverity(ProblemSeverity severity); - /// clear the profile - void clear(); + void applyMinecraftVersion(const QString& id); + void applyMainClass(const QString& mainClass); + void applyAppletClass(const QString& appletClass); + void applyMinecraftArguments(const QString& minecraftArguments); + void applyMinecraftVersionType(const QString& type); + void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); + void applyTraits(const QSet &traits); + void applyTweakers(const QStringList &tweakers); + void applyJarMods(const QList &jarMods); + void applyMods(const QList &jarMods); + void applyLibrary(LibraryPtr library); + void applyMainJar(LibraryPtr jar); + void applyProblemSeverity(ProblemSeverity severity); + /// clear the profile + void clear(); public: /* getters for profile variables */ - QString getMinecraftVersion() const; - QString getMainClass() const; - QString getAppletClass() const; - QString getMinecraftVersionType() const; - MojangAssetIndexInfo::Ptr getMinecraftAssets() const; - QString getMinecraftArguments() const; - const QSet & getTraits() const; - const QStringList & getTweakers() const; - const QList & getJarMods() const; - const QList & getLibraries() const; - const QList & getNativeLibraries() const; - const LibraryPtr getMainJar() const; - void getLibraryFiles( - const QString & architecture, - QStringList & jars, - QStringList & nativeJars, - const QString & overridePath, - const QString & tempPath - ) const; - bool hasTrait(const QString & trait) const; - ProblemSeverity getProblemSeverity() const override; - const QList getProblems() const override; + QString getMinecraftVersion() const; + QString getMainClass() const; + QString getAppletClass() const; + QString getMinecraftVersionType() const; + MojangAssetIndexInfo::Ptr getMinecraftAssets() const; + QString getMinecraftArguments() const; + const QSet & getTraits() const; + const QStringList & getTweakers() const; + const QList & getJarMods() const; + const QList & getLibraries() const; + const QList & getNativeLibraries() const; + const LibraryPtr getMainJar() const; + void getLibraryFiles( + const QString & architecture, + QStringList & jars, + QStringList & nativeJars, + const QString & overridePath, + const QString & tempPath + ) const; + bool hasTrait(const QString & trait) const; + ProblemSeverity getProblemSeverity() const override; + const QList getProblems() const override; private: - /// the version of Minecraft - jar to use - QString m_minecraftVersion; + /// the version of Minecraft - jar to use + QString m_minecraftVersion; - /// Release type - "release" or "snapshot" - QString m_minecraftVersionType; + /// Release type - "release" or "snapshot" + QString m_minecraftVersionType; - /// Assets type - "legacy" or a version ID - MojangAssetIndexInfo::Ptr m_minecraftAssets; + /// Assets type - "legacy" or a version ID + MojangAssetIndexInfo::Ptr m_minecraftAssets; - /** - * arguments that should be used for launching minecraft - * - * ex: "--username ${auth_player_name} --session ${auth_session} - * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" - */ - QString m_minecraftArguments; + /** + * arguments that should be used for launching minecraft + * + * ex: "--username ${auth_player_name} --session ${auth_session} + * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" + */ + QString m_minecraftArguments; - /// A list of all tweaker classes - QStringList m_tweakers; + /// A list of all tweaker classes + QStringList m_tweakers; - /// The main class to load first - QString m_mainClass; + /// The main class to load first + QString m_mainClass; - /// The applet class, for some very old minecraft releases - QString m_appletClass; + /// The applet class, for some very old minecraft releases + QString m_appletClass; - /// the list of libraries - QList m_libraries; + /// the list of libraries + QList m_libraries; - /// the main jar - LibraryPtr m_mainJar; + /// the main jar + LibraryPtr m_mainJar; - /// the list of libraries - QList m_nativeLibraries; + /// the list of libraries + QList m_nativeLibraries; - /// traits, collected from all the version files (version files can only add) - QSet m_traits; + /// traits, collected from all the version files (version files can only add) + QSet m_traits; - /// A list of jar mods. version files can add those. - QList m_jarMods; + /// A list of jar mods. version files can add those. + QList m_jarMods; - /// the list of mods - QList m_mods; + /// the list of mods + QList m_mods; - ProblemSeverity m_problemSeverity = ProblemSeverity::None; + ProblemSeverity m_problemSeverity = ProblemSeverity::None; }; diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp index cd1afde4..a56e8110 100644 --- a/api/logic/minecraft/Library.cpp +++ b/api/logic/minecraft/Library.cpp @@ -9,316 +9,313 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, - QStringList& native64, const QString &overridePath) const + QStringList& native64, const QString &overridePath) const { - bool local = isLocal(); - auto actualPath = [&](QString relPath) - { - QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); - if(local && !overridePath.isEmpty()) - { - QString fileName = out.fileName(); - auto fullPath = FS::PathCombine(overridePath, fileName); - qDebug() << fullPath; - QFileInfo fileinfo(fullPath); - if(fileinfo.exists()) - { - return fileinfo.absoluteFilePath(); - } - } - return out.absoluteFilePath(); - }; - QString raw_storage = storageSuffix(system); - if(isNative()) - { - if (raw_storage.contains("${arch}")) - { - auto nat32Storage = raw_storage; - nat32Storage.replace("${arch}", "32"); - auto nat64Storage = raw_storage; - nat64Storage.replace("${arch}", "64"); - native32 += actualPath(nat32Storage); - native64 += actualPath(nat64Storage); - } - else - { - native += actualPath(raw_storage); - } - } - else - { - jar += actualPath(raw_storage); - } + bool local = isLocal(); + auto actualPath = [&](QString relPath) + { + QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); + if(local && !overridePath.isEmpty()) + { + QString fileName = out.fileName(); + return QFileInfo(FS::PathCombine(overridePath, fileName)).absoluteFilePath(); + } + return out.absoluteFilePath(); + }; + QString raw_storage = storageSuffix(system); + if(isNative()) + { + if (raw_storage.contains("${arch}")) + { + auto nat32Storage = raw_storage; + nat32Storage.replace("${arch}", "32"); + auto nat64Storage = raw_storage; + nat64Storage.replace("${arch}", "64"); + native32 += actualPath(nat32Storage); + native64 += actualPath(nat64Storage); + } + else + { + native += actualPath(raw_storage); + } + } + else + { + jar += actualPath(raw_storage); + } } -QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class HttpMetaCache* cache, - QStringList& failedFiles, const QString & overridePath) const +QList< std::shared_ptr< NetAction > > Library::getDownloads( + OpSys system, + class HttpMetaCache* cache, + QStringList& failedLocalFiles, + const QString & overridePath +) const { - QList out; - bool isAlwaysStale = (hint() == "always-stale"); - bool local = isLocal(); - bool isForge = (hint() == "forge-pack-xz"); + QList out; + bool stale = isAlwaysStale(); + bool local = isLocal(); - auto add_download = [&](QString storage, QString url, QString sha1 = QString()) - { - auto entry = cache->resolveEntry("libraries", storage); - if(isAlwaysStale) - { - entry->setStale(true); - } - if (!entry->isStale()) - return true; - if(local) - { - if(!overridePath.isEmpty()) - { - QString fileName; - int position = storage.lastIndexOf('/'); - if(position == -1) - { - fileName = storage; - } - else - { - fileName = storage.mid(position); - } - auto fullPath = FS::PathCombine(overridePath, fileName); - QFileInfo fileinfo(fullPath); - if(fileinfo.exists()) - { - return true; - } - } - QFileInfo fileinfo(entry->getFullPath()); - if(!fileinfo.exists()) - { - failedFiles.append(entry->getFullPath()); - return false; - } - return true; - } - Net::Download::Options options; - if(isAlwaysStale) - { - options |= Net::Download::Option::AcceptLocalFiles; - } - if (isForge) - { - qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url; - out.append(ForgeXzDownload::make(storage, entry)); - } - else - { - if(sha1.size()) - { - auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); - auto dl = Net::Download::makeCached(url, entry, options); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url; - out.append(dl); - } - else - { - out.append(Net::Download::makeCached(url, entry, options)); - qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url; - } - } - return true; - }; + auto check_local_file = [&](QString storage) + { + QFileInfo fileinfo(storage); + QString fileName = fileinfo.fileName(); + auto fullPath = FS::PathCombine(overridePath, fileName); + QFileInfo localFileInfo(fullPath); + if(!localFileInfo.exists()) + { + failedLocalFiles.append(localFileInfo.filePath()); + return false; + } + return true; + }; - QString raw_storage = storageSuffix(system); - if(m_mojangDownloads) - { - if(isNative()) - { - if(m_nativeClassifiers.contains(system)) - { - auto nativeClassifier = m_nativeClassifiers[system]; - if(nativeClassifier.contains("${arch}")) - { - auto nat32Classifier = nativeClassifier; - nat32Classifier.replace("${arch}", "32"); - auto nat64Classifier = nativeClassifier; - nat64Classifier.replace("${arch}", "64"); - auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); - if(nat32info) - { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "32"); - add_download(cooked_storage, nat32info->url, nat32info->sha1); - } - auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); - if(nat64info) - { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "64"); - add_download(cooked_storage, nat64info->url, nat64info->sha1); - } - } - else - { - auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); - if(info) - { - add_download(raw_storage, info->url, info->sha1); - } - } - } - else - { - qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS"; - } - } - else - { - if(m_mojangDownloads->artifact) - { - auto artifact = m_mojangDownloads->artifact; - add_download(raw_storage, artifact->url, artifact->sha1); - } - else - { - qDebug() << "Ignoring java library" << m_name << "because it has no artifact"; - } - } - } - else - { - auto raw_dl = [&](){ - if (!m_absoluteURL.isEmpty()) - { - return m_absoluteURL; - } + auto add_download = [&](QString storage, QString url, QString sha1) + { + if(local) + { + return check_local_file(storage); + } + auto entry = cache->resolveEntry("libraries", storage); + if(stale) + { + entry->setStale(true); + } + if (!entry->isStale()) + return true; + Net::Download::Options options; + if(stale) + { + options |= Net::Download::Option::AcceptLocalFiles; + } + if (isForge()) + { + qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url; + out.append(ForgeXzDownload::make(url, storage, entry)); + } + else + { + if(sha1.size()) + { + auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); + auto dl = Net::Download::makeCached(url, entry, options); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url; + out.append(dl); + } + else + { + out.append(Net::Download::makeCached(url, entry, options)); + qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url; + } + } + return true; + }; - if (m_repositoryURL.isEmpty()) - { - return QString("https://" + URLConstants::LIBRARY_BASE) + raw_storage; - } + QString raw_storage = storageSuffix(system); + if(m_mojangDownloads) + { + if(isNative()) + { + if(m_nativeClassifiers.contains(system)) + { + auto nativeClassifier = m_nativeClassifiers[system]; + if(nativeClassifier.contains("${arch}")) + { + auto nat32Classifier = nativeClassifier; + nat32Classifier.replace("${arch}", "32"); + auto nat64Classifier = nativeClassifier; + nat64Classifier.replace("${arch}", "64"); + auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); + if(nat32info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "32"); + add_download(cooked_storage, nat32info->url, nat32info->sha1); + } + auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); + if(nat64info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "64"); + add_download(cooked_storage, nat64info->url, nat64info->sha1); + } + } + else + { + auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); + if(info) + { + add_download(raw_storage, info->url, info->sha1); + } + } + } + else + { + qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS"; + } + } + else + { + if(m_mojangDownloads->artifact) + { + auto artifact = m_mojangDownloads->artifact; + add_download(raw_storage, artifact->url, artifact->sha1); + } + else + { + qDebug() << "Ignoring java library" << m_name << "because it has no artifact"; + } + } + } + else + { + auto raw_dl = [&]() + { + if (!m_absoluteURL.isEmpty()) + { + return m_absoluteURL; + } - if(m_repositoryURL.endsWith('/')) - { - return m_repositoryURL + raw_storage; - } - else - { - return m_repositoryURL + QChar('/') + raw_storage; - } - }(); - if (raw_storage.contains("${arch}")) - { - QString cooked_storage = raw_storage; - QString cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); - cooked_storage = raw_storage; - cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); - } - else - { - add_download(raw_storage, raw_dl); - } - } - return out; + if (m_repositoryURL.isEmpty()) + { + return URLConstants::LIBRARY_BASE + raw_storage; + } + + if(m_repositoryURL.endsWith('/')) + { + return m_repositoryURL + raw_storage; + } + else + { + return m_repositoryURL + QChar('/') + raw_storage; + } + }(); + if (raw_storage.contains("${arch}")) + { + QString cooked_storage = raw_storage; + QString cooked_dl = raw_dl; + add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"), QString()); + cooked_storage = raw_storage; + cooked_dl = raw_dl; + add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"), QString()); + } + else + { + add_download(raw_storage, raw_dl, QString()); + } + } + return out; } bool Library::isActive() const { - bool result = true; - if (m_rules.empty()) - { - result = true; - } - else - { - RuleAction ruleResult = Disallow; - for (auto rule : m_rules) - { - RuleAction temp = rule->apply(this); - if (temp != Defer) - ruleResult = temp; - } - result = result && (ruleResult == Allow); - } - if (isNative()) - { - result = result && m_nativeClassifiers.contains(currentSystem); - } - return result; + bool result = true; + if (m_rules.empty()) + { + result = true; + } + else + { + RuleAction ruleResult = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + ruleResult = temp; + } + result = result && (ruleResult == Allow); + } + if (isNative()) + { + result = result && m_nativeClassifiers.contains(currentSystem); + } + return result; } bool Library::isLocal() const { - return m_hint == "local"; + return m_hint == "local"; +} + +bool Library::isAlwaysStale() const +{ + return m_hint == "always-stale"; +} + +bool Library::isForge() const +{ + return m_hint == "forge-pack-xz"; } void Library::setStoragePrefix(QString prefix) { - m_storagePrefix = prefix; + m_storagePrefix = prefix; } QString Library::defaultStoragePrefix() { - return "libraries/"; + return "libraries/"; } QString Library::storagePrefix() const { - if(m_storagePrefix.isEmpty()) - { - return defaultStoragePrefix(); - } - return m_storagePrefix; + if(m_storagePrefix.isEmpty()) + { + return defaultStoragePrefix(); + } + return m_storagePrefix; } QString Library::filename(OpSys system) const { - if(!m_filename.isEmpty()) - { - return m_filename; - } - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.getFileName(); - } + if(!m_filename.isEmpty()) + { + return m_filename; + } + // non-native? use only the gradle specifier + if (!isNative()) + { + return m_name.getFileName(); + } - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.getFileName(); + // otherwise native, override classifiers. Mojang HACK! + GradleSpecifier nativeSpec = m_name; + if (m_nativeClassifiers.contains(system)) + { + nativeSpec.setClassifier(m_nativeClassifiers[system]); + } + else + { + nativeSpec.setClassifier("INVALID"); + } + return nativeSpec.getFileName(); } QString Library::displayName(OpSys system) const { - if(!m_displayname.isEmpty()) - return m_displayname; - return filename(system); + if(!m_displayname.isEmpty()) + return m_displayname; + return filename(system); } QString Library::storageSuffix(OpSys system) const { - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.toPath(m_filename); - } + // non-native? use only the gradle specifier + if (!isNative()) + { + return m_name.toPath(m_filename); + } - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.toPath(m_filename); + // otherwise native, override classifiers. Mojang HACK! + GradleSpecifier nativeSpec = m_name; + if (m_nativeClassifiers.contains(system)) + { + nativeSpec.setClassifier(m_nativeClassifiers[system]); + } + else + { + nativeSpec.setClassifier("INVALID"); + } + return nativeSpec.toPath(m_filename); } diff --git a/api/logic/minecraft/Library.h b/api/logic/minecraft/Library.h index 9577de33..014f6745 100644 --- a/api/logic/minecraft/Library.h +++ b/api/logic/minecraft/Library.h @@ -24,191 +24,197 @@ typedef std::shared_ptr LibraryPtr; class MULTIMC_LOGIC_EXPORT Library { - friend class OneSixVersionFormat; - friend class MojangVersionFormat; - friend class LibraryTest; + friend class OneSixVersionFormat; + friend class MojangVersionFormat; + friend class LibraryTest; public: - Library() - { - } - Library(const QString &name) - { - m_name = name; - } - /// limited copy without some data. TODO: why? - static LibraryPtr limitedCopy(LibraryPtr base) - { - auto newlib = std::make_shared(); - newlib->m_name = base->m_name; - newlib->m_repositoryURL = base->m_repositoryURL; - newlib->m_hint = base->m_hint; - newlib->m_absoluteURL = base->m_absoluteURL; - newlib->m_extractExcludes = base->m_extractExcludes; - newlib->m_nativeClassifiers = base->m_nativeClassifiers; - newlib->m_rules = base->m_rules; - newlib->m_storagePrefix = base->m_storagePrefix; - newlib->m_mojangDownloads = base->m_mojangDownloads; - newlib->m_filename = base->m_filename; - return newlib; - } + Library() + { + } + Library(const QString &name) + { + m_name = name; + } + /// limited copy without some data. TODO: why? + static LibraryPtr limitedCopy(LibraryPtr base) + { + auto newlib = std::make_shared(); + newlib->m_name = base->m_name; + newlib->m_repositoryURL = base->m_repositoryURL; + newlib->m_hint = base->m_hint; + newlib->m_absoluteURL = base->m_absoluteURL; + newlib->m_extractExcludes = base->m_extractExcludes; + newlib->m_nativeClassifiers = base->m_nativeClassifiers; + newlib->m_rules = base->m_rules; + newlib->m_storagePrefix = base->m_storagePrefix; + newlib->m_mojangDownloads = base->m_mojangDownloads; + newlib->m_filename = base->m_filename; + return newlib; + } public: /* methods */ - /// Returns the raw name field - const GradleSpecifier & rawName() const - { - return m_name; - } - - void setRawName(const GradleSpecifier & spec) - { - m_name = spec; - } - - void setClassifier(const QString & spec) - { - m_name.setClassifier(spec); - } - - /// returns the full group and artifact prefix - QString artifactPrefix() const - { - return m_name.artifactPrefix(); - } - - /// get the artifact ID - QString artifactId() const - { - return m_name.artifactId(); - } - - /// get the artifact version - QString version() const - { - return m_name.version(); - } - - /// Returns true if the library is native - bool isNative() const - { - return m_nativeClassifiers.size() != 0; - } - - void setStoragePrefix(QString prefix = QString()); - - /// Set the url base for downloads - void setRepositoryURL(const QString &base_url) - { - m_repositoryURL = base_url; - } - - void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, - QStringList & native32, QStringList & native64, const QString & overridePath) const; - - void setAbsoluteUrl(const QString &absolute_url) - { - m_absoluteURL = absolute_url; - } - - void setFilename(const QString &filename) - { - m_filename = filename; - } - - /// Get the file name of the library - QString filename(OpSys system) const; - - // DEPRECATED: set a display name, used by jar mods only - void setDisplayName(const QString & displayName) - { - m_displayname = displayName; - } - - /// Get the file name of the library - QString displayName(OpSys system) const; - - void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) - { - m_mojangDownloads = info; - } - - void setHint(const QString &hint) - { - m_hint = hint; - } - - /// Set the load rules - void setRules(QList> rules) - { - m_rules = rules; - } - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive() const; - - /// Returns true if the library is contained in an instance and false if it is shared - bool isLocal() const; - - // Get a list of downloads for this library - QList getDownloads(OpSys system, class HttpMetaCache * cache, - QStringList & failedFiles, const QString & overridePath) const; + /// Returns the raw name field + const GradleSpecifier & rawName() const + { + return m_name; + } + + void setRawName(const GradleSpecifier & spec) + { + m_name = spec; + } + + void setClassifier(const QString & spec) + { + m_name.setClassifier(spec); + } + + /// returns the full group and artifact prefix + QString artifactPrefix() const + { + return m_name.artifactPrefix(); + } + + /// get the artifact ID + QString artifactId() const + { + return m_name.artifactId(); + } + + /// get the artifact version + QString version() const + { + return m_name.version(); + } + + /// Returns true if the library is native + bool isNative() const + { + return m_nativeClassifiers.size() != 0; + } + + void setStoragePrefix(QString prefix = QString()); + + /// Set the url base for downloads + void setRepositoryURL(const QString &base_url) + { + m_repositoryURL = base_url; + } + + void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, + QStringList & native32, QStringList & native64, const QString & overridePath) const; + + void setAbsoluteUrl(const QString &absolute_url) + { + m_absoluteURL = absolute_url; + } + + void setFilename(const QString &filename) + { + m_filename = filename; + } + + /// Get the file name of the library + QString filename(OpSys system) const; + + // DEPRECATED: set a display name, used by jar mods only + void setDisplayName(const QString & displayName) + { + m_displayname = displayName; + } + + /// Get the file name of the library + QString displayName(OpSys system) const; + + void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) + { + m_mojangDownloads = info; + } + + void setHint(const QString &hint) + { + m_hint = hint; + } + + /// Set the load rules + void setRules(QList> rules) + { + m_rules = rules; + } + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + + /// Returns true if the library is contained in an instance and false if it is shared + bool isLocal() const; + + /// Returns true if the library is to always be checked for updates + bool isAlwaysStale() const; + + /// Return true if the library requires forge XZ hacks + bool isForge() const; + + // Get a list of downloads for this library + QList getDownloads(OpSys system, class HttpMetaCache * cache, + QStringList & failedLocalFiles, const QString & overridePath) const; private: /* methods */ - /// the default storage prefix used by MultiMC - static QString defaultStoragePrefix(); + /// the default storage prefix used by MultiMC + static QString defaultStoragePrefix(); - /// Get the prefix - root of the storage to be used - QString storagePrefix() const; + /// Get the prefix - root of the storage to be used + QString storagePrefix() const; - /// Get the relative file path where the library should be saved - QString storageSuffix(OpSys system) const; + /// Get the relative file path where the library should be saved + QString storageSuffix(OpSys system) const; - QString hint() const - { - return m_hint; - } + QString hint() const + { + return m_hint; + } protected: /* data */ - /// the basic gradle dependency specifier. - GradleSpecifier m_name; + /// the basic gradle dependency specifier. + GradleSpecifier m_name; - /// DEPRECATED URL prefix of the maven repo where the file can be downloaded - QString m_repositoryURL; + /// DEPRECATED URL prefix of the maven repo where the file can be downloaded + QString m_repositoryURL; - /// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined - QString m_absoluteURL; + /// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined + QString m_absoluteURL; - /// MultiMC extension - filename override - QString m_filename; + /// MultiMC extension - filename override + QString m_filename; - /// DEPRECATED MultiMC extension - display name - QString m_displayname; + /// DEPRECATED MultiMC extension - display name + QString m_displayname; - /** - * MultiMC-specific type hint - modifies how the library is treated - */ - QString m_hint; + /** + * MultiMC-specific type hint - modifies how the library is treated + */ + QString m_hint; - /** - * storage - by default the local libraries folder in multimc, but could be elsewhere - * MultiMC specific, because of FTB. - */ - QString m_storagePrefix; + /** + * storage - by default the local libraries folder in multimc, but could be elsewhere + * MultiMC specific, because of FTB. + */ + QString m_storagePrefix; - /// true if the library had an extract/excludes section (even empty) - bool m_hasExcludes = false; + /// true if the library had an extract/excludes section (even empty) + bool m_hasExcludes = false; - /// a list of files that shouldn't be extracted from the library - QStringList m_extractExcludes; + /// a list of files that shouldn't be extracted from the library + QStringList m_extractExcludes; - /// native suffixes per OS - QMap m_nativeClassifiers; + /// native suffixes per OS + QMap m_nativeClassifiers; - /// true if the library had a rules section (even empty) - bool applyRules = false; + /// true if the library had a rules section (even empty) + bool applyRules = false; - /// rules associated with the library - QList> m_rules; + /// rules associated with the library + QList> m_rules; - /// MOJANG: container with Mojang style download info - MojangLibraryDownloadInfo::Ptr m_mojangDownloads; + /// MOJANG: container with Mojang style download info + MojangLibraryDownloadInfo::Ptr m_mojangDownloads; }; diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp index 1aac951b..c3d6150d 100644 --- a/api/logic/minecraft/Library_test.cpp +++ b/api/logic/minecraft/Library_test.cpp @@ -9,261 +9,261 @@ class LibraryTest : public QObject { - Q_OBJECT + Q_OBJECT private: - LibraryPtr readMojangJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file); - } - // get absolute path to expected storage, assuming default cache prefix - QStringList getStorage(QString relative) - { - return {FS::PathCombine(cache->getBasePath("libraries"), relative)}; - } + LibraryPtr readMojangJson(const char *file) + { + auto path = QFINDTESTDATA(file); + QFile jsonFile(path); + jsonFile.open(QIODevice::ReadOnly); + auto data = jsonFile.readAll(); + jsonFile.close(); + return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file); + } + // get absolute path to expected storage, assuming default cache prefix + QStringList getStorage(QString relative) + { + return {FS::PathCombine(cache->getBasePath("libraries"), relative)}; + } private slots: - void initTestCase() - { - cache.reset(new HttpMetaCache()); - cache->addBase("libraries", QDir("libraries").absolutePath()); - dataDir = QDir("data").absolutePath(); - } - void test_legacy() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.artifactPrefix(), QString("test.package:testname")); - QCOMPARE(test.isNative(), false); + void initTestCase() + { + cache.reset(new HttpMetaCache()); + cache->addBase("libraries", QDir("libraries").absolutePath()); + dataDir = QDir("data").absolutePath(); + } + void test_legacy() + { + Library test("test.package:testname:testversion"); + QCOMPARE(test.artifactPrefix(), QString("test.package:testname")); + QCOMPARE(test.isNative(), false); - QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_url() - { - QStringList failedFiles; - Library test("test.package:testname:testversion"); - test.setRepositoryURL("file://foo/bar"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 1); - QCOMPARE(failedFiles, {}); - NetActionPtr dl = downloads[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); - } - void test_legacy_url_local_broken() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 0); - QCOMPARE(failedFiles, getStorage("test/package/testname/testversion/testname-testversion.jar")); - } - void test_legacy_url_local_override() - { - Library test("com.paulscode:codecwav:20101023"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); - QCOMPARE(downloads.size(), 0); - qDebug() << failedFiles; - QCOMPARE(failedFiles.size(), 0); + QStringList jar, native, native32, native64; + test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString()); + QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar")); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + void test_legacy_url() + { + QStringList failedFiles; + Library test("test.package:testname:testversion"); + test.setRepositoryURL("file://foo/bar"); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); + QCOMPARE(downloads.size(), 1); + QCOMPARE(failedFiles, {}); + NetActionPtr dl = downloads[0]; + QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); + } + void test_legacy_url_local_broken() + { + Library test("test.package:testname:testversion"); + QCOMPARE(test.isNative(), false); + QStringList failedFiles; + test.setHint("local"); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); + QCOMPARE(downloads.size(), 0); + QCOMPARE(failedFiles, {"testname-testversion.jar"}); + } + void test_legacy_url_local_override() + { + Library test("com.paulscode:codecwav:20101023"); + QCOMPARE(test.isNative(), false); + QStringList failedFiles; + test.setHint("local"); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); + QCOMPARE(downloads.size(), 0); + qDebug() << failedFiles; + QCOMPARE(failedFiles.size(), 0); - QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_native() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - auto dl = dls[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); - } - } - void test_legacy_native_arch() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}"; - test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); - } - } - void test_legacy_native_arch_local_override() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.setHint("local"); - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()}); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")}); - } - } - void test_onenine() - { - auto test = readMojangJson("data/lib-simple.json"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - } - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_local_override() - { - auto test = readMojangJson("data/lib-simple.json"); - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_native() - { - auto test = readMojangJson("data/lib-native.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, QStringList()); - QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); - } - void test_onenine_native_arch() - { - auto test = readMojangJson("data/lib-native-arch.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - QStringList failedFiles; - auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - } + QStringList jar, native, native32, native64; + test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + void test_legacy_native() + { + Library test("test.package:testname:testversion"); + test.m_nativeClassifiers[OpSys::Os_Linux]="linux"; + QCOMPARE(test.isNative(), true); + test.setRepositoryURL("file://foo/bar"); + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar")); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 1); + QCOMPARE(failedFiles, {}); + auto dl = dls[0]; + QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); + } + } + void test_legacy_native_arch() + { + Library test("test.package:testname:testversion"); + test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; + test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}"; + test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}"; + QCOMPARE(test.isNative(), true); + test.setRepositoryURL("file://foo/bar"); + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar")); + QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); + } + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar")); + QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar")); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); + } + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar")); + QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar")); + QStringList failedFiles; + auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); + } + } + void test_legacy_native_arch_local_override() + { + Library test("test.package:testname:testversion"); + test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; + test.setHint("local"); + QCOMPARE(test.isNative(), true); + test.setRepositoryURL("file://foo/bar"); + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()}); + QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()}); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + QCOMPARE(dls.size(), 0); + QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"}); + } + } + void test_onenine() + { + auto test = readMojangJson("data/lib-simple.json"); + { + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar")); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + { + QStringList failedFiles; + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 1); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); + } + test->setHint("local"); + { + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + { + QStringList failedFiles; + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + QCOMPARE(dls.size(), 0); + QCOMPARE(failedFiles, {}); + } + } + void test_onenine_local_override() + { + auto test = readMojangJson("data/lib-simple.json"); + test->setHint("local"); + { + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + { + QStringList failedFiles; + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + QCOMPARE(dls.size(), 0); + QCOMPARE(failedFiles, {}); + } + } + void test_onenine_native() + { + auto test = readMojangJson("data/lib-native.json"); + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + QCOMPARE(jar, QStringList()); + QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + QStringList failedFiles; + auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 1); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); + } + void test_onenine_native_arch() + { + auto test = readMojangJson("data/lib-native-arch.json"); + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); + QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); + QStringList failedFiles; + auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); + } private: - std::unique_ptr cache; - QString dataDir; + std::unique_ptr cache; + QString dataDir; }; QTEST_GUILESS_MAIN(LibraryTest) diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index 0fffc99f..28073edf 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -20,12 +20,14 @@ #include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ClaimAccount.h" +#include "minecraft/launch/ReconstructAssets.h" +#include "minecraft/launch/ScanModFolders.h" #include "java/launch/CheckJava.h" #include "java/JavaUtils.h" #include "meta/Index.h" #include "meta/VersionList.h" -#include "ModList.h" +#include "mod/ModFolderModel.h" #include "WorldList.h" #include "icons/IIconList.h" @@ -35,6 +37,7 @@ #include "AssetsUtils.h" #include "MinecraftUpdate.h" #include "MinecraftLoadAndCheck.h" +#include #define IBUS "@im=ibus" @@ -42,909 +45,934 @@ // if either of the settings {a, b} is true, this also resolves to true class OrSetting : public Setting { - Q_OBJECT + Q_OBJECT public: - OrSetting(QString id, std::shared_ptr a, std::shared_ptr b) - :Setting({id}, false), m_a(a), m_b(b) - { - } - virtual QVariant get() const - { - bool a = m_a->get().toBool(); - bool b = m_b->get().toBool(); - return a || b; - } - virtual void reset() {} - virtual void set(QVariant value) {} + OrSetting(QString id, std::shared_ptr a, std::shared_ptr b) + :Setting({id}, false), m_a(a), m_b(b) + { + } + virtual QVariant get() const + { + bool a = m_a->get().toBool(); + bool b = m_b->get().toBool(); + return a || b; + } + virtual void reset() {} + virtual void set(QVariant value) {} private: - std::shared_ptr m_a; - std::shared_ptr m_b; + std::shared_ptr m_a; + std::shared_ptr m_b; }; MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) -{ - // Java Settings - auto javaOverride = m_settings->registerSetting("OverrideJava", false); - auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); - auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); - - // combinations - auto javaOrLocation = std::make_shared("JavaOrLocationOverride", javaOverride, locationOverride); - auto javaOrArgs = std::make_shared("JavaOrArgsOverride", javaOverride, argsOverride); - - m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); - m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); - - // special! - m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); - - // Window Size - auto windowSetting = m_settings->registerSetting("OverrideWindow", false); - m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); - - // Memory - auto memorySetting = m_settings->registerSetting("OverrideMemory", false); - m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); - - // Minecraft launch method - auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); - m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); - - // DEPRECATED: Read what versions the user configuration thinks should be used - m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); - m_settings->registerSetting("LWJGLVersion", ""); - m_settings->registerSetting("ForgeVersion", ""); - m_settings->registerSetting("LiteloaderVersion", ""); - - m_components.reset(new ComponentList(this)); - m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString()); - auto setting = m_settings->getSetting("LWJGLVersion"); - m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString()); - m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString()); - m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString()); -} - -void MinecraftInstance::init() -{ + : BaseInstance(globalSettings, settings, rootDir) +{ + // Java Settings + auto javaOverride = m_settings->registerSetting("OverrideJava", false); + auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); + auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); + + // combinations + auto javaOrLocation = std::make_shared("JavaOrLocationOverride", javaOverride, locationOverride); + auto javaOrArgs = std::make_shared("JavaOrArgsOverride", javaOverride, argsOverride); + + m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); + m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); + + // special! + m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); + m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); + m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); + + // Window Size + auto windowSetting = m_settings->registerSetting("OverrideWindow", false); + m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); + + // Memory + auto memorySetting = m_settings->registerSetting("OverrideMemory", false); + m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); + m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); + m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); + + // Minecraft launch method + auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); + m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); + + // DEPRECATED: Read what versions the user configuration thinks should be used + m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); + m_settings->registerSetting("LWJGLVersion", ""); + m_settings->registerSetting("ForgeVersion", ""); + m_settings->registerSetting("LiteloaderVersion", ""); + + m_components.reset(new ComponentList(this)); + m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString()); + auto setting = m_settings->getSetting("LWJGLVersion"); + m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString()); + m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString()); + m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString()); } void MinecraftInstance::saveNow() { - m_components->saveNow(); + m_components->saveNow(); } QString MinecraftInstance::typeName() const { - return "Minecraft"; + return "Minecraft"; } std::shared_ptr MinecraftInstance::getComponentList() const { - return m_components; + return m_components; } QSet MinecraftInstance::traits() const { - auto components = getComponentList(); - if (!components) - { - return {"version-incomplete"}; - } - auto profile = components->getProfile(); - if (!profile) - { - return {"version-incomplete"}; - } - return profile->getTraits(); + auto components = getComponentList(); + if (!components) + { + return {"version-incomplete"}; + } + auto profile = components->getProfile(); + if (!profile) + { + return {"version-incomplete"}; + } + return profile->getTraits(); } -QString MinecraftInstance::minecraftRoot() const +QString MinecraftInstance::gameRoot() const { - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); + QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); + if (mcDir.exists() && !dotMCDir.exists()) + return mcDir.filePath(); + else + return dotMCDir.filePath(); } QString MinecraftInstance::binRoot() const { - return FS::PathCombine(minecraftRoot(), "bin"); + return FS::PathCombine(gameRoot(), "bin"); } QString MinecraftInstance::getNativePath() const { - QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); - return natives_dir.absolutePath(); + QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); + return natives_dir.absolutePath(); } QString MinecraftInstance::getLocalLibraryPath() const { - QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); - return libraries_dir.absolutePath(); + QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); + return libraries_dir.absolutePath(); +} + +QString MinecraftInstance::jarModsDir() const +{ + QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/")); + return jarmods_dir.absolutePath(); } QString MinecraftInstance::loaderModsDir() const { - return FS::PathCombine(minecraftRoot(), "mods"); + return FS::PathCombine(gameRoot(), "mods"); +} + +QString MinecraftInstance::modsCacheLocation() const +{ + return FS::PathCombine(instanceRoot(), "mods.cache"); } QString MinecraftInstance::coreModsDir() const { - return FS::PathCombine(minecraftRoot(), "coremods"); + return FS::PathCombine(gameRoot(), "coremods"); } QString MinecraftInstance::resourcePacksDir() const { - return FS::PathCombine(minecraftRoot(), "resourcepacks"); + return FS::PathCombine(gameRoot(), "resourcepacks"); } QString MinecraftInstance::texturePacksDir() const { - return FS::PathCombine(minecraftRoot(), "texturepacks"); + return FS::PathCombine(gameRoot(), "texturepacks"); } QString MinecraftInstance::instanceConfigFolder() const { - return FS::PathCombine(minecraftRoot(), "config"); + return FS::PathCombine(gameRoot(), "config"); } -QString MinecraftInstance::jarModsDir() const +QString MinecraftInstance::libDir() const { - return FS::PathCombine(instanceRoot(), "jarmods"); + return FS::PathCombine(gameRoot(), "lib"); } -QString MinecraftInstance::libDir() const +QString MinecraftInstance::worldDir() const { - return FS::PathCombine(minecraftRoot(), "lib"); + return FS::PathCombine(gameRoot(), "saves"); } -QString MinecraftInstance::worldDir() const +QString MinecraftInstance::resourcesDir() const { - return FS::PathCombine(minecraftRoot(), "saves"); + return FS::PathCombine(gameRoot(), "resources"); } QDir MinecraftInstance::librariesPath() const { - return QDir::current().absoluteFilePath("libraries"); + return QDir::current().absoluteFilePath("libraries"); } QDir MinecraftInstance::jarmodsPath() const { - return QDir(jarModsDir()); + return QDir(jarModsDir()); } QDir MinecraftInstance::versionsPath() const { - return QDir::current().absoluteFilePath("versions"); + return QDir::current().absoluteFilePath("versions"); } QStringList MinecraftInstance::getClassPath() const { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return jars; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return jars; } QString MinecraftInstance::getMainClass() const { - auto profile = m_components->getProfile(); - return profile->getMainClass(); + auto profile = m_components->getProfile(); + return profile->getMainClass(); } QStringList MinecraftInstance::getNativeJars() const { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return nativeJars; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return nativeJars; } QStringList MinecraftInstance::extraArguments() const { - auto list = BaseInstance::extraArguments(); - auto version = getComponentList(); - if (!version) - return list; - auto jarMods = getJarMods(); - if (!jarMods.isEmpty()) - { - list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", - "-Dfml.ignorePatchDiscrepancies=true"}); - } - return list; + auto list = BaseInstance::extraArguments(); + auto version = getComponentList(); + if (!version) + return list; + auto jarMods = getJarMods(); + if (!jarMods.isEmpty()) + { + list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", + "-Dfml.ignorePatchDiscrepancies=true"}); + } + return list; } QStringList MinecraftInstance::javaArguments() const { - QStringList args; + QStringList args; - // custom args go first. we want to override them if we have our own here. - args.append(extraArguments()); + // custom args go first. we want to override them if we have our own here. + args.append(extraArguments()); - // OSX dock icon and name + // OSX dock icon and name #ifdef Q_OS_MAC - args << "-Xdock:icon=icon.png"; - args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); + args << "-Xdock:icon=icon.png"; + args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); #endif - auto traits_ = traits(); - // HACK: fix issues on macOS with 1.13 snapshots - // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them + auto traits_ = traits(); + // HACK: fix issues on macOS with 1.13 snapshots + // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them #ifdef Q_OS_MAC - if(traits_.contains("FirstThreadOnMacOS")) - { - args << QString("-XstartOnFirstThread"); - } + if(traits_.contains("FirstThreadOnMacOS")) + { + args << QString("-XstartOnFirstThread"); + } #endif - // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 + // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 #ifdef Q_OS_WIN32 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); + args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" + "minecraft.exe.heapdump"); #endif - int min = settings()->get("MinMemAlloc").toInt(); - int max = settings()->get("MaxMemAlloc").toInt(); - if(min < max) - { - args << QString("-Xms%1m").arg(min); - args << QString("-Xmx%1m").arg(max); - } - else - { - args << QString("-Xms%1m").arg(max); - args << QString("-Xmx%1m").arg(min); - } - - // No PermGen in newer java. - JavaVersion javaVersion = getJavaVersion(); - if(javaVersion.requiresPermGen()) - { - auto permgen = settings()->get("PermGen").toInt(); - if (permgen != 64) - { - args << QString("-XX:PermSize=%1m").arg(permgen); - } - } - - args << "-Duser.language=en"; - - return args; + int min = settings()->get("MinMemAlloc").toInt(); + int max = settings()->get("MaxMemAlloc").toInt(); + if(min < max) + { + args << QString("-Xms%1m").arg(min); + args << QString("-Xmx%1m").arg(max); + } + else + { + args << QString("-Xms%1m").arg(max); + args << QString("-Xmx%1m").arg(min); + } + + // No PermGen in newer java. + JavaVersion javaVersion = getJavaVersion(); + if(javaVersion.requiresPermGen()) + { + auto permgen = settings()->get("PermGen").toInt(); + if (permgen != 64) + { + args << QString("-XX:PermSize=%1m").arg(permgen); + } + } + + args << "-Duser.language=en"; + + return args; } QMap MinecraftInstance::getVariables() const { - QMap out; - out.insert("INST_NAME", name()); - out.insert("INST_ID", id()); - out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); - out.insert("INST_MC_DIR", QDir(minecraftRoot()).absolutePath()); - out.insert("INST_JAVA", settings()->get("JavaPath").toString()); - out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); - return out; + QMap out; + out.insert("INST_NAME", name()); + out.insert("INST_ID", id()); + out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); + out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath()); + out.insert("INST_JAVA", settings()->get("JavaPath").toString()); + out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); + return out; } QProcessEnvironment MinecraftInstance::createEnvironment() { - // prepare the process environment - QProcessEnvironment env = CleanEnviroment(); + // prepare the process environment + QProcessEnvironment env = CleanEnviroment(); - // export some infos - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - env.insert(it.key(), it.value()); - } - return env; + // export some infos + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + env.insert(it.key(), it.value()); + } + return env; } static QString replaceTokensIn(QString text, QMap with) { - QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) - { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); - auto iter = with.find(key); - if (iter != with.end()) - { - result.append(*iter); - } - head += token_regexp.matchedLength(); - tail = head; - } - result.append(text.mid(tail)); - return result; + QString result; + QRegExp token_regexp("\\$\\{(.+)\\}"); + token_regexp.setMinimal(true); + QStringList list; + int tail = 0; + int head = 0; + while ((head = token_regexp.indexIn(text, head)) != -1) + { + result.append(text.mid(tail, head - tail)); + QString key = token_regexp.cap(1); + auto iter = with.find(key); + if (iter != with.end()) + { + result.append(*iter); + } + head += token_regexp.matchedLength(); + tail = head; + } + result.append(text.mid(tail)); + return result; } QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const { - auto profile = m_components->getProfile(); - QString args_pattern = profile->getMinecraftArguments(); - for (auto tweaker : profile->getTweakers()) - { - args_pattern += " --tweakClass " + tweaker; - } - - QMap token_mapping; - // yggdrasil! - if(session) - { - token_mapping["auth_username"] = session->username; - token_mapping["auth_session"] = session->session; - token_mapping["auth_access_token"] = session->access_token; - token_mapping["auth_player_name"] = session->player_name; - token_mapping["auth_uuid"] = session->uuid; - token_mapping["user_properties"] = session->serializeUserProperties(); - token_mapping["user_type"] = session->user_type; - } - - // blatant self-promotion. - token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; - - token_mapping["version_type"] = profile->getMinecraftVersionType(); - - QString absRootDir = QDir(minecraftRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - auto assets = profile->getMinecraftAssets(); - // FIXME: this is wrong and should be run as an async task - token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); - - // 1.7.3+ assets tokens - token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = assets->id; - - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); - for (int i = 0; i < parts.length(); i++) - { - parts[i] = replaceTokensIn(parts[i], token_mapping); - } - return parts; + auto profile = m_components->getProfile(); + QString args_pattern = profile->getMinecraftArguments(); + for (auto tweaker : profile->getTweakers()) + { + args_pattern += " --tweakClass " + tweaker; + } + + QMap token_mapping; + // yggdrasil! + if(session) + { + token_mapping["auth_username"] = session->username; + token_mapping["auth_session"] = session->session; + token_mapping["auth_access_token"] = session->access_token; + token_mapping["auth_player_name"] = session->player_name; + token_mapping["auth_uuid"] = session->uuid; + token_mapping["user_properties"] = session->serializeUserProperties(); + token_mapping["user_type"] = session->user_type; + } + + // blatant self-promotion. + token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; + + token_mapping["version_type"] = profile->getMinecraftVersionType(); + + QString absRootDir = QDir(gameRoot()).absolutePath(); + token_mapping["game_directory"] = absRootDir; + QString absAssetsDir = QDir("assets/").absolutePath(); + auto assets = profile->getMinecraftAssets(); + token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath(); + + // 1.7.3+ assets tokens + token_mapping["assets_root"] = absAssetsDir; + token_mapping["assets_index_name"] = assets->id; + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) + { + parts[i] = replaceTokensIn(parts[i], token_mapping); + } + return parts; } QString MinecraftInstance::createLaunchScript(AuthSessionPtr session) { - QString launchScript; - - if (!m_components) - return QString(); - auto profile = m_components->getProfile(); - if(!profile) - return QString(); - - auto mainClass = getMainClass(); - if (!mainClass.isEmpty()) - { - launchScript += "mainClass " + mainClass + "\n"; - } - auto appletClass = profile->getAppletClass(); - if (!appletClass.isEmpty()) - { - launchScript += "appletClass " + appletClass + "\n"; - } - - // generic minecraft params - for (auto param : processMinecraftArgs(session)) - { - launchScript += "param " + param + "\n"; - } - - // window size, title and state, legacy - { - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - windowParams = "max"; - else - windowParams = QString("%1x%2") - .arg(settings()->get("MinecraftWinWidth").toInt()) - .arg(settings()->get("MinecraftWinHeight").toInt()); - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "windowParams " + windowParams + "\n"; - } - - // legacy auth - if(session) - { - launchScript += "userName " + session->player_name + "\n"; - launchScript += "sessionId " + session->session + "\n"; - } - - // libraries and class path. - { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - for(auto file: jars) - { - launchScript += "cp " + file + "\n"; - } - for(auto file: nativeJars) - { - launchScript += "ext " + file + "\n"; - } - launchScript += "natives " + getNativePath() + "\n"; - } - - for (auto trait : profile->getTraits()) - { - launchScript += "traits " + trait + "\n"; - } - launchScript += "launcher onesix\n"; - // qDebug() << "Generated launch script:" << launchScript; - return launchScript; + QString launchScript; + + if (!m_components) + return QString(); + auto profile = m_components->getProfile(); + if(!profile) + return QString(); + + auto mainClass = getMainClass(); + if (!mainClass.isEmpty()) + { + launchScript += "mainClass " + mainClass + "\n"; + } + auto appletClass = profile->getAppletClass(); + if (!appletClass.isEmpty()) + { + launchScript += "appletClass " + appletClass + "\n"; + } + + // generic minecraft params + for (auto param : processMinecraftArgs(session)) + { + launchScript += "param " + param + "\n"; + } + + // window size, title and state, legacy + { + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + windowParams = "max"; + else + windowParams = QString("%1x%2") + .arg(settings()->get("MinecraftWinWidth").toInt()) + .arg(settings()->get("MinecraftWinHeight").toInt()); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "windowParams " + windowParams + "\n"; + } + + // legacy auth + if(session) + { + launchScript += "userName " + session->player_name + "\n"; + launchScript += "sessionId " + session->session + "\n"; + } + + // libraries and class path. + { + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + for(auto file: jars) + { + launchScript += "cp " + file + "\n"; + } + for(auto file: nativeJars) + { + launchScript += "ext " + file + "\n"; + } + launchScript += "natives " + getNativePath() + "\n"; + } + + for (auto trait : profile->getTraits()) + { + launchScript += "traits " + trait + "\n"; + } + launchScript += "launcher onesix\n"; + // qDebug() << "Generated launch script:" << launchScript; + return launchScript; } QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session) { - QStringList out; - out << "Main Class:" << " " + getMainClass() << ""; - out << "Native path:" << " " + getNativePath() << ""; - - auto profile = m_components->getProfile(); - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << "traits " + trait; - } - out << ""; - } - - // libraries and class path. - { - out << "Libraries:"; - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - auto printLibFile = [&](const QString & path) - { - QFileInfo info(path); - if(info.exists()) - { - out << " " + path; - } - else - { - out << " " + path + " (missing)"; - } - }; - for(auto file: jars) - { - printLibFile(file); - } - out << ""; - out << "Native libraries:"; - for(auto file: nativeJars) - { - printLibFile(file); - } - out << ""; - } - - if(loaderModList()->size()) - { - out << "Mods:"; - for(auto & mod: loaderModList()->allMods()) - { - if(!mod.enabled()) - continue; - if(mod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + mod.filename().completeBaseName(); - } - out << ""; - } - - if(coreModList()->size()) - { - out << "Core Mods:"; - for(auto & coremod: coreModList()->allMods()) - { - if(!coremod.enabled()) - continue; - if(coremod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + coremod.filename().completeBaseName(); - } - out << ""; - } - - auto & jarMods = profile->getJarMods(); - if(jarMods.size()) - { - out << "Jar Mods:"; - for(auto & jarmod: jarMods) - { - auto displayname = jarmod->displayName(currentSystem); - auto realname = jarmod->filename(currentSystem); - if(displayname != realname) - { - out << " " + displayname + " (" + realname + ")"; - } - else - { - out << " " + realname; - } - } - out << ""; - } - - auto params = processMinecraftArgs(nullptr); - out << "Params:"; - out << " " + params.join(' '); - out << ""; - - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; + QStringList out; + out << "Main Class:" << " " + getMainClass() << ""; + out << "Native path:" << " " + getNativePath() << ""; + + auto profile = m_components->getProfile(); + + auto alltraits = traits(); + if(alltraits.size()) + { + out << "Traits:"; + for (auto trait : alltraits) + { + out << "traits " + trait; + } + out << ""; + } + + // libraries and class path. + { + out << "Libraries:"; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + auto printLibFile = [&](const QString & path) + { + QFileInfo info(path); + if(info.exists()) + { + out << " " + path; + } + else + { + out << " " + path + " (missing)"; + } + }; + for(auto file: jars) + { + printLibFile(file); + } + out << ""; + out << "Native libraries:"; + for(auto file: nativeJars) + { + printLibFile(file); + } + out << ""; + } + + auto printModList = [&](const QString & label, ModFolderModel & model) { + if(model.size()) + { + out << QString("%1:").arg(label); + auto modList = model.allMods(); + std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) { + auto aName = a.filename().completeBaseName(); + auto bName = b.filename().completeBaseName(); + return aName.localeAwareCompare(bName) < 0; + }); + for(auto & mod: modList) + { + if(mod.type() == Mod::MOD_FOLDER) + { + out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)"; + continue; + } + + if(mod.enabled()) { + out << u8" [✔️] " + mod.filename().completeBaseName(); + } + else { + out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)"; + } + + } + out << ""; + } + }; + + printModList("Mods", *(loaderModList().get())); + printModList("Core Mods", *(coreModList().get())); + + auto & jarMods = profile->getJarMods(); + if(jarMods.size()) + { + out << "Jar Mods:"; + for(auto & jarmod: jarMods) + { + auto displayname = jarmod->displayName(currentSystem); + auto realname = jarmod->filename(currentSystem); + if(displayname != realname) + { + out << " " + displayname + " (" + realname + ")"; + } + else + { + out << " " + realname; + } + } + out << ""; + } + + auto params = processMinecraftArgs(nullptr); + out << "Params:"; + out << " " + params.join(' '); + out << ""; + + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + { + out << "Window size: max (if available)"; + } + else + { + auto width = settings()->get("MinecraftWinWidth").toInt(); + auto height = settings()->get("MinecraftWinHeight").toInt(); + out << "Window size: " + QString::number(width) + " x " + QString::number(height); + } + out << ""; + return out; } QMap MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session) { - if(!session) - { - return QMap(); - } - auto & sessionRef = *session.get(); - QMap filter; - auto addToFilter = [&filter](QString key, QString value) - { - if(key.trimmed().size()) - { - filter[key] = value; - } - }; - if (sessionRef.session != "-") - { - addToFilter(sessionRef.session, tr("")); - } - addToFilter(sessionRef.access_token, tr("")); - addToFilter(sessionRef.client_token, tr("")); - addToFilter(sessionRef.uuid, tr("")); - - auto i = sessionRef.u.properties.begin(); - while (i != sessionRef.u.properties.end()) - { - if(i.key() == "preferredLanguage") - { - ++i; - continue; - } - addToFilter(i.value(), "<" + i.key().toUpper() + ">"); - ++i; - } - return filter; + if(!session) + { + return QMap(); + } + auto & sessionRef = *session.get(); + QMap filter; + auto addToFilter = [&filter](QString key, QString value) + { + if(key.trimmed().size()) + { + filter[key] = value; + } + }; + if (sessionRef.session != "-") + { + addToFilter(sessionRef.session, tr("")); + } + addToFilter(sessionRef.access_token, tr("")); + addToFilter(sessionRef.client_token, tr("")); + addToFilter(sessionRef.uuid, tr("")); + + auto i = sessionRef.u.properties.begin(); + while (i != sessionRef.u.properties.end()) + { + if(i.value().length() <= 3) { + ++i; + continue; + } + addToFilter(i.value(), "<" + i.key().toUpper() + ">"); + ++i; + } + return filter; } MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level) { - QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); - auto match = re.match(line); - if(match.hasMatch()) - { - // New style logs from log4j - QString timestamp = match.captured("timestamp"); - QString levelStr = match.captured("level"); - if(levelStr == "INFO") - level = MessageLevel::Message; - if(levelStr == "WARN") - level = MessageLevel::Warning; - if(levelStr == "ERROR") - level = MessageLevel::Error; - if(levelStr == "FATAL") - level = MessageLevel::Fatal; - if(levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; - } - else - { - // Old style forge logs - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || - line.contains("[FINER]") || line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - } - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - //NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; - if (line.contains("Exception in thread") - || line.contains(QRegularExpression("\\s+at " + javaSymbol)) - || line.contains(QRegularExpression("Caused by: " + javaSymbol)) - || line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) - || line.contains(QRegularExpression("... \\d+ more$")) - ) - return MessageLevel::Error; - return level; + QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + auto match = re.match(line); + if(match.hasMatch()) + { + // New style logs from log4j + QString timestamp = match.captured("timestamp"); + QString levelStr = match.captured("level"); + if(levelStr == "INFO") + level = MessageLevel::Message; + if(levelStr == "WARN") + level = MessageLevel::Warning; + if(levelStr == "ERROR") + level = MessageLevel::Error; + if(levelStr == "FATAL") + level = MessageLevel::Fatal; + if(levelStr == "TRACE" || levelStr == "DEBUG") + level = MessageLevel::Debug; + } + else + { + // Old style forge logs + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || + line.contains("[FINER]") || line.contains("[FINEST]")) + level = MessageLevel::Message; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + } + if (line.contains("overwriting existing")) + return MessageLevel::Fatal; + //NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * + static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; + if (line.contains("Exception in thread") + || line.contains(QRegularExpression("\\s+at " + javaSymbol)) + || line.contains(QRegularExpression("Caused by: " + javaSymbol)) + || line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) + || line.contains(QRegularExpression("... \\d+ more$")) + ) + return MessageLevel::Error; + return level; } IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() { - auto combined = std::make_shared(); - combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); - combined->add(std::make_shared("crash-.*\\.txt")); - combined->add(std::make_shared("IDMap dump.*\\.txt$")); - combined->add(std::make_shared("ModLoader\\.txt(\\..*)?$")); - return combined; + auto combined = std::make_shared(); + combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); + combined->add(std::make_shared("crash-.*\\.txt")); + combined->add(std::make_shared("IDMap dump.*\\.txt$")); + combined->add(std::make_shared("ModLoader\\.txt(\\..*)?$")); + return combined; } QString MinecraftInstance::getLogFileRoot() { - return minecraftRoot(); + return gameRoot(); } QString MinecraftInstance::prettifyTimeDuration(int64_t duration) { - int seconds = (int) (duration % 60); - duration /= 60; - int minutes = (int) (duration % 60); - duration /= 60; - int hours = (int) (duration % 24); - int days = (int) (duration / 24); - if((hours == 0)&&(days == 0)) - { - return tr("%1m %2s").arg(minutes).arg(seconds); - } - if (days == 0) - { - return tr("%1h %2m").arg(hours).arg(minutes); - } - return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); + int seconds = (int) (duration % 60); + duration /= 60; + int minutes = (int) (duration % 60); + duration /= 60; + int hours = (int) (duration % 24); + int days = (int) (duration / 24); + if((hours == 0)&&(days == 0)) + { + return tr("%1m %2s").arg(minutes).arg(seconds); + } + if (days == 0) + { + return tr("%1h %2m").arg(hours).arg(minutes); + } + return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); } QString MinecraftInstance::getStatusbarDescription() { - QStringList traits; - if (hasVersionBroken()) - { - traits.append(tr("broken")); - } - - QString description; - description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); - if(totalTimePlayed() > 0) - { - description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); - } - if(hasCrashed()) - { - description.append(tr(", has crashed.")); - } - return description; + QStringList traits; + if (hasVersionBroken()) + { + traits.append(tr("broken")); + } + + QString description; + description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); + if(totalTimePlayed() > 0) + { + description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); + } + if(hasCrashed()) + { + description.append(tr(", has crashed.")); + } + return description; } shared_qobject_ptr MinecraftInstance::createUpdateTask(Net::Mode mode) { - switch (mode) - { - case Net::Mode::Offline: - { - return shared_qobject_ptr(new MinecraftLoadAndCheck(this)); - } - case Net::Mode::Online: - { - return shared_qobject_ptr(new OneSixUpdate(this)); - } - } - return nullptr; -} - -std::shared_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session) -{ - auto process = LaunchTask::create(std::dynamic_pointer_cast(getSharedPtr())); - auto pptr = process.get(); - - ENV.icons()->saveIcon(iconKey(), FS::PathCombine(minecraftRoot(), "icon.png"), "PNG"); - - // print a header - { - process->appendStep(std::make_shared(pptr, "Minecraft folder is:\n" + minecraftRoot() + "\n\n", MessageLevel::MultiMC)); - } - - // check java - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - // check launch method - QStringList validMethods = {"LauncherPart", "DirectJava"}; - QString method = launchMethod(); - if(!validMethods.contains(method)) - { - process->appendStep(std::make_shared(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); - return process; - } - - // run pre-launch command if that's needed - if(getPreLaunchCommand().size()) - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - process->appendStep(step); - } - - // if we aren't in offline mode,. - if(session->status != AuthSession::PlayableOffline) - { - process->appendStep(std::make_shared(pptr, session)); - process->appendStep(std::make_shared(pptr, Net::Mode::Online)); - } - else - { - process->appendStep(std::make_shared(pptr, Net::Mode::Offline)); - } - - // if there are any jar mods - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - // print some instance info here... - { - auto step = std::make_shared(pptr, session); - process->appendStep(step); - } - - // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732) - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - // extract native jars if needed - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - { - // actually launch the game - auto method = launchMethod(); - if(method == "LauncherPart") - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - process->appendStep(step); - } - else if (method == "DirectJava") - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - process->appendStep(step); - } - } - - // run post-exit command if that's needed - if(getPostExitCommand().size()) - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - process->appendStep(step); - } - if (session) - { - process->setCensorFilter(createCensorFilterFromSession(session)); - } - m_launchProcess = process; - emit launchTaskChanged(m_launchProcess); - return m_launchProcess; + switch (mode) + { + case Net::Mode::Offline: + { + return shared_qobject_ptr(new MinecraftLoadAndCheck(this)); + } + case Net::Mode::Online: + { + return shared_qobject_ptr(new MinecraftUpdate(this)); + } + } + return nullptr; +} + +shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session) +{ + // FIXME: get rid of shared_from_this ... + auto process = LaunchTask::create(std::dynamic_pointer_cast(shared_from_this())); + auto pptr = process.get(); + + ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG"); + + // print a header + { + process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC)); + } + + // check java + { + process->appendStep(new CheckJava(pptr)); + } + + // check launch method + QStringList validMethods = {"LauncherPart", "DirectJava"}; + QString method = launchMethod(); + if(!validMethods.contains(method)) + { + process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); + return process; + } + + // run pre-launch command if that's needed + if(getPreLaunchCommand().size()) + { + auto step = new PreLaunchCommand(pptr); + step->setWorkingDirectory(gameRoot()); + process->appendStep(step); + } + + // if we aren't in offline mode,. + if(session->status != AuthSession::PlayableOffline) + { + process->appendStep(new ClaimAccount(pptr, session)); + process->appendStep(new Update(pptr, Net::Mode::Online)); + } + else + { + process->appendStep(new Update(pptr, Net::Mode::Offline)); + } + + // if there are any jar mods + { + process->appendStep(new ModMinecraftJar(pptr)); + } + + // if there are any jar mods + { + process->appendStep(new ScanModFolders(pptr)); + } + + // print some instance info here... + { + process->appendStep(new PrintInstanceInfo(pptr, session)); + } + + // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732) + { + process->appendStep(new CreateServerResourcePacksFolder(pptr)); + } + + // extract native jars if needed + { + process->appendStep(new ExtractNatives(pptr)); + } + + // reconstruct assets if needed + { + process->appendStep(new ReconstructAssets(pptr)); + } + + { + // actually launch the game + auto method = launchMethod(); + if(method == "LauncherPart") + { + auto step = new LauncherPartLaunch(pptr); + step->setWorkingDirectory(gameRoot()); + step->setAuthSession(session); + process->appendStep(step); + } + else if (method == "DirectJava") + { + auto step = new DirectJavaLaunch(pptr); + step->setWorkingDirectory(gameRoot()); + step->setAuthSession(session); + process->appendStep(step); + } + } + + // run post-exit command if that's needed + if(getPostExitCommand().size()) + { + auto step = new PostLaunchCommand(pptr); + step->setWorkingDirectory(gameRoot()); + process->appendStep(step); + } + if (session) + { + process->setCensorFilter(createCensorFilterFromSession(session)); + } + m_launchProcess = process; + emit launchTaskChanged(m_launchProcess); + return m_launchProcess; } QString MinecraftInstance::launchMethod() { - return m_settings->get("MCLaunchMethod").toString(); + return m_settings->get("MCLaunchMethod").toString(); } JavaVersion MinecraftInstance::getJavaVersion() const { - return JavaVersion(settings()->get("JavaVersion").toString()); + return JavaVersion(settings()->get("JavaVersion").toString()); } -std::shared_ptr MinecraftInstance::loaderModList() const +std::shared_ptr MinecraftInstance::loaderModList() const { - if (!m_loader_mod_list) - { - m_loader_mod_list.reset(new ModList(loaderModsDir())); - } - m_loader_mod_list->update(); - return m_loader_mod_list; + if (!m_loader_mod_list) + { + m_loader_mod_list.reset(new ModFolderModel(loaderModsDir())); + m_loader_mod_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); + } + return m_loader_mod_list; } -std::shared_ptr MinecraftInstance::coreModList() const +std::shared_ptr MinecraftInstance::coreModList() const { - if (!m_core_mod_list) - { - m_core_mod_list.reset(new ModList(coreModsDir())); - } - m_core_mod_list->update(); - return m_core_mod_list; + if (!m_core_mod_list) + { + m_core_mod_list.reset(new ModFolderModel(coreModsDir())); + m_core_mod_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); + } + return m_core_mod_list; } -std::shared_ptr MinecraftInstance::resourcePackList() const +std::shared_ptr MinecraftInstance::resourcePackList() const { - if (!m_resource_pack_list) - { - m_resource_pack_list.reset(new ModList(resourcePacksDir())); - } - m_resource_pack_list->update(); - return m_resource_pack_list; + if (!m_resource_pack_list) + { + m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir())); + m_resource_pack_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction); + } + return m_resource_pack_list; } -std::shared_ptr MinecraftInstance::texturePackList() const +std::shared_ptr MinecraftInstance::texturePackList() const { - if (!m_texture_pack_list) - { - m_texture_pack_list.reset(new ModList(texturePacksDir())); - } - m_texture_pack_list->update(); - return m_texture_pack_list; + if (!m_texture_pack_list) + { + m_texture_pack_list.reset(new ModFolderModel(texturePacksDir())); + m_texture_pack_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction); + } + return m_texture_pack_list; } std::shared_ptr MinecraftInstance::worldList() const { - if (!m_world_list) - { - m_world_list.reset(new WorldList(worldDir())); - } - return m_world_list; + if (!m_world_list) + { + m_world_list.reset(new WorldList(worldDir())); + } + return m_world_list; +} + +std::shared_ptr MinecraftInstance::gameOptionsModel() const +{ + if (!m_game_options) + { + m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt"))); + } + return m_game_options; } QList< Mod > MinecraftInstance::getJarMods() const { - auto profile = m_components->getProfile(); - QList mods; - for (auto jarmod : profile->getJarMods()) - { - QStringList jar, temp1, temp2, temp3; - jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); - // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); - mods.push_back(Mod(QFileInfo(jar[0]))); - } - return mods; + auto profile = m_components->getProfile(); + QList mods; + for (auto jarmod : profile->getJarMods()) + { + QStringList jar, temp1, temp2, temp3; + jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); + // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); + mods.push_back(Mod(QFileInfo(jar[0]))); + } + return mods; } diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h index 446a39d5..a8f64109 100644 --- a/api/logic/minecraft/MinecraftInstance.h +++ b/api/logic/minecraft/MinecraftInstance.h @@ -1,124 +1,132 @@ #pragma once #include "BaseInstance.h" #include -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" #include #include #include "multimc_logic_export.h" -class ModList; +class ModFolderModel; class WorldList; +class GameOptions; class LaunchStep; class ComponentList; class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance { - Q_OBJECT + Q_OBJECT public: - MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~MinecraftInstance() {}; - virtual void init() override; - virtual void saveNow(); - - // FIXME: remove - QString typeName() const override; - // FIXME: remove - QSet traits() const override; - - bool canEdit() const override - { - return true; - } - - bool canExport() const override - { - return true; - } - - ////// Directories and files ////// - QString jarModsDir() const; - QString resourcePacksDir() const; - QString texturePacksDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString libDir() const; - QString worldDir() const; - QDir jarmodsPath() const; - QDir librariesPath() const; - QDir versionsPath() const; - QString instanceConfigFolder() const override; - QString minecraftRoot() const; // Path to the instance's minecraft directory. - QString binRoot() const; // Path to the instance's minecraft bin directory. - QString getNativePath() const; // where to put the natives during/before launch - QString getLocalLibraryPath() const; // where the instance-local libraries should be - - - ////// Profile management ////// - std::shared_ptr getComponentList() const; - - ////// Mod Lists ////// - std::shared_ptr loaderModList() const; - std::shared_ptr coreModList() const; - std::shared_ptr resourcePackList() const; - std::shared_ptr texturePackList() const; - std::shared_ptr worldList() const; - - - ////// Launch stuff ////// - shared_qobject_ptr createUpdateTask(Net::Mode mode) override; - std::shared_ptr createLaunchTask(AuthSessionPtr account) override; - QStringList extraArguments() const override; - QStringList verboseDescription(AuthSessionPtr session) override; - QList getJarMods() const; - QString createLaunchScript(AuthSessionPtr session); - /// get arguments passed to java - QStringList javaArguments() const; - - /// get variables for launch command variable substitution/environment - QMap getVariables() const override; - - /// create an environment for launching processes - QProcessEnvironment createEnvironment() override; - - /// guess log level from a line of minecraft log - MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - - IPathMatcher::Ptr getLogFileMatcher() override; - - QString getLogFileRoot() override; - - QString getStatusbarDescription() override; - - // FIXME: remove - virtual QStringList getClassPath() const; - // FIXME: remove - virtual QStringList getNativeJars() const; - // FIXME: remove - virtual QString getMainClass() const; - - // FIXME: remove - virtual QStringList processMinecraftArgs(AuthSessionPtr account) const; - - virtual JavaVersion getJavaVersion() const; - -signals: - void versionReloaded(); + MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); + virtual ~MinecraftInstance() {}; + virtual void saveNow() override; + + // FIXME: remove + QString typeName() const override; + // FIXME: remove + QSet traits() const override; + + bool canEdit() const override + { + return true; + } + + bool canExport() const override + { + return true; + } + + ////// Directories and files ////// + QString jarModsDir() const; + QString resourcePacksDir() const; + QString texturePacksDir() const; + QString loaderModsDir() const; + QString coreModsDir() const; + QString modsCacheLocation() const; + QString libDir() const; + QString worldDir() const; + QString resourcesDir() const; + QDir jarmodsPath() const; + QDir librariesPath() const; + QDir versionsPath() const; + QString instanceConfigFolder() const override; + + // Path to the instance's minecraft directory. + QString gameRoot() const override; + + // Path to the instance's minecraft bin directory. + QString binRoot() const; + + // where to put the natives during/before launch + QString getNativePath() const; + + // where the instance-local libraries should be + QString getLocalLibraryPath() const; + + + ////// Profile management ////// + std::shared_ptr getComponentList() const; + + ////// Mod Lists ////// + std::shared_ptr loaderModList() const; + std::shared_ptr coreModList() const; + std::shared_ptr resourcePackList() const; + std::shared_ptr texturePackList() const; + std::shared_ptr worldList() const; + std::shared_ptr gameOptionsModel() const; + + ////// Launch stuff ////// + shared_qobject_ptr createUpdateTask(Net::Mode mode) override; + shared_qobject_ptr createLaunchTask(AuthSessionPtr account) override; + QStringList extraArguments() const override; + QStringList verboseDescription(AuthSessionPtr session) override; + QList getJarMods() const; + QString createLaunchScript(AuthSessionPtr session); + /// get arguments passed to java + QStringList javaArguments() const; + + /// get variables for launch command variable substitution/environment + QMap getVariables() const override; + + /// create an environment for launching processes + QProcessEnvironment createEnvironment() override; + + /// guess log level from a line of minecraft log + MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; + + IPathMatcher::Ptr getLogFileMatcher() override; + + QString getLogFileRoot() override; + + QString getStatusbarDescription() override; + + // FIXME: remove + virtual QStringList getClassPath() const; + // FIXME: remove + virtual QStringList getNativeJars() const; + // FIXME: remove + virtual QString getMainClass() const; + + // FIXME: remove + virtual QStringList processMinecraftArgs(AuthSessionPtr account) const; + + virtual JavaVersion getJavaVersion() const; protected: - QMap createCensorFilterFromSession(AuthSessionPtr session); - QStringList validLaunchMethods(); - QString launchMethod(); + QMap createCensorFilterFromSession(AuthSessionPtr session); + QStringList validLaunchMethods(); + QString launchMethod(); private: - QString prettifyTimeDuration(int64_t duration); + QString prettifyTimeDuration(int64_t duration); protected: // data - std::shared_ptr m_components; - mutable std::shared_ptr m_loader_mod_list; - mutable std::shared_ptr m_core_mod_list; - mutable std::shared_ptr m_resource_pack_list; - mutable std::shared_ptr m_texture_pack_list; - mutable std::shared_ptr m_world_list; + std::shared_ptr m_components; + mutable std::shared_ptr m_loader_mod_list; + mutable std::shared_ptr m_core_mod_list; + mutable std::shared_ptr m_resource_pack_list; + mutable std::shared_ptr m_texture_pack_list; + mutable std::shared_ptr m_world_list; + mutable std::shared_ptr m_game_options; }; typedef std::shared_ptr MinecraftInstancePtr; diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp index c64bbddf..593e2fc4 100644 --- a/api/logic/minecraft/MinecraftLoadAndCheck.cpp +++ b/api/logic/minecraft/MinecraftLoadAndCheck.cpp @@ -8,38 +8,38 @@ MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *p void MinecraftLoadAndCheck::executeTask() { - // add offline metadata load task - auto components = m_inst->getComponentList(); - components->reload(Net::Mode::Offline); - m_task = components->getCurrentTask(); + // add offline metadata load task + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Offline); + m_task = components->getCurrentTask(); - if(!m_task) - { - emitSucceeded(); - return; - } - connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); - connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); - connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); - connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); + if(!m_task) + { + emitSucceeded(); + return; + } + connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); + connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); + connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); + connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); } void MinecraftLoadAndCheck::subtaskSucceeded() { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - emitSucceeded(); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + emitSucceeded(); } void MinecraftLoadAndCheck::subtaskFailed(QString error) { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - emitFailed(error); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + emitFailed(error); } diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h index 00515f2d..6924ca2d 100644 --- a/api/logic/minecraft/MinecraftLoadAndCheck.h +++ b/api/logic/minecraft/MinecraftLoadAndCheck.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,19 +29,20 @@ class MinecraftInstance; class MinecraftLoadAndCheck : public Task { - Q_OBJECT + Q_OBJECT public: - explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0); - void executeTask() override; + explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0); + virtual ~MinecraftLoadAndCheck() {}; + void executeTask() override; private slots: - void subtaskSucceeded(); - void subtaskFailed(QString error); + void subtaskSucceeded(); + void subtaskFailed(QString error); private: - MinecraftInstance *m_inst = nullptr; - shared_qobject_ptr m_task; - QString m_preFailure; - QString m_fail_reason; + MinecraftInstance *m_inst = nullptr; + shared_qobject_ptr m_task; + QString m_preFailure; + QString m_fail_reason; }; diff --git a/api/logic/minecraft/MinecraftUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp index 86835fa4..00558e37 100644 --- a/api/logic/minecraft/MinecraftUpdate.cpp +++ b/api/logic/minecraft/MinecraftUpdate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,148 +37,148 @@ #include #include -OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) +MinecraftUpdate::MinecraftUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { } -void OneSixUpdate::executeTask() +void MinecraftUpdate::executeTask() { - m_tasks.clear(); - // create folders - { - m_tasks.append(std::make_shared(m_inst)); - } - - // add metadata update task if necessary - { - auto components = m_inst->getComponentList(); - components->reload(Net::Mode::Online); - auto task = components->getCurrentTask(); - if(task) - { - m_tasks.append(task.unwrap()); - } - } - - // libraries download - { - m_tasks.append(std::make_shared(m_inst)); - } - - // FML libraries download and copy into the instance - { - m_tasks.append(std::make_shared(m_inst)); - } - - // assets update - { - m_tasks.append(std::make_shared(m_inst)); - } - - if(!m_preFailure.isEmpty()) - { - emitFailed(m_preFailure); - return; - } - next(); + m_tasks.clear(); + // create folders + { + m_tasks.append(std::make_shared(m_inst)); + } + + // add metadata update task if necessary + { + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Online); + auto task = components->getCurrentTask(); + if(task) + { + m_tasks.append(task.unwrap()); + } + } + + // libraries download + { + m_tasks.append(std::make_shared(m_inst)); + } + + // FML libraries download and copy into the instance + { + m_tasks.append(std::make_shared(m_inst)); + } + + // assets update + { + m_tasks.append(std::make_shared(m_inst)); + } + + if(!m_preFailure.isEmpty()) + { + emitFailed(m_preFailure); + return; + } + next(); } -void OneSixUpdate::next() +void MinecraftUpdate::next() { - if(m_abort) - { - emitFailed(tr("Aborted by user.")); - return; - } - if(m_failed_out_of_order) - { - emitFailed(m_fail_reason); - return; - } - m_currentTask ++; - if(m_currentTask > 0) - { - auto task = m_tasks[m_currentTask - 1]; - disconnect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); - disconnect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed); - disconnect(task.get(), &Task::progress, this, &OneSixUpdate::progress); - disconnect(task.get(), &Task::status, this, &OneSixUpdate::setStatus); - } - if(m_currentTask == m_tasks.size()) - { - emitSucceeded(); - return; - } - auto task = m_tasks[m_currentTask]; - // if the task is already finished by the time we look at it, skip it - if(task->isFinished()) - { - qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); - next(); - } - connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); - connect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed); - connect(task.get(), &Task::progress, this, &OneSixUpdate::progress); - connect(task.get(), &Task::status, this, &OneSixUpdate::setStatus); - // if the task is already running, do not start it again - if(!task->isRunning()) - { - task->start(); - } + if(m_abort) + { + emitFailed(tr("Aborted by user.")); + return; + } + if(m_failed_out_of_order) + { + emitFailed(m_fail_reason); + return; + } + m_currentTask ++; + if(m_currentTask > 0) + { + auto task = m_tasks[m_currentTask - 1]; + disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); + disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); + disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + } + if(m_currentTask == m_tasks.size()) + { + emitSucceeded(); + return; + } + auto task = m_tasks[m_currentTask]; + // if the task is already finished by the time we look at it, skip it + if(task->isFinished()) + { + qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); + next(); + } + connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); + connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); + connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + // if the task is already running, do not start it again + if(!task->isRunning()) + { + task->start(); + } } -void OneSixUpdate::subtaskSucceeded() +void MinecraftUpdate::subtaskSucceeded() { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order."; - return; - } - next(); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order."; + return; + } + next(); } -void OneSixUpdate::subtaskFailed(QString error) +void MinecraftUpdate::subtaskFailed(QString error) { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order."; - m_failed_out_of_order = true; - m_fail_reason = error; - return; - } - emitFailed(error); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order."; + m_failed_out_of_order = true; + m_fail_reason = error; + return; + } + emitFailed(error); } -bool OneSixUpdate::abort() +bool MinecraftUpdate::abort() { - if(!m_abort) - { - m_abort = true; - auto task = m_tasks[m_currentTask]; - if(task->canAbort()) - { - return task->abort(); - } - } - return true; + if(!m_abort) + { + m_abort = true; + auto task = m_tasks[m_currentTask]; + if(task->canAbort()) + { + return task->abort(); + } + } + return true; } -bool OneSixUpdate::canAbort() const +bool MinecraftUpdate::canAbort() const { - return true; + return true; } diff --git a/api/logic/minecraft/MinecraftUpdate.h b/api/logic/minecraft/MinecraftUpdate.h index 78c02049..eb8e74db 100644 --- a/api/logic/minecraft/MinecraftUpdate.h +++ b/api/logic/minecraft/MinecraftUpdate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,29 +27,31 @@ class MinecraftVersion; class MinecraftInstance; -class OneSixUpdate : public Task +class MinecraftUpdate : public Task { - Q_OBJECT + Q_OBJECT public: - explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0); - void executeTask() override; - bool canAbort() const override; + explicit MinecraftUpdate(MinecraftInstance *inst, QObject *parent = 0); + virtual ~MinecraftUpdate() {}; + + void executeTask() override; + bool canAbort() const override; private slots: - bool abort() override; - void subtaskSucceeded(); - void subtaskFailed(QString error); + bool abort() override; + void subtaskSucceeded(); + void subtaskFailed(QString error); private: - void next(); + void next(); private: - MinecraftInstance *m_inst = nullptr; - QList> m_tasks; - QString m_preFailure; - int m_currentTask = -1; - bool m_abort = false; - bool m_failed_out_of_order = false; - QString m_fail_reason; + MinecraftInstance *m_inst = nullptr; + QList> m_tasks; + QString m_preFailure; + int m_currentTask = -1; + bool m_abort = false; + bool m_failed_out_of_order = false; + QString m_fail_reason; }; diff --git a/api/logic/minecraft/Mod.cpp b/api/logic/minecraft/Mod.cpp deleted file mode 100644 index 03e04b2b..00000000 --- a/api/logic/minecraft/Mod.cpp +++ /dev/null @@ -1,378 +0,0 @@ -/* Copyright 2013-2018 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Mod.h" -#include "settings/INIFile.h" -#include -#include - -Mod::Mod(const QFileInfo &file) -{ - repath(file); - m_changedDateTime = file.lastModified(); -} - -void Mod::repath(const QFileInfo &file) -{ - m_file = file; - QString name_base = file.fileName(); - - m_type = Mod::MOD_UNKNOWN; - - if (m_file.isDir()) - { - m_type = MOD_FOLDER; - m_name = name_base; - m_mmc_id = name_base; - } - else if (m_file.isFile()) - { - if (name_base.endsWith(".disabled")) - { - m_enabled = false; - name_base.chop(9); - } - else - { - m_enabled = true; - } - m_mmc_id = name_base; - if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) - { - m_type = MOD_ZIPFILE; - name_base.chop(4); - } - else if (name_base.endsWith(".litemod")) - { - m_type = MOD_LITEMOD; - name_base.chop(8); - } - else - { - m_type = MOD_SINGLEFILE; - } - m_name = name_base; - } - - if (m_type == MOD_ZIPFILE) - { - QuaZip zip(m_file.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("mcmod.info")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - ReadMCModInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - else if (zip.setCurrentFile("forgeversion.properties")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - ReadForgeInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - - zip.close(); - } - else if (m_type == MOD_FOLDER) - { - QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info")); - if (mcmod_info.isFile()) - { - QFile mcmod(mcmod_info.filePath()); - if (!mcmod.open(QIODevice::ReadOnly)) - return; - auto data = mcmod.readAll(); - if (data.isEmpty() || data.isNull()) - return; - ReadMCModInfo(data); - } - } - else if (m_type == MOD_LITEMOD) - { - QuaZip zip(m_file.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("litemod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - ReadLiteModInfo(file.readAll()); - file.close(); - } - zip.close(); - } -} - -// NEW format -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3 - -// OLD format: -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc -void Mod::ReadMCModInfo(QByteArray contents) -{ - auto getInfoFromArray = [&](QJsonArray arr)->void - { - if (!arr.at(0).isObject()) - return; - auto firstObj = arr.at(0).toObject(); - m_mod_id = firstObj.value("modid").toString(); - m_name = firstObj.value("name").toString(); - m_version = firstObj.value("version").toString(); - m_homeurl = firstObj.value("url").toString(); - m_updateurl = firstObj.value("updateUrl").toString(); - m_homeurl = m_homeurl.trimmed(); - if(!m_homeurl.isEmpty()) - { - // fix up url. - if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") && - !m_homeurl.startsWith("ftp://")) - { - m_homeurl.prepend("http://"); - } - } - m_description = firstObj.value("description").toString(); - QJsonArray authors = firstObj.value("authorList").toArray(); - if (authors.size() == 0) - authors = firstObj.value("authors").toArray(); - - if (authors.size() == 0) - m_authors = ""; - else if (authors.size() >= 1) - { - m_authors = authors.at(0).toString(); - for (int i = 1; i < authors.size(); i++) - { - m_authors += ", " + authors.at(i).toString(); - } - } - m_credits = firstObj.value("credits").toString(); - return; - } - ; - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - // this is the very old format that had just the array - if (jsonDoc.isArray()) - { - getInfoFromArray(jsonDoc.array()); - } - else if (jsonDoc.isObject()) - { - auto val = jsonDoc.object().value("modinfoversion"); - if(val.isUndefined()) - val = jsonDoc.object().value("modListVersion"); - int version = val.toDouble(); - if (version != 2) - { - qCritical() << "BAD stuff happened to mod json:"; - qCritical() << contents; - return; - } - auto arrVal = jsonDoc.object().value("modlist"); - if(arrVal.isUndefined()) - arrVal = jsonDoc.object().value("modList"); - if (arrVal.isArray()) - { - getInfoFromArray(arrVal.toArray()); - } - } -} - -void Mod::ReadForgeInfo(QByteArray contents) -{ - // Read the data - m_name = "Minecraft Forge"; - m_mod_id = "Forge"; - m_homeurl = "http://www.minecraftforge.net/forum/"; - INIFile ini; - if (!ini.loadFile(contents)) - return; - - QString major = ini.get("forge.major.number", "0").toString(); - QString minor = ini.get("forge.minor.number", "0").toString(); - QString revision = ini.get("forge.revision.number", "0").toString(); - QString build = ini.get("forge.build.number", "0").toString(); - - m_version = major + "." + minor + "." + revision + "." + build; -} - -void Mod::ReadLiteModInfo(QByteArray contents) -{ - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = jsonDoc.object(); - if (object.contains("name")) - { - m_mod_id = m_name = object.value("name").toString(); - } - if (object.contains("version")) - { - m_version = object.value("version").toString(""); - } - else - { - m_version = object.value("revision").toString(""); - } - m_mcversion = object.value("mcversion").toString(); - m_authors = object.value("author").toString(); - m_description = object.value("description").toString(); - m_homeurl = object.value("url").toString(); -} - -bool Mod::replace(Mod &with) -{ - if (!destroy()) - return false; - bool success = false; - auto t = with.type(); - - if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD) - { - qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath(); - success = QFile::copy(with.m_file.filePath(), m_file.filePath()); - } - if (t == MOD_FOLDER) - { - success = FS::copy(with.m_file.filePath(), m_file.path())(); - } - if (success) - { - m_name = with.m_name; - m_mmc_id = with.m_mmc_id; - m_mod_id = with.m_mod_id; - m_version = with.m_version; - m_mcversion = with.m_mcversion; - m_description = with.m_description; - m_authors = with.m_authors; - m_credits = with.m_credits; - m_homeurl = with.m_homeurl; - m_type = with.m_type; - m_file.refresh(); - } - return success; -} - -bool Mod::destroy() -{ - if (m_type == MOD_FOLDER) - { - QDir d(m_file.filePath()); - if (d.removeRecursively()) - { - m_type = MOD_UNKNOWN; - return true; - } - return false; - } - else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD) - { - QFile f(m_file.filePath()); - if (f.remove()) - { - m_type = MOD_UNKNOWN; - return true; - } - return false; - } - return true; -} - -QString Mod::version() const -{ - switch (type()) - { - case MOD_ZIPFILE: - case MOD_LITEMOD: - return m_version; - case MOD_FOLDER: - return "Folder"; - case MOD_SINGLEFILE: - return "File"; - default: - return "VOID"; - } -} - -bool Mod::enable(bool value) -{ - if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) - return false; - - if (m_enabled == value) - return false; - - QString path = m_file.absoluteFilePath(); - if (value) - { - QFile foo(path); - if (!path.endsWith(".disabled")) - return false; - path.chop(9); - if (!foo.rename(path)) - return false; - } - else - { - QFile foo(path); - path += ".disabled"; - if (!foo.rename(path)) - return false; - } - m_file = QFileInfo(path); - m_enabled = value; - return true; -} -bool Mod::operator==(const Mod &other) const -{ - return mmc_id() == other.mmc_id(); -} -bool Mod::strongCompare(const Mod &other) const -{ - return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type(); -} diff --git a/api/logic/minecraft/Mod.h b/api/logic/minecraft/Mod.h deleted file mode 100644 index ccab1867..00000000 --- a/api/logic/minecraft/Mod.h +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2013-2018 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -class Mod -{ -public: - enum ModType - { - MOD_UNKNOWN, //!< Indicates an unspecified mod type. - MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. - MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). - MOD_FOLDER, //!< The mod is in a folder on the filesystem. - MOD_LITEMOD, //!< The mod is a litemod - }; - - Mod(const QFileInfo &file); - - QFileInfo filename() const - { - return m_file; - } - QString mmc_id() const - { - return m_mmc_id; - } - QString mod_id() const - { - return m_mod_id; - } - ModType type() const - { - return m_type; - } - QString mcversion() const - { - return m_mcversion; - } - ; - bool valid() - { - return m_type != MOD_UNKNOWN; - } - QString name() const - { - QString name = m_name.trimmed(); - if(name.isEmpty() || name == "Example Mod") - { - return m_mmc_id; - } - return m_name; - } - - QString version() const; - - QString homeurl() const - { - return m_homeurl; - } - - QString description() const - { - return m_description; - } - - QString authors() const - { - return m_authors; - } - - QString credits() const - { - return m_credits; - } - - QDateTime dateTimeChanged() const - { - return m_changedDateTime; - } - - bool enabled() const - { - return m_enabled; - } - - bool enable(bool value); - - // delete all the files of this mod - bool destroy(); - // replace this mod with a copy of the other - bool replace(Mod &with); - // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) - void repath(const QFileInfo &file); - - // WEAK compare operator - used for replacing mods - bool operator==(const Mod &other) const; - bool strongCompare(const Mod &other) const; - -private: - void ReadMCModInfo(QByteArray contents); - void ReadForgeInfo(QByteArray contents); - void ReadLiteModInfo(QByteArray contents); - -protected: - - // FIXME: what do do with those? HMM... - /* - void ReadModInfoData(QString info); - void ReadForgeInfoData(QString infoFileData); - */ - - QFileInfo m_file; - QDateTime m_changedDateTime; - QString m_mmc_id; - QString m_mod_id; - bool m_enabled = true; - QString m_name; - QString m_version; - QString m_mcversion; - QString m_homeurl; - QString m_updateurl; - QString m_description; - QString m_authors; - QString m_credits; - - ModType m_type; -}; diff --git a/api/logic/minecraft/ModList.cpp b/api/logic/minecraft/ModList.cpp deleted file mode 100644 index 6ccf20e2..00000000 --- a/api/logic/minecraft/ModList.cpp +++ /dev/null @@ -1,367 +0,0 @@ -/* Copyright 2013-2018 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ModList.h" -#include -#include -#include -#include -#include -#include -#include - -ModList::ModList(const QString &dir) : QAbstractListModel(), m_dir(dir) -{ - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | - QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_watcher = new QFileSystemWatcher(this); - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); -} - -void ModList::startWatching() -{ - if(is_watching) - return; - - update(); - - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } -} - -void ModList::stopWatching() -{ - if(!is_watching) - return; - - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} - -bool ModList::update() -{ - if (!isValid()) - return false; - - QList orderedMods; - QList newMods; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - bool orderOrStateChanged = false; - - // if there are any untracked files... - if (folderContents.size()) - { - // the order surely changed! - for (auto entry : folderContents) - { - newMods.append(Mod(entry)); - } - orderedMods.append(newMods); - orderOrStateChanged = true; - } - // otherwise, if we were already tracking some mods - else if (mods.size()) - { - // if the number doesn't match, order changed. - if (mods.size() != orderedMods.size()) - orderOrStateChanged = true; - // if it does match, compare the mods themselves - else - for (int i = 0; i < mods.size(); i++) - { - if (!mods[i].strongCompare(orderedMods[i])) - { - orderOrStateChanged = true; - break; - } - } - } - beginResetModel(); - mods.swap(orderedMods); - endResetModel(); - if (orderOrStateChanged) - { - emit changed(); - } - return true; -} - -void ModList::directoryChanged(QString path) -{ - update(); -} - -bool ModList::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -bool ModList::installMod(const QString &filename) -{ - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - QFileInfo fileinfo(FS::NormalizePath(filename)); - - qDebug() << "installing: " << fileinfo.absoluteFilePath(); - - if (!fileinfo.exists() || !fileinfo.isReadable()) - { - return false; - } - Mod m(fileinfo); - if (!m.valid()) - return false; - - auto type = m.type(); - if (type == Mod::MOD_UNKNOWN) - return false; - if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) - { - QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!QFile::copy(fileinfo.filePath(), newpath)) - return false; - FS::updateTimestamp(newpath); - m.repath(newpath); - update(); - return true; - } - else if (type == Mod::MOD_FOLDER) - { - QString from = fileinfo.filePath(); - QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!FS::copy(from, to)()) - return false; - m.repath(to); - update(); - return true; - } - return false; -} - -bool ModList::enableMods(const QModelIndexList& indexes, bool enable) -{ - if(indexes.isEmpty()) - return true; - - for (auto i: indexes) - { - Mod &m = mods[i.row()]; - m.enable(enable); - emit dataChanged(i, i); - } - emit changed(); - return true; -} - -bool ModList::deleteMods(const QModelIndexList& indexes) -{ - if(indexes.isEmpty()) - return true; - - for (auto i: indexes) - { - Mod &m = mods[i.row()]; - m.destroy(); - } - emit changed(); - return true; -} - -int ModList::columnCount(const QModelIndex &parent) const -{ - return NUM_COLUMNS; -} - -QVariant ModList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= mods.size()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return mods[row].name(); - case VersionColumn: - return mods[row].version(); - case DateColumn: - return mods[row].dateTimeChanged(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return mods[row].mmc_id(); - - case Qt::CheckStateRole: - switch (column) - { - case ActiveColumn: - return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - default: - return QVariant(); - } -} - -bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - auto &mod = mods[index.row()]; - if (mod.enable(!mod.enabled())) - { - emit dataChanged(index, index); - return true; - } - } - return false; -} - -QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return QString(); - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - case DateColumn: - return tr("Last changed"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case ActiveColumn: - return tr("Is the mod enabled?"); - case NameColumn: - return tr("The name of the mod."); - case VersionColumn: - return tr("The version of the mod."); - case DateColumn: - return tr("The date and time this mod was last changed (or added)."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -Qt::ItemFlags ModList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled | - defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; -} - -Qt::DropActions ModList::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -QStringList ModList::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} - -bool ModList::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) -{ - if (action == Qt::IgnoreAction) - { - return true; - } - - // check if the action is supported - if (!data || !(action & supportedDropActions())) - { - return false; - } - - // files dropped from outside? - if (data->hasUrls()) - { - bool was_watching = is_watching; - if (was_watching) - { - stopWatching(); - } - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - { - continue; - } - // TODO: implement not only copy, but also move - // FIXME: handle errors here - installMod(url.toLocalFile()); - } - if (was_watching) - { - startWatching(); - } - return true; - } - return false; -} diff --git a/api/logic/minecraft/ModList.h b/api/logic/minecraft/ModList.h deleted file mode 100644 index 72f50edb..00000000 --- a/api/logic/minecraft/ModList.h +++ /dev/null @@ -1,121 +0,0 @@ -/* Copyright 2013-2018 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -#include "minecraft/Mod.h" - -#include "multimc_logic_export.h" - -class LegacyInstance; -class BaseInstance; -class QFileSystemWatcher; - -/** - * A legacy mod list. - * Backed by a folder. - */ -class MULTIMC_LOGIC_EXPORT ModList : public QAbstractListModel -{ - Q_OBJECT -public: - enum Columns - { - ActiveColumn = 0, - NameColumn, - DateColumn, - VersionColumn, - NUM_COLUMNS - }; - ModList(const QString &dir); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::DropActions supportedDropActions() const override; - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - QStringList mimeTypes() const override; - bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; - - virtual int rowCount(const QModelIndex &) const override - { - return size(); - } - ; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual int columnCount(const QModelIndex &parent) const override; - - size_t size() const - { - return mods.size(); - } - ; - bool empty() const - { - return size() == 0; - } - Mod &operator[](size_t index) - { - return mods[index]; - } - - /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /** - * Adds the given mod to the list at the given index - if the list supports custom ordering - */ - bool installMod(const QString& filename); - - /// Deletes all the selected mods - virtual bool deleteMods(const QModelIndexList &indexes); - - /// Enable or disable listed mods - virtual bool enableMods(const QModelIndexList &indexes, bool enable = true); - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); - - QDir dir() - { - return m_dir; - } - - const QList & allMods() - { - return mods; - } - -private -slots: - void directoryChanged(QString path); - -signals: - void changed(); - -protected: - QFileSystemWatcher *m_watcher; - bool is_watching = false; - QDir m_dir; - QList mods; -}; diff --git a/api/logic/minecraft/ModList_test.cpp b/api/logic/minecraft/ModList_test.cpp deleted file mode 100644 index 155c238a..00000000 --- a/api/logic/minecraft/ModList_test.cpp +++ /dev/null @@ -1,53 +0,0 @@ - -#include -#include -#include "TestUtil.h" - -#include "FileSystem.h" -#include "minecraft/ModList.h" - -class ModListTest : public QObject -{ - Q_OBJECT - -private -slots: - // test for GH-1178 - install a folder with files to a mod list - void test_1178() - { - // source - QString source = QFINDTESTDATA("data/test_folder"); - - // sanity check - QVERIFY(!source.endsWith('/')); - - auto verify = [](QString path) - { - QDir target_dir(FS::PathCombine(path, "test_folder")); - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // 1. test with no trailing / - { - QString folder = source; - QTemporaryDir tempDir; - ModList m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - - // 2. test with trailing / - { - QString folder = source + '/'; - QTemporaryDir tempDir; - ModList m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - } -}; - -QTEST_GUILESS_MAIN(ModListTest) - -#include "ModList_test.moc" diff --git a/api/logic/minecraft/MojangDownloadInfo.h b/api/logic/minecraft/MojangDownloadInfo.h index 7399a56b..88f87287 100644 --- a/api/logic/minecraft/MojangDownloadInfo.h +++ b/api/logic/minecraft/MojangDownloadInfo.h @@ -5,78 +5,78 @@ struct MojangDownloadInfo { - // types - typedef std::shared_ptr Ptr; + // types + typedef std::shared_ptr Ptr; - // data - /// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested! - QString path; - /// absolute URL of this file - QString url; - /// sha-1 checksum of the file - QString sha1; - /// size of the file in bytes - int size; + // data + /// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested! + QString path; + /// absolute URL of this file + QString url; + /// sha-1 checksum of the file + QString sha1; + /// size of the file in bytes + int size; }; struct MojangLibraryDownloadInfo { - MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {}; - MojangLibraryDownloadInfo() {}; + MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {}; + MojangLibraryDownloadInfo() {}; - // types - typedef std::shared_ptr Ptr; + // types + typedef std::shared_ptr Ptr; - // methods - MojangDownloadInfo *getDownloadInfo(QString classifier) - { - if (classifier.isNull()) - { - return artifact.get(); - } - - return classifiers[classifier].get(); - } + // methods + MojangDownloadInfo *getDownloadInfo(QString classifier) + { + if (classifier.isNull()) + { + return artifact.get(); + } + + return classifiers[classifier].get(); + } - // data - MojangDownloadInfo::Ptr artifact; - QMap classifiers; + // data + MojangDownloadInfo::Ptr artifact; + QMap classifiers; }; struct MojangAssetIndexInfo : public MojangDownloadInfo { - // types - typedef std::shared_ptr Ptr; + // types + typedef std::shared_ptr Ptr; - // methods - MojangAssetIndexInfo() - { - } + // methods + MojangAssetIndexInfo() + { + } - MojangAssetIndexInfo(QString id) - { - this->id = id; - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - if(id == "legacy") - { - url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; - } - // HACK - else - { - url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json"; - } - known = false; - } + MojangAssetIndexInfo(QString id) + { + this->id = id; + // HACK: ignore assets from other version files than Minecraft + // workaround for stupid assets issue caused by amazon: + // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ + if(id == "legacy") + { + url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; + } + // HACK + else + { + url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json"; + } + known = false; + } - // data - int totalSize; - QString id; - bool known = true; + // data + int totalSize; + QString id; + bool known = true; }; diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp index a0aa2894..33d3c54c 100644 --- a/api/logic/minecraft/MojangVersionFormat.cpp +++ b/api/logic/minecraft/MojangVersionFormat.cpp @@ -19,361 +19,361 @@ namespace Bits { static void readString(const QJsonObject &root, const QString &key, QString &variable) { - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } + if (root.contains(key)) + { + variable = requireString(root.value(key)); + } } static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject &obj) { - // optional, not used - readString(obj, "path", out->path); - // required! - out->sha1 = requireString(obj, "sha1"); - out->url = requireString(obj, "url"); - out->size = requireInteger(obj, "size"); + // optional, not used + readString(obj, "path", out->path); + // required! + out->sha1 = requireString(obj, "sha1"); + out->url = requireString(obj, "url"); + out->size = requireInteger(obj, "size"); } static void readAssetIndex(MojangAssetIndexInfo::Ptr out, const QJsonObject &obj) { - out->totalSize = requireInteger(obj, "totalSize"); - out->id = requireString(obj, "id"); - // out->known = true; + out->totalSize = requireInteger(obj, "totalSize"); + out->id = requireString(obj, "id"); + // out->known = true; } } MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject &obj) { - auto out = std::make_shared(); - Bits::readDownloadInfo(out, obj); - return out; + auto out = std::make_shared(); + Bits::readDownloadInfo(out, obj); + return out; } MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject &obj) { - auto out = std::make_shared(); - Bits::readDownloadInfo(out, obj); - Bits::readAssetIndex(out, obj); - return out; + auto out = std::make_shared(); + Bits::readDownloadInfo(out, obj); + Bits::readAssetIndex(out, obj); + return out; } QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info) { - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - return out; + QJsonObject out; + if(!info->path.isNull()) + { + out.insert("path", info->path); + } + out.insert("sha1", info->sha1); + out.insert("size", info->size); + out.insert("url", info->url); + return out; } MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject &libObj) { - auto out = std::make_shared(); - auto dlObj = requireObject(libObj.value("downloads")); - if(dlObj.contains("artifact")) - { - out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact")); - } - if(dlObj.contains("classifiers")) - { - auto classifiersObj = requireObject(dlObj, "classifiers"); - for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->classifiers[classifier] = downloadInfoFromJson(classifierObj); - } - } - return out; + auto out = std::make_shared(); + auto dlObj = requireObject(libObj.value("downloads")); + if(dlObj.contains("artifact")) + { + out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact")); + } + if(dlObj.contains("classifiers")) + { + auto classifiersObj = requireObject(dlObj, "classifiers"); + for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++) + { + auto classifier = iter.key(); + auto classifierObj = requireObject(iter.value()); + out->classifiers[classifier] = downloadInfoFromJson(classifierObj); + } + } + return out; } QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo) { - QJsonObject out; - if(libinfo->artifact) - { - out.insert("artifact", downloadInfoToJson(libinfo->artifact)); - } - if(libinfo->classifiers.size()) - { - QJsonObject classifiersOut; - for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) - { - classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("classifiers", classifiersOut); - } - return out; + QJsonObject out; + if(libinfo->artifact) + { + out.insert("artifact", downloadInfoToJson(libinfo->artifact)); + } + if(libinfo->classifiers.size()) + { + QJsonObject classifiersOut; + for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) + { + classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value())); + } + out.insert("classifiers", classifiersOut); + } + return out; } QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr info) { - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - out.insert("totalSize", info->totalSize); - out.insert("id", info->id); - return out; + QJsonObject out; + if(!info->path.isNull()) + { + out.insert("path", info->path); + } + out.insert("sha1", info->sha1); + out.insert("size", info->size); + out.insert("url", info->url); + out.insert("totalSize", info->totalSize); + out.insert("id", info->id); + return out; } void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFile *out) { - Bits::readString(in, "id", out->minecraftVersion); - Bits::readString(in, "mainClass", out->mainClass); - Bits::readString(in, "minecraftArguments", out->minecraftArguments); - if(out->minecraftArguments.isEmpty()) - { - QString processArguments; - Bits::readString(in, "processArguments", processArguments); - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - out->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } - else if (!toCompare.isEmpty()) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments)); - } - } - Bits::readString(in, "type", out->type); + Bits::readString(in, "id", out->minecraftVersion); + Bits::readString(in, "mainClass", out->mainClass); + Bits::readString(in, "minecraftArguments", out->minecraftArguments); + if(out->minecraftArguments.isEmpty()) + { + QString processArguments; + Bits::readString(in, "processArguments", processArguments); + QString toCompare = processArguments.toLower(); + if (toCompare == "legacy") + { + out->minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; + } + else if (!toCompare.isEmpty()) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments)); + } + } + Bits::readString(in, "type", out->type); - Bits::readString(in, "assets", out->assets); - if(in.contains("assetIndex")) - { - out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex")); - } - else if (!out->assets.isNull()) - { - out->mojangAssetIndex = std::make_shared(out->assets); - } + Bits::readString(in, "assets", out->assets); + if(in.contains("assetIndex")) + { + out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex")); + } + else if (!out->assets.isNull()) + { + out->mojangAssetIndex = std::make_shared(out->assets); + } - out->releaseTime = timeFromS3Time(in.value("releaseTime").toString("")); - out->updateTime = timeFromS3Time(in.value("time").toString("")); + out->releaseTime = timeFromS3Time(in.value("releaseTime").toString("")); + out->updateTime = timeFromS3Time(in.value("time").toString("")); - if (in.contains("minimumLauncherVersion")) - { - out->minimumLauncherVersion = requireInteger(in.value("minimumLauncherVersion")); - if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) - { - out->addProblem( - ProblemSeverity::Warning, - QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by MultiMC (%2). It might not work properly!") - .arg(out->minimumLauncherVersion) - .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)); - } - } - if(in.contains("downloads")) - { - auto downloadsObj = requireObject(in, "downloads"); - for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj); - } - } + if (in.contains("minimumLauncherVersion")) + { + out->minimumLauncherVersion = requireInteger(in.value("minimumLauncherVersion")); + if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) + { + out->addProblem( + ProblemSeverity::Warning, + QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by MultiMC (%2). It might not work properly!") + .arg(out->minimumLauncherVersion) + .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)); + } + } + if(in.contains("downloads")) + { + auto downloadsObj = requireObject(in, "downloads"); + for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++) + { + auto classifier = iter.key(); + auto classifierObj = requireObject(iter.value()); + out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj); + } + } } VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename) { - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } + VersionFilePtr out(new VersionFile()); + if (doc.isEmpty() || doc.isNull()) + { + throw JSONValidationError(filename + " is empty or null"); + } + if (!doc.isObject()) + { + throw JSONValidationError(filename + " is not an object"); + } - QJsonObject root = doc.object(); + QJsonObject root = doc.object(); - readVersionProperties(root, out.get()); + readVersionProperties(root, out.get()); - out->name = "Minecraft"; - out->uid = "net.minecraft"; - out->version = out->minecraftVersion; - // out->filename = filename; + out->name = "Minecraft"; + out->uid = "net.minecraft"; + out->version = out->minecraftVersion; + // out->filename = filename; - if (root.contains("libraries")) - { - for (auto libVal : requireArray(root.value("libraries"))) - { - auto libObj = requireObject(libVal); + if (root.contains("libraries")) + { + for (auto libVal : requireArray(root.value("libraries"))) + { + auto libObj = requireObject(libVal); - auto lib = MojangVersionFormat::libraryFromJson(libObj, filename); - out->libraries.append(lib); - } - } - return out; + auto lib = MojangVersionFormat::libraryFromJson(libObj, filename); + out->libraries.append(lib); + } + } + return out; } void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObject& out) { - writeString(out, "id", in->minecraftVersion); - writeString(out, "mainClass", in->mainClass); - writeString(out, "minecraftArguments", in->minecraftArguments); - writeString(out, "type", in->type); - if(!in->releaseTime.isNull()) - { - writeString(out, "releaseTime", timeToS3Time(in->releaseTime)); - } - if(!in->updateTime.isNull()) - { - writeString(out, "time", timeToS3Time(in->updateTime)); - } - if(in->minimumLauncherVersion != -1) - { - out.insert("minimumLauncherVersion", in->minimumLauncherVersion); - } - writeString(out, "assets", in->assets); - if(in->mojangAssetIndex && in->mojangAssetIndex->known) - { - out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); - } - if(in->mojangDownloads.size()) - { - QJsonObject downloadsOut; - for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) - { - downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("downloads", downloadsOut); - } + writeString(out, "id", in->minecraftVersion); + writeString(out, "mainClass", in->mainClass); + writeString(out, "minecraftArguments", in->minecraftArguments); + writeString(out, "type", in->type); + if(!in->releaseTime.isNull()) + { + writeString(out, "releaseTime", timeToS3Time(in->releaseTime)); + } + if(!in->updateTime.isNull()) + { + writeString(out, "time", timeToS3Time(in->updateTime)); + } + if(in->minimumLauncherVersion != -1) + { + out.insert("minimumLauncherVersion", in->minimumLauncherVersion); + } + writeString(out, "assets", in->assets); + if(in->mojangAssetIndex && in->mojangAssetIndex->known) + { + out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); + } + if(in->mojangDownloads.size()) + { + QJsonObject downloadsOut; + for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) + { + downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value())); + } + out.insert("downloads", downloadsOut); + } } QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch) { - QJsonObject root; - writeVersionProperties(patch.get(), root); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(MojangVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } + QJsonObject root; + writeVersionProperties(patch.get(), root); + if (!patch->libraries.isEmpty()) + { + QJsonArray array; + for (auto value: patch->libraries) + { + array.append(MojangVersionFormat::libraryToJson(value.get())); + } + root.insert("libraries", array); + } - // write the contents to a json document. - { - QJsonDocument out; - out.setObject(root); - return out; - } + // write the contents to a json document. + { + QJsonDocument out; + out.setObject(root); + return out; + } } LibraryPtr MojangVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename) { - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); - } - out->m_name = libObj.value("name").toString(); + LibraryPtr out(new Library()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); + } + out->m_name = libObj.value("name").toString(); - Bits::readString(libObj, "url", out->m_repositoryURL); - if (libObj.contains("extract")) - { - out->m_hasExcludes = true; - auto extractObj = requireObject(libObj.value("extract")); - for (auto excludeVal : requireArray(extractObj.value("exclude"))) - { - out->m_extractExcludes.append(requireString(excludeVal)); - } - } - if (libObj.contains("natives")) - { - QJsonObject nativesObj = requireObject(libObj.value("natives")); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) - { - if (!it.value().isString()) - { - qWarning() << filename << "contains an invalid native (skipping)"; - } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->m_nativeClassifiers[opSys] = it.value().toString(); - } - } - } - if (libObj.contains("rules")) - { - out->applyRules = true; - out->m_rules = rulesFromJsonV4(libObj); - } - if (libObj.contains("downloads")) - { - out->m_mojangDownloads = libDownloadInfoFromJson(libObj); - } - return out; + Bits::readString(libObj, "url", out->m_repositoryURL); + if (libObj.contains("extract")) + { + out->m_hasExcludes = true; + auto extractObj = requireObject(libObj.value("extract")); + for (auto excludeVal : requireArray(extractObj.value("exclude"))) + { + out->m_extractExcludes.append(requireString(excludeVal)); + } + } + if (libObj.contains("natives")) + { + QJsonObject nativesObj = requireObject(libObj.value("natives")); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + if (!it.value().isString()) + { + qWarning() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out->m_nativeClassifiers[opSys] = it.value().toString(); + } + } + } + if (libObj.contains("rules")) + { + out->applyRules = true; + out->m_rules = rulesFromJsonV4(libObj); + } + if (libObj.contains("downloads")) + { + out->m_mojangDownloads = libDownloadInfoFromJson(libObj); + } + return out; } QJsonObject MojangVersionFormat::libraryToJson(Library *library) { - QJsonObject libRoot; - libRoot.insert("name", (QString)library->m_name); - if (!library->m_repositoryURL.isEmpty()) - { - libRoot.insert("url", library->m_repositoryURL); - } - if (library->isNative()) - { - QJsonObject nativeList; - auto iter = library->m_nativeClassifiers.begin(); - while (iter != library->m_nativeClassifiers.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - if (library->m_extractExcludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : library->m_extractExcludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - } - if (library->m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : library->m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - if(library->m_mojangDownloads) - { - auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads); - libRoot.insert("downloads", downloadsObj); - } - return libRoot; + QJsonObject libRoot; + libRoot.insert("name", (QString)library->m_name); + if (!library->m_repositoryURL.isEmpty()) + { + libRoot.insert("url", library->m_repositoryURL); + } + if (library->isNative()) + { + QJsonObject nativeList; + auto iter = library->m_nativeClassifiers.begin(); + while (iter != library->m_nativeClassifiers.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + if (library->m_extractExcludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : library->m_extractExcludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + } + if (library->m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : library->m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + if(library->m_mojangDownloads) + { + auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads); + libRoot.insert("downloads", downloadsObj); + } + return libRoot; } diff --git a/api/logic/minecraft/MojangVersionFormat.h b/api/logic/minecraft/MojangVersionFormat.h index 4e141088..76c529e9 100644 --- a/api/logic/minecraft/MojangVersionFormat.h +++ b/api/logic/minecraft/MojangVersionFormat.h @@ -10,16 +10,16 @@ class MULTIMC_LOGIC_EXPORT MojangVersionFormat { friend class OneSixVersionFormat; protected: - // does not include libraries - static void readVersionProperties(const QJsonObject& in, VersionFile* out); - // does not include libraries - static void writeVersionProperties(const VersionFile* in, QJsonObject& out); + // does not include libraries + static void readVersionProperties(const QJsonObject& in, VersionFile* out); + // does not include libraries + static void writeVersionProperties(const VersionFile* in, QJsonObject& out); public: - // version files / profile patches - static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch); + // version files / profile patches + static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename); + static QJsonDocument versionFileToJson(const VersionFilePtr &patch); - // libraries - static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); + // libraries + static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject libraryToJson(Library *library); }; diff --git a/api/logic/minecraft/MojangVersionFormat_test.cpp b/api/logic/minecraft/MojangVersionFormat_test.cpp index 9c8482ce..9d095340 100644 --- a/api/logic/minecraft/MojangVersionFormat_test.cpp +++ b/api/logic/minecraft/MojangVersionFormat_test.cpp @@ -6,47 +6,47 @@ class MojangVersionFormatTest : public QObject { - Q_OBJECT - - static QJsonDocument readJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - return QJsonDocument::fromJson(data); - } - static void writeJson(const char *file, QJsonDocument doc) - { - QFile jsonFile(file); - jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); - auto data = doc.toJson(QJsonDocument::Indented); - qDebug() << QString::fromUtf8(data); - jsonFile.write(data); - jsonFile.close(); - } + Q_OBJECT + + static QJsonDocument readJson(const char *file) + { + auto path = QFINDTESTDATA(file); + QFile jsonFile(path); + jsonFile.open(QIODevice::ReadOnly); + auto data = jsonFile.readAll(); + jsonFile.close(); + return QJsonDocument::fromJson(data); + } + static void writeJson(const char *file, QJsonDocument doc) + { + QFile jsonFile(file); + jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); + auto data = doc.toJson(QJsonDocument::Indented); + qDebug() << QString::fromUtf8(data); + jsonFile.write(data); + jsonFile.close(); + } private slots: - void test_Through_Simple() - { - QJsonDocument doc = readJson("data/1.9-simple.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-simple-passthorugh.json", doc2); - - QCOMPARE(doc.toJson(), doc2.toJson()); - } - - void test_Through() - { - QJsonDocument doc = readJson("data/1.9.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-passthorugh.json", doc2); - QCOMPARE(doc.toJson(), doc2.toJson()); - } + void test_Through_Simple() + { + QJsonDocument doc = readJson("data/1.9-simple.json"); + auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); + auto doc2 = MojangVersionFormat::versionFileToJson(vfile); + writeJson("1.9-simple-passthorugh.json", doc2); + + QCOMPARE(doc.toJson(), doc2.toJson()); + } + + void test_Through() + { + QJsonDocument doc = readJson("data/1.9.json"); + auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); + auto doc2 = MojangVersionFormat::versionFileToJson(vfile); + writeJson("1.9-passthorugh.json", doc2); + QCOMPARE(doc.toJson(), doc2.toJson()); + } }; QTEST_GUILESS_MAIN(MojangVersionFormatTest) diff --git a/api/logic/minecraft/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp index f7ab25b3..6f3b926b 100644 --- a/api/logic/minecraft/OneSixVersionFormat.cpp +++ b/api/logic/minecraft/OneSixVersionFormat.cpp @@ -7,366 +7,366 @@ using namespace Json; static void readString(const QJsonObject &root, const QString &key, QString &variable) { - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } + if (root.contains(key)) + { + variable = requireString(root.value(key)); + } } LibraryPtr OneSixVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename) { - LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename); - readString(libObj, "MMC-hint", out->m_hint); - readString(libObj, "MMC-absulute_url", out->m_absoluteURL); - readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); - readString(libObj, "MMC-filename", out->m_filename); - readString(libObj, "MMC-displayname", out->m_displayname); - return out; + LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename); + readString(libObj, "MMC-hint", out->m_hint); + readString(libObj, "MMC-absulute_url", out->m_absoluteURL); + readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); + readString(libObj, "MMC-filename", out->m_filename); + readString(libObj, "MMC-displayname", out->m_displayname); + return out; } QJsonObject OneSixVersionFormat::libraryToJson(Library *library) { - QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); - if (library->m_absoluteURL.size()) - libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); - if (library->m_hint.size()) - libRoot.insert("MMC-hint", library->m_hint); - if (library->m_filename.size()) - libRoot.insert("MMC-filename", library->m_filename); - if (library->m_displayname.size()) - libRoot.insert("MMC-displayname", library->m_displayname); - return libRoot; + QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); + if (library->m_absoluteURL.size()) + libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); + if (library->m_hint.size()) + libRoot.insert("MMC-hint", library->m_hint); + if (library->m_filename.size()) + libRoot.insert("MMC-filename", library->m_filename); + if (library->m_displayname.size()) + libRoot.insert("MMC-displayname", library->m_displayname); + return libRoot; } VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder) { - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } - - QJsonObject root = doc.object(); - - Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false); - switch(formatVersion) - { - case Meta::MetadataVersion::InitialRelease: - break; - case Meta::MetadataVersion::Invalid: - throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format."); - } - - if (requireOrder) - { - if (root.contains("order")) - { - out->order = requireInteger(root.value("order")); - } - else - { - // FIXME: evaluate if we don't want to throw exceptions here instead - qCritical() << filename << "doesn't contain an order field"; - } - } - - out->name = root.value("name").toString(); - - if(root.contains("uid")) - { - out->uid = root.value("uid").toString(); - } - else - { - out->uid = root.value("fileId").toString(); - } - - out->version = root.value("version").toString(); - - MojangVersionFormat::readVersionProperties(root, out.get()); - - // added for legacy Minecraft window embedding, TODO: remove - readString(root, "appletClass", out->appletClass); - - if (root.contains("+tweakers")) - { - for (auto tweakerVal : requireArray(root.value("+tweakers"))) - { - out->addTweakers.append(requireString(tweakerVal)); - } - } - - if (root.contains("+traits")) - { - for (auto tweakerVal : requireArray(root.value("+traits"))) - { - out->traits.insert(requireString(tweakerVal)); - } - } - - - if (root.contains("jarMods")) - { - for (auto libVal : requireArray(root.value("jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::jarModFromJson(libObj, filename); - // and add to jar mods - out->jarMods.append(lib); - } - } - else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility - { - for (auto libVal : requireArray(root.value("+jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name); - // and add to jar mods - out->jarMods.append(lib); - } - } - - if (root.contains("mods")) - { - for (auto libVal : requireArray(root.value("mods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::modFromJson(libObj, filename); - // and add to jar mods - out->mods.append(lib); - } - } - - auto readLibs = [&](const char * which) - { - for (auto libVal : requireArray(root.value(which))) - { - QJsonObject libObj = requireObject(libVal); - // parse the library - auto lib = libraryFromJson(libObj, filename); - out->libraries.append(lib); - } - }; - bool hasPlusLibs = root.contains("+libraries"); - bool hasLibs = root.contains("libraries"); - if (hasPlusLibs && hasLibs) - { - out->addProblem(ProblemSeverity::Warning, - QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported.")); - readLibs("libraries"); - readLibs("+libraries"); - } - else if (hasLibs) - { - readLibs("libraries"); - } - else if(hasPlusLibs) - { - readLibs("+libraries"); - } - - // if we have mainJar, just use it - if(root.contains("mainJar")) - { - QJsonObject libObj = requireObject(root, "mainJar"); - out->mainJar = libraryFromJson(libObj, filename); - } - // else reconstruct it from downloads and id ... if that's available - else if(!out->minecraftVersion.isEmpty()) - { - auto lib = std::make_shared(); - lib->setRawName(GradleSpecifier(QString("com.mojang:minecraft:%1:client").arg(out->minecraftVersion))); - // we have a reliable client download, use it. - if(out->mojangDownloads.contains("client")) - { - auto LibDLInfo = std::make_shared(); - LibDLInfo->artifact = out->mojangDownloads["client"]; - lib->setMojangDownloadInfo(LibDLInfo); - } - // we got nothing... guess based on ancient hardcoded Mojang behaviour - // FIXME: this will eventually break... - else - { - lib->setAbsoluteUrl(URLConstants::getLegacyJarUrl(out->minecraftVersion)); - } - out->mainJar = lib; - } - - if (root.contains("requires")) - { - Meta::parseRequires(root, &out->requires); - } - QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); - if(!dependsOnMinecraftVersion.isEmpty()) - { - Meta::Require mcReq; - mcReq.uid = "net.minecraft"; - mcReq.equalsVersion = dependsOnMinecraftVersion; - if (out->requires.count(mcReq) == 0) - { - out->requires.insert(mcReq); - } - } - if (root.contains("conflicts")) - { - Meta::parseRequires(root, &out->conflicts); - } - if (root.contains("volatile")) - { - out->m_volatile = requireBoolean(root, "volatile"); - } - - /* removed features that shouldn't be used */ - if (root.contains("tweakers")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); - } - if (root.contains("-libraries")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'")); - } - if (root.contains("-tweakers")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'")); - } - if (root.contains("-minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'")); - } - if (root.contains("+minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'")); - } - return out; + VersionFilePtr out(new VersionFile()); + if (doc.isEmpty() || doc.isNull()) + { + throw JSONValidationError(filename + " is empty or null"); + } + if (!doc.isObject()) + { + throw JSONValidationError(filename + " is not an object"); + } + + QJsonObject root = doc.object(); + + Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false); + switch(formatVersion) + { + case Meta::MetadataVersion::InitialRelease: + break; + case Meta::MetadataVersion::Invalid: + throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format."); + } + + if (requireOrder) + { + if (root.contains("order")) + { + out->order = requireInteger(root.value("order")); + } + else + { + // FIXME: evaluate if we don't want to throw exceptions here instead + qCritical() << filename << "doesn't contain an order field"; + } + } + + out->name = root.value("name").toString(); + + if(root.contains("uid")) + { + out->uid = root.value("uid").toString(); + } + else + { + out->uid = root.value("fileId").toString(); + } + + out->version = root.value("version").toString(); + + MojangVersionFormat::readVersionProperties(root, out.get()); + + // added for legacy Minecraft window embedding, TODO: remove + readString(root, "appletClass", out->appletClass); + + if (root.contains("+tweakers")) + { + for (auto tweakerVal : requireArray(root.value("+tweakers"))) + { + out->addTweakers.append(requireString(tweakerVal)); + } + } + + if (root.contains("+traits")) + { + for (auto tweakerVal : requireArray(root.value("+traits"))) + { + out->traits.insert(requireString(tweakerVal)); + } + } + + + if (root.contains("jarMods")) + { + for (auto libVal : requireArray(root.value("jarMods"))) + { + QJsonObject libObj = requireObject(libVal); + // parse the jarmod + auto lib = OneSixVersionFormat::jarModFromJson(libObj, filename); + // and add to jar mods + out->jarMods.append(lib); + } + } + else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility + { + for (auto libVal : requireArray(root.value("+jarMods"))) + { + QJsonObject libObj = requireObject(libVal); + // parse the jarmod + auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name); + // and add to jar mods + out->jarMods.append(lib); + } + } + + if (root.contains("mods")) + { + for (auto libVal : requireArray(root.value("mods"))) + { + QJsonObject libObj = requireObject(libVal); + // parse the jarmod + auto lib = OneSixVersionFormat::modFromJson(libObj, filename); + // and add to jar mods + out->mods.append(lib); + } + } + + auto readLibs = [&](const char * which) + { + for (auto libVal : requireArray(root.value(which))) + { + QJsonObject libObj = requireObject(libVal); + // parse the library + auto lib = libraryFromJson(libObj, filename); + out->libraries.append(lib); + } + }; + bool hasPlusLibs = root.contains("+libraries"); + bool hasLibs = root.contains("libraries"); + if (hasPlusLibs && hasLibs) + { + out->addProblem(ProblemSeverity::Warning, + QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported.")); + readLibs("libraries"); + readLibs("+libraries"); + } + else if (hasLibs) + { + readLibs("libraries"); + } + else if(hasPlusLibs) + { + readLibs("+libraries"); + } + + // if we have mainJar, just use it + if(root.contains("mainJar")) + { + QJsonObject libObj = requireObject(root, "mainJar"); + out->mainJar = libraryFromJson(libObj, filename); + } + // else reconstruct it from downloads and id ... if that's available + else if(!out->minecraftVersion.isEmpty()) + { + auto lib = std::make_shared(); + lib->setRawName(GradleSpecifier(QString("com.mojang:minecraft:%1:client").arg(out->minecraftVersion))); + // we have a reliable client download, use it. + if(out->mojangDownloads.contains("client")) + { + auto LibDLInfo = std::make_shared(); + LibDLInfo->artifact = out->mojangDownloads["client"]; + lib->setMojangDownloadInfo(LibDLInfo); + } + // we got nothing... guess based on ancient hardcoded Mojang behaviour + // FIXME: this will eventually break... + else + { + lib->setAbsoluteUrl(URLConstants::getLegacyJarUrl(out->minecraftVersion)); + } + out->mainJar = lib; + } + + if (root.contains("requires")) + { + Meta::parseRequires(root, &out->requires); + } + QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); + if(!dependsOnMinecraftVersion.isEmpty()) + { + Meta::Require mcReq; + mcReq.uid = "net.minecraft"; + mcReq.equalsVersion = dependsOnMinecraftVersion; + if (out->requires.count(mcReq) == 0) + { + out->requires.insert(mcReq); + } + } + if (root.contains("conflicts")) + { + Meta::parseRequires(root, &out->conflicts); + } + if (root.contains("volatile")) + { + out->m_volatile = requireBoolean(root, "volatile"); + } + + /* removed features that shouldn't be used */ + if (root.contains("tweakers")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); + } + if (root.contains("-libraries")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'")); + } + if (root.contains("-tweakers")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'")); + } + if (root.contains("-minecraftArguments")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'")); + } + if (root.contains("+minecraftArguments")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'")); + } + return out; } QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch) { - QJsonObject root; - writeString(root, "name", patch->name); - - writeString(root, "uid", patch->uid); - - writeString(root, "version", patch->version); - - Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); - - MojangVersionFormat::writeVersionProperties(patch.get(), root); - - if(patch->mainJar) - { - root.insert("mainJar", libraryToJson(patch->mainJar.get())); - } - writeString(root, "appletClass", patch->appletClass); - writeStringList(root, "+tweakers", patch->addTweakers); - writeStringList(root, "+traits", patch->traits.toList()); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(OneSixVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } - if (!patch->jarMods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::jarModtoJson(value.get())); - } - root.insert("jarMods", array); - } - if (!patch->mods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::modtoJson(value.get())); - } - root.insert("mods", array); - } - if(!patch->requires.empty()) - { - Meta::serializeRequires(root, &patch->requires, "requires"); - } - if(!patch->conflicts.empty()) - { - Meta::serializeRequires(root, &patch->conflicts, "conflicts"); - } - if(patch->m_volatile) - { - root.insert("volatile", true); - } - // write the contents to a json document. - { - QJsonDocument out; - out.setObject(root); - return out; - } + QJsonObject root; + writeString(root, "name", patch->name); + + writeString(root, "uid", patch->uid); + + writeString(root, "version", patch->version); + + Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); + + MojangVersionFormat::writeVersionProperties(patch.get(), root); + + if(patch->mainJar) + { + root.insert("mainJar", libraryToJson(patch->mainJar.get())); + } + writeString(root, "appletClass", patch->appletClass); + writeStringList(root, "+tweakers", patch->addTweakers); + writeStringList(root, "+traits", patch->traits.toList()); + if (!patch->libraries.isEmpty()) + { + QJsonArray array; + for (auto value: patch->libraries) + { + array.append(OneSixVersionFormat::libraryToJson(value.get())); + } + root.insert("libraries", array); + } + if (!patch->jarMods.isEmpty()) + { + QJsonArray array; + for (auto value: patch->jarMods) + { + array.append(OneSixVersionFormat::jarModtoJson(value.get())); + } + root.insert("jarMods", array); + } + if (!patch->mods.isEmpty()) + { + QJsonArray array; + for (auto value: patch->jarMods) + { + array.append(OneSixVersionFormat::modtoJson(value.get())); + } + root.insert("mods", array); + } + if(!patch->requires.empty()) + { + Meta::serializeRequires(root, &patch->requires, "requires"); + } + if(!patch->conflicts.empty()) + { + Meta::serializeRequires(root, &patch->conflicts, "conflicts"); + } + if(patch->m_volatile) + { + root.insert("volatile", true); + } + // write the contents to a json document. + { + QJsonDocument out; + out.setObject(root); + return out; + } } LibraryPtr OneSixVersionFormat::plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName) { - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a jarmod that doesn't have a 'name' field"); - } - - // just make up something unique on the spot for the library name. - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - - // filename override is the old name - out->setFilename(libObj.value("name").toString()); - - // it needs to be local, it is stored in the instance jarmods folder - out->setHint("local"); - - // read the original name if present - some versions did not set it - // it is the original jar mod filename before it got renamed at the point of addition - auto displayName = libObj.value("originalName").toString(); - if(displayName.isEmpty()) - { - auto fixed = originalName; - fixed.remove(" (jar mod)"); - out->setDisplayName(fixed); - } - else - { - out->setDisplayName(displayName); - } - return out; + LibraryPtr out(new Library()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a jarmod that doesn't have a 'name' field"); + } + + // just make up something unique on the spot for the library name. + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); + out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); + + // filename override is the old name + out->setFilename(libObj.value("name").toString()); + + // it needs to be local, it is stored in the instance jarmods folder + out->setHint("local"); + + // read the original name if present - some versions did not set it + // it is the original jar mod filename before it got renamed at the point of addition + auto displayName = libObj.value("originalName").toString(); + if(displayName.isEmpty()) + { + auto fixed = originalName; + fixed.remove(" (jar mod)"); + out->setDisplayName(fixed); + } + else + { + out->setDisplayName(displayName); + } + return out; } LibraryPtr OneSixVersionFormat::jarModFromJson(const QJsonObject& libObj, const QString& filename) { - return libraryFromJson(libObj, filename); + return libraryFromJson(libObj, filename); } QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod) { - return libraryToJson(jarmod); + return libraryToJson(jarmod); } LibraryPtr OneSixVersionFormat::modFromJson(const QJsonObject& libObj, const QString& filename) { - return libraryFromJson(libObj, filename); + return libraryFromJson(libObj, filename); } QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod) { - return libraryToJson(jarmod); + return libraryToJson(jarmod); } diff --git a/api/logic/minecraft/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h index 8b782db0..6bbebca0 100644 --- a/api/logic/minecraft/OneSixVersionFormat.h +++ b/api/logic/minecraft/OneSixVersionFormat.h @@ -8,22 +8,22 @@ class OneSixVersionFormat { public: - // version files / profile patches - static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch); + // version files / profile patches + static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); + static QJsonDocument versionFileToJson(const VersionFilePtr &patch); - // libraries - static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); + // libraries + static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject libraryToJson(Library *library); - // DEPRECATED: old 'plus' jar mods generated by the application - static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName); + // DEPRECATED: old 'plus' jar mods generated by the application + static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName); - // new jar mods derived from libraries - static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject jarModtoJson(Library * jarmod); + // new jar mods derived from libraries + static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject jarModtoJson(Library * jarmod); - // mods, also derived from libraries - static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject modtoJson(Library * jarmod); + // mods, also derived from libraries + static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject modtoJson(Library * jarmod); }; diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp index 2165fa7f..5e1e6c2b 100644 --- a/api/logic/minecraft/OpSys.cpp +++ b/api/logic/minecraft/OpSys.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,26 +17,26 @@ OpSys OpSys_fromString(QString name) { - if (name == "linux") - return Os_Linux; - if (name == "windows") - return Os_Windows; - if (name == "osx") - return Os_OSX; - return Os_Other; + if (name == "linux") + return Os_Linux; + if (name == "windows") + return Os_Windows; + if (name == "osx") + return Os_OSX; + return Os_Other; } QString OpSys_toString(OpSys name) { - switch (name) - { - case Os_Linux: - return "linux"; - case Os_OSX: - return "osx"; - case Os_Windows: - return "windows"; - default: - return "other"; - } + switch (name) + { + case Os_Linux: + return "linux"; + case Os_OSX: + return "osx"; + case Os_Windows: + return "windows"; + default: + return "other"; + } } \ No newline at end of file diff --git a/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h index e44b1e07..58852acb 100644 --- a/api/logic/minecraft/OpSys.h +++ b/api/logic/minecraft/OpSys.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,10 @@ #include enum OpSys { - Os_Windows, - Os_Linux, - Os_OSX, - Os_Other + Os_Windows, + Os_Linux, + Os_OSX, + Os_Other }; OpSys OpSys_fromString(QString); diff --git a/api/logic/minecraft/ParseUtils.cpp b/api/logic/minecraft/ParseUtils.cpp index ca188432..c9640e77 100644 --- a/api/logic/minecraft/ParseUtils.cpp +++ b/api/logic/minecraft/ParseUtils.cpp @@ -6,29 +6,29 @@ QDateTime timeFromS3Time(QString str) { - return QDateTime::fromString(str, Qt::ISODate); + return QDateTime::fromString(str, Qt::ISODate); } QString timeToS3Time(QDateTime time) { - // this all because Qt can't format timestamps right. - int offsetRaw = time.offsetFromUtc(); - bool negative = offsetRaw < 0; - int offsetAbs = std::abs(offsetRaw); + // this all because Qt can't format timestamps right. + int offsetRaw = time.offsetFromUtc(); + bool negative = offsetRaw < 0; + int offsetAbs = std::abs(offsetRaw); - int offsetSeconds = offsetAbs % 60; - offsetAbs -= offsetSeconds; + int offsetSeconds = offsetAbs % 60; + offsetAbs -= offsetSeconds; - int offsetMinutes = offsetAbs % 3600; - offsetAbs -= offsetMinutes; - offsetMinutes /= 60; - - int offsetHours = offsetAbs / 3600; + int offsetMinutes = offsetAbs % 3600; + offsetAbs -= offsetMinutes; + offsetMinutes /= 60; + + int offsetHours = offsetAbs / 3600; - QString raw = time.toString("yyyy-MM-ddTHH:mm:ss"); - raw += (negative ? QChar('-') : QChar('+')); - raw += QString("%1").arg(offsetHours, 2, 10, QChar('0')); - raw += ":"; - raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0')); - return raw; + QString raw = time.toString("yyyy-MM-ddTHH:mm:ss"); + raw += (negative ? QChar('-') : QChar('+')); + raw += QString("%1").arg(offsetHours, 2, 10, QChar('0')); + raw += ":"; + raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0')); + return raw; } diff --git a/api/logic/minecraft/ParseUtils_test.cpp b/api/logic/minecraft/ParseUtils_test.cpp index 1ce96248..fcc137e5 100644 --- a/api/logic/minecraft/ParseUtils_test.cpp +++ b/api/logic/minecraft/ParseUtils_test.cpp @@ -5,37 +5,37 @@ class ParseUtilsTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_Through_data() - { - QTest::addColumn("timestamp"); - const char * timestamps[] = - { - "2016-02-29T13:49:54+01:00", - "2016-02-26T15:21:11+00:01", - "2016-02-24T15:52:36+01:13", - "2016-02-18T17:41:00+00:00", - "2016-02-17T15:23:19+00:00", - "2016-02-16T15:22:39+09:22", - "2016-02-10T15:06:41+00:00", - "2016-02-04T15:28:02-05:33" - }; - for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++) - { - QTest::newRow(timestamps[i]) << QString(timestamps[i]); - } - } - void test_Through() - { - QFETCH(QString, timestamp); - - auto time_parsed = timeFromS3Time(timestamp); - auto time_serialized = timeToS3Time(time_parsed); - - QCOMPARE(time_serialized, timestamp); - } + void test_Through_data() + { + QTest::addColumn("timestamp"); + const char * timestamps[] = + { + "2016-02-29T13:49:54+01:00", + "2016-02-26T15:21:11+00:01", + "2016-02-24T15:52:36+01:13", + "2016-02-18T17:41:00+00:00", + "2016-02-17T15:23:19+00:00", + "2016-02-16T15:22:39+09:22", + "2016-02-10T15:06:41+00:00", + "2016-02-04T15:28:02-05:33" + }; + for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++) + { + QTest::newRow(timestamps[i]) << QString(timestamps[i]); + } + } + void test_Through() + { + QFETCH(QString, timestamp); + + auto time_parsed = timeFromS3Time(timestamp); + auto time_serialized = timeToS3Time(time_parsed); + + QCOMPARE(time_serialized, timestamp); + } }; diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp index a6d2028d..8ca24cc8 100644 --- a/api/logic/minecraft/ProfileUtils.cpp +++ b/api/logic/minecraft/ProfileUtils.cpp @@ -16,163 +16,163 @@ static const int currentOrderFileVersion = 1; bool readOverrideOrders(QString path, PatchOrder &order) { - QFile orderFile(path); - if (!orderFile.exists()) - { - qWarning() << "Order file doesn't exist. Ignoring."; - return false; - } - if (!orderFile.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } + QFile orderFile(path); + if (!orderFile.exists()) + { + qWarning() << "Order file doesn't exist. Ignoring."; + return false; + } + if (!orderFile.open(QFile::ReadOnly)) + { + qCritical() << "Couldn't open" << orderFile.fileName() + << " for reading:" << orderFile.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } - // and then read it and process it if all above is true. - try - { - auto obj = Json::requireObject(doc); - // check order file version. - auto version = Json::requireInteger(obj.value("version")); - if (version != currentOrderFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") - .arg(currentOrderFileVersion)); - } - auto orderArray = Json::requireArray(obj.value("order")); - for(auto item: orderArray) - { - order.append(Json::requireString(item)); - } - } - catch (JSONValidationError &err) - { - qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - qWarning() << "Ignoring overriden order"; - order.clear(); - return false; - } - return true; + // and then read it and process it if all above is true. + try + { + auto obj = Json::requireObject(doc); + // check order file version. + auto version = Json::requireInteger(obj.value("version")); + if (version != currentOrderFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") + .arg(currentOrderFileVersion)); + } + auto orderArray = Json::requireArray(obj.value("order")); + for(auto item: orderArray) + { + order.append(Json::requireString(item)); + } + } + catch (const JSONValidationError &err) + { + qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; + qWarning() << "Ignoring overriden order"; + order.clear(); + return false; + } + return true; } static VersionFilePtr createErrorVersionFile(QString fileId, QString filepath, QString error) { - auto outError = std::make_shared(); - outError->uid = outError->name = fileId; - // outError->filename = filepath; - outError->addProblem(ProblemSeverity::Error, error); - return outError; + auto outError = std::make_shared(); + outError->uid = outError->name = fileId; + // outError->filename = filepath; + outError->addProblem(ProblemSeverity::Error, error); + return outError; } static VersionFilePtr guardedParseJson(const QJsonDocument & doc,const QString &fileId,const QString &filepath,const bool &requireOrder) { - try - { - return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder); - } - catch (Exception & e) - { - return createErrorVersionFile(fileId, filepath, e.cause()); - } + try + { + return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder); + } + catch (const Exception &e) + { + return createErrorVersionFile(fileId, filepath, e.cause()); + } } VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) { - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonParseError error; - auto data = file.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - file.close(); - if (error.error != QJsonParseError::NoError) - { - int line = 1; - int column = 0; - for(int i = 0; i < error.offset; i++) - { - if(data[i] == '\n') - { - line++; - column = 0; - continue; - } - column++; - } - auto errorStr = QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(line).arg(column); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder); + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); + return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); + } + QJsonParseError error; + auto data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + file.close(); + if (error.error != QJsonParseError::NoError) + { + int line = 1; + int column = 0; + for(int i = 0; i < error.offset; i++) + { + if(data[i] == '\n') + { + line++; + column = 0; + continue; + } + column++; + } + auto errorStr = QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.") + .arg(fileInfo.fileName(), error.errorString()) + .arg(line).arg(column); + return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); + } + return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder); } bool saveJsonFile(const QJsonDocument doc, const QString & filename) { - auto data = doc.toJson(); - QSaveFile jsonFile(filename); - if(!jsonFile.open(QIODevice::WriteOnly)) - { - jsonFile.cancelWriting(); - qWarning() << "Couldn't open" << filename << "for writing"; - return false; - } - jsonFile.write(data); - if(!jsonFile.commit()) - { - qWarning() << "Couldn't save" << filename; - return false; - } - return true; + auto data = doc.toJson(); + QSaveFile jsonFile(filename); + if(!jsonFile.open(QIODevice::WriteOnly)) + { + jsonFile.cancelWriting(); + qWarning() << "Couldn't open" << filename << "for writing"; + return false; + } + jsonFile.write(data); + if(!jsonFile.commit()) + { + qWarning() << "Couldn't save" << filename; + return false; + } + return true; } VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) { - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); - file.close(); - if (doc.isNull()) - { - file.remove(); - throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false); + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); + return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); + } + QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); + file.close(); + if (doc.isNull()) + { + file.remove(); + throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); + } + return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false); } void removeLwjglFromPatch(VersionFilePtr patch) { - auto filter = [](QList& libs) - { - QList filteredLibs; - for (auto lib : libs) - { - if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) - { - filteredLibs.append(lib); - } - } - libs = filteredLibs; - }; - filter(patch->libraries); + auto filter = [](QList& libs) + { + QList filteredLibs; + for (auto lib : libs) + { + if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) + { + filteredLibs.append(lib); + } + } + libs = filteredLibs; + }; + filter(patch->libraries); } } diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp index 43d673c2..cd10504f 100644 --- a/api/logic/minecraft/Rule.cpp +++ b/api/logic/minecraft/Rule.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,74 +20,74 @@ RuleAction RuleAction_fromString(QString name) { - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; } QList> rulesFromJsonV4(const QJsonObject &objectWithRules) { - QList> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; + QList> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; - QJsonArray ruleList = rulesVal.toArray(); - for (auto ruleVal : ruleList) - { - std::shared_ptr rule; - if (!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if (!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if (action == Defer) - continue; + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr rule; + if (!ruleVal.isObject()) + continue; + auto ruleObj = ruleVal.toObject(); + auto actionVal = ruleObj.value("action"); + if (!actionVal.isString()) + continue; + auto action = RuleAction_fromString(actionVal.toString()); + if (action == Defer) + continue; - auto osVal = ruleObj.value("os"); - if (!osVal.isObject()) - { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - continue; - } + auto osVal = ruleObj.value("os"); + if (!osVal.isObject()) + { + // add a new implicit action rule + rules.append(ImplicitRule::create(action)); + continue; + } - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if (!osNameVal.isString()) - continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); - } + auto osObj = osVal.toObject(); + auto osNameVal = osObj.value("name"); + if (!osNameVal.isString()) + continue; + OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString versionRegex = osObj.value("version").toString(); + // add a new OS rule + rules.append(OsRule::create(action, requiredOs, versionRegex)); + } return rules; } QJsonObject ImplicitRule::toJson() { - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - return ruleObj; + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + return ruleObj; } QJsonObject OsRule::toJson() { - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - QJsonObject osObj; - { - osObj.insert("name", OpSys_toString(m_system)); - if(!m_version_regexp.isEmpty()) - { - osObj.insert("version", m_version_regexp); - } - } - ruleObj.insert("os", osObj); - return ruleObj; + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + if(!m_version_regexp.isEmpty()) + { + osObj.insert("version", m_version_regexp); + } + } + ruleObj.insert("os", osObj); + return ruleObj; } diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h index dc1eee0b..ef10a6c3 100644 --- a/api/logic/minecraft/Rule.h +++ b/api/logic/minecraft/Rule.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ class Rule; enum RuleAction { - Allow, - Disallow, - Defer + Allow, + Disallow, + Defer }; QList> rulesFromJsonV4(const QJsonObject &objectWithRules); @@ -36,66 +36,66 @@ QList> rulesFromJsonV4(const QJsonObject &objectWithRules) class Rule { protected: - RuleAction m_result; - virtual bool applies(const Library *parent) = 0; + RuleAction m_result; + virtual bool applies(const Library *parent) = 0; public: - Rule(RuleAction result) : m_result(result) - { - } - virtual ~Rule() {}; - virtual QJsonObject toJson() = 0; - RuleAction apply(const Library *parent) - { - if (applies(parent)) - return m_result; - else - return Defer; - } + Rule(RuleAction result) : m_result(result) + { + } + virtual ~Rule() {}; + virtual QJsonObject toJson() = 0; + RuleAction apply(const Library *parent) + { + if (applies(parent)) + return m_result; + else + return Defer; + } }; class OsRule : public Rule { private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; protected: - virtual bool applies(const Library *) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) - { - } + virtual bool applies(const Library *) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) + { + } public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result, OpSys system, - QString version_regexp) - { - return std::shared_ptr(new OsRule(result, system, version_regexp)); - } + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result, OpSys system, + QString version_regexp) + { + return std::shared_ptr(new OsRule(result, system, version_regexp)); + } }; class ImplicitRule : public Rule { protected: - virtual bool applies(const Library *) - { - return true; - } - ImplicitRule(RuleAction result) : Rule(result) - { - } + virtual bool applies(const Library *) + { + return true; + } + ImplicitRule(RuleAction result) : Rule(result) + { + } public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result) - { - return std::shared_ptr(new ImplicitRule(result)); - } + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result) + { + return std::shared_ptr(new ImplicitRule(result)); + } }; diff --git a/api/logic/minecraft/SkinUpload.cpp b/api/logic/minecraft/SkinUpload.cpp index bd246139..83bdf592 100644 --- a/api/logic/minecraft/SkinUpload.cpp +++ b/api/logic/minecraft/SkinUpload.cpp @@ -4,66 +4,66 @@ #include QByteArray getModelString(SkinUpload::Model model) { - switch (model) { - case SkinUpload::STEVE: - return ""; - case SkinUpload::ALEX: - return "slim"; - default: - qDebug() << "Unknown skin type!"; - return ""; - } + switch (model) { + case SkinUpload::STEVE: + return ""; + case SkinUpload::ALEX: + return "slim"; + default: + qDebug() << "Unknown skin type!"; + return ""; + } } SkinUpload::SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, SkinUpload::Model model) - : Task(parent), m_model(model), m_skin(skin), m_session(session) + : Task(parent), m_model(model), m_skin(skin), m_session(session) { } void SkinUpload::executeTask() { - QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid))); - request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit()); + QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid))); + request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit()); - QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart model; - model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\"")); - model.setBody(getModelString(m_model)); + QHttpPart model; + model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\"")); + model.setBody(getModelString(m_model)); - QHttpPart skin; - skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); - skin.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(m_skin); + QHttpPart skin; + skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); + skin.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); + skin.setBody(m_skin); - multiPart->append(model); - multiPart->append(skin); + multiPart->append(model); + multiPart->append(skin); - QNetworkReply *rep = ENV.qnam().put(request, multiPart); - m_reply = std::shared_ptr(rep); + QNetworkReply *rep = ENV.qnam().put(request, multiPart); + m_reply = std::shared_ptr(rep); - setStatus(tr("Uploading skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + setStatus(tr("Uploading skin")); + connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } void SkinUpload::downloadError(QNetworkReply::NetworkError error) { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + // error happened during download. + qCritical() << "Network error: " << error; + emitFailed(m_reply->errorString()); } void SkinUpload::downloadFinished() { - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + // if the download failed + if (m_reply->error() != QNetworkReply::NetworkError::NoError) + { + emitFailed(QString("Network error: %1").arg(m_reply->errorString())); + m_reply.reset(); + return; + } + emitSucceeded(); } diff --git a/api/logic/minecraft/SkinUpload.h b/api/logic/minecraft/SkinUpload.h index 5b331fa9..c77abb03 100644 --- a/api/logic/minecraft/SkinUpload.h +++ b/api/logic/minecraft/SkinUpload.h @@ -11,30 +11,29 @@ typedef std::shared_ptr SkinUploadPtr; class MULTIMC_LOGIC_EXPORT SkinUpload : public Task { - Q_OBJECT + Q_OBJECT public: - enum Model - { - STEVE, - ALEX - }; + enum Model + { + STEVE, + ALEX + }; - // Note this class takes ownership of the file. - SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE); - - virtual ~SkinUpload() {} + // Note this class takes ownership of the file. + SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE); + virtual ~SkinUpload() {} private: - Model m_model; - QByteArray m_skin; - AuthSessionPtr m_session; - std::shared_ptr m_reply; + Model m_model; + QByteArray m_skin; + AuthSessionPtr m_session; + std::shared_ptr m_reply; protected: - virtual void executeTask(); + virtual void executeTask(); public slots: - void downloadError(QNetworkReply::NetworkError); + void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); + void downloadFinished(); }; diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp index 9f485c55..cfb9e504 100644 --- a/api/logic/minecraft/VersionFile.cpp +++ b/api/logic/minecraft/VersionFile.cpp @@ -12,45 +12,45 @@ static bool isMinecraftVersion(const QString &uid) { - return uid == "net.minecraft"; + return uid == "net.minecraft"; } void VersionFile::applyTo(LaunchProfile *profile) { - // Only real Minecraft can set those. Don't let anything override them. - if (isMinecraftVersion(uid)) - { - profile->applyMinecraftVersion(minecraftVersion); - profile->applyMinecraftVersionType(type); - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - profile->applyMinecraftAssets(mojangAssetIndex); - } - - profile->applyMainJar(mainJar); - profile->applyMainClass(mainClass); - profile->applyAppletClass(appletClass); - profile->applyMinecraftArguments(minecraftArguments); - profile->applyTweakers(addTweakers); - profile->applyJarMods(jarMods); - profile->applyMods(mods); - profile->applyTraits(traits); - - for (auto library : libraries) - { - profile->applyLibrary(library); - } - profile->applyProblemSeverity(getProblemSeverity()); + // Only real Minecraft can set those. Don't let anything override them. + if (isMinecraftVersion(uid)) + { + profile->applyMinecraftVersion(minecraftVersion); + profile->applyMinecraftVersionType(type); + // HACK: ignore assets from other version files than Minecraft + // workaround for stupid assets issue caused by amazon: + // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ + profile->applyMinecraftAssets(mojangAssetIndex); + } + + profile->applyMainJar(mainJar); + profile->applyMainClass(mainClass); + profile->applyAppletClass(appletClass); + profile->applyMinecraftArguments(minecraftArguments); + profile->applyTweakers(addTweakers); + profile->applyJarMods(jarMods); + profile->applyMods(mods); + profile->applyTraits(traits); + + for (auto library : libraries) + { + profile->applyLibrary(library); + } + profile->applyProblemSeverity(getProblemSeverity()); } /* - auto theirVersion = profile->getMinecraftVersion(); - if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull()) - { - if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1) - { - throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion); - } - } + auto theirVersion = profile->getMinecraftVersion(); + if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull()) + { + if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1) + { + throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion); + } + } */ \ No newline at end of file diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h index 5aea7a7a..3dc9db96 100644 --- a/api/logic/minecraft/VersionFile.h +++ b/api/logic/minecraft/VersionFile.h @@ -21,91 +21,91 @@ struct MojangAssetIndexInfo; using VersionFilePtr = std::shared_ptr; class VersionFile : public ProblemContainer { - friend class MojangVersionFormat; - friend class OneSixVersionFormat; + friend class MojangVersionFormat; + friend class OneSixVersionFormat; public: /* methods */ - void applyTo(LaunchProfile* profile); + void applyTo(LaunchProfile* profile); public: /* data */ - /// MultiMC: order hint for this version file if no explicit order is set - int order = 0; + /// MultiMC: order hint for this version file if no explicit order is set + int order = 0; - /// MultiMC: human readable name of this package - QString name; + /// MultiMC: human readable name of this package + QString name; - /// MultiMC: package ID of this package - QString uid; + /// MultiMC: package ID of this package + QString uid; - /// MultiMC: version of this package - QString version; + /// MultiMC: version of this package + QString version; - /// MultiMC: DEPRECATED dependency on a Minecraft version - QString dependsOnMinecraftVersion; + /// MultiMC: DEPRECATED dependency on a Minecraft version + QString dependsOnMinecraftVersion; - /// Mojang: DEPRECATED used to version the Mojang version format - int minimumLauncherVersion = -1; + /// Mojang: DEPRECATED used to version the Mojang version format + int minimumLauncherVersion = -1; - /// Mojang: DEPRECATED version of Minecraft this is - QString minecraftVersion; + /// Mojang: DEPRECATED version of Minecraft this is + QString minecraftVersion; - /// Mojang: class to launch Minecraft with - QString mainClass; + /// Mojang: class to launch Minecraft with + QString mainClass; - /// MultiMC: class to launch legacy Minecraft with (embed in a custom window) - QString appletClass; + /// MultiMC: class to launch legacy Minecraft with (embed in a custom window) + QString appletClass; - /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) - QString minecraftArguments; + /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) + QString minecraftArguments; - /// Mojang: type of the Minecraft version - QString type; + /// Mojang: type of the Minecraft version + QString type; - /// Mojang: the time this version was actually released by Mojang - QDateTime releaseTime; + /// Mojang: the time this version was actually released by Mojang + QDateTime releaseTime; - /// Mojang: DEPRECATED the time this version was last updated by Mojang - QDateTime updateTime; + /// Mojang: DEPRECATED the time this version was last updated by Mojang + QDateTime updateTime; - /// Mojang: DEPRECATED asset group to be used with Minecraft - QString assets; + /// Mojang: DEPRECATED asset group to be used with Minecraft + QString assets; - /// MultiMC: list of tweaker mod arguments for launchwrapper - QStringList addTweakers; + /// MultiMC: list of tweaker mod arguments for launchwrapper + QStringList addTweakers; - /// Mojang: list of libraries to add to the version - QList libraries; + /// Mojang: list of libraries to add to the version + QList libraries; - /// The main jar (Minecraft version library, normally) - LibraryPtr mainJar; + /// The main jar (Minecraft version library, normally) + LibraryPtr mainJar; - /// MultiMC: list of attached traits of this version file - used to enable features - QSet traits; + /// MultiMC: list of attached traits of this version file - used to enable features + QSet traits; - /// MultiMC: list of jar mods added to this version - QList jarMods; + /// MultiMC: list of jar mods added to this version + QList jarMods; - /// MultiMC: list of mods added to this version - QList mods; + /// MultiMC: list of mods added to this version + QList mods; - /** - * MultiMC: set of packages this depends on - * NOTE: this is shared with the meta format!!! - */ - Meta::RequireSet requires; + /** + * MultiMC: set of packages this depends on + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet requires; - /** - * MultiMC: set of packages this conflicts with - * NOTE: this is shared with the meta format!!! - */ - Meta::RequireSet conflicts; + /** + * MultiMC: set of packages this conflicts with + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet conflicts; - /// is volatile -- may be removed as soon as it is no longer needed by something else - bool m_volatile = false; + /// is volatile -- may be removed as soon as it is no longer needed by something else + bool m_volatile = false; public: - // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. - QMap > mojangDownloads; + // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. + QMap > mojangDownloads; - // Mojang: extended asset index download information - std::shared_ptr mojangAssetIndex; + // Mojang: extended asset index download information + std::shared_ptr mojangAssetIndex; }; diff --git a/api/logic/minecraft/VersionFilterData.cpp b/api/logic/minecraft/VersionFilterData.cpp index 5f4ceee6..11f7eea9 100644 --- a/api/logic/minecraft/VersionFilterData.cpp +++ b/api/logic/minecraft/VersionFilterData.cpp @@ -5,64 +5,64 @@ VersionFilterData g_VersionFilterData = VersionFilterData(); VersionFilterData::VersionFilterData() { - // 1.3.* - auto libs13 = - QList{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; + // 1.3.* + auto libs13 = + QList{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; - fmlLibsMapping["1.3.2"] = libs13; + fmlLibsMapping["1.3.2"] = libs13; - // 1.4.* - auto libs14 = QList{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, - {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; + // 1.4.* + auto libs14 = QList{ + {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, + {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; - fmlLibsMapping["1.4"] = libs14; - fmlLibsMapping["1.4.1"] = libs14; - fmlLibsMapping["1.4.2"] = libs14; - fmlLibsMapping["1.4.3"] = libs14; - fmlLibsMapping["1.4.4"] = libs14; - fmlLibsMapping["1.4.5"] = libs14; - fmlLibsMapping["1.4.6"] = libs14; - fmlLibsMapping["1.4.7"] = libs14; + fmlLibsMapping["1.4"] = libs14; + fmlLibsMapping["1.4.1"] = libs14; + fmlLibsMapping["1.4.2"] = libs14; + fmlLibsMapping["1.4.3"] = libs14; + fmlLibsMapping["1.4.4"] = libs14; + fmlLibsMapping["1.4.5"] = libs14; + fmlLibsMapping["1.4.6"] = libs14; + fmlLibsMapping["1.4.7"] = libs14; - // 1.5 - fmlLibsMapping["1.5"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + // 1.5 + fmlLibsMapping["1.5"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - // 1.5.1 - fmlLibsMapping["1.5.1"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + // 1.5.1 + fmlLibsMapping["1.5.1"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - // 1.5.2 - fmlLibsMapping["1.5.2"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + // 1.5.2 + fmlLibsMapping["1.5.2"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - // don't use installers for those. - forgeInstallerBlacklist = QSet({"1.5.2"}); + // don't use installers for those. + forgeInstallerBlacklist = QSet({"1.5.2"}); - // FIXME: remove, used for deciding when core mods should display - legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); - lwjglWhitelist = - QSet{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", - "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", - "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; + // FIXME: remove, used for deciding when core mods should display + legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); + lwjglWhitelist = + QSet{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", + "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", + "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; } diff --git a/api/logic/minecraft/VersionFilterData.h b/api/logic/minecraft/VersionFilterData.h index 2408e704..88e91f11 100644 --- a/api/logic/minecraft/VersionFilterData.h +++ b/api/logic/minecraft/VersionFilterData.h @@ -8,21 +8,21 @@ struct FMLlib { - QString filename; - QString checksum; - bool ours; + QString filename; + QString checksum; + bool ours; }; struct VersionFilterData { - VersionFilterData(); - // mapping between minecraft versions and FML libraries required - QMap> fmlLibsMapping; - // set of minecraft versions for which using forge installers is blacklisted - QSet forgeInstallerBlacklist; - // no new versions below this date will be accepted from Mojang servers - QDateTime legacyCutoffDate; - // Libraries that belong to LWJGL - QSet lwjglWhitelist; + VersionFilterData(); + // mapping between minecraft versions and FML libraries required + QMap> fmlLibsMapping; + // set of minecraft versions for which using forge installers is blacklisted + QSet forgeInstallerBlacklist; + // no new versions below this date will be accepted from Mojang servers + QDateTime legacyCutoffDate; + // Libraries that belong to LWJGL + QSet lwjglWhitelist; }; extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData; diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp index 68c0a5cc..17dbf4ae 100644 --- a/api/logic/minecraft/World.cpp +++ b/api/logic/minecraft/World.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,356 +30,402 @@ #include #include +#include + +QString gameTypeToString(GameType type) +{ + switch (type) + { + case GameType::Survival: + return QCoreApplication::translate("GameType", "Survival"); + case GameType::Creative: + return QCoreApplication::translate("GameType", "Creative"); + case GameType::Adventure: + return QCoreApplication::translate("GameType", "Adventure"); + case GameType::Spectator: + return QCoreApplication::translate("GameType", "Spectator"); + default: + break; + } + return QObject::tr("Unknown"); +} + std::unique_ptr parseLevelDat(QByteArray data) { - QByteArray output; - if(!GZip::unzip(data, output)) - { - return nullptr; - } - std::istringstream foo(std::string(output.constData(), output.size())); - auto pair = nbt::io::read_compound(foo); + QByteArray output; + if(!GZip::unzip(data, output)) + { + return nullptr; + } + std::istringstream foo(std::string(output.constData(), output.size())); + auto pair = nbt::io::read_compound(foo); - if(pair.first != "") - return nullptr; + if(pair.first != "") + return nullptr; - if(pair.second == nullptr) - return nullptr; + if(pair.second == nullptr) + return nullptr; - return std::move(pair.second); + return std::move(pair.second); } QByteArray serializeLevelDat(nbt::tag_compound * levelInfo) { - std::ostringstream s; - nbt::io::write_tag("", *levelInfo, s); - QByteArray val( s.str().data(), (int) s.str().size() ); - return val; + std::ostringstream s; + nbt::io::write_tag("", *levelInfo, s); + QByteArray val( s.str().data(), (int) s.str().size() ); + return val; } QString getLevelDatFromFS(const QFileInfo &file) { - QDir worldDir(file.filePath()); - if(!file.isDir() || !worldDir.exists("level.dat")) - { - return QString(); - } - return worldDir.absoluteFilePath("level.dat"); + QDir worldDir(file.filePath()); + if(!file.isDir() || !worldDir.exists("level.dat")) + { + return QString(); + } + return worldDir.absoluteFilePath("level.dat"); } QByteArray getLevelDatDataFromFS(const QFileInfo &file) { - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return QByteArray(); - } - QFile f(fullFilePath); - if(!f.open(QIODevice::ReadOnly)) - { - return QByteArray(); - } - return f.readAll(); + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return QByteArray(); + } + QFile f(fullFilePath); + if(!f.open(QIODevice::ReadOnly)) + { + return QByteArray(); + } + return f.readAll(); } bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data) { - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return false; - } - QSaveFile f(fullFilePath); - if(!f.open(QIODevice::WriteOnly)) - { - return false; - } - QByteArray compressed; - if(!GZip::zip(data, compressed)) - { - return false; - } - if(f.write(compressed) != compressed.size()) - { - f.cancelWriting(); - return false; - } - return f.commit(); + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return false; + } + QSaveFile f(fullFilePath); + if(!f.open(QIODevice::WriteOnly)) + { + return false; + } + QByteArray compressed; + if(!GZip::zip(data, compressed)) + { + return false; + } + if(f.write(compressed) != compressed.size()) + { + f.cancelWriting(); + return false; + } + return f.commit(); } World::World(const QFileInfo &file) { - repath(file); + repath(file); } void World::repath(const QFileInfo &file) { - m_containerFile = file; - m_folderName = file.fileName(); - if(file.isFile() && file.suffix() == "zip") - { - readFromZip(file); - } - else if(file.isDir()) - { - readFromFS(file); - } + m_containerFile = file; + m_folderName = file.fileName(); + if(file.isFile() && file.suffix() == "zip") + { + readFromZip(file); + } + else if(file.isDir()) + { + readFromFS(file); + } } void World::readFromFS(const QFileInfo &file) { - auto bytes = getLevelDatDataFromFS(file); - if(bytes.isEmpty()) - { - is_valid = false; - return; - } - loadFromLevelDat(bytes); - levelDatTime = file.lastModified(); + auto bytes = getLevelDatDataFromFS(file); + if(bytes.isEmpty()) + { + is_valid = false; + return; + } + loadFromLevelDat(bytes); + levelDatTime = file.lastModified(); } void World::readFromZip(const QFileInfo &file) { - QuaZip zip(file.absoluteFilePath()); - is_valid = zip.open(QuaZip::mdUnzip); - if (!is_valid) - { - return; - } - auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - is_valid = !location.isEmpty(); - if (!is_valid) - { - return; - } - m_containerOffsetPath = location; - QuaZipFile zippedFile(&zip); - // read the install profile - is_valid = zip.setCurrentFile(location + "level.dat"); - if (!is_valid) - { - return; - } - is_valid = zippedFile.open(QIODevice::ReadOnly); - QuaZipFileInfo64 levelDatInfo; - zippedFile.getFileInfo(&levelDatInfo); - auto modTime = levelDatInfo.getNTFSmTime(); - if(!modTime.isValid()) - { - modTime = levelDatInfo.dateTime; - } - levelDatTime = modTime; - if (!is_valid) - { - return; - } - loadFromLevelDat(zippedFile.readAll()); - zippedFile.close(); + QuaZip zip(file.absoluteFilePath()); + is_valid = zip.open(QuaZip::mdUnzip); + if (!is_valid) + { + return; + } + auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); + is_valid = !location.isEmpty(); + if (!is_valid) + { + return; + } + m_containerOffsetPath = location; + QuaZipFile zippedFile(&zip); + // read the install profile + is_valid = zip.setCurrentFile(location + "level.dat"); + if (!is_valid) + { + return; + } + is_valid = zippedFile.open(QIODevice::ReadOnly); + QuaZipFileInfo64 levelDatInfo; + zippedFile.getFileInfo(&levelDatInfo); + auto modTime = levelDatInfo.getNTFSmTime(); + if(!modTime.isValid()) + { + modTime = levelDatInfo.dateTime; + } + levelDatTime = modTime; + if (!is_valid) + { + return; + } + loadFromLevelDat(zippedFile.readAll()); + zippedFile.close(); } bool World::install(const QString &to, const QString &name) { - auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to)); - if(!FS::ensureFolderPathExists(finalPath)) - { - return false; - } - bool ok = false; - if(m_containerFile.isFile()) - { - QuaZip zip(m_containerFile.absoluteFilePath()); - if (!zip.open(QuaZip::mdUnzip)) - { - return false; - } - ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); - } - else if(m_containerFile.isDir()) - { - QString from = m_containerFile.filePath(); - ok = FS::copy(from, finalPath)(); - } - - if(ok && !name.isEmpty() && m_actualName != name) - { - World newWorld(finalPath); - if(newWorld.isValid()) - { - newWorld.rename(name); - } - } - return ok; + auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to)); + if(!FS::ensureFolderPathExists(finalPath)) + { + return false; + } + bool ok = false; + if(m_containerFile.isFile()) + { + QuaZip zip(m_containerFile.absoluteFilePath()); + if (!zip.open(QuaZip::mdUnzip)) + { + return false; + } + ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); + } + else if(m_containerFile.isDir()) + { + QString from = m_containerFile.filePath(); + ok = FS::copy(from, finalPath)(); + } + + if(ok && !name.isEmpty() && m_actualName != name) + { + World newWorld(finalPath); + if(newWorld.isValid()) + { + newWorld.rename(name); + } + } + return ok; } bool World::rename(const QString &newName) { - if(m_containerFile.isFile()) - { - return false; - } - - auto data = getLevelDatDataFromFS(m_containerFile); - if(data.isEmpty()) - { - return false; - } - - auto worldData = parseLevelDat(data); - if(!worldData) - { - return false; - } - auto &val = worldData->at("Data"); - if(val.get_type() != nbt::tag_type::Compound) - { - return false; - } - auto &dataCompound = val.as(); - dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); - data = serializeLevelDat(worldData.get()); - - putLevelDatDataToFS(m_containerFile, data); - - m_actualName = newName; - - QDir parentDir(m_containerFile.absoluteFilePath()); - parentDir.cdUp(); - QFile container(m_containerFile.absoluteFilePath()); - auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath()); - container.rename(parentDir.absoluteFilePath(dirName)); - - return true; + if(m_containerFile.isFile()) + { + return false; + } + + auto data = getLevelDatDataFromFS(m_containerFile); + if(data.isEmpty()) + { + return false; + } + + auto worldData = parseLevelDat(data); + if(!worldData) + { + return false; + } + auto &val = worldData->at("Data"); + if(val.get_type() != nbt::tag_type::Compound) + { + return false; + } + auto &dataCompound = val.as(); + dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); + data = serializeLevelDat(worldData.get()); + + putLevelDatDataToFS(m_containerFile, data); + + m_actualName = newName; + + QDir parentDir(m_containerFile.absoluteFilePath()); + parentDir.cdUp(); + QFile container(m_containerFile.absoluteFilePath()); + auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath()); + container.rename(parentDir.absoluteFilePath(dirName)); + + return true; } static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString()) { - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::String) - { - return fallback; - } - auto & tag_str = namedValue.as(); - return QString::fromStdString(tag_str.get()); - } - catch(std::out_of_range e) - { - // fallback for old world formats - qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; - } - catch(std::bad_cast e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback; - return fallback; - } -}; + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::String) + { + return fallback; + } + auto & tag_str = namedValue.as(); + return QString::fromStdString(tag_str.get()); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback; + return fallback; + } +} static int64_t read_long (nbt::value& parent, const char * name, const int64_t & fallback = 0) { - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::Long) - { - return fallback; - } - auto & tag_str = namedValue.as(); - return tag_str.get(); - } - catch(std::out_of_range e) - { - // fallback for old world formats - qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; - } - catch(std::bad_cast e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback; - return fallback; - } -}; + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::Long) + { + return fallback; + } + auto & tag_str = namedValue.as(); + return tag_str.get(); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback; + return fallback; + } +} + +static int read_int (nbt::value& parent, const char * name, const int & fallback = 0) +{ + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::Int) + { + return fallback; + } + auto & tag_str = namedValue.as(); + return tag_str.get(); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "Int NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to int. Defaulting to" << fallback; + return fallback; + } +} void World::loadFromLevelDat(QByteArray data) { - try - { - auto levelData = parseLevelDat(data); - if(!levelData) - { - is_valid = false; - return; - } - - auto &val = levelData->at("Data"); - is_valid = val.get_type() == nbt::tag_type::Compound; - if(!is_valid) - return; - - m_actualName = read_string(val, "LevelName", m_folderName); - - - int64_t temp = read_long(val, "LastPlayed", 0); - if(temp == 0) - { - m_lastPlayed = levelDatTime; - } - else - { - m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp); - } - - m_randomSeed = read_long(val, "RandomSeed", 0); - - qDebug() << "World Name:" << m_actualName; - qDebug() << "Last Played:" << m_lastPlayed.toString(); - qDebug() << "Seed:" << m_randomSeed; - } - catch (nbt::io::input_error e) - { - qWarning() << "Unable to load" << m_folderName << ":" << e.what(); - is_valid = false; - return; - } + try + { + auto levelData = parseLevelDat(data); + if(!levelData) + { + is_valid = false; + return; + } + + auto &val = levelData->at("Data"); + is_valid = val.get_type() == nbt::tag_type::Compound; + if(!is_valid) + return; + + m_actualName = read_string(val, "LevelName", m_folderName); + + + int64_t temp = read_long(val, "LastPlayed", 0); + if(temp == 0) + { + m_lastPlayed = levelDatTime; + } + else + { + m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp); + } + + int GameType_val = read_int(val, "GameType", 0); + m_gameType = (GameType) GameType_val; + + m_randomSeed = read_long(val, "RandomSeed", 0); + + qDebug() << "World Name:" << m_actualName; + qDebug() << "Last Played:" << m_lastPlayed.toString(); + qDebug() << "Seed:" << m_randomSeed; + qDebug() << "GameMode:" << GameType_val; + } + catch (const nbt::io::input_error &e) + { + qWarning() << "Unable to load" << m_folderName << ":" << e.what(); + is_valid = false; + return; + } } bool World::replace(World &with) { - if (!destroy()) - return false; - bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())(); - if (success) - { - m_folderName = with.m_folderName; - m_containerFile.refresh(); - } - return success; + if (!destroy()) + return false; + bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())(); + if (success) + { + m_folderName = with.m_folderName; + m_containerFile.refresh(); + } + return success; } bool World::destroy() { - if(!is_valid) return false; - if (m_containerFile.isDir()) - { - QDir d(m_containerFile.filePath()); - return d.removeRecursively(); - } - else if(m_containerFile.isFile()) - { - QFile file(m_containerFile.absoluteFilePath()); - return file.remove(); - } - return true; + if(!is_valid) return false; + if (m_containerFile.isDir()) + { + QDir d(m_containerFile.filePath()); + return d.removeRecursively(); + } + else if(m_containerFile.isFile()) + { + QFile file(m_containerFile.absoluteFilePath()); + return file.remove(); + } + return true; } bool World::operator==(const World &other) const { - return is_valid == other.is_valid && folderName() == other.folderName(); -} -bool World::strongCompare(const World &other) const -{ - return is_valid == other.is_valid && folderName() == other.folderName(); + return is_valid == other.is_valid && folderName() == other.folderName(); } diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h index 7087bf48..818701fa 100644 --- a/api/logic/minecraft/World.h +++ b/api/logic/minecraft/World.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,65 +19,78 @@ #include "multimc_logic_export.h" +enum class GameType +{ + Survival, + Creative, + Adventure, + Spectator +}; +QString MULTIMC_LOGIC_EXPORT gameTypeToString(GameType type); + class MULTIMC_LOGIC_EXPORT World { public: - World(const QFileInfo &file); - QString folderName() const - { - return m_folderName; - } - QString name() const - { - return m_actualName; - } - QDateTime lastPlayed() const - { - return m_lastPlayed; - } - int64_t seed() const - { - return m_randomSeed; - } - bool isValid() const - { - return is_valid; - } - bool isOnFS() const - { - return m_containerFile.isDir(); - } - QFileInfo container() const - { - return m_containerFile; - } - // delete all the files of this world - bool destroy(); - // replace this world with a copy of the other - bool replace(World &with); - // change the world's filesystem path (used by world lists for *MAGIC* purposes) - void repath(const QFileInfo &file); + World(const QFileInfo &file); + QString folderName() const + { + return m_folderName; + } + QString name() const + { + return m_actualName; + } + QDateTime lastPlayed() const + { + return m_lastPlayed; + } + GameType gameType() const + { + return m_gameType; + } + int64_t seed() const + { + return m_randomSeed; + } + bool isValid() const + { + return is_valid; + } + bool isOnFS() const + { + return m_containerFile.isDir(); + } + QFileInfo container() const + { + return m_containerFile; + } + // delete all the files of this world + bool destroy(); + // replace this world with a copy of the other + bool replace(World &with); + // change the world's filesystem path (used by world lists for *MAGIC* purposes) + void repath(const QFileInfo &file); - bool rename(const QString &to); - bool install(const QString &to, const QString &name= QString()); + bool rename(const QString &to); + bool install(const QString &to, const QString &name= QString()); - // WEAK compare operator - used for replacing worlds - bool operator==(const World &other) const; - bool strongCompare(const World &other) const; + // WEAK compare operator - used for replacing worlds + bool operator==(const World &other) const; private: - void readFromZip(const QFileInfo &file); - void readFromFS(const QFileInfo &file); - void loadFromLevelDat(QByteArray data); + void readFromZip(const QFileInfo &file); + void readFromFS(const QFileInfo &file); + void loadFromLevelDat(QByteArray data); protected: - QFileInfo m_containerFile; - QString m_containerOffsetPath; - QString m_folderName; - QString m_actualName; - QDateTime levelDatTime; - QDateTime m_lastPlayed; - int64_t m_randomSeed = 0; - bool is_valid = false; + QFileInfo m_containerFile; + QString m_containerOffsetPath; + QString m_folderName; + QString m_actualName; + QDateTime levelDatTime; + QDateTime m_lastPlayed; + int64_t m_randomSeed = 0; + GameType m_gameType = GameType::Survival; + bool is_valid = false; }; diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp index 4278c2f7..b7a24434 100644 --- a/api/logic/minecraft/WorldList.cpp +++ b/api/logic/minecraft/WorldList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,216 +23,223 @@ #include WorldList::WorldList(const QString &dir) - : QAbstractListModel(), m_dir(dir) + : QAbstractListModel(), m_dir(dir) { - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | - QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_watcher = new QFileSystemWatcher(this); - is_watching = false; - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | + QDir::NoSymLinks); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + m_watcher = new QFileSystemWatcher(this); + is_watching = false; + connect(m_watcher, SIGNAL(directoryChanged(QString)), this, + SLOT(directoryChanged(QString))); } void WorldList::startWatching() { - if(is_watching) - { - return; - } - update(); - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } + if(is_watching) + { + return; + } + update(); + is_watching = m_watcher->addPath(m_dir.absolutePath()); + if (is_watching) + { + qDebug() << "Started watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to start watching " << m_dir.absolutePath(); + } } void WorldList::stopWatching() { - if(!is_watching) - { - return; - } - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } + if(!is_watching) + { + return; + } + is_watching = !m_watcher->removePath(m_dir.absolutePath()); + if (!is_watching) + { + qDebug() << "Stopped watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + } } bool WorldList::update() { - if (!isValid()) - return false; - - QList newWorlds; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - // if there are any untracked files... - for (QFileInfo entry : folderContents) - { - if(!entry.isDir()) - continue; - - World w(entry); - if(w.isValid()) - { - newWorlds.append(w); - } - } - beginResetModel(); - worlds.swap(newWorlds); - endResetModel(); - return true; + if (!isValid()) + return false; + + QList newWorlds; + m_dir.refresh(); + auto folderContents = m_dir.entryInfoList(); + // if there are any untracked files... + for (QFileInfo entry : folderContents) + { + if(!entry.isDir()) + continue; + + World w(entry); + if(w.isValid()) + { + newWorlds.append(w); + } + } + beginResetModel(); + worlds.swap(newWorlds); + endResetModel(); + return true; } void WorldList::directoryChanged(QString path) { - update(); + update(); } bool WorldList::isValid() { - return m_dir.exists() && m_dir.isReadable(); + return m_dir.exists() && m_dir.isReadable(); } bool WorldList::deleteWorld(int index) { - if (index >= worlds.size() || index < 0) - return false; - World &m = worlds[index]; - if (m.destroy()) - { - beginRemoveRows(QModelIndex(), index, index); - worlds.removeAt(index); - endRemoveRows(); - emit changed(); - return true; - } - return false; + if (index >= worlds.size() || index < 0) + return false; + World &m = worlds[index]; + if (m.destroy()) + { + beginRemoveRows(QModelIndex(), index, index); + worlds.removeAt(index); + endRemoveRows(); + emit changed(); + return true; + } + return false; } bool WorldList::deleteWorlds(int first, int last) { - for (int i = first; i <= last; i++) - { - World &m = worlds[i]; - m.destroy(); - } - beginRemoveRows(QModelIndex(), first, last); - worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); - endRemoveRows(); - emit changed(); - return true; + for (int i = first; i <= last; i++) + { + World &m = worlds[i]; + m.destroy(); + } + beginRemoveRows(QModelIndex(), first, last); + worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); + endRemoveRows(); + emit changed(); + return true; } int WorldList::columnCount(const QModelIndex &parent) const { - return 2; + return 3; } QVariant WorldList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= worlds.size()) - return QVariant(); - - auto & world = worlds[row]; - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return world.name(); - - case LastPlayedColumn: - return world.lastPlayed(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - { - return world.folderName(); - } - case ObjectRole: - { - return QVariant::fromValue((void *)&world); - } - case FolderRole: - { - return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName())); - } - case SeedRole: - { - return qVariantFromValue(world.seed()); - } - case NameRole: - { - return world.name(); - } - case LastPlayedRole: - { - return world.lastPlayed(); - } - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= worlds.size()) + return QVariant(); + + auto & world = worlds[row]; + switch (role) + { + case Qt::DisplayRole: + switch (column) + { + case NameColumn: + return world.name(); + + case GameModeColumn: + return gameTypeToString(world.gameType()); + + case LastPlayedColumn: + return world.lastPlayed(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + { + return world.folderName(); + } + case ObjectRole: + { + return QVariant::fromValue((void *)&world); + } + case FolderRole: + { + return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName())); + } + case SeedRole: + { + return qVariantFromValue(world.seed()); + } + case NameRole: + { + return world.name(); + } + case LastPlayedRole: + { + return world.lastPlayed(); + } + default: + return QVariant(); + } } QVariant WorldList::headerData(int section, Qt::Orientation orientation, int role) const { - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case NameColumn: - return tr("Name"); - case LastPlayedColumn: - return tr("Last Played"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the world."); - case LastPlayedColumn: - return tr("Date and time the world was last played."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case NameColumn: + return tr("Name"); + case GameModeColumn: + return tr("Game Mode"); + case LastPlayedColumn: + return tr("Last Played"); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case NameColumn: + return tr("The name of the world."); + case GameModeColumn: + return tr("Game mode of the world."); + case LastPlayedColumn: + return tr("Date and time the world was last played."); + default: + return QVariant(); + } + default: + return QVariant(); + } + return QVariant(); } QStringList WorldList::mimeTypes() const { - QStringList types; - types << "text/uri-list"; - return types; + QStringList types; + types << "text/uri-list"; + return types; } class WorldMimeData : public QMimeData @@ -240,124 +247,124 @@ class WorldMimeData : public QMimeData Q_OBJECT public: - WorldMimeData(QList worlds) - { - m_worlds = worlds; + WorldMimeData(QList worlds) + { + m_worlds = worlds; - } - QStringList formats() const - { - return QMimeData::formats() << "text/uri-list"; - } + } + QStringList formats() const + { + return QMimeData::formats() << "text/uri-list"; + } protected: - QVariant retrieveData(const QString &mimetype, QVariant::Type type) const - { - QList urls; - for(auto &world: m_worlds) - { - if(!world.isValid() || !world.isOnFS()) - continue; - QString worldPath = world.container().absoluteFilePath(); - qDebug() << worldPath; - urls.append(QUrl::fromLocalFile(worldPath)); - } - const_cast(this)->setUrls(urls); - return QMimeData::retrieveData(mimetype, type); - } + QVariant retrieveData(const QString &mimetype, QVariant::Type type) const + { + QList urls; + for(auto &world: m_worlds) + { + if(!world.isValid() || !world.isOnFS()) + continue; + QString worldPath = world.container().absoluteFilePath(); + qDebug() << worldPath; + urls.append(QUrl::fromLocalFile(worldPath)); + } + const_cast(this)->setUrls(urls); + return QMimeData::retrieveData(mimetype, type); + } private: - QList m_worlds; + QList m_worlds; }; QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const { - if (indexes.size() == 0) - return new QMimeData(); - - QList worlds; - for(auto idx : indexes) - { - if(idx.column() != 0) - continue; - int row = idx.row(); - if (row < 0 || row >= this->worlds.size()) - continue; - worlds.append(this->worlds[row]); - } - if(!worlds.size()) - { - return new QMimeData(); - } - return new WorldMimeData(worlds); + if (indexes.size() == 0) + return new QMimeData(); + + QList worlds; + for(auto idx : indexes) + { + if(idx.column() != 0) + continue; + int row = idx.row(); + if (row < 0 || row >= this->worlds.size()) + continue; + worlds.append(this->worlds[row]); + } + if(!worlds.size()) + { + return new QMimeData(); + } + return new WorldMimeData(worlds); } Qt::ItemFlags WorldList::flags(const QModelIndex &index) const { - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | - defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + if (index.isValid()) + return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | + defaultFlags; + else + return Qt::ItemIsDropEnabled | defaultFlags; } Qt::DropActions WorldList::supportedDragActions() const { - // move to other mod lists or VOID - return Qt::MoveAction; + // move to other mod lists or VOID + return Qt::MoveAction; } Qt::DropActions WorldList::supportedDropActions() const { - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; + // copy from outside, move from within and other mod lists + return Qt::CopyAction | Qt::MoveAction; } void WorldList::installWorld(QFileInfo filename) { - qDebug() << "installing: " << filename.absoluteFilePath(); - World w(filename); - if(!w.isValid()) - { - return; - } - w.install(m_dir.absolutePath()); + qDebug() << "installing: " << filename.absoluteFilePath(); + World w(filename); + if(!w.isValid()) + { + return; + } + w.install(m_dir.absolutePath()); } bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent) + const QModelIndex &parent) { - if (action == Qt::IgnoreAction) - return true; - // check if the action is supported - if (!data || !(action & supportedDropActions())) - return false; - // files dropped from outside? - if (data->hasUrls()) - { - bool was_watching = is_watching; - if (was_watching) - stopWatching(); - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - continue; - QString filename = url.toLocalFile(); - - QFileInfo worldInfo(filename); - - if(!m_dir.entryInfoList().contains(worldInfo)) - { - installWorld(worldInfo); - } - } - if (was_watching) - startWatching(); - return true; - } - return false; + if (action == Qt::IgnoreAction) + return true; + // check if the action is supported + if (!data || !(action & supportedDropActions())) + return false; + // files dropped from outside? + if (data->hasUrls()) + { + bool was_watching = is_watching; + if (was_watching) + stopWatching(); + auto urls = data->urls(); + for (auto url : urls) + { + // only local files may be dropped... + if (!url.isLocalFile()) + continue; + QString filename = url.toLocalFile(); + + QFileInfo worldInfo(filename); + + if(!m_dir.entryInfoList().contains(worldInfo)) + { + installWorld(worldInfo); + } + } + if (was_watching) + startWatching(); + return true; + } + return false; } #include "WorldList.moc" diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h index 811393cd..37ad330a 100644 --- a/api/logic/minecraft/WorldList.h +++ b/api/logic/minecraft/WorldList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,98 +28,100 @@ class QFileSystemWatcher; class MULTIMC_LOGIC_EXPORT WorldList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum Columns - { - NameColumn, - LastPlayedColumn - }; - - enum Roles - { - ObjectRole = Qt::UserRole + 1, - FolderRole, - SeedRole, - NameRole, - LastPlayedRole - }; - - WorldList(const QString &dir); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return size(); - }; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; - virtual int columnCount(const QModelIndex &parent) const; - - size_t size() const - { - return worlds.size(); - }; - bool empty() const - { - return size() == 0; - } - World &operator[](size_t index) - { - return worlds[index]; - } - - /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /// Install a world from location - void installWorld(QFileInfo filename); - - /// Deletes the mod at the given index. - virtual bool deleteWorld(int index); - - /// Deletes all the selected mods - virtual bool deleteWorlds(int first, int last); - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - /// get data for drag action - virtual QMimeData *mimeData(const QModelIndexList &indexes) const; - /// get the supported mime types - virtual QStringList mimeTypes() const; - /// process data from drop action - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - /// what drag actions do we support? - virtual Qt::DropActions supportedDragActions() const; - - /// what drop actions do we support? - virtual Qt::DropActions supportedDropActions() const; - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); - - QDir dir() const - { - return m_dir; - } - - const QList &allWorlds() const - { - return worlds; - } + enum Columns + { + NameColumn, + GameModeColumn, + LastPlayedColumn + }; + + enum Roles + { + ObjectRole = Qt::UserRole + 1, + FolderRole, + SeedRole, + NameRole, + GameModeRole, + LastPlayedRole + }; + + WorldList(const QString &dir); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const + { + return size(); + }; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual int columnCount(const QModelIndex &parent) const; + + size_t size() const + { + return worlds.size(); + }; + bool empty() const + { + return size() == 0; + } + World &operator[](size_t index) + { + return worlds[index]; + } + + /// Reloads the mod list and returns true if the list changed. + virtual bool update(); + + /// Install a world from location + void installWorld(QFileInfo filename); + + /// Deletes the mod at the given index. + virtual bool deleteWorld(int index); + + /// Deletes all the selected mods + virtual bool deleteWorlds(int first, int last); + + /// flags, mostly to support drag&drop + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + /// get data for drag action + virtual QMimeData *mimeData(const QModelIndexList &indexes) const; + /// get the supported mime types + virtual QStringList mimeTypes() const; + /// process data from drop action + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + /// what drag actions do we support? + virtual Qt::DropActions supportedDragActions() const; + + /// what drop actions do we support? + virtual Qt::DropActions supportedDropActions() const; + + void startWatching(); + void stopWatching(); + + virtual bool isValid(); + + QDir dir() const + { + return m_dir; + } + + const QList &allWorlds() const + { + return worlds; + } private slots: - void directoryChanged(QString path); + void directoryChanged(QString path); signals: - void changed(); + void changed(); protected: - QFileSystemWatcher *m_watcher; - bool is_watching; - QDir m_dir; - QList worlds; + QFileSystemWatcher *m_watcher; + bool is_watching; + QDir m_dir; + QList worlds; }; diff --git a/api/logic/minecraft/auth/AuthSession.cpp b/api/logic/minecraft/auth/AuthSession.cpp index 8758bfbd..4e858796 100644 --- a/api/logic/minecraft/auth/AuthSession.cpp +++ b/api/logic/minecraft/auth/AuthSession.cpp @@ -6,25 +6,25 @@ QString AuthSession::serializeUserProperties() { - QJsonObject userAttrs; - for (auto key : u.properties.keys()) - { - auto array = QJsonArray::fromStringList(u.properties.values(key)); - userAttrs.insert(key, array); - } - QJsonDocument value(userAttrs); - return value.toJson(QJsonDocument::Compact); + QJsonObject userAttrs; + for (auto key : u.properties.keys()) + { + auto array = QJsonArray::fromStringList(u.properties.values(key)); + userAttrs.insert(key, array); + } + QJsonDocument value(userAttrs); + return value.toJson(QJsonDocument::Compact); } bool AuthSession::MakeOffline(QString offline_playername) { - if (status != PlayableOffline && status != PlayableOnline) - { - return false; - } - session = "-"; - player_name = offline_playername; - status = PlayableOffline; - return true; + if (status != PlayableOffline && status != PlayableOnline) + { + return false; + } + session = "-"; + player_name = offline_playername; + status = PlayableOffline; + return true; } diff --git a/api/logic/minecraft/auth/AuthSession.h b/api/logic/minecraft/auth/AuthSession.h index d2f66db8..b397d9a1 100644 --- a/api/logic/minecraft/auth/AuthSession.h +++ b/api/logic/minecraft/auth/AuthSession.h @@ -10,45 +10,45 @@ class MojangAccount; struct User { - QString id; - QMultiMap properties; + QString id; + QMultiMap properties; }; struct MULTIMC_LOGIC_EXPORT AuthSession { - bool MakeOffline(QString offline_playername); - - QString serializeUserProperties(); - - enum Status - { - Undetermined, - RequiresPassword, - PlayableOffline, - PlayableOnline - } status = Undetermined; - - User u; - - // client token - QString client_token; - // account user name - QString username; - // combined session ID - QString session; - // volatile auth token - QString access_token; - // profile name - QString player_name; - // profile ID - QString uuid; - // 'legacy' or 'mojang', depending on account type - QString user_type; - // Did the auth server reply? - bool auth_server_online = false; - // Did the user request online mode? - bool wants_online = true; - std::shared_ptr m_accountPtr; + bool MakeOffline(QString offline_playername); + + QString serializeUserProperties(); + + enum Status + { + Undetermined, + RequiresPassword, + PlayableOffline, + PlayableOnline + } status = Undetermined; + + User u; + + // client token + QString client_token; + // account user name + QString username; + // combined session ID + QString session; + // volatile auth token + QString access_token; + // profile name + QString player_name; + // profile ID + QString uuid; + // 'legacy' or 'mojang', depending on account type + QString user_type; + // Did the auth server reply? + bool auth_server_online = false; + // Did the user request online mode? + bool wants_online = true; + std::shared_ptr m_accountPtr; }; typedef std::shared_ptr AuthSessionPtr; diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp index edad4344..657e0009 100644 --- a/api/logic/minecraft/auth/MojangAccount.cpp +++ b/api/logic/minecraft/auth/MojangAccount.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -30,286 +30,286 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) { - // The JSON object must at least have a username for it to be valid. - if (!object.value("username").isString()) - { - qCritical() << "Can't load Mojang account info from JSON object. Username field is " - "missing or of the wrong type."; - return nullptr; - } + // The JSON object must at least have a username for it to be valid. + if (!object.value("username").isString()) + { + qCritical() << "Can't load Mojang account info from JSON object. Username field is " + "missing or of the wrong type."; + return nullptr; + } - QString username = object.value("username").toString(""); - QString clientToken = object.value("clientToken").toString(""); - QString accessToken = object.value("accessToken").toString(""); + QString username = object.value("username").toString(""); + QString clientToken = object.value("clientToken").toString(""); + QString accessToken = object.value("accessToken").toString(""); - QJsonArray profileArray = object.value("profiles").toArray(); - if (profileArray.size() < 1) - { - qCritical() << "Can't load Mojang account with username \"" << username - << "\". No profiles found."; - return nullptr; - } + QJsonArray profileArray = object.value("profiles").toArray(); + if (profileArray.size() < 1) + { + qCritical() << "Can't load Mojang account with username \"" << username + << "\". No profiles found."; + return nullptr; + } - QList profiles; - for (QJsonValue profileVal : profileArray) - { - QJsonObject profileObject = profileVal.toObject(); - QString id = profileObject.value("id").toString(""); - QString name = profileObject.value("name").toString(""); - bool legacy = profileObject.value("legacy").toBool(false); - if (id.isEmpty() || name.isEmpty()) - { - qWarning() << "Unable to load a profile because it was missing an ID or a name."; - continue; - } - profiles.append({id, name, legacy}); - } + QList profiles; + for (QJsonValue profileVal : profileArray) + { + QJsonObject profileObject = profileVal.toObject(); + QString id = profileObject.value("id").toString(""); + QString name = profileObject.value("name").toString(""); + bool legacy = profileObject.value("legacy").toBool(false); + if (id.isEmpty() || name.isEmpty()) + { + qWarning() << "Unable to load a profile because it was missing an ID or a name."; + continue; + } + profiles.append({id, name, legacy}); + } - MojangAccountPtr account(new MojangAccount()); - if (object.value("user").isObject()) - { - User u; - QJsonObject userStructure = object.value("user").toObject(); - u.id = userStructure.value("id").toString(); - /* - QJsonObject propMap = userStructure.value("properties").toObject(); - for(auto key: propMap.keys()) - { - auto values = propMap.operator[](key).toArray(); - for(auto value: values) - u.properties.insert(key, value.toString()); - } - */ - account->m_user = u; - } - account->m_username = username; - account->m_clientToken = clientToken; - account->m_accessToken = accessToken; - account->m_profiles = profiles; + MojangAccountPtr account(new MojangAccount()); + if (object.value("user").isObject()) + { + User u; + QJsonObject userStructure = object.value("user").toObject(); + u.id = userStructure.value("id").toString(); + /* + QJsonObject propMap = userStructure.value("properties").toObject(); + for(auto key: propMap.keys()) + { + auto values = propMap.operator[](key).toArray(); + for(auto value: values) + u.properties.insert(key, value.toString()); + } + */ + account->m_user = u; + } + account->m_username = username; + account->m_clientToken = clientToken; + account->m_accessToken = accessToken; + account->m_profiles = profiles; - // Get the currently selected profile. - QString currentProfile = object.value("activeProfile").toString(""); - if (!currentProfile.isEmpty()) - account->setCurrentProfile(currentProfile); + // Get the currently selected profile. + QString currentProfile = object.value("activeProfile").toString(""); + if (!currentProfile.isEmpty()) + account->setCurrentProfile(currentProfile); - return account; + return account; } MojangAccountPtr MojangAccount::createFromUsername(const QString &username) { - MojangAccountPtr account(new MojangAccount()); - account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->m_username = username; - return account; + MojangAccountPtr account(new MojangAccount()); + account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->m_username = username; + return account; } QJsonObject MojangAccount::saveToJson() const { - QJsonObject json; - json.insert("username", m_username); - json.insert("clientToken", m_clientToken); - json.insert("accessToken", m_accessToken); + QJsonObject json; + json.insert("username", m_username); + json.insert("clientToken", m_clientToken); + json.insert("accessToken", m_accessToken); - QJsonArray profileArray; - for (AccountProfile profile : m_profiles) - { - QJsonObject profileObj; - profileObj.insert("id", profile.id); - profileObj.insert("name", profile.name); - profileObj.insert("legacy", profile.legacy); - profileArray.append(profileObj); - } - json.insert("profiles", profileArray); + QJsonArray profileArray; + for (AccountProfile profile : m_profiles) + { + QJsonObject profileObj; + profileObj.insert("id", profile.id); + profileObj.insert("name", profile.name); + profileObj.insert("legacy", profile.legacy); + profileArray.append(profileObj); + } + json.insert("profiles", profileArray); - QJsonObject userStructure; - { - userStructure.insert("id", m_user.id); - /* - QJsonObject userAttrs; - for(auto key: m_user.properties.keys()) - { - auto array = QJsonArray::fromStringList(m_user.properties.values(key)); - userAttrs.insert(key, array); - } - userStructure.insert("properties", userAttrs); - */ - } - json.insert("user", userStructure); + QJsonObject userStructure; + { + userStructure.insert("id", m_user.id); + /* + QJsonObject userAttrs; + for(auto key: m_user.properties.keys()) + { + auto array = QJsonArray::fromStringList(m_user.properties.values(key)); + userAttrs.insert(key, array); + } + userStructure.insert("properties", userAttrs); + */ + } + json.insert("user", userStructure); - if (m_currentProfile != -1) - json.insert("activeProfile", currentProfile()->id); + if (m_currentProfile != -1) + json.insert("activeProfile", currentProfile()->id); - return json; + return json; } bool MojangAccount::setCurrentProfile(const QString &profileId) { - for (int i = 0; i < m_profiles.length(); i++) - { - if (m_profiles[i].id == profileId) - { - m_currentProfile = i; - return true; - } - } - return false; + for (int i = 0; i < m_profiles.length(); i++) + { + if (m_profiles[i].id == profileId) + { + m_currentProfile = i; + return true; + } + } + return false; } const AccountProfile *MojangAccount::currentProfile() const { - if (m_currentProfile == -1) - return nullptr; - return &m_profiles[m_currentProfile]; + if (m_currentProfile == -1) + return nullptr; + return &m_profiles[m_currentProfile]; } AccountStatus MojangAccount::accountStatus() const { - if (m_accessToken.isEmpty()) - return NotVerified; - else - return Verified; + if (m_accessToken.isEmpty()) + return NotVerified; + else + return Verified; } std::shared_ptr MojangAccount::login(AuthSessionPtr session, QString password) { - Q_ASSERT(m_currentTask.get() == nullptr); + Q_ASSERT(m_currentTask.get() == nullptr); - // take care of the true offline status - if (accountStatus() == NotVerified && password.isEmpty()) - { - if (session) - { - session->status = AuthSession::RequiresPassword; - fillSession(session); - } - return nullptr; - } + // take care of the true offline status + if (accountStatus() == NotVerified && password.isEmpty()) + { + if (session) + { + session->status = AuthSession::RequiresPassword; + fillSession(session); + } + return nullptr; + } - if(accountStatus() == Verified && !session->wants_online) - { - session->status = AuthSession::PlayableOffline; - session->auth_server_online = false; - fillSession(session); - return nullptr; - } - else - { - if (password.isEmpty()) - { - m_currentTask.reset(new RefreshTask(this)); - } - else - { - m_currentTask.reset(new AuthenticateTask(this, password)); - } - m_currentTask->assignSession(session); + if(accountStatus() == Verified && !session->wants_online) + { + session->status = AuthSession::PlayableOffline; + session->auth_server_online = false; + fillSession(session); + return nullptr; + } + else + { + if (password.isEmpty()) + { + m_currentTask.reset(new RefreshTask(this)); + } + else + { + m_currentTask.reset(new AuthenticateTask(this, password)); + } + m_currentTask->assignSession(session); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); - } - return m_currentTask; + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + } + return m_currentTask; } void MojangAccount::authSucceeded() { - auto session = m_currentTask->getAssignedSession(); - if (session) - { - session->status = - session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; - fillSession(session); - session->auth_server_online = true; - } - m_currentTask.reset(); - emit changed(); + auto session = m_currentTask->getAssignedSession(); + if (session) + { + session->status = + session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; + fillSession(session); + session->auth_server_online = true; + } + m_currentTask.reset(); + emit changed(); } void MojangAccount::authFailed(QString reason) { - auto session = m_currentTask->getAssignedSession(); - // This is emitted when the yggdrasil tasks time out or are cancelled. - // -> we treat the error as no-op - if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT) - { - if (session) - { - session->status = accountStatus() == Verified ? AuthSession::PlayableOffline - : AuthSession::RequiresPassword; - session->auth_server_online = false; - fillSession(session); - } - } - else - { - m_accessToken = QString(); - emit changed(); - if (session) - { - session->status = AuthSession::RequiresPassword; - session->auth_server_online = true; - fillSession(session); - } - } - m_currentTask.reset(); + auto session = m_currentTask->getAssignedSession(); + // This is emitted when the yggdrasil tasks time out or are cancelled. + // -> we treat the error as no-op + if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT) + { + if (session) + { + session->status = accountStatus() == Verified ? AuthSession::PlayableOffline + : AuthSession::RequiresPassword; + session->auth_server_online = false; + fillSession(session); + } + } + else + { + m_accessToken = QString(); + emit changed(); + if (session) + { + session->status = AuthSession::RequiresPassword; + session->auth_server_online = true; + fillSession(session); + } + } + m_currentTask.reset(); } void MojangAccount::fillSession(AuthSessionPtr session) { - // the user name. you have to have an user name - session->username = m_username; - // volatile auth token - session->access_token = m_accessToken; - // the semi-permanent client token - session->client_token = m_clientToken; - if (currentProfile()) - { - // profile name - session->player_name = currentProfile()->name; - // profile ID - session->uuid = currentProfile()->id; - // 'legacy' or 'mojang', depending on account type - session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; - if (!session->access_token.isEmpty()) - { - session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; - } - else - { - session->session = "-"; - } - } - else - { - session->player_name = "Player"; - session->session = "-"; - } - session->u = user(); - session->m_accountPtr = shared_from_this(); + // the user name. you have to have an user name + session->username = m_username; + // volatile auth token + session->access_token = m_accessToken; + // the semi-permanent client token + session->client_token = m_clientToken; + if (currentProfile()) + { + // profile name + session->player_name = currentProfile()->name; + // profile ID + session->uuid = currentProfile()->id; + // 'legacy' or 'mojang', depending on account type + session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; + if (!session->access_token.isEmpty()) + { + session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; + } + else + { + session->session = "-"; + } + } + else + { + session->player_name = "Player"; + session->session = "-"; + } + session->u = user(); + session->m_accountPtr = shared_from_this(); } void MojangAccount::decrementUses() { - Usable::decrementUses(); - if(!isInUse()) - { - emit changed(); - qWarning() << "Account" << m_username << "is no longer in use."; - } + Usable::decrementUses(); + if(!isInUse()) + { + emit changed(); + qWarning() << "Account" << m_username << "is no longer in use."; + } } void MojangAccount::incrementUses() { - bool wasInUse = isInUse(); - Usable::incrementUses(); - if(!wasInUse) - { - emit changed(); - qWarning() << "Account" << m_username << "is now in use."; - } + bool wasInUse = isInUse(); + Usable::incrementUses(); + if(!wasInUse) + { + emit changed(); + qWarning() << "Account" << m_username << "is now in use."; + } } void MojangAccount::invalidateClientToken() { - m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - emit changed(); + m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + emit changed(); } diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h index b2bbc357..7006435e 100644 --- a/api/logic/minecraft/auth/MojangAccount.h +++ b/api/logic/minecraft/auth/MojangAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,15 +44,15 @@ Q_DECLARE_METATYPE(MojangAccountPtr) */ struct AccountProfile { - QString id; - QString name; - bool legacy; + QString id; + QString name; + bool legacy; }; enum AccountStatus { - NotVerified, - Verified + NotVerified, + Verified }; /** @@ -62,121 +62,121 @@ enum AccountStatus * token if the user chose to stay logged in. */ class MULTIMC_LOGIC_EXPORT MojangAccount : - public QObject, - public Usable, - public std::enable_shared_from_this + public QObject, + public Usable, + public std::enable_shared_from_this { - Q_OBJECT + Q_OBJECT public: /* construction */ - //! Do not copy accounts. ever. - explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; + //! Do not copy accounts. ever. + explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; - //! Default constructor - explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; + //! Default constructor + explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; - //! Creates an empty account for the specified user name. - static MojangAccountPtr createFromUsername(const QString &username); + //! Creates an empty account for the specified user name. + static MojangAccountPtr createFromUsername(const QString &username); - //! Loads a MojangAccount from the given JSON object. - static MojangAccountPtr loadFromJson(const QJsonObject &json); + //! Loads a MojangAccount from the given JSON object. + static MojangAccountPtr loadFromJson(const QJsonObject &json); - //! Saves a MojangAccount to a JSON object and returns it. - QJsonObject saveToJson() const; + //! Saves a MojangAccount to a JSON object and returns it. + QJsonObject saveToJson() const; public: /* manipulation */ - /** - * Sets the currently selected profile to the profile with the given ID string. - * If profileId is not in the list of available profiles, the function will simply return - * false. - */ - bool setCurrentProfile(const QString &profileId); - - /** - * Attempt to login. Empty password means we use the token. - * If the attempt fails because we already are performing some task, it returns false. - */ - std::shared_ptr login(AuthSessionPtr session, QString password = QString()); - void invalidateClientToken(); + /** + * Sets the currently selected profile to the profile with the given ID string. + * If profileId is not in the list of available profiles, the function will simply return + * false. + */ + bool setCurrentProfile(const QString &profileId); + + /** + * Attempt to login. Empty password means we use the token. + * If the attempt fails because we already are performing some task, it returns false. + */ + std::shared_ptr login(AuthSessionPtr session, QString password = QString()); + void invalidateClientToken(); public: /* queries */ - const QString &username() const - { - return m_username; - } + const QString &username() const + { + return m_username; + } - const QString &clientToken() const - { - return m_clientToken; - } + const QString &clientToken() const + { + return m_clientToken; + } - const QString &accessToken() const - { - return m_accessToken; - } + const QString &accessToken() const + { + return m_accessToken; + } - const QList &profiles() const - { - return m_profiles; - } + const QList &profiles() const + { + return m_profiles; + } - const User &user() - { - return m_user; - } + const User &user() + { + return m_user; + } - //! Returns the currently selected profile (if none, returns nullptr) - const AccountProfile *currentProfile() const; + //! Returns the currently selected profile (if none, returns nullptr) + const AccountProfile *currentProfile() const; - //! Returns whether the account is NotVerified, Verified or Online - AccountStatus accountStatus() const; + //! Returns whether the account is NotVerified, Verified or Online + AccountStatus accountStatus() const; signals: - /** - * This signal is emitted when the account changes - */ - void changed(); + /** + * This signal is emitted when the account changes + */ + void changed(); - // TODO: better signalling for the various possible state changes - especially errors + // TODO: better signalling for the various possible state changes - especially errors protected: /* variables */ - QString m_username; + QString m_username; - // Used to identify the client - the user can have multiple clients for the same account - // Think: different launchers, all connecting to the same account/profile - QString m_clientToken; + // Used to identify the client - the user can have multiple clients for the same account + // Think: different launchers, all connecting to the same account/profile + QString m_clientToken; - // Blank if not logged in. - QString m_accessToken; + // Blank if not logged in. + QString m_accessToken; - // Index of the selected profile within the list of available - // profiles. -1 if nothing is selected. - int m_currentProfile = -1; + // Index of the selected profile within the list of available + // profiles. -1 if nothing is selected. + int m_currentProfile = -1; - // List of available profiles. - QList m_profiles; + // List of available profiles. + QList m_profiles; - // the user structure, whatever it is. - User m_user; + // the user structure, whatever it is. + User m_user; - // current task we are executing here - std::shared_ptr m_currentTask; + // current task we are executing here + std::shared_ptr m_currentTask; protected: /* methods */ - void incrementUses() override; - void decrementUses() override; + void incrementUses() override; + void decrementUses() override; private slots: - void authSucceeded(); - void authFailed(QString reason); + void authSucceeded(); + void authFailed(QString reason); private: - void fillSession(AuthSessionPtr session); + void fillSession(AuthSessionPtr session); public: - friend class YggdrasilTask; - friend class AuthenticateTask; - friend class ValidateTask; - friend class RefreshTask; + friend class YggdrasilTask; + friend class AuthenticateTask; + friend class ValidateTask; + friend class RefreshTask; }; diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp index 21ae188a..b594c652 100644 --- a/api/logic/minecraft/auth/MojangAccountList.cpp +++ b/api/logic/minecraft/auth/MojangAccountList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,432 +37,432 @@ MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(paren MojangAccountPtr MojangAccountList::findAccount(const QString &username) const { - for (int i = 0; i < count(); i++) - { - MojangAccountPtr account = at(i); - if (account->username() == username) - return account; - } - return nullptr; + for (int i = 0; i < count(); i++) + { + MojangAccountPtr account = at(i); + if (account->username() == username) + return account; + } + return nullptr; } const MojangAccountPtr MojangAccountList::at(int i) const { - return MojangAccountPtr(m_accounts.at(i)); + return MojangAccountPtr(m_accounts.at(i)); } void MojangAccountList::addAccount(const MojangAccountPtr account) { - int row = m_accounts.count(); - beginInsertRows(QModelIndex(), row, row); - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - endInsertRows(); - onListChanged(); + int row = m_accounts.count(); + beginInsertRows(QModelIndex(), row, row); + connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); + m_accounts.append(account); + endInsertRows(); + onListChanged(); } void MojangAccountList::removeAccount(const QString &username) { - int idx = 0; - for (auto account : m_accounts) - { - if (account->username() == username) - { - beginRemoveRows(QModelIndex(), idx, idx); - m_accounts.removeOne(account); - endRemoveRows(); - return; - } - idx++; - } - onListChanged(); + int idx = 0; + for (auto account : m_accounts) + { + if (account->username() == username) + { + beginRemoveRows(QModelIndex(), idx, idx); + m_accounts.removeOne(account); + endRemoveRows(); + return; + } + idx++; + } + onListChanged(); } void MojangAccountList::removeAccount(QModelIndex index) { - int row = index.row(); - if(index.isValid() && row >= 0 && row < m_accounts.size()) - { - auto & account = m_accounts[row]; - if(account == m_activeAccount) - { - m_activeAccount = nullptr; - onActiveChanged(); - } - beginRemoveRows(QModelIndex(), row, row); - m_accounts.removeAt(index.row()); - endRemoveRows(); - onListChanged(); - } + int row = index.row(); + if(index.isValid() && row >= 0 && row < m_accounts.size()) + { + auto & account = m_accounts[row]; + if(account == m_activeAccount) + { + m_activeAccount = nullptr; + onActiveChanged(); + } + beginRemoveRows(QModelIndex(), row, row); + m_accounts.removeAt(index.row()); + endRemoveRows(); + onListChanged(); + } } MojangAccountPtr MojangAccountList::activeAccount() const { - return m_activeAccount; + return m_activeAccount; } void MojangAccountList::setActiveAccount(const QString &username) { - if (username.isEmpty() && m_activeAccount) - { - int idx = 0; - auto prevActiveAcc = m_activeAccount; - m_activeAccount = nullptr; - for (MojangAccountPtr account : m_accounts) - { - if (account == prevActiveAcc) - { - emit dataChanged(index(idx), index(idx)); - } - idx ++; - } - onActiveChanged(); - } - else - { - auto currentActiveAccount = m_activeAccount; - int currentActiveAccountIdx = -1; - auto newActiveAccount = m_activeAccount; - int newActiveAccountIdx = -1; - int idx = 0; - for (MojangAccountPtr account : m_accounts) - { - if (account->username() == username) - { - newActiveAccount = account; - newActiveAccountIdx = idx; - } - if(currentActiveAccount == account) - { - currentActiveAccountIdx = idx; - } - idx++; - } - if(currentActiveAccount != newActiveAccount) - { - emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx)); - emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx)); - m_activeAccount = newActiveAccount; - onActiveChanged(); - } - } + if (username.isEmpty() && m_activeAccount) + { + int idx = 0; + auto prevActiveAcc = m_activeAccount; + m_activeAccount = nullptr; + for (MojangAccountPtr account : m_accounts) + { + if (account == prevActiveAcc) + { + emit dataChanged(index(idx), index(idx)); + } + idx ++; + } + onActiveChanged(); + } + else + { + auto currentActiveAccount = m_activeAccount; + int currentActiveAccountIdx = -1; + auto newActiveAccount = m_activeAccount; + int newActiveAccountIdx = -1; + int idx = 0; + for (MojangAccountPtr account : m_accounts) + { + if (account->username() == username) + { + newActiveAccount = account; + newActiveAccountIdx = idx; + } + if(currentActiveAccount == account) + { + currentActiveAccountIdx = idx; + } + idx++; + } + if(currentActiveAccount != newActiveAccount) + { + emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx)); + emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx)); + m_activeAccount = newActiveAccount; + onActiveChanged(); + } + } } void MojangAccountList::accountChanged() { - // the list changed. there is no doubt. - onListChanged(); + // the list changed. there is no doubt. + onListChanged(); } void MojangAccountList::onListChanged() { - if (m_autosave) - // TODO: Alert the user if this fails. - saveList(); + if (m_autosave) + // TODO: Alert the user if this fails. + saveList(); - emit listChanged(); + emit listChanged(); } void MojangAccountList::onActiveChanged() { - if (m_autosave) - saveList(); + if (m_autosave) + saveList(); - emit activeAccountChanged(); + emit activeAccountChanged(); } int MojangAccountList::count() const { - return m_accounts.count(); + return m_accounts.count(); } QVariant MojangAccountList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - MojangAccountPtr account = at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case NameColumn: - return account->username(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return account->username(); - - case PointerRole: - return qVariantFromValue(account); - - case Qt::CheckStateRole: - switch (index.column()) - { - case ActiveColumn: - return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; - } - - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + MojangAccountPtr account = at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case NameColumn: + return account->username(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return account->username(); + + case PointerRole: + return qVariantFromValue(account); + + case Qt::CheckStateRole: + switch (index.column()) + { + case ActiveColumn: + return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; + } + + default: + return QVariant(); + } } QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const { - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return tr("Active?"); - - case NameColumn: - return tr("Name"); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the version."); - - default: - return QVariant(); - } - - default: - return QVariant(); - } + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case ActiveColumn: + return tr("Active?"); + + case NameColumn: + return tr("Name"); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case NameColumn: + return tr("The name of the version."); + + default: + return QVariant(); + } + + default: + return QVariant(); + } } int MojangAccountList::rowCount(const QModelIndex &) const { - // Return count - return count(); + // Return count + return count(); } int MojangAccountList::columnCount(const QModelIndex &) const { - return 2; + return 2; } Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const { - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return Qt::NoItemFlags; - } + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return Qt::NoItemFlags; + } - return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value, int role) { - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if(role == Qt::CheckStateRole) - { - if(value == Qt::Checked) - { - MojangAccountPtr account = this->at(index.row()); - this->setActiveAccount(account->username()); - } - } - - emit dataChanged(index, index); - return true; + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return false; + } + + if(role == Qt::CheckStateRole) + { + if(value == Qt::Checked) + { + MojangAccountPtr account = this->at(index.row()); + this->setActiveAccount(account->username()); + } + } + + emit dataChanged(index, index); + return true; } void MojangAccountList::updateListData(QList versions) { - beginResetModel(); - m_accounts = versions; - endResetModel(); + beginResetModel(); + m_accounts = versions; + endResetModel(); } bool MojangAccountList::loadList(const QString &filePath) { - QString path = filePath; - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't load Mojang account list. No file path given and no default set."; - return false; - } - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << QString("Failed to parse account list file: %1 at offset %2") - .arg(parseError.errorString(), QString::number(parseError.offset)) - .toUtf8(); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid account list JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - // Make sure the format version matches. - if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) - { - QString newName = "accounts-old.json"; - qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to" - << newName; - - // Attempt to rename the old version. - file.rename(newName); - return false; - } - - // Now, load the accounts array. - beginResetModel(); - QJsonArray accounts = root.value("accounts").toArray(); - for (QJsonValue accountVal : accounts) - { - QJsonObject accountObj = accountVal.toObject(); - MojangAccountPtr account = MojangAccount::loadFromJson(accountObj); - if (account.get() != nullptr) - { - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - } - else - { - qWarning() << "Failed to load an account."; - } - } - // Load the active account. - m_activeAccount = findAccount(root.value("activeAccount").toString("")); - endResetModel(); - return true; + QString path = filePath; + if (path.isEmpty()) + path = m_listFilePath; + if (path.isEmpty()) + { + qCritical() << "Can't load Mojang account list. No file path given and no default set."; + return false; + } + + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::ReadOnly)) + { + qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); + return false; + } + + // Read the file and close it. + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); + + // Fail if the JSON is invalid. + if (parseError.error != QJsonParseError::NoError) + { + qCritical() << QString("Failed to parse account list file: %1 at offset %2") + .arg(parseError.errorString(), QString::number(parseError.offset)) + .toUtf8(); + return false; + } + + // Make sure the root is an object. + if (!jsonDoc.isObject()) + { + qCritical() << "Invalid account list JSON: Root should be an array."; + return false; + } + + QJsonObject root = jsonDoc.object(); + + // Make sure the format version matches. + if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) + { + QString newName = "accounts-old.json"; + qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to" + << newName; + + // Attempt to rename the old version. + file.rename(newName); + return false; + } + + // Now, load the accounts array. + beginResetModel(); + QJsonArray accounts = root.value("accounts").toArray(); + for (QJsonValue accountVal : accounts) + { + QJsonObject accountObj = accountVal.toObject(); + MojangAccountPtr account = MojangAccount::loadFromJson(accountObj); + if (account.get() != nullptr) + { + connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); + m_accounts.append(account); + } + else + { + qWarning() << "Failed to load an account."; + } + } + // Load the active account. + m_activeAccount = findAccount(root.value("activeAccount").toString("")); + endResetModel(); + return true; } bool MojangAccountList::saveList(const QString &filePath) { - QString path(filePath); - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't save Mojang account list. No file path given and no default set."; - return false; - } - - // make sure the parent folder exists - if(!FS::ensureFilePathExists(path)) - return false; - - // make sure the file wasn't overwritten with a folder before (fixes a bug) - QFileInfo finfo(path); - if(finfo.isDir()) - { - QDir badDir(path); - badDir.removeRecursively(); - } - - qDebug() << "Writing account list to" << path; - - qDebug() << "Building JSON data structure."; - // Build the JSON document to write to the list file. - QJsonObject root; - - root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION); - - // Build a list of accounts. - qDebug() << "Building account array."; - QJsonArray accounts; - for (MojangAccountPtr account : m_accounts) - { - QJsonObject accountObj = account->saveToJson(); - accounts.append(accountObj); - } - - // Insert the account list into the root object. - root.insert("accounts", accounts); - - if(m_activeAccount) - { - // Save the active account. - root.insert("activeAccount", m_activeAccount->username()); - } - - // Create a JSON document object to convert our JSON to bytes. - QJsonDocument doc(root); - - // Now that we're done building the JSON object, we can write it to the file. - qDebug() << "Writing account list to file."; - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::WriteOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Write the JSON to the file. - file.write(doc.toJson()); - file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser); - file.close(); - - qDebug() << "Saved account list to" << path; - - return true; + QString path(filePath); + if (path.isEmpty()) + path = m_listFilePath; + if (path.isEmpty()) + { + qCritical() << "Can't save Mojang account list. No file path given and no default set."; + return false; + } + + // make sure the parent folder exists + if(!FS::ensureFilePathExists(path)) + return false; + + // make sure the file wasn't overwritten with a folder before (fixes a bug) + QFileInfo finfo(path); + if(finfo.isDir()) + { + QDir badDir(path); + badDir.removeRecursively(); + } + + qDebug() << "Writing account list to" << path; + + qDebug() << "Building JSON data structure."; + // Build the JSON document to write to the list file. + QJsonObject root; + + root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION); + + // Build a list of accounts. + qDebug() << "Building account array."; + QJsonArray accounts; + for (MojangAccountPtr account : m_accounts) + { + QJsonObject accountObj = account->saveToJson(); + accounts.append(accountObj); + } + + // Insert the account list into the root object. + root.insert("accounts", accounts); + + if(m_activeAccount) + { + // Save the active account. + root.insert("activeAccount", m_activeAccount->username()); + } + + // Create a JSON document object to convert our JSON to bytes. + QJsonDocument doc(root); + + // Now that we're done building the JSON object, we can write it to the file. + qDebug() << "Writing account list to file."; + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::WriteOnly)) + { + qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); + return false; + } + + // Write the JSON to the file. + file.write(doc.toJson()); + file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser); + file.close(); + + qDebug() << "Saved account list to" << path; + + return true; } void MojangAccountList::setListFilePath(QString path, bool autosave) { - m_listFilePath = path; - m_autosave = autosave; + m_listFilePath = path; + m_autosave = autosave; } bool MojangAccountList::anyAccountIsValid() { - for(auto account:m_accounts) - { - if(account->accountStatus() != NotVerified) - return true; - } - return false; + for(auto account:m_accounts) + { + if(account->accountStatus() != NotVerified) + return true; + } + return false; } diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h index dd6c07e8..7760b32f 100644 --- a/api/logic/minecraft/auth/MojangAccountList.h +++ b/api/logic/minecraft/auth/MojangAccountList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,167 +35,167 @@ */ class MULTIMC_LOGIC_EXPORT MojangAccountList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum ModelRoles - { - PointerRole = 0x34B1CB48 - }; - - enum VListColumns - { - // TODO: Add icon column. - - // First column - Active? - ActiveColumn = 0, - - // Second column - Name - NameColumn, - }; - - explicit MojangAccountList(QObject *parent = 0); - - //! Gets the account at the given index. - virtual const MojangAccountPtr at(int i) const; - - //! Returns the number of accounts in the list. - virtual int count() const; - - //////// List Model Functions //////// - virtual QVariant data(const QModelIndex &index, int role) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent) const; - virtual int columnCount(const QModelIndex &parent) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role); - - /*! - * Adds a the given Mojang account to the account list. - */ - virtual void addAccount(const MojangAccountPtr account); - - /*! - * Removes the mojang account with the given username from the account list. - */ - virtual void removeAccount(const QString &username); - - /*! - * Removes the account at the given QModelIndex. - */ - virtual void removeAccount(QModelIndex index); - - /*! - * \brief Finds an account by its username. - * \param The username of the account to find. - * \return A const pointer to the account with the given username. NULL if - * one doesn't exist. - */ - virtual MojangAccountPtr findAccount(const QString &username) const; - - /*! - * Sets the default path to save the list file to. - * If autosave is true, this list will automatically save to the given path whenever it changes. - * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately - * after calling this function to ensure an autosaved change doesn't overwrite the list you intended - * to load. - */ - virtual void setListFilePath(QString path, bool autosave = false); - - /*! - * \brief Loads the account list from the given file path. - * If the given file is an empty string (default), will load from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool loadList(const QString &file = ""); - - /*! - * \brief Saves the account list to the given file. - * If the given file is an empty string (default), will save from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool saveList(const QString &file = ""); - - /*! - * \brief Gets a pointer to the account that the user has selected as their "active" account. - * Which account is active can be overridden on a per-instance basis, but this will return the one that - * is set as active globally. - * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer. - */ - virtual MojangAccountPtr activeAccount() const; - - /*! - * Sets the given account as the current active account. - * If the username given is an empty string, sets the active account to nothing. - */ - virtual void setActiveAccount(const QString &username); - - /*! - * Returns true if any of the account is at least Validated - */ - bool anyAccountIsValid(); + enum ModelRoles + { + PointerRole = 0x34B1CB48 + }; + + enum VListColumns + { + // TODO: Add icon column. + + // First column - Active? + ActiveColumn = 0, + + // Second column - Name + NameColumn, + }; + + explicit MojangAccountList(QObject *parent = 0); + + //! Gets the account at the given index. + virtual const MojangAccountPtr at(int i) const; + + //! Returns the number of accounts in the list. + virtual int count() const; + + //////// List Model Functions //////// + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role); + + /*! + * Adds a the given Mojang account to the account list. + */ + virtual void addAccount(const MojangAccountPtr account); + + /*! + * Removes the mojang account with the given username from the account list. + */ + virtual void removeAccount(const QString &username); + + /*! + * Removes the account at the given QModelIndex. + */ + virtual void removeAccount(QModelIndex index); + + /*! + * \brief Finds an account by its username. + * \param The username of the account to find. + * \return A const pointer to the account with the given username. NULL if + * one doesn't exist. + */ + virtual MojangAccountPtr findAccount(const QString &username) const; + + /*! + * Sets the default path to save the list file to. + * If autosave is true, this list will automatically save to the given path whenever it changes. + * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately + * after calling this function to ensure an autosaved change doesn't overwrite the list you intended + * to load. + */ + virtual void setListFilePath(QString path, bool autosave = false); + + /*! + * \brief Loads the account list from the given file path. + * If the given file is an empty string (default), will load from the default account list file. + * \return True if successful, otherwise false. + */ + virtual bool loadList(const QString &file = ""); + + /*! + * \brief Saves the account list to the given file. + * If the given file is an empty string (default), will save from the default account list file. + * \return True if successful, otherwise false. + */ + virtual bool saveList(const QString &file = ""); + + /*! + * \brief Gets a pointer to the account that the user has selected as their "active" account. + * Which account is active can be overridden on a per-instance basis, but this will return the one that + * is set as active globally. + * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer. + */ + virtual MojangAccountPtr activeAccount() const; + + /*! + * Sets the given account as the current active account. + * If the username given is an empty string, sets the active account to nothing. + */ + virtual void setActiveAccount(const QString &username); + + /*! + * Returns true if any of the account is at least Validated + */ + bool anyAccountIsValid(); signals: - /*! - * Signal emitted to indicate that the account list has changed. - * This will also fire if the value of an element in the list changes (will be implemented - * later). - */ - void listChanged(); - - /*! - * Signal emitted to indicate that the active account has changed. - */ - void activeAccountChanged(); + /*! + * Signal emitted to indicate that the account list has changed. + * This will also fire if the value of an element in the list changes (will be implemented + * later). + */ + void listChanged(); + + /*! + * Signal emitted to indicate that the active account has changed. + */ + void activeAccountChanged(); public slots: - /** - * This is called when one of the accounts changes and the list needs to be updated - */ - void accountChanged(); + /** + * This is called when one of the accounts changes and the list needs to be updated + */ + void accountChanged(); protected: - /*! - * Called whenever the list changes. - * This emits the listChanged() signal and autosaves the list (if autosave is enabled). - */ - void onListChanged(); - - /*! - * Called whenever the active account changes. - * Emits the activeAccountChanged() signal and autosaves the list if enabled. - */ - void onActiveChanged(); - - QList m_accounts; - - /*! - * Account that is currently active. - */ - MojangAccountPtr m_activeAccount; - - //! Path to the account list file. Empty string if there isn't one. - QString m_listFilePath; - - /*! - * If true, the account list will automatically save to the account list path when it changes. - * Ignored if m_listFilePath is blank. - */ - bool m_autosave = false; + /*! + * Called whenever the list changes. + * This emits the listChanged() signal and autosaves the list (if autosave is enabled). + */ + void onListChanged(); + + /*! + * Called whenever the active account changes. + * Emits the activeAccountChanged() signal and autosaves the list if enabled. + */ + void onActiveChanged(); + + QList m_accounts; + + /*! + * Account that is currently active. + */ + MojangAccountPtr m_activeAccount; + + //! Path to the account list file. Empty string if there isn't one. + QString m_listFilePath; + + /*! + * If true, the account list will automatically save to the account list path when it changes. + * Ignored if m_listFilePath is blank. + */ + bool m_autosave = false; protected slots: - /*! - * Updates this list with the given list of accounts. - * This is done by copying each account in the given list and inserting it - * into this one. - * We need to do this so that we can set the parents of the accounts are set to this - * account list. This can't be done in the load task, because the accounts the load - * task creates are on the load task's thread and Qt won't allow their parents - * to be set to something created on another thread. - * To get around that problem, we invoke this method on the GUI thread, which - * then copies the accounts and sets their parents correctly. - * \param accounts List of accounts whose parents should be set. - */ - virtual void updateListData(QList versions); + /*! + * Updates this list with the given list of accounts. + * This is done by copying each account in the given list and inserting it + * into this one. + * We need to do this so that we can set the parents of the accounts are set to this + * account list. This can't be done in the load task, because the accounts the load + * task creates are on the load task's thread and Qt won't allow their parents + * to be set to something created on another thread. + * To get around that problem, we invoke this method on the GUI thread, which + * then copies the accounts and sets their parents correctly. + * \param accounts List of accounts whose parents should be set. + */ + virtual void updateListData(QList versions); }; diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp index f17d803f..71f5d950 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.cpp +++ b/api/logic/minecraft/auth/YggdrasilTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,226 +30,226 @@ #include YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) - : Task(parent), m_account(account) + : Task(parent), m_account(account) { - changeState(STATE_CREATED); + changeState(STATE_CREATED); } void YggdrasilTask::executeTask() { - changeState(STATE_SENDING_REQUEST); - - // Get the content of the request we're going to send to the server. - QJsonDocument doc(getRequestContent()); - - QUrl reqUrl("https://" + URLConstants::AUTH_BASE + getEndpoint()); - QNetworkRequest netRequest(reqUrl); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QByteArray requestData = doc.toJson(); - m_netReply = ENV.qnam().post(netRequest, requestData); - connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); - connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors); - timeout_keeper.setSingleShot(true); - timeout_keeper.start(timeout_max); - counter.setSingleShot(false); - counter.start(time_step); - progress(0, timeout_max); - connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout); - connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); + changeState(STATE_SENDING_REQUEST); + + // Get the content of the request we're going to send to the server. + QJsonDocument doc(getRequestContent()); + + QUrl reqUrl(URLConstants::AUTH_BASE + getEndpoint()); + QNetworkRequest netRequest(reqUrl); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QByteArray requestData = doc.toJson(); + m_netReply = ENV.qnam().post(netRequest, requestData); + connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); + connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); + connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); + connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors); + timeout_keeper.setSingleShot(true); + timeout_keeper.start(timeout_max); + counter.setSingleShot(false); + counter.start(time_step); + progress(0, timeout_max); + connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout); + connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); } void YggdrasilTask::refreshTimers(qint64, qint64) { - timeout_keeper.stop(); - timeout_keeper.start(timeout_max); - progress(count = 0, timeout_max); + timeout_keeper.stop(); + timeout_keeper.start(timeout_max); + progress(count = 0, timeout_max); } void YggdrasilTask::heartbeat() { - count += time_step; - progress(count, timeout_max); + count += time_step; + progress(count, timeout_max); } bool YggdrasilTask::abort() { - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_USER; - m_netReply->abort(); - return true; + progress(timeout_max, timeout_max); + // TODO: actually use this in a meaningful way + m_aborted = YggdrasilTask::BY_USER; + m_netReply->abort(); + return true; } void YggdrasilTask::abortByTimeout() { - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_TIMEOUT; - m_netReply->abort(); + progress(timeout_max, timeout_max); + // TODO: actually use this in a meaningful way + m_aborted = YggdrasilTask::BY_TIMEOUT; + m_netReply->abort(); } void YggdrasilTask::sslErrors(QList errors) { - int i = 1; - for (auto error : errors) - { - qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } + int i = 1; + for (auto error : errors) + { + qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } } void YggdrasilTask::processReply() { - changeState(STATE_PROCESSING_RESPONSE); - - switch (m_netReply->error()) - { - case QNetworkReply::NoError: - break; - case QNetworkReply::TimeoutError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out.")); - return; - case QNetworkReply::OperationCanceledError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); - return; - case QNetworkReply::SslHandshakeFailedError: - changeState( - STATE_FAILED_SOFT, - tr("SSL Handshake failed.
There might be a few causes for it:
" - "
    " - "
  • You use Windows XP and need to update " - "your root certificates
  • " - "
  • Some device on your network is interfering with SSL traffic. In that case, " - "you have bigger worries than Minecraft not starting.
  • " - "
  • Possibly something else. Check the MultiMC log file for details
  • " - "
")); - return; - // used for invalid credentials and similar errors. Fall through. - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - break; - default: - changeState(STATE_FAILED_SOFT, - tr("Authentication operation failed due to a network error: %1 (%2)") - .arg(m_netReply->errorString()).arg(m_netReply->error())); - return; - } - - // Try to parse the response regardless of the response code. - // Sometimes the auth server will give more information and an error code. - QJsonParseError jsonError; - QByteArray replyData = m_netReply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); - // Check the response code. - int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) - { - // If the response code was 200, then there shouldn't be an error. Make sure - // anyways. - // Also, sometimes an empty reply indicates success. If there was no data received, - // pass an empty json object to the processResponse function. - if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) - { - processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); - return; - } - else - { - changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response " - "JSON response: %1 at offset %2.") - .arg(jsonError.errorString()) - .arg(jsonError.offset)); - qCritical() << replyData; - } - return; - } - - // If the response code was not 200, then Yggdrasil may have given us information - // about the error. - // If we can parse the response, then get information from it. Otherwise just say - // there was an unknown error. - if (jsonError.error == QJsonParseError::NoError) - { - // We were able to parse the server's response. Woo! - // Call processError. If a subclass has overridden it then they'll handle their - // stuff there. - qDebug() << "The request failed, but the server gave us an error message. " - "Processing error."; - processError(doc.object()); - } - else - { - // The server didn't say anything regarding the error. Give the user an unknown - // error. - qDebug() - << "The request failed and the server gave no error message. Unknown error."; - changeState(STATE_FAILED_SOFT, - tr("An unknown error occurred when trying to communicate with the " - "authentication server: %1").arg(m_netReply->errorString())); - } + changeState(STATE_PROCESSING_RESPONSE); + + switch (m_netReply->error()) + { + case QNetworkReply::NoError: + break; + case QNetworkReply::TimeoutError: + changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out.")); + return; + case QNetworkReply::OperationCanceledError: + changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); + return; + case QNetworkReply::SslHandshakeFailedError: + changeState( + STATE_FAILED_SOFT, + tr("SSL Handshake failed.
There might be a few causes for it:
" + "
    " + "
  • You use Windows XP and need to update " + "your root certificates
  • " + "
  • Some device on your network is interfering with SSL traffic. In that case, " + "you have bigger worries than Minecraft not starting.
  • " + "
  • Possibly something else. Check the MultiMC log file for details
  • " + "
")); + return; + // used for invalid credentials and similar errors. Fall through. + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + break; + default: + changeState(STATE_FAILED_SOFT, + tr("Authentication operation failed due to a network error: %1 (%2)") + .arg(m_netReply->errorString()).arg(m_netReply->error())); + return; + } + + // Try to parse the response regardless of the response code. + // Sometimes the auth server will give more information and an error code. + QJsonParseError jsonError; + QByteArray replyData = m_netReply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); + // Check the response code. + int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (responseCode == 200) + { + // If the response code was 200, then there shouldn't be an error. Make sure + // anyways. + // Also, sometimes an empty reply indicates success. If there was no data received, + // pass an empty json object to the processResponse function. + if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) + { + processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); + return; + } + else + { + changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response " + "JSON response: %1 at offset %2.") + .arg(jsonError.errorString()) + .arg(jsonError.offset)); + qCritical() << replyData; + } + return; + } + + // If the response code was not 200, then Yggdrasil may have given us information + // about the error. + // If we can parse the response, then get information from it. Otherwise just say + // there was an unknown error. + if (jsonError.error == QJsonParseError::NoError) + { + // We were able to parse the server's response. Woo! + // Call processError. If a subclass has overridden it then they'll handle their + // stuff there. + qDebug() << "The request failed, but the server gave us an error message. " + "Processing error."; + processError(doc.object()); + } + else + { + // The server didn't say anything regarding the error. Give the user an unknown + // error. + qDebug() + << "The request failed and the server gave no error message. Unknown error."; + changeState(STATE_FAILED_SOFT, + tr("An unknown error occurred when trying to communicate with the " + "authentication server: %1").arg(m_netReply->errorString())); + } } void YggdrasilTask::processError(QJsonObject responseData) { - QJsonValue errorVal = responseData.value("error"); - QJsonValue errorMessageValue = responseData.value("errorMessage"); - QJsonValue causeVal = responseData.value("cause"); - - if (errorVal.isString() && errorMessageValue.isString()) - { - m_error = std::shared_ptr(new Error{ - errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); - changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose); - } - else - { - // Error is not in standard format. Don't set m_error and return unknown error. - changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); - } + QJsonValue errorVal = responseData.value("error"); + QJsonValue errorMessageValue = responseData.value("errorMessage"); + QJsonValue causeVal = responseData.value("cause"); + + if (errorVal.isString() && errorMessageValue.isString()) + { + m_error = std::shared_ptr(new Error{ + errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); + changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose); + } + else + { + // Error is not in standard format. Don't set m_error and return unknown error. + changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); + } } QString YggdrasilTask::getStateMessage() const { - switch (m_state) - { - case STATE_CREATED: - return "Waiting..."; - case STATE_SENDING_REQUEST: - return tr("Sending request to auth servers..."); - case STATE_PROCESSING_RESPONSE: - return tr("Processing response from servers..."); - case STATE_SUCCEEDED: - return tr("Authentication task succeeded."); - case STATE_FAILED_SOFT: - return tr("Failed to contact the authentication server."); - case STATE_FAILED_HARD: - return tr("Failed to authenticate."); - default: - return tr("..."); - } + switch (m_state) + { + case STATE_CREATED: + return "Waiting..."; + case STATE_SENDING_REQUEST: + return tr("Sending request to auth servers..."); + case STATE_PROCESSING_RESPONSE: + return tr("Processing response from servers..."); + case STATE_SUCCEEDED: + return tr("Authentication task succeeded."); + case STATE_FAILED_SOFT: + return tr("Failed to contact the authentication server."); + case STATE_FAILED_HARD: + return tr("Failed to authenticate."); + default: + return tr("..."); + } } void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason) { - m_state = newState; - setStatus(getStateMessage()); - if (newState == STATE_SUCCEEDED) - { - emitSucceeded(); - } - else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT) - { - emitFailed(reason); - } + m_state = newState; + setStatus(getStateMessage()); + if (newState == STATE_SUCCEEDED) + { + emitSucceeded(); + } + else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT) + { + emitFailed(reason); + } } YggdrasilTask::State YggdrasilTask::state() { - return m_state; + return m_state; } diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h index b165d3e9..c5352386 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.h +++ b/api/logic/minecraft/auth/YggdrasilTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,120 +31,121 @@ class QNetworkReply; */ class YggdrasilTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); - - /** - * assign a session to this task. the session will be filled with required infomration - * upon completion - */ - void assignSession(AuthSessionPtr session) - { - m_session = session; - } - - /// get the assigned session for filling with information. - AuthSessionPtr getAssignedSession() - { - return m_session; - } - - /** - * Class describing a Yggdrasil error response. - */ - struct Error - { - QString m_errorMessageShort; - QString m_errorMessageVerbose; - QString m_cause; - }; - - enum AbortedBy - { - BY_NOTHING, - BY_USER, - BY_TIMEOUT - } m_aborted = BY_NOTHING; - - /** - * Enum for describing the state of the current task. - * Used by the getStateMessage function to determine what the status message should be. - */ - enum State - { - STATE_CREATED, - STATE_SENDING_REQUEST, - STATE_PROCESSING_RESPONSE, - STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated - STATE_FAILED_HARD, //!< hard failure. auth is invalid - STATE_SUCCEEDED - } m_state = STATE_CREATED; + explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); + virtual ~YggdrasilTask() {}; + + /** + * assign a session to this task. the session will be filled with required infomration + * upon completion + */ + void assignSession(AuthSessionPtr session) + { + m_session = session; + } + + /// get the assigned session for filling with information. + AuthSessionPtr getAssignedSession() + { + return m_session; + } + + /** + * Class describing a Yggdrasil error response. + */ + struct Error + { + QString m_errorMessageShort; + QString m_errorMessageVerbose; + QString m_cause; + }; + + enum AbortedBy + { + BY_NOTHING, + BY_USER, + BY_TIMEOUT + } m_aborted = BY_NOTHING; + + /** + * Enum for describing the state of the current task. + * Used by the getStateMessage function to determine what the status message should be. + */ + enum State + { + STATE_CREATED, + STATE_SENDING_REQUEST, + STATE_PROCESSING_RESPONSE, + STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated + STATE_FAILED_HARD, //!< hard failure. auth is invalid + STATE_SUCCEEDED + } m_state = STATE_CREATED; protected: - virtual void executeTask() override; - - /** - * Gets the JSON object that will be sent to the authentication server. - * Should be overridden by subclasses. - */ - virtual QJsonObject getRequestContent() const = 0; - - /** - * Gets the endpoint to POST to. - * No leading slash. - */ - virtual QString getEndpoint() const = 0; - - /** - * Processes the response received from the server. - * If an error occurred, this should emit a failed signal and return false. - * If Yggdrasil gave an error response, it should call setError() first, and then return false. - * Otherwise, it should return true. - * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with - * an empty QJsonObject. - */ - virtual void processResponse(QJsonObject responseData) = 0; - - /** - * Processes an error response received from the server. - * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. - * \returns a QString error message that will be passed to emitFailed. - */ - virtual void processError(QJsonObject responseData); - - /** - * Returns the state message for the given state. - * Used to set the status message for the task. - * Should be overridden by subclasses that want to change messages for a given state. - */ - virtual QString getStateMessage() const; + virtual void executeTask() override; + + /** + * Gets the JSON object that will be sent to the authentication server. + * Should be overridden by subclasses. + */ + virtual QJsonObject getRequestContent() const = 0; + + /** + * Gets the endpoint to POST to. + * No leading slash. + */ + virtual QString getEndpoint() const = 0; + + /** + * Processes the response received from the server. + * If an error occurred, this should emit a failed signal and return false. + * If Yggdrasil gave an error response, it should call setError() first, and then return false. + * Otherwise, it should return true. + * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with + * an empty QJsonObject. + */ + virtual void processResponse(QJsonObject responseData) = 0; + + /** + * Processes an error response received from the server. + * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. + * \returns a QString error message that will be passed to emitFailed. + */ + virtual void processError(QJsonObject responseData); + + /** + * Returns the state message for the given state. + * Used to set the status message for the task. + * Should be overridden by subclasses that want to change messages for a given state. + */ + virtual QString getStateMessage() const; protected slots: - void processReply(); - void refreshTimers(qint64, qint64); - void heartbeat(); - void sslErrors(QList); + void processReply(); + void refreshTimers(qint64, qint64); + void heartbeat(); + void sslErrors(QList); - void changeState(State newState, QString reason=QString()); + void changeState(State newState, QString reason=QString()); public slots: - virtual bool abort() override; - void abortByTimeout(); - State state(); + virtual bool abort() override; + void abortByTimeout(); + State state(); protected: - // FIXME: segfault disaster waiting to happen - MojangAccount *m_account = nullptr; - QNetworkReply *m_netReply = nullptr; - std::shared_ptr m_error; - QTimer timeout_keeper; - QTimer counter; - int count = 0; // num msec since time reset - - const int timeout_max = 30000; - const int time_step = 50; - - AuthSessionPtr m_session; + // FIXME: segfault disaster waiting to happen + MojangAccount *m_account = nullptr; + QNetworkReply *m_netReply = nullptr; + std::shared_ptr m_error; + QTimer timeout_keeper; + QTimer counter; + int count = 0; // num msec since time reset + + const int timeout_max = 30000; + const int time_step = 50; + + AuthSessionPtr m_session; }; diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp index ff4e7d47..8286c7a4 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,177 +26,177 @@ #include AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password, - QObject *parent) - : YggdrasilTask(account, parent), m_password(password) + QObject *parent) + : YggdrasilTask(account, parent), m_password(password) { } QJsonObject AuthenticateTask::getRequestContent() const { - /* - * { - * "agent": { // optional - * "name": "Minecraft", // So far this is the only encountered value - * "version": 1 // This number might be increased - * // by the vanilla client in the future - * }, - * "username": "mojang account name", // Can be an email address or player name for - // unmigrated accounts - * "password": "mojang account password", - * "clientToken": "client identifier" // optional - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - - { - QJsonObject agent; - // C++ makes string literals void* for some stupid reason, so we have to tell it - // QString... Thanks Obama. - agent.insert("name", QString("Minecraft")); - agent.insert("version", 1); - req.insert("agent", agent); - } - - req.insert("username", m_account->username()); - req.insert("password", m_password); - req.insert("requestUser", true); - - // If we already have a client token, give it to the server. - // Otherwise, let the server give us one. - - if(m_account->m_clientToken.isEmpty()) - { - auto uuid = QUuid::createUuid(); - auto uuidString = uuid.toString().remove('{').remove('-').remove('}'); - m_account->m_clientToken = uuidString; - } - req.insert("clientToken", m_account->m_clientToken); - - return req; + /* + * { + * "agent": { // optional + * "name": "Minecraft", // So far this is the only encountered value + * "version": 1 // This number might be increased + * // by the vanilla client in the future + * }, + * "username": "mojang account name", // Can be an email address or player name for + // unmigrated accounts + * "password": "mojang account password", + * "clientToken": "client identifier" // optional + * "requestUser": true/false // request the user structure + * } + */ + QJsonObject req; + + { + QJsonObject agent; + // C++ makes string literals void* for some stupid reason, so we have to tell it + // QString... Thanks Obama. + agent.insert("name", QString("Minecraft")); + agent.insert("version", 1); + req.insert("agent", agent); + } + + req.insert("username", m_account->username()); + req.insert("password", m_password); + req.insert("requestUser", true); + + // If we already have a client token, give it to the server. + // Otherwise, let the server give us one. + + if(m_account->m_clientToken.isEmpty()) + { + auto uuid = QUuid::createUuid(); + auto uuidString = uuid.toString().remove('{').remove('-').remove('}'); + m_account->m_clientToken = uuidString; + } + req.insert("clientToken", m_account->m_clientToken); + + return req; } void AuthenticateTask::processResponse(QJsonObject responseData) { - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - qDebug() << "Getting client token."; - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - // Set the client token. - m_account->m_clientToken = clientToken; - - // Now, we set the access token. - qDebug() << "Getting access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - // Set the access token. - m_account->m_accessToken = accessToken; - - // Now we load the list of available profiles. - // Mojang hasn't yet implemented the profile system, - // but we might as well support what's there so we - // don't have trouble implementing it later. - qDebug() << "Loading profile list."; - QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); - QList loadedProfiles; - for (auto iter : availableProfiles) - { - QJsonObject profile = iter.toObject(); - // Profiles are easy, we just need their ID and name. - QString id = profile.value("id").toString(""); - QString name = profile.value("name").toString(""); - bool legacy = profile.value("legacy").toBool(false); - - if (id.isEmpty() || name.isEmpty()) - { - // This should never happen, but we might as well - // warn about it if it does so we can debug it easily. - // You never know when Mojang might do something truly derpy. - qWarning() << "Found entry in available profiles list with missing ID or name " - "field. Ignoring it."; - } - - // Now, add a new AccountProfile entry to the list. - loadedProfiles.append({id, name, legacy}); - } - // Put the list of profiles we loaded into the MojangAccount object. - m_account->m_profiles = loadedProfiles; - - // Finally, we set the current profile to the correct value. This is pretty simple. - // We do need to make sure that the current profile that the server gave us - // is actually in the available profiles list. - // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know). - qDebug() << "Setting current profile."; - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (currentProfileId.isEmpty()) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium.")); - return; - } - if (!m_account->setCurrentProfile(currentProfileId)) - { - changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list.")); - return; - } - - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } - - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading authentication response."; - changeState(STATE_SUCCEEDED); + // Read the response data. We need to get the client token, access token, and the selected + // profile. + qDebug() << "Processing authentication response."; + // qDebug() << responseData; + // If we already have a client token, make sure the one the server gave us matches our + // existing one. + qDebug() << "Getting client token."; + QString clientToken = responseData.value("clientToken").toString(""); + if (clientToken.isEmpty()) + { + // Fail if the server gave us an empty client token + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); + return; + } + if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) + { + changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); + return; + } + // Set the client token. + m_account->m_clientToken = clientToken; + + // Now, we set the access token. + qDebug() << "Getting access token."; + QString accessToken = responseData.value("accessToken").toString(""); + if (accessToken.isEmpty()) + { + // Fail if the server didn't give us an access token. + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); + return; + } + // Set the access token. + m_account->m_accessToken = accessToken; + + // Now we load the list of available profiles. + // Mojang hasn't yet implemented the profile system, + // but we might as well support what's there so we + // don't have trouble implementing it later. + qDebug() << "Loading profile list."; + QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); + QList loadedProfiles; + for (auto iter : availableProfiles) + { + QJsonObject profile = iter.toObject(); + // Profiles are easy, we just need their ID and name. + QString id = profile.value("id").toString(""); + QString name = profile.value("name").toString(""); + bool legacy = profile.value("legacy").toBool(false); + + if (id.isEmpty() || name.isEmpty()) + { + // This should never happen, but we might as well + // warn about it if it does so we can debug it easily. + // You never know when Mojang might do something truly derpy. + qWarning() << "Found entry in available profiles list with missing ID or name " + "field. Ignoring it."; + } + + // Now, add a new AccountProfile entry to the list. + loadedProfiles.append({id, name, legacy}); + } + // Put the list of profiles we loaded into the MojangAccount object. + m_account->m_profiles = loadedProfiles; + + // Finally, we set the current profile to the correct value. This is pretty simple. + // We do need to make sure that the current profile that the server gave us + // is actually in the available profiles list. + // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know). + qDebug() << "Setting current profile."; + QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); + QString currentProfileId = currentProfile.value("id").toString(""); + if (currentProfileId.isEmpty()) + { + changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium.")); + return; + } + if (!m_account->setCurrentProfile(currentProfileId)) + { + changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list.")); + return; + } + + // this is what the vanilla launcher passes to the userProperties launch param + if (responseData.contains("user")) + { + User u; + auto obj = responseData.value("user").toObject(); + u.id = obj.value("id").toString(); + auto propArray = obj.value("properties").toArray(); + for (auto prop : propArray) + { + auto propTuple = prop.toObject(); + auto name = propTuple.value("name").toString(); + auto value = propTuple.value("value").toString(); + u.properties.insert(name, value); + } + m_account->m_user = u; + } + + // We've made it through the minefield of possible errors. Return true to indicate that + // we've succeeded. + qDebug() << "Finished reading authentication response."; + changeState(STATE_SUCCEEDED); } QString AuthenticateTask::getEndpoint() const { - return "authenticate"; + return "authenticate"; } QString AuthenticateTask::getStateMessage() const { - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Authenticating: Sending request..."); - case STATE_PROCESSING_RESPONSE: - return tr("Authenticating: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } + switch (m_state) + { + case STATE_SENDING_REQUEST: + return tr("Authenticating: Sending request..."); + case STATE_PROCESSING_RESPONSE: + return tr("Authenticating: Processing response..."); + default: + return YggdrasilTask::getStateMessage(); + } } diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h index ab7a6e64..e95837a9 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.h +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,19 +28,19 @@ */ class AuthenticateTask : public YggdrasilTask { - Q_OBJECT + Q_OBJECT public: - AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); + AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); protected: - virtual QJsonObject getRequestContent() const override; + virtual QJsonObject getRequestContent() const override; - virtual QString getEndpoint() const override; + virtual QString getEndpoint() const override; - virtual void processResponse(QJsonObject responseData) override; + virtual void processResponse(QJsonObject responseData) override; - virtual QString getStateMessage() const override; + virtual QString getStateMessage() const override; private: - QString m_password; + QString m_password; }; diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp index 73050907..7a789f42 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.cpp +++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,116 +29,116 @@ RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account) QJsonObject RefreshTask::getRequestContent() const { - /* - * { - * "clientToken": "client identifier" - * "accessToken": "current access token to be refreshed" - * "selectedProfile": // specifying this causes errors - * { - * "id": "profile ID" - * "name": "profile name" - * } - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - req.insert("clientToken", m_account->m_clientToken); - req.insert("accessToken", m_account->m_accessToken); - /* - { - auto currentProfile = m_account->currentProfile(); - QJsonObject profile; - profile.insert("id", currentProfile->id()); - profile.insert("name", currentProfile->name()); - req.insert("selectedProfile", profile); - } - */ - req.insert("requestUser", true); + /* + * { + * "clientToken": "client identifier" + * "accessToken": "current access token to be refreshed" + * "selectedProfile": // specifying this causes errors + * { + * "id": "profile ID" + * "name": "profile name" + * } + * "requestUser": true/false // request the user structure + * } + */ + QJsonObject req; + req.insert("clientToken", m_account->m_clientToken); + req.insert("accessToken", m_account->m_accessToken); + /* + { + auto currentProfile = m_account->currentProfile(); + QJsonObject profile; + profile.insert("id", currentProfile->id()); + profile.insert("name", currentProfile->name()); + req.insert("selectedProfile", profile); + } + */ + req.insert("requestUser", true); - return req; + return req; } void RefreshTask::processResponse(QJsonObject responseData) { - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; + // Read the response data. We need to get the client token, access token, and the selected + // profile. + qDebug() << "Processing authentication response."; - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } + // qDebug() << responseData; + // If we already have a client token, make sure the one the server gave us matches our + // existing one. + QString clientToken = responseData.value("clientToken").toString(""); + if (clientToken.isEmpty()) + { + // Fail if the server gave us an empty client token + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); + return; + } + if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) + { + changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); + return; + } - // Now, we set the access token. - qDebug() << "Getting new access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } + // Now, we set the access token. + qDebug() << "Getting new access token."; + QString accessToken = responseData.value("accessToken").toString(""); + if (accessToken.isEmpty()) + { + // Fail if the server didn't give us an access token. + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); + return; + } - // we validate that the server responded right. (our current profile = returned current - // profile) - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (m_account->currentProfile()->id != currentProfileId) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected.")); - return; - } + // we validate that the server responded right. (our current profile = returned current + // profile) + QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); + QString currentProfileId = currentProfile.value("id").toString(""); + if (m_account->currentProfile()->id != currentProfileId) + { + changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected.")); + return; + } - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } + // this is what the vanilla launcher passes to the userProperties launch param + if (responseData.contains("user")) + { + User u; + auto obj = responseData.value("user").toObject(); + u.id = obj.value("id").toString(); + auto propArray = obj.value("properties").toArray(); + for (auto prop : propArray) + { + auto propTuple = prop.toObject(); + auto name = propTuple.value("name").toString(); + auto value = propTuple.value("value").toString(); + u.properties.insert(name, value); + } + m_account->m_user = u; + } - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading refresh response."; - // Reset the access token. - m_account->m_accessToken = accessToken; - changeState(STATE_SUCCEEDED); + // We've made it through the minefield of possible errors. Return true to indicate that + // we've succeeded. + qDebug() << "Finished reading refresh response."; + // Reset the access token. + m_account->m_accessToken = accessToken; + changeState(STATE_SUCCEEDED); } QString RefreshTask::getEndpoint() const { - return "refresh"; + return "refresh"; } QString RefreshTask::getStateMessage() const { - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Refreshing login token..."); - case STATE_PROCESSING_RESPONSE: - return tr("Refreshing login token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } + switch (m_state) + { + case STATE_SENDING_REQUEST: + return tr("Refreshing login token..."); + case STATE_PROCESSING_RESPONSE: + return tr("Refreshing login token: Processing response..."); + default: + return YggdrasilTask::getStateMessage(); + } } diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h index a97b54e6..b2dfd261 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.h +++ b/api/logic/minecraft/auth/flows/RefreshTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,17 +28,17 @@ */ class RefreshTask : public YggdrasilTask { - Q_OBJECT + Q_OBJECT public: - RefreshTask(MojangAccount * account); + RefreshTask(MojangAccount * account); protected: - virtual QJsonObject getRequestContent() const override; + virtual QJsonObject getRequestContent() const override; - virtual QString getEndpoint() const override; + virtual QString getEndpoint() const override; - virtual void processResponse(QJsonObject responseData) override; + virtual void processResponse(QJsonObject responseData) override; - virtual QString getStateMessage() const override; + virtual QString getStateMessage() const override; }; diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp index b6d6c823..01f0f819 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.cpp +++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,37 +25,37 @@ #include ValidateTask::ValidateTask(MojangAccount * account, QObject *parent) - : YggdrasilTask(account, parent) + : YggdrasilTask(account, parent) { } QJsonObject ValidateTask::getRequestContent() const { - QJsonObject req; - req.insert("accessToken", m_account->m_accessToken); - return req; + QJsonObject req; + req.insert("accessToken", m_account->m_accessToken); + return req; } void ValidateTask::processResponse(QJsonObject responseData) { - // Assume that if processError wasn't called, then the request was successful. - changeState(YggdrasilTask::STATE_SUCCEEDED); + // Assume that if processError wasn't called, then the request was successful. + changeState(YggdrasilTask::STATE_SUCCEEDED); } QString ValidateTask::getEndpoint() const { - return "validate"; + return "validate"; } QString ValidateTask::getStateMessage() const { - switch (m_state) - { - case YggdrasilTask::STATE_SENDING_REQUEST: - return tr("Validating access token: Sending request..."); - case YggdrasilTask::STATE_PROCESSING_RESPONSE: - return tr("Validating access token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } + switch (m_state) + { + case YggdrasilTask::STATE_SENDING_REQUEST: + return tr("Validating access token: Sending request..."); + case YggdrasilTask::STATE_PROCESSING_RESPONSE: + return tr("Validating access token: Processing response..."); + default: + return YggdrasilTask::getStateMessage(); + } } diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h index 77de24c7..dcb76b2b 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.h +++ b/api/logic/minecraft/auth/flows/ValidateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,18 +30,18 @@ */ class ValidateTask : public YggdrasilTask { - Q_OBJECT + Q_OBJECT public: - ValidateTask(MojangAccount *account, QObject *parent = 0); + ValidateTask(MojangAccount *account, QObject *parent = 0); protected: - virtual QJsonObject getRequestContent() const override; + virtual QJsonObject getRequestContent() const override; - virtual QString getEndpoint() const override; + virtual QString getEndpoint() const override; - virtual void processResponse(QJsonObject responseData) override; + virtual void processResponse(QJsonObject responseData) override; - virtual QString getStateMessage() const override; + virtual QString getStateMessage() const override; private: }; diff --git a/api/logic/minecraft/forge/ForgeXzDownload.cpp b/api/logic/minecraft/forge/ForgeXzDownload.cpp index a05d0f8d..c6465469 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.cpp +++ b/api/logic/minecraft/forge/ForgeXzDownload.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,140 +23,140 @@ #include #include -ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction() +ForgeXzDownload::ForgeXzDownload(QString url, QString relative_path, MetaEntryPtr entry) : NetAction() { - m_entry = entry; - m_target_path = entry->getFullPath(); - m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX"); - m_status = Job_NotStarted; - m_url_path = relative_path; - m_url = "http://files.minecraftforge.net/maven/" + m_url_path + ".pack.xz"; + m_entry = entry; + m_target_path = entry->getFullPath(); + m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX"); + m_status = Job_NotStarted; + m_url_path = relative_path; + m_url = url + ".pack.xz"; } void ForgeXzDownload::start() { - if(m_status == Job_Aborted) - { - qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); - emit aborted(m_index_within_job); - return; - } - m_status = Job_InProgress; - if (!m_entry->isStale()) - { - m_status = Job_Finished; - emit succeeded(m_index_within_job); - return; - } - // can we actually create the real, final file? - if (!FS::ensureFilePathExists(m_target_path)) - { - m_status = Job_Failed; - emit failed(m_index_within_job); - return; - } - - qDebug() << "Downloading " << m_url.toString(); - QNetworkRequest request(m_url); - request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); - - QNetworkReply *rep = ENV.qnam().get(request); - - m_reply.reset(rep); - connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); - connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); + if(m_status == Job_Aborted) + { + qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + emit aborted(m_index_within_job); + return; + } + m_status = Job_InProgress; + if (!m_entry->isStale()) + { + m_status = Job_Finished; + emit succeeded(m_index_within_job); + return; + } + // can we actually create the real, final file? + if (!FS::ensureFilePathExists(m_target_path)) + { + m_status = Job_Failed; + emit failed(m_index_within_job); + return; + } + + qDebug() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); + + QNetworkReply *rep = ENV.qnam().get(request); + + m_reply.reset(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); } void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error) { - if(error == QNetworkReply::OperationCanceledError) - { - qCritical() << "Aborted " << m_url.toString(); - m_status = Job_Aborted; - } - else - { - // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; - m_status = Job_Failed; - } + if(error == QNetworkReply::OperationCanceledError) + { + qCritical() << "Aborted " << m_url.toString(); + m_status = Job_Aborted; + } + else + { + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_status = Job_Failed; + } } void ForgeXzDownload::failAndTryNextMirror() { - m_status = Job_Failed; - emit failed(m_index_within_job); + m_status = Job_Failed; + emit failed(m_index_within_job); } void ForgeXzDownload::downloadFinished() { - // if the download succeeded - if (m_status != Job_Failed && m_status != Job_Aborted) - { - // nothing went wrong... - m_status = Job_Finished; - if (m_pack200_xz_file.isOpen()) - { - // we actually downloaded something! process and isntall it - decompressAndInstall(); - return; - } - else - { - // something bad happened -- on the local machine! - m_status = Job_Failed; - m_pack200_xz_file.remove(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - } - else if(m_status == Job_Aborted) - { - m_pack200_xz_file.remove(); - m_reply.reset(); - emit failed(m_index_within_job); - emit aborted(m_index_within_job); - return; - } - // else the download failed - else - { - m_status = Job_Failed; - m_pack200_xz_file.close(); - m_pack200_xz_file.remove(); - m_reply.reset(); - failAndTryNextMirror(); - return; - } + // if the download succeeded + if (m_status != Job_Failed && m_status != Job_Aborted) + { + // nothing went wrong... + m_status = Job_Finished; + if (m_pack200_xz_file.isOpen()) + { + // we actually downloaded something! process and isntall it + decompressAndInstall(); + return; + } + else + { + // something bad happened -- on the local machine! + m_status = Job_Failed; + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + } + else if(m_status == Job_Aborted) + { + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(m_index_within_job); + emit aborted(m_index_within_job); + return; + } + // else the download failed + else + { + m_status = Job_Failed; + m_pack200_xz_file.close(); + m_pack200_xz_file.remove(); + m_reply.reset(); + failAndTryNextMirror(); + return; + } } void ForgeXzDownload::downloadReadyRead() { - if (!m_pack200_xz_file.isOpen()) - { - if (!m_pack200_xz_file.open()) - { - /* - * Can't open the file... the job failed - */ - m_reply->abort(); - emit failed(m_index_within_job); - return; - } - } - m_pack200_xz_file.write(m_reply->readAll()); + if (!m_pack200_xz_file.isOpen()) + { + if (!m_pack200_xz_file.open()) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(m_index_within_job); + return; + } + } + m_pack200_xz_file.write(m_reply->readAll()); } #include "xz.h" @@ -169,225 +169,225 @@ const size_t buffer_size = 8196; // NOTE: once this gets here, it can't be aborted anymore. we don't care. void ForgeXzDownload::decompressAndInstall() { - // rewind the downloaded temp file - m_pack200_xz_file.seek(0); - // de-xz'd file - QTemporaryFile pack200_file("./dl_temp.XXXXXX"); - pack200_file.open(); - - bool xz_success = false; - // first, de-xz - { - uint8_t in[buffer_size]; - uint8_t out[buffer_size]; - struct xz_buf b; - struct xz_dec *s; - enum xz_ret ret; - xz_crc32_init(); - xz_crc64_init(); - s = xz_dec_init(XZ_DYNALLOC, 1 << 26); - if (s == nullptr) - { - xz_dec_end(s); - failAndTryNextMirror(); - return; - } - b.in = in; - b.in_pos = 0; - b.in_size = 0; - b.out = out; - b.out_pos = 0; - b.out_size = buffer_size; - while (!xz_success) - { - if (b.in_pos == b.in_size) - { - b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in)); - b.in_pos = 0; - } - - ret = xz_dec_run(s, &b); - - if (b.out_pos == sizeof(out)) - { - auto wresult = pack200_file.write((char *)out, b.out_pos); - if (wresult < 0 || size_t(wresult) != b.out_pos) - { - // msg = "Write error\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - } - - b.out_pos = 0; - } - - if (ret == XZ_OK) - continue; - - if (ret == XZ_UNSUPPORTED_CHECK) - { - // unsupported check. this is OK, but we should log this - continue; - } - - auto wresult = pack200_file.write((char *)out, b.out_pos); - if (wresult < 0 || size_t(wresult) != b.out_pos) - { - // write error - pack200_file.close(); - xz_dec_end(s); - return; - } - - switch (ret) - { - case XZ_STREAM_END: - xz_dec_end(s); - xz_success = true; - break; - - case XZ_MEM_ERROR: - qCritical() << "Memory allocation failed\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_MEMLIMIT_ERROR: - qCritical() << "Memory usage limit reached\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_FORMAT_ERROR: - qCritical() << "Not a .xz file\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_OPTIONS_ERROR: - qCritical() << "Unsupported options in the .xz headers\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_DATA_ERROR: - case XZ_BUF_ERROR: - qCritical() << "File is corrupt\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - default: - qCritical() << "Bug!\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - } - } - } - m_pack200_xz_file.remove(); - - // revert pack200 - pack200_file.seek(0); - int handle_in = pack200_file.handle(); - // FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects. - if(handle_in == -1) - { - qCritical() << "Error reopening " << pack200_file.fileName(); - failAndTryNextMirror(); - return; - } - int handle_in_dup = dup (handle_in); - if(handle_in_dup == -1) - { - qCritical() << "Error reopening " << pack200_file.fileName(); - failAndTryNextMirror(); - return; - } - FILE *file_in = fdopen (handle_in_dup, "rb"); - if(!file_in) - { - qCritical() << "Error reopening " << pack200_file.fileName(); - failAndTryNextMirror(); - return; - } - QFile qfile_out(m_target_path); - if(!qfile_out.open(QIODevice::WriteOnly)) - { - qCritical() << "Error opening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - int handle_out = qfile_out.handle(); - if(handle_out == -1) - { - qCritical() << "Error opening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - int handle_out_dup = dup (handle_out); - if(handle_out_dup == -1) - { - qCritical() << "Error reopening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - FILE *file_out = fdopen (handle_out_dup, "wb"); - if(!file_out) - { - qCritical() << "Error opening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - try - { - // NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above. - unpack_200(file_in, file_out); - } - catch (std::runtime_error &err) - { - m_status = Job_Failed; - qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what(); - QFile f(m_target_path); - if (f.exists()) - f.remove(); - failAndTryNextMirror(); - return; - } - pack200_file.remove(); - - QFile jar_file(m_target_path); - - if (!jar_file.open(QIODevice::ReadOnly)) - { - jar_file.remove(); - failAndTryNextMirror(); - return; - } - auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5); - m_entry->setMD5Sum(hash.toHex().constData()); - jar_file.close(); - - QFileInfo output_file_info(m_target_path); - m_entry->setETag(m_reply->rawHeader("ETag").constData()); - m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); - m_entry->setStale(false); - ENV.metacache()->updateEntry(m_entry); - - m_reply.reset(); - emit succeeded(m_index_within_job); + // rewind the downloaded temp file + m_pack200_xz_file.seek(0); + // de-xz'd file + QTemporaryFile pack200_file("./dl_temp.XXXXXX"); + pack200_file.open(); + + bool xz_success = false; + // first, de-xz + { + uint8_t in[buffer_size]; + uint8_t out[buffer_size]; + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + xz_crc32_init(); + xz_crc64_init(); + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == nullptr) + { + xz_dec_end(s); + failAndTryNextMirror(); + return; + } + b.in = in; + b.in_pos = 0; + b.in_size = 0; + b.out = out; + b.out_pos = 0; + b.out_size = buffer_size; + while (!xz_success) + { + if (b.in_pos == b.in_size) + { + b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in)); + b.in_pos = 0; + } + + ret = xz_dec_run(s, &b); + + if (b.out_pos == sizeof(out)) + { + auto wresult = pack200_file.write((char *)out, b.out_pos); + if (wresult < 0 || size_t(wresult) != b.out_pos) + { + // msg = "Write error\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + } + + b.out_pos = 0; + } + + if (ret == XZ_OK) + continue; + + if (ret == XZ_UNSUPPORTED_CHECK) + { + // unsupported check. this is OK, but we should log this + continue; + } + + auto wresult = pack200_file.write((char *)out, b.out_pos); + if (wresult < 0 || size_t(wresult) != b.out_pos) + { + // write error + pack200_file.close(); + xz_dec_end(s); + return; + } + + switch (ret) + { + case XZ_STREAM_END: + xz_dec_end(s); + xz_success = true; + break; + + case XZ_MEM_ERROR: + qCritical() << "Memory allocation failed\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_MEMLIMIT_ERROR: + qCritical() << "Memory usage limit reached\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_FORMAT_ERROR: + qCritical() << "Not a .xz file\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_OPTIONS_ERROR: + qCritical() << "Unsupported options in the .xz headers\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + qCritical() << "File is corrupt\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + default: + qCritical() << "Bug!\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + } + } + } + m_pack200_xz_file.remove(); + + // revert pack200 + pack200_file.seek(0); + int handle_in = pack200_file.handle(); + // FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects. + if(handle_in == -1) + { + qCritical() << "Error reopening " << pack200_file.fileName(); + failAndTryNextMirror(); + return; + } + int handle_in_dup = dup (handle_in); + if(handle_in_dup == -1) + { + qCritical() << "Error reopening " << pack200_file.fileName(); + failAndTryNextMirror(); + return; + } + FILE *file_in = fdopen (handle_in_dup, "rb"); + if(!file_in) + { + qCritical() << "Error reopening " << pack200_file.fileName(); + failAndTryNextMirror(); + return; + } + QFile qfile_out(m_target_path); + if(!qfile_out.open(QIODevice::WriteOnly)) + { + qCritical() << "Error opening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + int handle_out = qfile_out.handle(); + if(handle_out == -1) + { + qCritical() << "Error opening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + int handle_out_dup = dup (handle_out); + if(handle_out_dup == -1) + { + qCritical() << "Error reopening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + FILE *file_out = fdopen (handle_out_dup, "wb"); + if(!file_out) + { + qCritical() << "Error opening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + try + { + // NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above. + unpack_200(file_in, file_out); + } + catch (const std::runtime_error &err) + { + m_status = Job_Failed; + qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what(); + QFile f(m_target_path); + if (f.exists()) + f.remove(); + failAndTryNextMirror(); + return; + } + pack200_file.remove(); + + QFile jar_file(m_target_path); + + if (!jar_file.open(QIODevice::ReadOnly)) + { + jar_file.remove(); + failAndTryNextMirror(); + return; + } + auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5); + m_entry->setMD5Sum(hash.toHex().constData()); + jar_file.close(); + + QFileInfo output_file_info(m_target_path); + m_entry->setETag(m_reply->rawHeader("ETag").constData()); + m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); + m_entry->setStale(false); + ENV.metacache()->updateEntry(m_entry); + + m_reply.reset(); + emit succeeded(m_index_within_job); } bool ForgeXzDownload::abort() { - if(m_reply) - m_reply->abort(); - m_status = Job_Aborted; - return true; + if(m_reply) + m_reply->abort(); + m_status = Job_Aborted; + return true; } bool ForgeXzDownload::canAbort() { - return true; + return true; } diff --git a/api/logic/minecraft/forge/ForgeXzDownload.h b/api/logic/minecraft/forge/ForgeXzDownload.h index cee402ef..63e75f71 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.h +++ b/api/logic/minecraft/forge/ForgeXzDownload.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,38 +24,38 @@ typedef std::shared_ptr ForgeXzDownloadPtr; class ForgeXzDownload : public NetAction { - Q_OBJECT + Q_OBJECT public: - MetaEntryPtr m_entry; - /// if saving to file, use the one specified in this string - QString m_target_path; - /// this is the output file, if any - QTemporaryFile m_pack200_xz_file; - /// path relative to the mirror base - QString m_url_path; + MetaEntryPtr m_entry; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QTemporaryFile m_pack200_xz_file; + /// path relative to the mirror base + QString m_url_path; public: - explicit ForgeXzDownload(QString relative_path, MetaEntryPtr entry); - static ForgeXzDownloadPtr make(QString relative_path, MetaEntryPtr entry) - { - return ForgeXzDownloadPtr(new ForgeXzDownload(relative_path, entry)); - } - virtual ~ForgeXzDownload(){}; - bool canAbort() override; + explicit ForgeXzDownload(QString url, QString relative_path, MetaEntryPtr entry); + static ForgeXzDownloadPtr make(QString url, QString relative_path, MetaEntryPtr entry) + { + return ForgeXzDownloadPtr(new ForgeXzDownload(url, relative_path, entry)); + } + virtual ~ForgeXzDownload(){}; + bool canAbort() override; protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; - void downloadFinished() override; - void downloadReadyRead() override; + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void downloadFinished() override; + void downloadReadyRead() override; public slots: - void start() override; - bool abort() override; + void start() override; + bool abort() override; private: - void decompressAndInstall(); - void failAndTryNextMirror(); + void decompressAndInstall(); + void failAndTryNextMirror(); }; diff --git a/api/logic/minecraft/gameoptions/GameOptions.cpp b/api/logic/minecraft/gameoptions/GameOptions.cpp new file mode 100644 index 00000000..e547b32a --- /dev/null +++ b/api/logic/minecraft/gameoptions/GameOptions.cpp @@ -0,0 +1,144 @@ +#include "GameOptions.h" +#include "FileSystem.h" +#include +#include + +namespace { +bool load(const QString& path, std::vector &contents, int & version) +{ + contents.clear(); + QFile file(path); + if (!file.open(QFile::ReadOnly)) + { + qWarning() << "Failed to read options file."; + return false; + } + version = 0; + while(!file.atEnd()) + { + auto line = file.readLine(); + if(line.endsWith('\n')) + { + line.chop(1); + } + auto separatorIndex = line.indexOf(':'); + if(separatorIndex == -1) + { + continue; + } + auto key = QString::fromUtf8(line.data(), separatorIndex); + auto value = QString::fromUtf8(line.data() + separatorIndex + 1, line.size() - 1 - separatorIndex); + qDebug() << "!!" << key << "!!"; + if(key == "version") + { + version = value.toInt(); + continue; + } + contents.emplace_back(GameOptionItem{key, value}); + } + qDebug() << "Loaded" << path << "with version:" << version; + return true; +} +bool save(const QString& path, std::vector &mapping, int version) +{ + QSaveFile out(path); + if(!out.open(QIODevice::WriteOnly)) + { + return false; + } + if(version != 0) + { + QString versionLine = QString("version:%1\n").arg(version); + out.write(versionLine.toUtf8()); + } + auto iter = mapping.begin(); + while (iter != mapping.end()) + { + out.write(iter->key.toUtf8()); + out.write(":"); + out.write(iter->value.toUtf8()); + out.write("\n"); + iter++; + } + return out.commit(); +} +} + +GameOptions::GameOptions(const QString& path): + path(path) +{ + reload(); +} + +QVariant GameOptions::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(role != Qt::DisplayRole) + { + return QAbstractListModel::headerData(section, orientation, role); + } + switch(section) + { + case 0: + return tr("Key"); + case 1: + return tr("Value"); + default: + return QVariant(); + } +} + +QVariant GameOptions::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= int(contents.size())) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + if(column == 0) + { + return contents[row].key; + } + else + { + return contents[row].value; + } + default: + return QVariant(); + } + return QVariant(); +} + +int GameOptions::rowCount(const QModelIndex&) const +{ + return contents.size(); +} + +int GameOptions::columnCount(const QModelIndex&) const +{ + return 2; +} + +bool GameOptions::isLoaded() const +{ + return loaded; +} + +bool GameOptions::reload() +{ + beginResetModel(); + loaded = load(path, contents, version); + endResetModel(); + return loaded; +} + +bool GameOptions::save() +{ + return ::save(path, contents, version); +} diff --git a/api/logic/minecraft/gameoptions/GameOptions.h b/api/logic/minecraft/gameoptions/GameOptions.h new file mode 100644 index 00000000..c6d25492 --- /dev/null +++ b/api/logic/minecraft/gameoptions/GameOptions.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +struct GameOptionItem +{ + QString key; + QString value; +}; + +class GameOptions : public QAbstractListModel +{ + Q_OBJECT +public: + explicit GameOptions(const QString& path); + virtual ~GameOptions() = default; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & parent) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + bool isLoaded() const; + bool reload(); + bool save(); + +private: + std::vector contents; + bool loaded = false; + QString path; + int version = 0; +}; diff --git a/api/logic/minecraft/launch/ClaimAccount.cpp b/api/logic/minecraft/launch/ClaimAccount.cpp index 71670b4f..a1180f0a 100644 --- a/api/logic/minecraft/launch/ClaimAccount.cpp +++ b/api/logic/minecraft/launch/ClaimAccount.cpp @@ -3,22 +3,22 @@ ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session): LaunchStep(parent) { - if(session->status == AuthSession::Status::PlayableOnline) - { - m_account = session->m_accountPtr; - } + if(session->status == AuthSession::Status::PlayableOnline) + { + m_account = session->m_accountPtr; + } } void ClaimAccount::executeTask() { - if(m_account) - { - lock.reset(new UseLock(m_account)); - emitSucceeded(); - } + if(m_account) + { + lock.reset(new UseLock(m_account)); + emitSucceeded(); + } } void ClaimAccount::finalize() { - lock.reset(); + lock.reset(); } diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h index de9007d1..d0892981 100644 --- a/api/logic/minecraft/launch/ClaimAccount.h +++ b/api/logic/minecraft/launch/ClaimAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +20,18 @@ class ClaimAccount: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session); - void executeTask() override; - void finalize() override; - bool canAbort() const override - { - return false; - } + explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session); + virtual ~ClaimAccount() {}; + + void executeTask() override; + void finalize() override; + bool canAbort() const override + { + return false; + } private: - std::unique_ptr lock; - MojangAccountPtr m_account; + std::unique_ptr lock; + MojangAccountPtr m_account; }; diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp index 5398f7d0..ae426e31 100644 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp +++ b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp @@ -9,11 +9,11 @@ CreateServerResourcePacksFolder::CreateServerResourcePacksFolder(LaunchTask* par void CreateServerResourcePacksFolder::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->minecraftRoot(), "server-resource-packs"))) - { - emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error); - } - emitSucceeded(); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) + { + emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error); + } + emitSucceeded(); } diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h index 92026ecb..00ab2f2d 100644 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h +++ b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,16 @@ // HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created. class CreateServerResourcePacksFolder: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit CreateServerResourcePacksFolder(LaunchTask *parent); - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } + explicit CreateServerResourcePacksFolder(LaunchTask *parent); + virtual ~CreateServerResourcePacksFolder() {}; + + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } }; diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp index 4ccc5c3c..5c2bb91e 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp +++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,128 +22,128 @@ DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent) { - connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state); + connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state); } void DirectJavaLaunch::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - QStringList args = minecraftInstance->javaArguments(); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + QStringList args = minecraftInstance->javaArguments(); - args.append("-Djava.library.path=" + minecraftInstance->getNativePath()); + args.append("-Djava.library.path=" + minecraftInstance->getNativePath()); - auto classPathEntries = minecraftInstance->getClassPath(); - args.append("-cp"); - QString classpath; + auto classPathEntries = minecraftInstance->getClassPath(); + args.append("-cp"); + QString classpath; #ifdef Q_OS_WIN32 - classpath = classPathEntries.join(';'); + classpath = classPathEntries.join(';'); #else - classpath = classPathEntries.join(':'); + classpath = classPathEntries.join(':'); #endif - args.append(classpath); - args.append(minecraftInstance->getMainClass()); - - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - - m_process.setProcessEnvironment(instance->createEnvironment()); - - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); - - auto mcArgs = minecraftInstance->processMinecraftArgs(m_session); - args.append(mcArgs); - - QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); - if(!wrapperCommandStr.isEmpty()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } + args.append(classpath); + args.append(minecraftInstance->getMainClass()); + + QString allArgs = args.join(", "); + emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); + + auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); + + m_process.setProcessEnvironment(instance->createEnvironment()); + + // make detachable - this will keep the process running even if the object is destroyed + m_process.setDetachable(true); + + auto mcArgs = minecraftInstance->processMinecraftArgs(m_session); + args.append(mcArgs); + + QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); + if(!wrapperCommandStr.isEmpty()) + { + auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); + auto wrapperCommand = wrapperArgs.takeFirst(); + auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); + if (realWrapperCommand.isEmpty()) + { + QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); + args.prepend(javaPath); + m_process.start(wrapperCommand, wrapperArgs + args); + } + else + { + m_process.start(javaPath, args); + } } void DirectJavaLaunch::on_state(LoggedProcess::State state) { - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instace can't start - QString reason = tr("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - - { - m_parent->setPid(-1); - emitFailed("Game crashed."); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed("Game crashed."); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - break; - default: - break; - } + switch(state) + { + case LoggedProcess::FailedToStart: + { + //: Error message displayed if instace can't start + QString reason = tr("Could not launch minecraft!"); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + + { + m_parent->setPid(-1); + emitFailed("Game crashed."); + return; + } + case LoggedProcess::Finished: + { + m_parent->setPid(-1); + // if the exit code wasn't 0, report this as a crash + auto exitCode = m_process.exitCode(); + if(exitCode != 0) + { + emitFailed("Game crashed."); + return; + } + //FIXME: make this work again + // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); + // run post-exit + emitSucceeded(); + break; + } + case LoggedProcess::Running: + emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); + m_parent->setPid(m_process.processId()); + m_parent->instance()->setLastLaunch(); + break; + default: + break; + } } void DirectJavaLaunch::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } void DirectJavaLaunch::proceed() { - // nil + // nil } bool DirectJavaLaunch::abort() { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + return true; } diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h index 19087b50..9f57272c 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.h +++ b/api/logic/minecraft/launch/DirectJavaLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,27 +21,29 @@ class DirectJavaLaunch: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit DirectJavaLaunch(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } + explicit DirectJavaLaunch(LaunchTask *parent); + virtual ~DirectJavaLaunch() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual void proceed(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); + void setAuthSession(AuthSessionPtr session) + { + m_session = session; + } private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; + LoggedProcess m_process; + QString m_command; + AuthSessionPtr m_session; }; diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp index 7ddde374..253d13bc 100644 --- a/api/logic/minecraft/launch/ExtractNatives.cpp +++ b/api/logic/minecraft/launch/ExtractNatives.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,76 +25,76 @@ static QString replaceSuffix (QString target, const QString &suffix, const QString &replacement) { - if (!target.endsWith(suffix)) - { - return target; - } - target.resize(target.length() - suffix.length()); - return target + replacement; + if (!target.endsWith(suffix)) + { + return target; + } + target.resize(target.length() - suffix.length()); + return target + replacement; } static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack) { - QuaZip zip(source); - if(!zip.open(QuaZip::mdUnzip)) - { - return false; - } - QDir directory(targetFolder); - if (!zip.goToFirstFile()) - { - return false; - } - do - { - QString name = zip.getCurrentFileName(); - if(applyJnilibHack) - { - name = replaceSuffix(name, ".jnilib", ".dylib"); - } - QString absFilePath = directory.absoluteFilePath(name); - if (!JlCompress::extractFile(&zip, "", absFilePath)) - { - return false; - } - } while (zip.goToNextFile()); - zip.close(); - if(zip.getZipError()!=0) - { - return false; - } - return true; + QuaZip zip(source); + if(!zip.open(QuaZip::mdUnzip)) + { + return false; + } + QDir directory(targetFolder); + if (!zip.goToFirstFile()) + { + return false; + } + do + { + QString name = zip.getCurrentFileName(); + if(applyJnilibHack) + { + name = replaceSuffix(name, ".jnilib", ".dylib"); + } + QString absFilePath = directory.absoluteFilePath(name); + if (!JlCompress::extractFile(&zip, "", absFilePath)) + { + return false; + } + } while (zip.goToNextFile()); + zip.close(); + if(zip.getZipError()!=0) + { + return false; + } + return true; } void ExtractNatives::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - auto toExtract = minecraftInstance->getNativeJars(); - if(toExtract.isEmpty()) - { - emitSucceeded(); - return; - } - auto outputPath = minecraftInstance->getNativePath(); - auto javaVersion = minecraftInstance->getJavaVersion(); - bool jniHackEnabled = javaVersion.major() >= 8; - for(const auto &source: toExtract) - { - if(!unzipNatives(source, outputPath, jniHackEnabled)) - { - auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - } - } - emitSucceeded(); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + auto toExtract = minecraftInstance->getNativeJars(); + if(toExtract.isEmpty()) + { + emitSucceeded(); + return; + } + auto outputPath = minecraftInstance->getNativePath(); + auto javaVersion = minecraftInstance->getJavaVersion(); + bool jniHackEnabled = javaVersion.major() >= 8; + for(const auto &source: toExtract) + { + if(!unzipNatives(source, outputPath, jniHackEnabled)) + { + auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + } + } + emitSucceeded(); } void ExtractNatives::finalize() { - auto instance = m_parent->instance(); - QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/"); - QDir dir(target_dir); - dir.removeRecursively(); + auto instance = m_parent->instance(); + QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/"); + QDir dir(target_dir); + dir.removeRecursively(); } diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h index 6e1e7cd4..abb3bd7a 100644 --- a/api/logic/minecraft/launch/ExtractNatives.h +++ b/api/logic/minecraft/launch/ExtractNatives.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,17 @@ // FIXME: temporary wrapper for existing task. class ExtractNatives: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){}; - virtual ~ExtractNatives(){}; + explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){}; + virtual ~ExtractNatives(){}; - void executeTask() override; - bool canAbort() const override - { - return false; - } - void finalize() override; + void executeTask() override; + bool canAbort() const override + { + return false; + } + void finalize() override; }; diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp index 1fe9c323..e854a3a6 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp +++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) { - connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); + connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); } #ifdef Q_OS_WIN @@ -33,187 +33,187 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) #include QString shortPathName(const QString & file) { - auto input = file.toStdWString(); - std::wstring output; - long length = GetShortPathNameW(input.c_str(), NULL, 0); - // NOTE: this resizing might seem weird... - // when GetShortPathNameW fails, it returns length including null character - // when it succeeds, it returns length excluding null character - // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx - output.resize(length); - GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length); - output.resize(length-1); - QString ret = QString::fromStdWString(output); - return ret; + auto input = file.toStdWString(); + std::wstring output; + long length = GetShortPathNameW(input.c_str(), NULL, 0); + // NOTE: this resizing might seem weird... + // when GetShortPathNameW fails, it returns length including null character + // when it succeeds, it returns length excluding null character + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx + output.resize(length); + GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length); + output.resize(length-1); + QString ret = QString::fromStdWString(output); + return ret; } #endif // if the string survives roundtrip through local 8bit encoding... bool fitsInLocal8bit(const QString & string) { - return string == QString::fromLocal8Bit(string.toLocal8Bit()); + return string == QString::fromLocal8Bit(string.toLocal8Bit()); } void LauncherPartLaunch::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - m_launchScript = minecraftInstance->createLaunchScript(m_session); - QStringList args = minecraftInstance->javaArguments(); - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); + m_launchScript = minecraftInstance->createLaunchScript(m_session); + QStringList args = minecraftInstance->javaArguments(); + QString allArgs = args.join(", "); + emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); + auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - m_process.setProcessEnvironment(instance->createEnvironment()); + m_process.setProcessEnvironment(instance->createEnvironment()); - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); + // make detachable - this will keep the process running even if the object is destroyed + m_process.setDetachable(true); - auto classPath = minecraftInstance->getClassPath(); - classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar")); + auto classPath = minecraftInstance->getClassPath(); + classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar")); - auto natPath = minecraftInstance->getNativePath(); + auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN - if (!fitsInLocal8bit(natPath)) - { - args << "-Djava.library.path=" + shortPathName(natPath); - } - else - { - args << "-Djava.library.path=" + natPath; - } + if (!fitsInLocal8bit(natPath)) + { + args << "-Djava.library.path=" + shortPathName(natPath); + } + else + { + args << "-Djava.library.path=" + natPath; + } #else - args << "-Djava.library.path=" + natPath; + args << "-Djava.library.path=" + natPath; #endif - args << "-cp"; + args << "-cp"; #ifdef Q_OS_WIN - QStringList processed; - for(auto & item: classPath) - { - if (!fitsInLocal8bit(item)) - { - processed << shortPathName(item); - } - else - { - processed << item; - } - } - args << processed.join(';'); + QStringList processed; + for(auto & item: classPath) + { + if (!fitsInLocal8bit(item)) + { + processed << shortPathName(item); + } + else + { + processed << item; + } + } + args << processed.join(';'); #else - args << classPath.join(':'); + args << classPath.join(':'); #endif - args << "org.multimc.EntryPoint"; - - qDebug() << args.join(' '); - - QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); - if(!wrapperCommandStr.isEmpty()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } + args << "org.multimc.EntryPoint"; + + qDebug() << args.join(' '); + + QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); + if(!wrapperCommandStr.isEmpty()) + { + auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); + auto wrapperCommand = wrapperArgs.takeFirst(); + auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); + if (realWrapperCommand.isEmpty()) + { + QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); + args.prepend(javaPath); + m_process.start(wrapperCommand, wrapperArgs + args); + } + else + { + m_process.start(javaPath, args); + } } void LauncherPartLaunch::on_state(LoggedProcess::State state) { - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instace can't start - QString reason = tr("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - - { - m_parent->setPid(-1); - emitFailed("Game crashed."); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed("Game crashed."); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - // send the launch script to the launcher part - m_process.write(m_launchScript.toUtf8()); - - mayProceed = true; - emit readyForLaunch(); - break; - default: - break; - } + switch(state) + { + case LoggedProcess::FailedToStart: + { + //: Error message displayed if instace can't start + QString reason = tr("Could not launch minecraft!"); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + + { + m_parent->setPid(-1); + emitFailed("Game crashed."); + return; + } + case LoggedProcess::Finished: + { + m_parent->setPid(-1); + // if the exit code wasn't 0, report this as a crash + auto exitCode = m_process.exitCode(); + if(exitCode != 0) + { + emitFailed("Game crashed."); + return; + } + //FIXME: make this work again + // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); + // run post-exit + emitSucceeded(); + break; + } + case LoggedProcess::Running: + emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); + m_parent->setPid(m_process.processId()); + m_parent->instance()->setLastLaunch(); + // send the launch script to the launcher part + m_process.write(m_launchScript.toUtf8()); + + mayProceed = true; + emit readyForLaunch(); + break; + default: + break; + } } void LauncherPartLaunch::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } void LauncherPartLaunch::proceed() { - if(mayProceed) - { - QString launchString("launch\n"); - m_process.write(launchString.toUtf8()); - mayProceed = false; - } + if(mayProceed) + { + QString launchString("launch\n"); + m_process.write(launchString.toUtf8()); + mayProceed = false; + } } bool LauncherPartLaunch::abort() { - if(mayProceed) - { - mayProceed = false; - QString launchString("abort\n"); - m_process.write(launchString.toUtf8()); - } - else - { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - } - return true; + if(mayProceed) + { + mayProceed = false; + QString launchString("abort\n"); + m_process.write(launchString.toUtf8()); + } + else + { + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + } + return true; } diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h index d384c2d1..1bc4a5f8 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.h +++ b/api/logic/minecraft/launch/LauncherPartLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,29 +21,31 @@ class LauncherPartLaunch: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit LauncherPartLaunch(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } + explicit LauncherPartLaunch(LaunchTask *parent); + virtual ~LauncherPartLaunch() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual void proceed(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); + void setAuthSession(AuthSessionPtr session) + { + m_session = session; + } private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; - QString m_launchScript; - bool mayProceed = false; + LoggedProcess m_process; + QString m_command; + AuthSessionPtr m_session; + QString m_launchScript; + bool mayProceed = false; }; diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp index 34825b45..ceaad175 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.cpp +++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,60 +23,60 @@ void ModMinecraftJar::executeTask() { - auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); - if(!m_inst->getJarMods().size()) - { - emitSucceeded(); - return; - } - // nuke obsolete stripped jar(s) if needed - if(!FS::ensureFolderPathExists(m_inst->binRoot())) - { - emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); - } + if(!m_inst->getJarMods().size()) + { + emitSucceeded(); + return; + } + // nuke obsolete stripped jar(s) if needed + if(!FS::ensureFolderPathExists(m_inst->binRoot())) + { + emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); + } - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - if(!removeJar()) - { - emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); - } + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + if(!removeJar()) + { + emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); + } - // create temporary modded jar, if needed - auto components = m_inst->getComponentList(); - auto profile = components->getProfile(); - auto jarMods = m_inst->getJarMods(); - if(jarMods.size()) - { - auto mainJar = profile->getMainJar(); - QStringList jars, temp1, temp2, temp3, temp4; - mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); - auto sourceJarPath = jars[0]; - if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - } - emitSucceeded(); + // create temporary modded jar, if needed + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto jarMods = m_inst->getJarMods(); + if(jarMods.size()) + { + auto mainJar = profile->getMainJar(); + QStringList jars, temp1, temp2, temp3, temp4; + mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); + auto sourceJarPath = jars[0]; + if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) + { + emitFailed(tr("Failed to create the custom Minecraft jar file.")); + return; + } + } + emitSucceeded(); } void ModMinecraftJar::finalize() { - removeJar(); + removeJar(); } bool ModMinecraftJar::removeJar() { - auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - QFile finalJar(finalJarPath); - if(finalJar.exists()) - { - if(!finalJar.remove()) - { - return false; - } - } - return true; + auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + QFile finalJar(finalJarPath); + if(finalJar.exists()) + { + if(!finalJar.remove()) + { + return false; + } + } + return true; } diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h index b9a2d35b..bed5079f 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.h +++ b/api/logic/minecraft/launch/ModMinecraftJar.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,17 @@ class ModMinecraftJar: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {}; - virtual ~ModMinecraftJar(){}; + explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {}; + virtual ~ModMinecraftJar(){}; - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } - void finalize() override; + virtual void executeTask() override; + virtual bool canAbort() const override + { + return false; + } + void finalize() override; private: - bool removeJar(); + bool removeJar(); }; diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp index 83bf584f..4cc180fd 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp +++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,67 +19,88 @@ #include "PrintInstanceInfo.h" #include -void PrintInstanceInfo::executeTask() -{ - auto instance = m_parent->instance(); - auto lines = instance->verboseDescription(m_session); - #ifdef Q_OS_LINUX +namespace { +void probeProcCpuinfo(QStringList &log) +{ std::ifstream cpuin("/proc/cpuinfo"); for (std::string line; std::getline(cpuin, line);) { if (strncmp(line.c_str(), "model name", 10) == 0) { - QStringList clines = (QStringList() << QString::fromStdString(line.substr(13, std::string::npos))); - logLines(clines, MessageLevel::MultiMC); + log << QString::fromStdString(line.substr(13, std::string::npos)); break; } } +} +void runLspci(QStringList &log) +{ + // FIXME: fixed size buffers... char buff[512]; int gpuline = -1; int cline = 0; - FILE *fp = popen("lspci -k", "r"); - if (fp != NULL) + FILE * lspci = popen("lspci -k", "r"); + + if (!lspci) + return; + + while (fgets(buff, 512, lspci) != NULL) { - while (fgets(buff, 512, fp) != NULL) + std::string str(buff); + if (str.length() < 9) + continue; + if (str.substr(8, 3) == "VGA") { - std::string str(buff); - if (str.length() < 9) - continue; - if (str.substr(8, 3) == "VGA") - { - gpuline = cline; - QStringList glines = (QStringList() << QString::fromStdString(str.substr(35, std::string::npos))); - logLines(glines, MessageLevel::MultiMC); - } - if (gpuline > -1 && gpuline != cline) + gpuline = cline; + log << QString::fromStdString(str.substr(35, std::string::npos)); + } + if (gpuline > -1 && gpuline != cline) + { + if (cline - gpuline < 3) { - if (cline - gpuline < 3) - { - QStringList alines = (QStringList() << QString::fromStdString(str.substr(1, std::string::npos))); - logLines(alines, MessageLevel::MultiMC); - } + log << QString::fromStdString(str.substr(1, std::string::npos)); } - cline++; } + cline++; } - - FILE *fp2 = popen("glxinfo", "r"); - if (fp2 != NULL) + pclose(lspci); +} + +void runGlxinfo(QStringList & log) +{ + // FIXME: fixed size buffers... + char buff[512]; + FILE *glxinfo = popen("glxinfo", "r"); + if (!glxinfo) + return; + + while (fgets(buff, 512, glxinfo) != NULL) { - while (fgets(buff, 512, fp2) != NULL) + if (strncmp(buff, "OpenGL version string:", 22) == 0) { - if (strncmp(buff, "OpenGL version string:", 22) == 0) - { - QStringList drlines = (QStringList() << QString::fromUtf8(buff)); - logLines(drlines, MessageLevel::MultiMC); - break; - } + log << QString::fromUtf8(buff); + break; } } + pclose(glxinfo); +} + +} +#endif + +void PrintInstanceInfo::executeTask() +{ + auto instance = m_parent->instance(); + QStringList log; + +#ifdef Q_OS_LINUX + ::probeProcCpuinfo(log); + ::runLspci(log); + ::runGlxinfo(log); #endif - logLines(lines, MessageLevel::MultiMC); + logLines(log, MessageLevel::MultiMC); + logLines(instance->verboseDescription(m_session), MessageLevel::MultiMC); emitSucceeded(); } diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h index 61615ba1..1d081f3e 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.h +++ b/api/logic/minecraft/launch/PrintInstanceInfo.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,17 @@ // FIXME: temporary wrapper for existing task. class PrintInstanceInfo: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {}; - virtual ~PrintInstanceInfo(){}; + explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {}; + virtual ~PrintInstanceInfo(){}; - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } private: - AuthSessionPtr m_session; + AuthSessionPtr m_session; }; diff --git a/api/logic/minecraft/launch/ReconstructAssets.cpp b/api/logic/minecraft/launch/ReconstructAssets.cpp new file mode 100644 index 00000000..af9af127 --- /dev/null +++ b/api/logic/minecraft/launch/ReconstructAssets.cpp @@ -0,0 +1,36 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ReconstructAssets.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" +#include "minecraft/AssetsUtils.h" +#include "launch/LaunchTask.h" + +void ReconstructAssets::executeTask() +{ + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + auto components = minecraftInstance->getComponentList(); + auto profile = components->getProfile(); + auto assets = profile->getMinecraftAssets(); + + if(!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) + { + emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error); + } + + emitSucceeded(); +} diff --git a/api/logic/minecraft/launch/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h new file mode 100644 index 00000000..228fa083 --- /dev/null +++ b/api/logic/minecraft/launch/ReconstructAssets.h @@ -0,0 +1,33 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +class ReconstructAssets: public LaunchStep +{ + Q_OBJECT +public: + explicit ReconstructAssets(LaunchTask *parent) : LaunchStep(parent){}; + virtual ~ReconstructAssets(){}; + + void executeTask() override; + bool canAbort() const override + { + return false; + } +}; diff --git a/api/logic/minecraft/launch/ScanModFolders.cpp b/api/logic/minecraft/launch/ScanModFolders.cpp new file mode 100644 index 00000000..485c3dc4 --- /dev/null +++ b/api/logic/minecraft/launch/ScanModFolders.cpp @@ -0,0 +1,54 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ScanModFolders.h" +#include "launch/LaunchTask.h" +#include "MMCZip.h" +#include "minecraft/OpSys.h" +#include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/mod/ModFolderModel.h" + +void ScanModFolders::executeTask() +{ + auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + + auto loaders = m_inst->loaderModList(); + connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); + loaders->update(); + + auto cores = m_inst->coreModList(); + connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone); + cores->update(); +} + +void ScanModFolders::modsDone() +{ + m_modsDone = true; + checkDone(); +} + +void ScanModFolders::coreModsDone() +{ + m_coreModsDone = true; + checkDone(); +} + +void ScanModFolders::checkDone() +{ + if(m_modsDone && m_coreModsDone) { + emitSucceeded(); + } +} diff --git a/api/logic/minecraft/launch/ScanModFolders.h b/api/logic/minecraft/launch/ScanModFolders.h new file mode 100644 index 00000000..360df98d --- /dev/null +++ b/api/logic/minecraft/launch/ScanModFolders.h @@ -0,0 +1,42 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +class ScanModFolders: public LaunchStep +{ + Q_OBJECT +public: + explicit ScanModFolders(LaunchTask *parent) : LaunchStep(parent) {}; + virtual ~ScanModFolders(){}; + + virtual void executeTask() override; + virtual bool canAbort() const override + { + return false; + } +private slots: + void coreModsDone(); + void modsDone(); +private: + void checkDone(); + +private: // DATA + bool m_modsDone = false; + bool m_coreModsDone = false; +}; diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp index 5338763c..b9d68a9c 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ b/api/logic/minecraft/legacy/LegacyInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,322 +21,236 @@ #include "LegacyInstance.h" #include "minecraft/legacy/LegacyModList.h" -#include "minecraft/ModList.h" #include "minecraft/WorldList.h" #include #include LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) + : BaseInstance(globalSettings, settings, rootDir) { - settings->registerSetting("NeedsRebuild", true); - settings->registerSetting("ShouldUpdate", false); - settings->registerSetting("JarVersion", QString()); - settings->registerSetting("IntendedJarVersion", QString()); - /* - * custom base jar has no default. it is determined in code... see the accessor methods for - *it - * - * for instances that DO NOT have the CustomBaseJar setting (legacy instances), - * [.]minecraft/bin/mcbackup.jar is the default base jar - */ - settings->registerSetting("UseCustomBaseJar", true); - settings->registerSetting("CustomBaseJar", ""); + settings->registerSetting("NeedsRebuild", true); + settings->registerSetting("ShouldUpdate", false); + settings->registerSetting("JarVersion", QString()); + settings->registerSetting("IntendedJarVersion", QString()); + /* + * custom base jar has no default. it is determined in code... see the accessor methods for + *it + * + * for instances that DO NOT have the CustomBaseJar setting (legacy instances), + * [.]minecraft/bin/mcbackup.jar is the default base jar + */ + settings->registerSetting("UseCustomBaseJar", true); + settings->registerSetting("CustomBaseJar", ""); } QString LegacyInstance::mainJarToPreserve() const { - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if(customJar) - { - auto base = baseJar(); - if(QFile::exists(base)) - { - return base; - } - } - auto runnable = runnableJar(); - if(QFile::exists(runnable)) - { - return runnable; - } - return QString(); + bool customJar = m_settings->get("UseCustomBaseJar").toBool(); + if(customJar) + { + auto base = baseJar(); + if(QFile::exists(base)) + { + return base; + } + } + auto runnable = runnableJar(); + if(QFile::exists(runnable)) + { + return runnable; + } + return QString(); } QString LegacyInstance::baseJar() const { - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if (customJar) - { - return customBaseJar(); - } - else - return defaultBaseJar(); + bool customJar = m_settings->get("UseCustomBaseJar").toBool(); + if (customJar) + { + return customBaseJar(); + } + else + return defaultBaseJar(); } QString LegacyInstance::customBaseJar() const { - QString value = m_settings->get("CustomBaseJar").toString(); - if (value.isNull() || value.isEmpty()) - { - return defaultCustomBaseJar(); - } - return value; + QString value = m_settings->get("CustomBaseJar").toString(); + if (value.isNull() || value.isEmpty()) + { + return defaultCustomBaseJar(); + } + return value; } bool LegacyInstance::shouldUseCustomBaseJar() const { - return m_settings->get("UseCustomBaseJar").toBool(); + return m_settings->get("UseCustomBaseJar").toBool(); } shared_qobject_ptr LegacyInstance::createUpdateTask(Net::Mode) { - return nullptr; + return nullptr; } -/* -class LegacyJarModTask : public Task -{ - //Q_OBJECT -public: - explicit LegacyJarModTask(std::shared_ptr inst) : Task(nullptr), m_inst(inst) - { - } - virtual void executeTask() - { - if (!m_inst->shouldRebuild()) - { - emitSucceeded(); - return; - } - - // Get the mod list - auto modList = m_inst->getJarMods(); - - QFileInfo runnableJar(m_inst->runnableJar()); - QFileInfo baseJar(m_inst->baseJar()); - bool base_is_custom = m_inst->shouldUseCustomBaseJar(); - - // Nothing to do if there are no jar mods to install, no backup and just the mc jar - if (base_is_custom) - { - // yes, this can happen if the instance only has the runnable jar and not the base jar - // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe - // because that's not something mmc4 guarantees - if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) - { - m_inst->setShouldRebuild(false); - emitSucceeded(); - return; - } - - setStatus(tr("Installing mods: Backing up minecraft.jar ...")); - if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) - { - emitFailed("It seems both the active and base jar are gone. A fresh base jar will " - "be used on next run."); - m_inst->setShouldRebuild(true); - m_inst->setShouldUpdate(true); - m_inst->setShouldUseCustomBaseJar(false); - return; - } - } - - if (!baseJar.exists()) - { - emitFailed("The base jar " + baseJar.filePath() + " does not exist"); - return; - } - - if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) - { - emitFailed("Failed to delete old minecraft.jar"); - return; - } - - setStatus(tr("Installing mods: Opening minecraft.jar ...")); - - QString outputJarPath = runnableJar.filePath(); - QString inputJarPath = baseJar.filePath(); - - if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - m_inst->setShouldRebuild(false); - // inst->UpdateVersion(true); - emitSucceeded(); - return; - - } - std::shared_ptr m_inst; -}; -*/ - std::shared_ptr LegacyInstance::jarModList() const { - if (!jar_mod_list) - { - auto list = new LegacyModList(jarModsDir(), modListFile()); - jar_mod_list.reset(list); - } - jar_mod_list->update(); - return jar_mod_list; -} - -QList LegacyInstance::getJarMods() const -{ - return jarModList()->allMods(); + if (!jar_mod_list) + { + auto list = new LegacyModList(jarModsDir(), modListFile()); + jar_mod_list.reset(list); + } + jar_mod_list->update(); + return jar_mod_list; } -QString LegacyInstance::minecraftRoot() const +QString LegacyInstance::gameRoot() const { - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); + QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); + if (mcDir.exists() && !dotMCDir.exists()) + return mcDir.filePath(); + else + return dotMCDir.filePath(); } QString LegacyInstance::binRoot() const { - return FS::PathCombine(minecraftRoot(), "bin"); + return FS::PathCombine(gameRoot(), "bin"); } QString LegacyInstance::jarModsDir() const { - return FS::PathCombine(instanceRoot(), "instMods"); + return FS::PathCombine(instanceRoot(), "instMods"); } QString LegacyInstance::libDir() const { - return FS::PathCombine(minecraftRoot(), "lib"); + return FS::PathCombine(gameRoot(), "lib"); } QString LegacyInstance::savesDir() const { - return FS::PathCombine(minecraftRoot(), "saves"); + return FS::PathCombine(gameRoot(), "saves"); } QString LegacyInstance::loaderModsDir() const { - return FS::PathCombine(minecraftRoot(), "mods"); + return FS::PathCombine(gameRoot(), "mods"); } QString LegacyInstance::coreModsDir() const { - return FS::PathCombine(minecraftRoot(), "coremods"); + return FS::PathCombine(gameRoot(), "coremods"); } QString LegacyInstance::resourceDir() const { - return FS::PathCombine(minecraftRoot(), "resources"); + return FS::PathCombine(gameRoot(), "resources"); } QString LegacyInstance::texturePacksDir() const { - return FS::PathCombine(minecraftRoot(), "texturepacks"); + return FS::PathCombine(gameRoot(), "texturepacks"); } QString LegacyInstance::runnableJar() const { - return FS::PathCombine(binRoot(), "minecraft.jar"); + return FS::PathCombine(binRoot(), "minecraft.jar"); } QString LegacyInstance::modListFile() const { - return FS::PathCombine(instanceRoot(), "modlist"); + return FS::PathCombine(instanceRoot(), "modlist"); } QString LegacyInstance::instanceConfigFolder() const { - return FS::PathCombine(minecraftRoot(), "config"); + return FS::PathCombine(gameRoot(), "config"); } bool LegacyInstance::shouldRebuild() const { - return m_settings->get("NeedsRebuild").toBool(); + return m_settings->get("NeedsRebuild").toBool(); } QString LegacyInstance::currentVersionId() const { - return m_settings->get("JarVersion").toString(); + return m_settings->get("JarVersion").toString(); } QString LegacyInstance::intendedVersionId() const { - return m_settings->get("IntendedJarVersion").toString(); + return m_settings->get("IntendedJarVersion").toString(); } bool LegacyInstance::shouldUpdate() const { - QVariant var = settings()->get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } - return true; + QVariant var = settings()->get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) + { + return intendedVersionId() != currentVersionId(); + } + return true; } QString LegacyInstance::defaultBaseJar() const { - return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; + return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; } QString LegacyInstance::defaultCustomBaseJar() const { - return FS::PathCombine(binRoot(), "mcbackup.jar"); + return FS::PathCombine(binRoot(), "mcbackup.jar"); } std::shared_ptr LegacyInstance::worldList() const { - if (!m_world_list) - { - m_world_list.reset(new WorldList(savesDir())); - } - return m_world_list; + if (!m_world_list) + { + m_world_list.reset(new WorldList(savesDir())); + } + return m_world_list; } QString LegacyInstance::typeName() const { - return tr("Legacy"); + return tr("Legacy"); } QString LegacyInstance::getStatusbarDescription() { - return tr("Instance from previous versions."); + return tr("Instance from previous versions."); } QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) { - QStringList out; - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << " " + trait; - } - out << ""; - } - - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; + QStringList out; + + auto alltraits = traits(); + if(alltraits.size()) + { + out << "Traits:"; + for (auto trait : alltraits) + { + out << " " + trait; + } + out << ""; + } + + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + { + out << "Window size: max (if available)"; + } + else + { + auto width = settings()->get("MinecraftWinWidth").toInt(); + auto height = settings()->get("MinecraftWinHeight").toInt(); + out << "Window size: " + QString::number(width) + " x " + QString::number(height); + } + out << ""; + return out; } diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h index 7ab89509..7c0b94e8 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ b/api/logic/minecraft/legacy/LegacyInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ #pragma once #include "BaseInstance.h" -#include "minecraft/Mod.h" +#include "launch/LaunchTask.h" #include "multimc_logic_export.h" -class ModList; +class ModFolderModel; class LegacyModList; class WorldList; class Task; @@ -29,115 +29,113 @@ class Task; */ class MULTIMC_LOGIC_EXPORT LegacyInstance : public BaseInstance { - Q_OBJECT + Q_OBJECT public: - explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - - virtual void init() override {} - virtual void saveNow() override {} - - /// Path to the instance's minecraft.jar - QString runnableJar() const; - - //! Path to the instance's modlist file. - QString modListFile() const; - - ////// Directories ////// - QString libDir() const; - QString savesDir() const; - QString texturePacksDir() const; - QString jarModsDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString resourceDir() const; - virtual QString instanceConfigFolder() const override; - QString minecraftRoot() const; // Path to the instance's minecraft directory. - QString binRoot() const; // Path to the instance's minecraft bin directory. - - /// Get the curent base jar of this instance. By default, it's the - /// versions/$version/$version.jar - QString baseJar() const; - - /// the default base jar of this instance - QString defaultBaseJar() const; - /// the default custom base jar of this instance - QString defaultCustomBaseJar() const; - - // the main jar that we actually want to keep when migrating the instance - QString mainJarToPreserve() const; - - /*! - * Whether or not custom base jar is used - */ - bool shouldUseCustomBaseJar() const; - - /*! - * The value of the custom base jar - */ - QString customBaseJar() const; - - std::shared_ptr jarModList() const; - QList getJarMods() const; - std::shared_ptr worldList() const; - - /*! - * Whether or not the instance's minecraft.jar needs to be rebuilt. - * If this is true, when the instance launches, its jar mods will be - * re-added to a fresh minecraft.jar file. - */ - bool shouldRebuild() const; - - QString currentVersionId() const; - QString intendedVersionId() const; - - QSet traits() const override - { - return {"legacy-instance", "texturepacks"}; - }; - - virtual bool shouldUpdate() const; - virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) override; - - virtual QString typeName() const override; - - bool canLaunch() const override - { - return false; - } - bool canEdit() const override - { - return true; - } - bool canExport() const override - { - return false; - } - std::shared_ptr createLaunchTask(AuthSessionPtr account) override - { - return nullptr; - } - IPathMatcher::Ptr getLogFileMatcher() override - { - return nullptr; - } - QString getLogFileRoot() override - { - return minecraftRoot(); - } - - QString getStatusbarDescription() override; - QStringList verboseDescription(AuthSessionPtr session) override; - - QProcessEnvironment createEnvironment() override - { - return QProcessEnvironment(); - } - QMap getVariables() const override - { - return {}; - } + explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); + + virtual void saveNow() override {} + + /// Path to the instance's minecraft.jar + QString runnableJar() const; + + //! Path to the instance's modlist file. + QString modListFile() const; + + ////// Directories ////// + QString libDir() const; + QString savesDir() const; + QString texturePacksDir() const; + QString jarModsDir() const; + QString loaderModsDir() const; + QString coreModsDir() const; + QString resourceDir() const; + virtual QString instanceConfigFolder() const override; + QString gameRoot() const override; // Path to the instance's minecraft directory. + QString binRoot() const; // Path to the instance's minecraft bin directory. + + /// Get the curent base jar of this instance. By default, it's the + /// versions/$version/$version.jar + QString baseJar() const; + + /// the default base jar of this instance + QString defaultBaseJar() const; + /// the default custom base jar of this instance + QString defaultCustomBaseJar() const; + + // the main jar that we actually want to keep when migrating the instance + QString mainJarToPreserve() const; + + /*! + * Whether or not custom base jar is used + */ + bool shouldUseCustomBaseJar() const; + + /*! + * The value of the custom base jar + */ + QString customBaseJar() const; + + std::shared_ptr jarModList() const; + std::shared_ptr worldList() const; + + /*! + * Whether or not the instance's minecraft.jar needs to be rebuilt. + * If this is true, when the instance launches, its jar mods will be + * re-added to a fresh minecraft.jar file. + */ + bool shouldRebuild() const; + + QString currentVersionId() const; + QString intendedVersionId() const; + + QSet traits() const override + { + return {"legacy-instance", "texturepacks"}; + }; + + virtual bool shouldUpdate() const; + virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) override; + + virtual QString typeName() const override; + + bool canLaunch() const override + { + return false; + } + bool canEdit() const override + { + return true; + } + bool canExport() const override + { + return false; + } + shared_qobject_ptr createLaunchTask(AuthSessionPtr account) override + { + return nullptr; + } + IPathMatcher::Ptr getLogFileMatcher() override + { + return nullptr; + } + QString getLogFileRoot() override + { + return gameRoot(); + } + + QString getStatusbarDescription() override; + QStringList verboseDescription(AuthSessionPtr session) override; + + QProcessEnvironment createEnvironment() override + { + return QProcessEnvironment(); + } + QMap getVariables() const override + { + return {}; + } protected: - mutable std::shared_ptr jar_mod_list; - mutable std::shared_ptr m_world_list; + mutable std::shared_ptr jar_mod_list; + mutable std::shared_ptr m_world_list; }; diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp index 638b2e21..23b837c1 100644 --- a/api/logic/minecraft/legacy/LegacyModList.cpp +++ b/api/logic/minecraft/legacy/LegacyModList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,153 +19,118 @@ #include LegacyModList::LegacyModList(const QString &dir, const QString &list_file) - : m_dir(dir), m_list_file(list_file) + : m_dir(dir), m_list_file(list_file) { - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | - QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); } - struct OrderItem - { - QString id; - bool enabled = false; - }; - typedef QList OrderList; + struct OrderItem + { + QString id; + bool enabled = false; + }; + typedef QList OrderList; -static void internalSort(QList &what) +static void internalSort(QList &what) { - auto predicate = [](const Mod &left, const Mod &right) - { - if (left.name() == right.name()) - { - return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0; - } - return left.name().localeAwareCompare(right.name()) < 0; - }; - std::sort(what.begin(), what.end(), predicate); + auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right) + { + return left.fileName().localeAwareCompare(right.fileName()) < 0; + }; + std::sort(what.begin(), what.end(), predicate); } static OrderList readListFile(const QString &m_list_file) { - OrderList itemList; - if (m_list_file.isNull() || m_list_file.isEmpty()) - return itemList; + OrderList itemList; + if (m_list_file.isNull() || m_list_file.isEmpty()) + return itemList; - QFile textFile(m_list_file); - if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) - return OrderList(); + QFile textFile(m_list_file); + if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return OrderList(); - QTextStream textStream; - textStream.setAutoDetectUnicode(true); - textStream.setDevice(&textFile); - while (true) - { - QString line = textStream.readLine(); - if (line.isNull() || line.isEmpty()) - break; - else - { - OrderItem it; - it.enabled = !line.endsWith(".disabled"); - if (!it.enabled) - { - line.chop(9); - } - it.id = line; - itemList.append(it); - } - } - textFile.close(); - return itemList; + QTextStream textStream; + textStream.setAutoDetectUnicode(true); + textStream.setDevice(&textFile); + while (true) + { + QString line = textStream.readLine(); + if (line.isNull() || line.isEmpty()) + break; + else + { + OrderItem it; + it.enabled = !line.endsWith(".disabled"); + if (!it.enabled) + { + line.chop(9); + } + it.id = line; + itemList.append(it); + } + } + textFile.close(); + return itemList; } bool LegacyModList::update() { - if (!m_dir.exists() || !m_dir.isReadable()) - return false; + if (!m_dir.exists() || !m_dir.isReadable()) + return false; - QList orderedMods; - QList newMods; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - bool orderOrStateChanged = false; + QList orderedMods; + QList newMods; + m_dir.refresh(); + auto folderContents = m_dir.entryInfoList(); - // first, process the ordered items (if any) - OrderList listOrder = readListFile(m_list_file); - for (auto item : listOrder) - { - QFileInfo infoEnabled(m_dir.filePath(item.id)); - QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); - int idxEnabled = folderContents.indexOf(infoEnabled); - int idxDisabled = folderContents.indexOf(infoDisabled); - bool isEnabled; - // if both enabled and disabled versions are present, it's a special case... - if (idxEnabled >= 0 && idxDisabled >= 0) - { - // we only process the one we actually have in the order file. - // and exactly as we have it. - // THIS IS A CORNER CASE - isEnabled = item.enabled; - } - else - { - // only one is present. - // we pick the one that we found. - // we assume the mod was enabled/disabled by external means - isEnabled = idxEnabled >= 0; - } - int idx = isEnabled ? idxEnabled : idxDisabled; - QFileInfo &info = isEnabled ? infoEnabled : infoDisabled; - // if the file from the index file exists - if (idx != -1) - { - // remove from the actual folder contents list - folderContents.takeAt(idx); - // append the new mod - orderedMods.append(Mod(info)); - if (isEnabled != item.enabled) - orderOrStateChanged = true; - } - else - { - orderOrStateChanged = true; - } - } - // if there are any untracked files... - if (folderContents.size()) - { - // the order surely changed! - for (auto entry : folderContents) - { - newMods.append(Mod(entry)); - } - internalSort(newMods); - orderedMods.append(newMods); - orderOrStateChanged = true; - } - // otherwise, if we were already tracking some mods - else if (mods.size()) - { - // if the number doesn't match, order changed. - if (mods.size() != orderedMods.size()) - orderOrStateChanged = true; - // if it does match, compare the mods themselves - else - for (int i = 0; i < mods.size(); i++) - { - if (!mods[i].strongCompare(orderedMods[i])) - { - orderOrStateChanged = true; - break; - } - } - } - mods.swap(orderedMods); - if (orderOrStateChanged && !m_list_file.isEmpty()) - { - qDebug() << "Mod list " << m_list_file << " changed!"; - } - return true; + // first, process the ordered items (if any) + OrderList listOrder = readListFile(m_list_file); + for (auto item : listOrder) + { + QFileInfo infoEnabled(m_dir.filePath(item.id)); + QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); + int idxEnabled = folderContents.indexOf(infoEnabled); + int idxDisabled = folderContents.indexOf(infoDisabled); + bool isEnabled; + // if both enabled and disabled versions are present, it's a special case... + if (idxEnabled >= 0 && idxDisabled >= 0) + { + // we only process the one we actually have in the order file. + // and exactly as we have it. + // THIS IS A CORNER CASE + isEnabled = item.enabled; + } + else + { + // only one is present. + // we pick the one that we found. + // we assume the mod was enabled/disabled by external means + isEnabled = idxEnabled >= 0; + } + int idx = isEnabled ? idxEnabled : idxDisabled; + QFileInfo &info = isEnabled ? infoEnabled : infoDisabled; + // if the file from the index file exists + if (idx != -1) + { + // remove from the actual folder contents list + folderContents.takeAt(idx); + // append the new mod + orderedMods.append(info); + } + } + // if there are any untracked files... append them sorted at the end + if (folderContents.size()) + { + for (auto entry : folderContents) + { + newMods.append(entry); + } + internalSort(newMods); + orderedMods.append(newMods); + } + mods.swap(orderedMods); + return true; } diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h index 19b191a7..9a7bea50 100644 --- a/api/logic/minecraft/legacy/LegacyModList.h +++ b/api/logic/minecraft/legacy/LegacyModList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,38 +19,31 @@ #include #include -#include "minecraft/Mod.h" - #include "multimc_logic_export.h" -class LegacyInstance; -class BaseInstance; - -/** - * A legacy mod list. - * Backed by a folder. - */ class MULTIMC_LOGIC_EXPORT LegacyModList { public: - LegacyModList(const QString &dir, const QString &list_file = QString()); + using Mod = QFileInfo; + + LegacyModList(const QString &dir, const QString &list_file = QString()); - /// Reloads the mod list and returns true if the list changed. - bool update(); + /// Reloads the mod list and returns true if the list changed. + bool update(); - QDir dir() - { - return m_dir; - } + QDir dir() + { + return m_dir; + } - const QList & allMods() - { - return mods; - } + const QList & allMods() + { + return mods; + } protected: - QDir m_dir; - QString m_list_file; - QList mods; + QDir m_dir; + QString m_list_file; + QList mods; }; diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp index 5cc3b5d9..9d86a7b5 100644 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp @@ -1,5 +1,4 @@ #include "LegacyUpgradeTask.h" -#include "BaseInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" #include "NullInstance.h" @@ -8,132 +7,132 @@ #include "LegacyInstance.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/ComponentList.h" +#include "LegacyModList.h" #include "classparser.h" LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance) { - m_origInstance = origInstance; + m_origInstance = origInstance; } void LegacyUpgradeTask::executeTask() { - setStatus(tr("Copying instance %1").arg(m_origInstance->name())); + setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(true); + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(true); - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); - connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &LegacyUpgradeTask::copyFinished); - connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &LegacyUpgradeTask::copyAborted); - m_copyFutureWatcher.setFuture(m_copyFuture); + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); + connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &LegacyUpgradeTask::copyFinished); + connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &LegacyUpgradeTask::copyAborted); + m_copyFutureWatcher.setFuture(m_copyFuture); } static QString decideVersion(const QString& currentVersion, const QString& intendedVersion) { - if(intendedVersion != currentVersion) - { - if(!intendedVersion.isEmpty()) - { - return intendedVersion; - } - else if(!currentVersion.isEmpty()) - { - return currentVersion; - } - } - else - { - if(!intendedVersion.isEmpty()) - { - return intendedVersion; - } - } - return QString(); + if(intendedVersion != currentVersion) + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + else if(!currentVersion.isEmpty()) + { + return currentVersion; + } + } + else + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + } + return QString(); } void LegacyUpgradeTask::copyFinished() { - auto successful = m_copyFuture.result(); - if(!successful) - { - emitFailed(tr("Instance folder copy failed.")); - return; - } - auto legacyInst = std::dynamic_pointer_cast(m_origInstance); + auto successful = m_copyFuture.result(); + if(!successful) + { + emitFailed(tr("Instance folder copy failed.")); + return; + } + auto legacyInst = std::dynamic_pointer_cast(m_origInstance); - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - // NOTE: this scope ensures the instance is fully saved before we emitSucceeded - { - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - inst.setName(m_instName); - inst.init(); + auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + // NOTE: this scope ensures the instance is fully saved before we emitSucceeded + { + MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); + inst.setName(m_instName); - QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId()); - if(preferredVersionNumber.isNull()) - { - // try to decide version based on the jar(s?) - preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar()); - if(preferredVersionNumber.isNull()) - { - preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar()); - if(preferredVersionNumber.isNull()) - { - emitFailed(tr("Could not decide Minecraft version.")); - return; - } - } - } - auto components = inst.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", preferredVersionNumber, true); + QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId()); + if(preferredVersionNumber.isNull()) + { + // try to decide version based on the jar(s?) + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar()); + if(preferredVersionNumber.isNull()) + { + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar()); + if(preferredVersionNumber.isNull()) + { + emitFailed(tr("Could not decide Minecraft version.")); + return; + } + } + } + auto components = inst.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", preferredVersionNumber, true); - QString jarPath = legacyInst->mainJarToPreserve(); - if(!jarPath.isNull()) - { - qDebug() << "Preserving base jar! : " << jarPath; - // FIXME: handle case when the jar is unreadable? - // TODO: check the hash, if it's the same as the upstream jar, do not do this - components->installCustomJar(jarPath); - } + QString jarPath = legacyInst->mainJarToPreserve(); + if(!jarPath.isNull()) + { + qDebug() << "Preserving base jar! : " << jarPath; + // FIXME: handle case when the jar is unreadable? + // TODO: check the hash, if it's the same as the upstream jar, do not do this + components->installCustomJar(jarPath); + } - auto jarMods = legacyInst->getJarMods(); - for(auto & jarMod: jarMods) - { - QString modPath = jarMod.filename().absoluteFilePath(); - qDebug() << "jarMod: " << modPath; - components->installJarMods({modPath}); - } + auto jarMods = legacyInst->jarModList()->allMods(); + for(auto & jarMod: jarMods) + { + QString modPath = jarMod.absoluteFilePath(); + qDebug() << "jarMod: " << modPath; + components->installJarMods({modPath}); + } - // remove all the extra garbage we no longer need - auto removeAll = [&](const QString &root, const QStringList &things) - { - for(auto &thing : things) - { - auto removePath = FS::PathCombine(root, thing); - QFileInfo stat(removePath); - if(stat.isDir()) - { - FS::deletePath(removePath); - } - else - { - QFile::remove(removePath); - } - } - }; - QStringList rootRemovables = {"modlist", "version", "instMods"}; - QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"}; - removeAll(inst.instanceRoot(), rootRemovables); - removeAll(inst.minecraftRoot(), mcRemovables); - } - emitSucceeded(); + // remove all the extra garbage we no longer need + auto removeAll = [&](const QString &root, const QStringList &things) + { + for(auto &thing : things) + { + auto removePath = FS::PathCombine(root, thing); + QFileInfo stat(removePath); + if(stat.isDir()) + { + FS::deletePath(removePath); + } + else + { + QFile::remove(removePath); + } + } + }; + QStringList rootRemovables = {"modlist", "version", "instMods"}; + QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"}; + removeAll(inst.instanceRoot(), rootRemovables); + removeAll(inst.gameRoot(), mcRemovables); + } + emitSucceeded(); } void LegacyUpgradeTask::copyAborted() { - emitFailed(tr("Instance folder copy has been aborted.")); - return; + emitFailed(tr("Instance folder copy has been aborted.")); + return; } diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.h b/api/logic/minecraft/legacy/LegacyUpgradeTask.h index a93dd0d9..e35e43b7 100644 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.h +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.h @@ -11,22 +11,20 @@ #include "BaseInstance.h" -class BaseInstanceProvider; - class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit LegacyUpgradeTask(InstancePtr origInstance); + explicit LegacyUpgradeTask(InstancePtr origInstance); protected: - //! Entry point for tasks. - virtual void executeTask() override; - void copyFinished(); - void copyAborted(); + //! Entry point for tasks. + virtual void executeTask() override; + void copyFinished(); + void copyAborted(); private: /* data */ - InstancePtr m_origInstance; - QFuture m_copyFuture; - QFutureWatcher m_copyFutureWatcher; + InstancePtr m_origInstance; + QFuture m_copyFuture; + QFutureWatcher m_copyFutureWatcher; }; diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp new file mode 100644 index 00000000..22ebd7d4 --- /dev/null +++ b/api/logic/minecraft/mod/LocalModParseTask.cpp @@ -0,0 +1,298 @@ +#include "LocalModParseTask.h" + +#include +#include +#include +#include +#include +#include + +#include "settings/INIFile.h" +#include "FileSystem.h" + +namespace { + +// NEW format +// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3 + +// OLD format: +// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc +std::shared_ptr ReadMCModInfo(QByteArray contents) +{ + auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr + { + if (!arr.at(0).isObject()) { + return nullptr; + } + std::shared_ptr details = std::make_shared(); + auto firstObj = arr.at(0).toObject(); + details->mod_id = firstObj.value("modid").toString(); + auto name = firstObj.value("name").toString(); + // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name + if(name != "Example Mod") { + details->name = name; + } + details->version = firstObj.value("version").toString(); + details->updateurl = firstObj.value("updateUrl").toString(); + auto homeurl = firstObj.value("url").toString().trimmed(); + if(!homeurl.isEmpty()) + { + // fix up url. + if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) + { + homeurl.prepend("http://"); + } + } + details->homeurl = homeurl; + details->description = firstObj.value("description").toString(); + QJsonArray authors = firstObj.value("authorList").toArray(); + if (authors.size() == 0) { + // FIXME: what is the format of this? is there any? + authors = firstObj.value("authors").toArray(); + } + + for (auto author: authors) + { + details->authors.append(author.toString()); + } + details->credits = firstObj.value("credits").toString(); + return details; + }; + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + // this is the very old format that had just the array + if (jsonDoc.isArray()) + { + return getInfoFromArray(jsonDoc.array()); + } + else if (jsonDoc.isObject()) + { + auto val = jsonDoc.object().value("modinfoversion"); + if(val.isUndefined()) { + val = jsonDoc.object().value("modListVersion"); + } + int version = val.toDouble(); + if (version != 2) + { + qCritical() << "BAD stuff happened to mod json:"; + qCritical() << contents; + return nullptr; + } + auto arrVal = jsonDoc.object().value("modlist"); + if(arrVal.isUndefined()) { + arrVal = jsonDoc.object().value("modList"); + } + if (arrVal.isArray()) + { + return getInfoFromArray(arrVal.toArray()); + } + } + return nullptr; +} + +// https://fabricmc.net/wiki/documentation:fabric_mod_json +std::shared_ptr ReadFabricModInfo(QByteArray contents) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + auto object = jsonDoc.object(); + auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0; + + std::shared_ptr details = std::make_shared(); + + details->mod_id = object.value("id").toString(); + details->version = object.value("version").toString(); + + details->name = object.contains("name") ? object.value("name").toString() : details->mod_id; + details->description = object.value("description").toString(); + + if (schemaVersion >= 1) + { + QJsonArray authors = object.value("authors").toArray(); + for (auto author: authors) + { + if(author.isObject()) { + details->authors.append(author.toObject().value("name").toString()); + } + else { + details->authors.append(author.toString()); + } + } + + if (object.contains("contact")) + { + QJsonObject contact = object.value("contact").toObject(); + + if (contact.contains("homepage")) + { + details->homeurl = contact.value("homepage").toString(); + } + } + } + return details; +} + +std::shared_ptr ReadForgeInfo(QByteArray contents) +{ + std::shared_ptr details = std::make_shared(); + // Read the data + details->name = "Minecraft Forge"; + details->mod_id = "Forge"; + details->homeurl = "http://www.minecraftforge.net/forum/"; + INIFile ini; + if (!ini.loadFile(contents)) + return details; + + QString major = ini.get("forge.major.number", "0").toString(); + QString minor = ini.get("forge.minor.number", "0").toString(); + QString revision = ini.get("forge.revision.number", "0").toString(); + QString build = ini.get("forge.build.number", "0").toString(); + + details->version = major + "." + minor + "." + revision + "." + build; + return details; +} + +std::shared_ptr ReadLiteModInfo(QByteArray contents) +{ + std::shared_ptr details = std::make_shared(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + auto object = jsonDoc.object(); + if (object.contains("name")) + { + details->mod_id = details->name = object.value("name").toString(); + } + if (object.contains("version")) + { + details->version = object.value("version").toString(""); + } + else + { + details->version = object.value("revision").toString(""); + } + details->mcversion = object.value("mcversion").toString(); + auto author = object.value("author").toString(); + if(!author.isEmpty()) { + details->authors.append(author); + } + details->description = object.value("description").toString(); + details->homeurl = object.value("url").toString(); + return details; +} + +} + +LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile): + m_token(token), + m_type(type), + m_modFile(modFile), + m_result(new Result()) +{ +} + +void LocalModParseTask::processAsZip() +{ + QuaZip zip(m_modFile.filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + if (zip.setCurrentFile("mcmod.info")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadMCModInfo(file.readAll()); + file.close(); + zip.close(); + return; + } + else if (zip.setCurrentFile("fabric.mod.json")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadFabricModInfo(file.readAll()); + file.close(); + zip.close(); + return; + } + else if (zip.setCurrentFile("forgeversion.properties")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadForgeInfo(file.readAll()); + file.close(); + zip.close(); + return; + } + + zip.close(); +} + +void LocalModParseTask::processAsFolder() +{ + QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info")); + if (mcmod_info.isFile()) + { + QFile mcmod(mcmod_info.filePath()); + if (!mcmod.open(QIODevice::ReadOnly)) + return; + auto data = mcmod.readAll(); + if (data.isEmpty() || data.isNull()) + return; + m_result->details = ReadMCModInfo(data); + } +} + +void LocalModParseTask::processAsLitemod() +{ + QuaZip zip(m_modFile.filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + if (zip.setCurrentFile("litemod.json")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadLiteModInfo(file.readAll()); + file.close(); + } + zip.close(); +} + +void LocalModParseTask::run() +{ + switch(m_type) + { + case Mod::MOD_ZIPFILE: + processAsZip(); + break; + case Mod::MOD_FOLDER: + processAsFolder(); + break; + case Mod::MOD_LITEMOD: + processAsLitemod(); + break; + default: + break; + } + emit finished(m_token); +} diff --git a/api/logic/minecraft/mod/LocalModParseTask.h b/api/logic/minecraft/mod/LocalModParseTask.h new file mode 100644 index 00000000..0f119ba6 --- /dev/null +++ b/api/logic/minecraft/mod/LocalModParseTask.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include "Mod.h" +#include "ModDetails.h" + +class LocalModParseTask : public QObject, public QRunnable +{ + Q_OBJECT +public: + struct Result { + QString id; + std::shared_ptr details; + }; + using ResultPtr = std::shared_ptr; + ResultPtr result() const { + return m_result; + } + + LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile); + void run(); + +signals: + void finished(int token); + +private: + void processAsZip(); + void processAsFolder(); + void processAsLitemod(); + +private: + int m_token; + Mod::ModType m_type; + QFileInfo m_modFile; + ResultPtr m_result; +}; diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp new file mode 100644 index 00000000..df8b406d --- /dev/null +++ b/api/logic/minecraft/mod/Mod.cpp @@ -0,0 +1,151 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "Mod.h" +#include +#include + +namespace { + +ModDetails invalidDetails; + +} + + +Mod::Mod(const QFileInfo &file) +{ + repath(file); + m_changedDateTime = file.lastModified(); +} + +void Mod::repath(const QFileInfo &file) +{ + m_file = file; + QString name_base = file.fileName(); + + m_type = Mod::MOD_UNKNOWN; + + m_mmc_id = name_base; + + if (m_file.isDir()) + { + m_type = MOD_FOLDER; + m_name = name_base; + } + else if (m_file.isFile()) + { + if (name_base.endsWith(".disabled")) + { + m_enabled = false; + name_base.chop(9); + } + else + { + m_enabled = true; + } + if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) + { + m_type = MOD_ZIPFILE; + name_base.chop(4); + } + else if (name_base.endsWith(".litemod")) + { + m_type = MOD_LITEMOD; + name_base.chop(8); + } + else + { + m_type = MOD_SINGLEFILE; + } + m_name = name_base; + } +} + +bool Mod::enable(bool value) +{ + if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) + return false; + + if (m_enabled == value) + return false; + + QString path = m_file.absoluteFilePath(); + if (value) + { + QFile foo(path); + if (!path.endsWith(".disabled")) + return false; + path.chop(9); + if (!foo.rename(path)) + return false; + } + else + { + QFile foo(path); + path += ".disabled"; + if (!foo.rename(path)) + return false; + } + repath(QFileInfo(path)); + m_enabled = value; + return true; +} + +bool Mod::destroy() +{ + m_type = MOD_UNKNOWN; + return FS::deletePath(m_file.filePath()); +} + + +const ModDetails & Mod::details() const +{ + if(!m_localDetails) + return invalidDetails; + return *m_localDetails; +} + + +QString Mod::version() const +{ + return details().version; +} + +QString Mod::name() const +{ + auto & d = details(); + if(!d.name.isEmpty()) { + return d.name; + } + return m_name; +} + +QString Mod::homeurl() const +{ + return details().homeurl; +} + +QString Mod::description() const +{ + return details().description; +} + +QStringList Mod::authors() const +{ + return details().authors; +} diff --git a/api/logic/minecraft/mod/Mod.h b/api/logic/minecraft/mod/Mod.h new file mode 100644 index 00000000..d787fb48 --- /dev/null +++ b/api/logic/minecraft/mod/Mod.h @@ -0,0 +1,117 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include + +#include "multimc_logic_export.h" + +#include "ModDetails.h" + + + +class MULTIMC_LOGIC_EXPORT Mod +{ +public: + enum ModType + { + MOD_UNKNOWN, //!< Indicates an unspecified mod type. + MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. + MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). + MOD_FOLDER, //!< The mod is in a folder on the filesystem. + MOD_LITEMOD, //!< The mod is a litemod + }; + + Mod() = default; + Mod(const QFileInfo &file); + + QFileInfo filename() const + { + return m_file; + } + QString mmc_id() const + { + return m_mmc_id; + } + ModType type() const + { + return m_type; + } + bool valid() + { + return m_type != MOD_UNKNOWN; + } + + QDateTime dateTimeChanged() const + { + return m_changedDateTime; + } + + bool enabled() const + { + return m_enabled; + } + + const ModDetails &details() const; + + QString name() const; + QString version() const; + QString homeurl() const; + QString description() const; + QStringList authors() const; + + bool enable(bool value); + + // delete all the files of this mod + bool destroy(); + + // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) + void repath(const QFileInfo &file); + + bool shouldResolve() { + return !m_resolving && !m_resolved; + } + bool isResolving() { + return m_resolving; + } + int resolutionTicket() + { + return m_resolutionTicket; + } + void setResolving(bool resolving, int resolutionTicket) { + m_resolving = resolving; + m_resolutionTicket = resolutionTicket; + } + void finishResolvingWithDetails(std::shared_ptr details){ + m_resolving = false; + m_resolved = true; + m_localDetails = details; + } + +protected: + QFileInfo m_file; + QDateTime m_changedDateTime; + QString m_mmc_id; + QString m_name; + bool m_enabled = true; + bool m_resolving = false; + bool m_resolved = false; + int m_resolutionTicket = 0; + ModType m_type = MOD_UNKNOWN; + std::shared_ptr m_localDetails; +}; diff --git a/api/logic/minecraft/mod/ModDetails.h b/api/logic/minecraft/mod/ModDetails.h new file mode 100644 index 00000000..6ab4aee7 --- /dev/null +++ b/api/logic/minecraft/mod/ModDetails.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +struct ModDetails +{ + QString mod_id; + QString name; + QString version; + QString mcversion; + QString homeurl; + QString updateurl; + QString description; + QStringList authors; + QString credits; +}; diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.cpp b/api/logic/minecraft/mod/ModFolderLoadTask.cpp new file mode 100644 index 00000000..88349877 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderLoadTask.cpp @@ -0,0 +1,18 @@ +#include "ModFolderLoadTask.h" +#include + +ModFolderLoadTask::ModFolderLoadTask(QDir dir) : + m_dir(dir), m_result(new Result()) +{ +} + +void ModFolderLoadTask::run() +{ + m_dir.refresh(); + for (auto entry : m_dir.entryInfoList()) + { + Mod m(entry); + m_result->mods[m.mmc_id()] = m; + } + emit succeeded(); +} diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.h b/api/logic/minecraft/mod/ModFolderLoadTask.h new file mode 100644 index 00000000..8d720e65 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderLoadTask.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include +#include "Mod.h" +#include + +class ModFolderLoadTask : public QObject, public QRunnable +{ + Q_OBJECT +public: + struct Result { + QMap mods; + }; + using ResultPtr = std::shared_ptr; + ResultPtr result() const { + return m_result; + } + +public: + ModFolderLoadTask(QDir dir); + void run(); +signals: + void succeeded(); +private: + QDir m_dir; + ResultPtr m_result; +}; diff --git a/api/logic/minecraft/mod/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp new file mode 100644 index 00000000..59ccaaba --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderModel.cpp @@ -0,0 +1,554 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModFolderModel.h" +#include +#include +#include +#include +#include +#include +#include +#include "ModFolderLoadTask.h" +#include +#include +#include "LocalModParseTask.h" + +ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) +{ + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + m_watcher = new QFileSystemWatcher(this); + connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); +} + +void ModFolderModel::startWatching() +{ + if(is_watching) + return; + + update(); + + is_watching = m_watcher->addPath(m_dir.absolutePath()); + if (is_watching) + { + qDebug() << "Started watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to start watching " << m_dir.absolutePath(); + } +} + +void ModFolderModel::stopWatching() +{ + if(!is_watching) + return; + + is_watching = !m_watcher->removePath(m_dir.absolutePath()); + if (!is_watching) + { + qDebug() << "Stopped watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + } +} + +bool ModFolderModel::update() +{ + if (!isValid()) { + return false; + } + if(m_update) { + scheduled_update = true; + return true; + } + + auto task = new ModFolderLoadTask(m_dir); + m_update = task->result(); + QThreadPool *threadPool = QThreadPool::globalInstance(); + connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate); + threadPool->start(task); + return true; +} + +void ModFolderModel::finishUpdate() +{ + QSet currentSet = modsIndex.keys().toSet(); + auto & newMods = m_update->mods; + QSet newSet = newMods.keys().toSet(); + + // see if the kept mods changed in some way + { + QSet kept = currentSet; + kept.intersect(newSet); + for(auto & keptMod: kept) { + auto & newMod = newMods[keptMod]; + auto row = modsIndex[keptMod]; + auto & currentMod = mods[row]; + if(newMod.dateTimeChanged() == currentMod.dateTimeChanged()) { + // no significant change, ignore... + continue; + } + auto & oldMod = mods[row]; + if(oldMod.isResolving()) { + activeTickets.remove(oldMod.resolutionTicket()); + } + oldMod = newMod; + resolveMod(mods[row]); + emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); + } + } + + // remove mods no longer present + { + QSet removed = currentSet; + QList removedRows; + removed.subtract(newSet); + for(auto & removedMod: removed) { + removedRows.append(modsIndex[removedMod]); + } + std::sort(removedRows.begin(), removedRows.end(), std::greater()); + for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) { + int removedIndex = *iter; + beginRemoveRows(QModelIndex(), removedIndex, removedIndex); + auto removedIter = mods.begin() + removedIndex; + if(removedIter->isResolving()) { + activeTickets.remove(removedIter->resolutionTicket()); + } + mods.erase(removedIter); + endRemoveRows(); + } + } + + // add new mods to the end + { + QSet added = newSet; + added.subtract(currentSet); + beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1); + for(auto & addedMod: added) { + mods.append(newMods[addedMod]); + resolveMod(mods.last()); + } + endInsertRows(); + } + + // update index + { + modsIndex.clear(); + int idx = 0; + for(auto & mod: mods) { + modsIndex[mod.mmc_id()] = idx; + idx++; + } + } + + m_update.reset(); + + emit updateFinished(); + + if(scheduled_update) { + scheduled_update = false; + update(); + } +} + +void ModFolderModel::resolveMod(Mod& m) +{ + if(!m.shouldResolve()) { + return; + } + + auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename()); + auto result = task->result(); + result->id = m.mmc_id(); + activeTickets.insert(nextResolutionTicket, result); + m.setResolving(true, nextResolutionTicket); + nextResolutionTicket++; + QThreadPool *threadPool = QThreadPool::globalInstance(); + connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse); + threadPool->start(task); +} + +void ModFolderModel::finishModParse(int token) +{ + auto iter = activeTickets.find(token); + if(iter == activeTickets.end()) { + return; + } + auto result = *iter; + activeTickets.remove(token); + int row = modsIndex[result->id]; + auto & mod = mods[row]; + mod.finishResolvingWithDetails(result->details); + emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); +} + +void ModFolderModel::disableInteraction(bool disabled) +{ + if (interaction_disabled == disabled) { + return; + } + interaction_disabled = disabled; + if(size()) { + emit dataChanged(index(0), index(size() - 1)); + } +} + +void ModFolderModel::directoryChanged(QString path) +{ + update(); +} + +bool ModFolderModel::isValid() +{ + return m_dir.exists() && m_dir.isReadable(); +} + +// FIXME: this does not take disabled mod (with extra .disable extension) into account... +bool ModFolderModel::installMod(const QString &filename) +{ + if(interaction_disabled) { + return false; + } + + // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName + auto originalPath = FS::NormalizePath(filename); + QFileInfo fileinfo(originalPath); + + if (!fileinfo.exists() || !fileinfo.isReadable()) + { + qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; + return false; + } + qDebug() << "installing: " << fileinfo.absoluteFilePath(); + + Mod installedMod(fileinfo); + if (!installedMod.valid()) + { + qDebug() << originalPath << "is not a valid mod. Ignoring it."; + return false; + } + + auto type = installedMod.type(); + if (type == Mod::MOD_UNKNOWN) + { + qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it."; + return false; + } + + auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName())); + if(originalPath == newpath) + { + qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; + return false; + } + + if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) + { + if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled"))) + { + if(!QFile::remove(newpath)) + { + // FIXME: report error in a user-visible way + qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; + return false; + } + qDebug() << newpath << "has been deleted."; + } + if (!QFile::copy(fileinfo.filePath(), newpath)) + { + qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; + // FIXME: report error in a user-visible way + return false; + } + FS::updateTimestamp(newpath); + installedMod.repath(newpath); + update(); + return true; + } + else if (type == Mod::MOD_FOLDER) + { + QString from = fileinfo.filePath(); + if(QFile::exists(newpath)) + { + qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath; + return false; + } + + if (!FS::copy(from, newpath)()) + { + qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed."; + return false; + } + installedMod.repath(newpath); + update(); + return true; + } + return false; +} + +bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) +{ + if(interaction_disabled) { + return false; + } + + if(indexes.isEmpty()) + return true; + + for (auto index: indexes) + { + if(index.column() != 0) { + continue; + } + setModStatus(index.row(), enable); + } + return true; +} + +bool ModFolderModel::deleteMods(const QModelIndexList& indexes) +{ + if(interaction_disabled) { + return false; + } + + if(indexes.isEmpty()) + return true; + + for (auto i: indexes) + { + Mod &m = mods[i.row()]; + m.destroy(); + } + return true; +} + +int ModFolderModel::columnCount(const QModelIndex &parent) const +{ + return NUM_COLUMNS; +} + +QVariant ModFolderModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= mods.size()) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + switch (column) + { + case NameColumn: + return mods[row].name(); + case VersionColumn: { + switch(mods[row].type()) { + case Mod::MOD_FOLDER: + return tr("Folder"); + case Mod::MOD_SINGLEFILE: + return tr("File"); + default: + break; + } + return mods[row].version(); + } + case DateColumn: + return mods[row].dateTimeChanged(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return mods[row].mmc_id(); + + case Qt::CheckStateRole: + switch (column) + { + case ActiveColumn: + return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return false; + } + + if (role == Qt::CheckStateRole) + { + return setModStatus(index.row(), Toggle); + } + return false; +} + +bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action) +{ + if(row < 0 || row >= mods.size()) { + return false; + } + + auto &mod = mods[row]; + bool desiredStatus; + switch(action) { + case Enable: + desiredStatus = true; + break; + case Disable: + desiredStatus = false; + break; + case Toggle: + default: + desiredStatus = !mod.enabled(); + break; + } + + if(desiredStatus == mod.enabled()) { + return true; + } + + // preserve the row, but change its ID + auto oldId = mod.mmc_id(); + if(!mod.enable(!mod.enabled())) { + return false; + } + auto newId = mod.mmc_id(); + if(modsIndex.contains(newId)) { + // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled + // But is it necessary? + } + modsIndex.remove(oldId); + modsIndex[newId] = row; + emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); + return true; +} + +QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case ActiveColumn: + return QString(); + case NameColumn: + return tr("Name"); + case VersionColumn: + return tr("Version"); + case DateColumn: + return tr("Last changed"); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case ActiveColumn: + return tr("Is the mod enabled?"); + case NameColumn: + return tr("The name of the mod."); + case VersionColumn: + return tr("The version of the mod."); + case DateColumn: + return tr("The date and time this mod was last changed (or added)."); + default: + return QVariant(); + } + default: + return QVariant(); + } + return QVariant(); +} + +Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + auto flags = defaultFlags; + if(interaction_disabled) { + flags &= ~Qt::ItemIsDropEnabled; + } + else + { + flags |= Qt::ItemIsDropEnabled; + if(index.isValid()) { + flags |= Qt::ItemIsUserCheckable; + } + } + return flags; +} + +Qt::DropActions ModFolderModel::supportedDropActions() const +{ + // copy from outside, move from within and other mod lists + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList ModFolderModel::mimeTypes() const +{ + QStringList types; + types << "text/uri-list"; + return types; +} + +bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) +{ + if (action == Qt::IgnoreAction) + { + return true; + } + + // check if the action is supported + if (!data || !(action & supportedDropActions())) + { + return false; + } + + // files dropped from outside? + if (data->hasUrls()) + { + auto urls = data->urls(); + for (auto url : urls) + { + // only local files may be dropped... + if (!url.isLocalFile()) + { + continue; + } + // TODO: implement not only copy, but also move + // FIXME: handle errors here + installMod(url.toLocalFile()); + } + return true; + } + return false; +} diff --git a/api/logic/minecraft/mod/ModFolderModel.h b/api/logic/minecraft/mod/ModFolderModel.h new file mode 100644 index 00000000..8394e405 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderModel.h @@ -0,0 +1,145 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Mod.h" + +#include "multimc_logic_export.h" +#include "ModFolderLoadTask.h" +#include "LocalModParseTask.h" + +class LegacyInstance; +class BaseInstance; +class QFileSystemWatcher; + +/** + * A legacy mod list. + * Backed by a folder. + */ +class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Columns + { + ActiveColumn = 0, + NameColumn, + VersionColumn, + DateColumn, + NUM_COLUMNS + }; + enum ModStatusAction { + Disable, + Enable, + Toggle + }; + ModFolderModel(const QString &dir); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::DropActions supportedDropActions() const override; + + /// flags, mostly to support drag&drop + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + QStringList mimeTypes() const override; + bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; + + virtual int rowCount(const QModelIndex &) const override + { + return size(); + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual int columnCount(const QModelIndex &parent) const override; + + size_t size() const + { + return mods.size(); + } + ; + bool empty() const + { + return size() == 0; + } + Mod &operator[](size_t index) + { + return mods[index]; + } + + /// Reloads the mod list and returns true if the list changed. + bool update(); + + /** + * Adds the given mod to the list at the given index - if the list supports custom ordering + */ + bool installMod(const QString& filename); + + /// Deletes all the selected mods + bool deleteMods(const QModelIndexList &indexes); + + /// Enable or disable listed mods + bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); + + void startWatching(); + void stopWatching(); + + bool isValid(); + + QDir dir() + { + return m_dir; + } + + const QList & allMods() + { + return mods; + } + +public slots: + void disableInteraction(bool disabled); + +private +slots: + void directoryChanged(QString path); + void finishUpdate(); + void finishModParse(int token); + +signals: + void updateFinished(); + +private: + void resolveMod(Mod& m); + bool setModStatus(int index, ModStatusAction action); + +protected: + QFileSystemWatcher *m_watcher; + bool is_watching = false; + ModFolderLoadTask::ResultPtr m_update; + bool scheduled_update = false; + bool interaction_disabled = false; + QDir m_dir; + QMap modsIndex; + QMap activeTickets; + int nextResolutionTicket = 0; + QList mods; +}; diff --git a/api/logic/minecraft/mod/ModFolderModel_test.cpp b/api/logic/minecraft/mod/ModFolderModel_test.cpp new file mode 100644 index 00000000..76f16ed5 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderModel_test.cpp @@ -0,0 +1,53 @@ + +#include +#include +#include "TestUtil.h" + +#include "FileSystem.h" +#include "minecraft/mod/ModFolderModel.h" + +class ModFolderModelTest : public QObject +{ + Q_OBJECT + +private +slots: + // test for GH-1178 - install a folder with files to a mod list + void test_1178() + { + // source + QString source = QFINDTESTDATA("data/test_folder"); + + // sanity check + QVERIFY(!source.endsWith('/')); + + auto verify = [](QString path) + { + QDir target_dir(FS::PathCombine(path, "test_folder")); + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // 1. test with no trailing / + { + QString folder = source; + QTemporaryDir tempDir; + ModFolderModel m(tempDir.path()); + m.installMod(folder); + verify(tempDir.path()); + } + + // 2. test with trailing / + { + QString folder = source + '/'; + QTemporaryDir tempDir; + ModFolderModel m(tempDir.path()); + m.installMod(folder); + verify(tempDir.path()); + } + } +}; + +QTEST_GUILESS_MAIN(ModFolderModelTest) + +#include "ModFolderModel_test.moc" diff --git a/api/logic/minecraft/testdata/lib-native-arch.json b/api/logic/minecraft/testdata/lib-native-arch.json index a73aac54..501826ae 100644 --- a/api/logic/minecraft/testdata/lib-native-arch.json +++ b/api/logic/minecraft/testdata/lib-native-arch.json @@ -1,46 +1,46 @@ { - "downloads": { - "classifiers": { - "natives-osx": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", - "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", - "size": 418331, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" - }, - "natives-windows-32": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", - "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", - "size": 386792, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" - }, - "natives-windows-64": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", - "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", - "size": 463390, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "tv.twitch:twitch-platform:5.16", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows-${arch}" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "linux" - } - } - ] + "downloads": { + "classifiers": { + "natives-osx": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", + "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", + "size": 418331, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" + }, + "natives-windows-32": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", + "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", + "size": 386792, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" + }, + "natives-windows-64": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", + "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", + "size": 463390, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "tv.twitch:twitch-platform:5.16", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows-${arch}" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "linux" + } + } + ] } diff --git a/api/logic/minecraft/testdata/lib-native.json b/api/logic/minecraft/testdata/lib-native.json index 0a95b2b9..5b9f3b55 100644 --- a/api/logic/minecraft/testdata/lib-native.json +++ b/api/logic/minecraft/testdata/lib-native.json @@ -1,52 +1,52 @@ { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", - "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", - "size": 22, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" - }, - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", - "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", - "size": 578680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", - "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", - "size": 426822, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", - "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", - "size": 613748, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", + "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", + "size": 22, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" + }, + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", + "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", + "size": 578680, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" + }, + "natives-osx": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", + "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", + "size": 426822, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" + }, + "natives-windows": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", + "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", + "size": 613748, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx" + } + } + ] } diff --git a/api/logic/minecraft/testdata/lib-simple.json b/api/logic/minecraft/testdata/lib-simple.json index 3ef0f490..90bbff07 100644 --- a/api/logic/minecraft/testdata/lib-simple.json +++ b/api/logic/minecraft/testdata/lib-simple.json @@ -1,11 +1,11 @@ { - "downloads": { - "artifact": { - "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", - "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", - "size": 5618, - "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" - } - }, - "name": "com.paulscode:codecwav:20101023" + "downloads": { + "artifact": { + "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", + "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", + "size": 5618, + "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" + } + }, + "name": "com.paulscode:codecwav:20101023" } diff --git a/api/logic/minecraft/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp index 2ad2b5b2..051b1279 100644 --- a/api/logic/minecraft/update/AssetUpdateTask.cpp +++ b/api/logic/minecraft/update/AssetUpdateTask.cpp @@ -7,96 +7,101 @@ AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst) { - m_inst = inst; + m_inst = inst; } + +AssetUpdateTask::~AssetUpdateTask() +{ +} + void AssetUpdateTask::executeTask() { - setStatus(tr("Updating assets index...")); - auto components = m_inst->getComponentList(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - QUrl indexUrl = assets->url; - QString localPath = assets->id + ".json"; - auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name())); + setStatus(tr("Updating assets index...")); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto assets = profile->getMinecraftAssets(); + QUrl indexUrl = assets->url; + QString localPath = assets->id + ".json"; + auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name())); - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", localPath); - entry->setStale(true); - auto hexSha1 = assets->sha1.toLatin1(); - qDebug() << "Asset index SHA1:" << hexSha1; - auto dl = Net::Download::makeCached(indexUrl, entry); - auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - job->addNetAction(dl); + auto metacache = ENV.metacache(); + auto entry = metacache->resolveEntry("asset_indexes", localPath); + entry->setStale(true); + auto hexSha1 = assets->sha1.toLatin1(); + qDebug() << "Asset index SHA1:" << hexSha1; + auto dl = Net::Download::makeCached(indexUrl, entry); + auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + job->addNetAction(dl); - downloadJob.reset(job); + downloadJob.reset(job); - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); + connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - qDebug() << m_inst->name() << ": Starting asset index download"; - downloadJob->start(); + qDebug() << m_inst->name() << ": Starting asset index download"; + downloadJob->start(); } bool AssetUpdateTask::canAbort() const { - return true; + return true; } void AssetUpdateTask::assetIndexFinished() { - AssetsIndex index; - qDebug() << m_inst->name() << ": Finished asset index download"; + AssetsIndex index; + qDebug() << m_inst->name() << ": Finished asset index download"; - auto components = m_inst->getComponentList(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto assets = profile->getMinecraftAssets(); - QString asset_fname = "assets/indexes/" + assets->id + ".json"; - // FIXME: this looks like a job for a generic validator based on json schema? - if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index)) - { - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); - metacache->evictEntry(entry); - emitFailed(tr("Failed to read the assets index!")); - } + QString asset_fname = "assets/indexes/" + assets->id + ".json"; + // FIXME: this looks like a job for a generic validator based on json schema? + if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, index)) + { + auto metacache = ENV.metacache(); + auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); + metacache->evictEntry(entry); + emitFailed(tr("Failed to read the assets index!")); + } - auto job = index.getDownloadJob(); - if(job) - { - setStatus(tr("Getting the assets files from Mojang...")); - downloadJob = job; - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - downloadJob->start(); - return; - } - emitSucceeded(); + auto job = index.getDownloadJob(); + if(job) + { + setStatus(tr("Getting the assets files from Mojang...")); + downloadJob = job; + connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); + connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + downloadJob->start(); + return; + } + emitSucceeded(); } void AssetUpdateTask::assetIndexFailed(QString reason) { - qDebug() << m_inst->name() << ": Failed asset index download"; - emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); + qDebug() << m_inst->name() << ": Failed asset index download"; + emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); } void AssetUpdateTask::assetsFailed(QString reason) { - emitFailed(tr("Failed to download assets:\n%1").arg(reason)); + emitFailed(tr("Failed to download assets:\n%1").arg(reason)); } bool AssetUpdateTask::abort() { - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted FMLLibrariesTask"; - } - return true; + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted AssetUpdateTask"; + } + return true; } diff --git a/api/logic/minecraft/update/AssetUpdateTask.h b/api/logic/minecraft/update/AssetUpdateTask.h index c666faa6..fdfa8f1c 100644 --- a/api/logic/minecraft/update/AssetUpdateTask.h +++ b/api/logic/minecraft/update/AssetUpdateTask.h @@ -5,22 +5,24 @@ class MinecraftInstance; class AssetUpdateTask : public Task { - Q_OBJECT + Q_OBJECT public: - AssetUpdateTask(MinecraftInstance * inst); - void executeTask() override; + AssetUpdateTask(MinecraftInstance * inst); + virtual ~AssetUpdateTask(); - bool canAbort() const override; + void executeTask() override; + + bool canAbort() const override; private slots: - void assetIndexFinished(); - void assetIndexFailed(QString reason); - void assetsFailed(QString reason); + void assetIndexFinished(); + void assetIndexFailed(QString reason); + void assetsFailed(QString reason); public slots: - bool abort() override; + bool abort() override; private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; + MinecraftInstance *m_inst; + NetJobPtr downloadJob; }; diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp index 1bd339e4..52a8375b 100644 --- a/api/logic/minecraft/update/FMLLibrariesTask.cpp +++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp @@ -7,125 +7,124 @@ FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst) { - m_inst = inst; + m_inst = inst; } void FMLLibrariesTask::executeTask() { - // Get the mod list - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - auto components = inst->getComponentList(); - auto profile = components->getProfile(); + // Get the mod list + MinecraftInstance *inst = (MinecraftInstance *)m_inst; + auto components = inst->getComponentList(); + auto profile = components->getProfile(); - if (!profile->hasTrait("legacyFML")) - { - emitSucceeded(); - return; - } + if (!profile->hasTrait("legacyFML")) + { + emitSucceeded(); + return; + } - QString version = components->getComponentVersion("net.minecraft"); - auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; - if (!fmlLibsMapping.contains(version)) - { - emitSucceeded(); - return; - } + QString version = components->getComponentVersion("net.minecraft"); + auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; + if (!fmlLibsMapping.contains(version)) + { + emitSucceeded(); + return; + } - auto &libList = fmlLibsMapping[version]; + auto &libList = fmlLibsMapping[version]; - // determine if we need some libs for FML or forge - setStatus(tr("Checking for FML libraries...")); - if(!components->getComponent("net.minecraftforge")) - { - emitSucceeded(); - return; - } + // determine if we need some libs for FML or forge + setStatus(tr("Checking for FML libraries...")); + if(!components->getComponent("net.minecraftforge")) + { + emitSucceeded(); + return; + } - // now check the lib folder inside the instance for files. - for (auto &lib : libList) - { - QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); - if (libInfo.exists()) - continue; - fmlLibsToProcess.append(lib); - } + // now check the lib folder inside the instance for files. + for (auto &lib : libList) + { + QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); + if (libInfo.exists()) + continue; + fmlLibsToProcess.append(lib); + } - // if everything is in place, there's nothing to do here... - if (fmlLibsToProcess.isEmpty()) - { - emitSucceeded(); - return; - } + // if everything is in place, there's nothing to do here... + if (fmlLibsToProcess.isEmpty()) + { + emitSucceeded(); + return; + } - // download missing libs to our place - setStatus(tr("Dowloading FML libraries...")); - auto dljob = new NetJob("FML libraries"); - auto metacache = ENV.metacache(); - for (auto &lib : fmlLibsToProcess) - { - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename - : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); - } + // download missing libs to our place + setStatus(tr("Dowloading FML libraries...")); + auto dljob = new NetJob("FML libraries"); + auto metacache = ENV.metacache(); + for (auto &lib : fmlLibsToProcess) + { + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + QString urlString = (lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL : URLConstants::FMLLIBS_FORGE_BASE_URL) + lib.filename; + dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); + } - connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); - connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); - connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); - downloadJob.reset(dljob); - downloadJob->start(); + connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); + connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); + connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); + downloadJob.reset(dljob); + downloadJob->start(); } bool FMLLibrariesTask::canAbort() const { - return true; + return true; } void FMLLibrariesTask::fmllibsFinished() { - downloadJob.reset(); - if (!fmlLibsToProcess.isEmpty()) - { - setStatus(tr("Copying FML libraries into the instance...")); - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - auto metacache = ENV.metacache(); - int index = 0; - for (auto &lib : fmlLibsToProcess) - { - progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - auto path = FS::PathCombine(inst->libDir(), lib.filename); - if (!FS::ensureFilePathExists(path)) - { - emitFailed(tr("Failed creating FML library folder inside the instance.")); - return; - } - if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) - { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); - return; - } - index++; - } - progress(index, fmlLibsToProcess.size()); - } - emitSucceeded(); + downloadJob.reset(); + if (!fmlLibsToProcess.isEmpty()) + { + setStatus(tr("Copying FML libraries into the instance...")); + MinecraftInstance *inst = (MinecraftInstance *)m_inst; + auto metacache = ENV.metacache(); + int index = 0; + for (auto &lib : fmlLibsToProcess) + { + progress(index, fmlLibsToProcess.size()); + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + auto path = FS::PathCombine(inst->libDir(), lib.filename); + if (!FS::ensureFilePathExists(path)) + { + emitFailed(tr("Failed creating FML library folder inside the instance.")); + return; + } + if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) + { + emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); + return; + } + index++; + } + progress(index, fmlLibsToProcess.size()); + } + emitSucceeded(); } void FMLLibrariesTask::fmllibsFailed(QString reason) { - QStringList failed = downloadJob->getFailedFiles(); - QString failed_all = failed.join("\n"); - emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); + QStringList failed = downloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); } bool FMLLibrariesTask::abort() { - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted FMLLibrariesTask"; - } - return true; + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted FMLLibrariesTask"; + } + return true; } diff --git a/api/logic/minecraft/update/FMLLibrariesTask.h b/api/logic/minecraft/update/FMLLibrariesTask.h index 85f45468..a1e70ed4 100644 --- a/api/logic/minecraft/update/FMLLibrariesTask.h +++ b/api/logic/minecraft/update/FMLLibrariesTask.h @@ -7,24 +7,25 @@ class MinecraftInstance; class FMLLibrariesTask : public Task { - Q_OBJECT + Q_OBJECT public: - FMLLibrariesTask(MinecraftInstance * inst); + FMLLibrariesTask(MinecraftInstance * inst); + virtual ~FMLLibrariesTask() {}; - void executeTask() override; + void executeTask() override; - bool canAbort() const override; + bool canAbort() const override; private slots: - void fmllibsFinished(); - void fmllibsFailed(QString reason); + void fmllibsFinished(); + void fmllibsFailed(QString reason); public slots: - bool abort() override; + bool abort() override; private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; - QList fmlLibsToProcess; + MinecraftInstance *m_inst; + NetJobPtr downloadJob; + QList fmlLibsToProcess; }; diff --git a/api/logic/minecraft/update/FoldersTask.cpp b/api/logic/minecraft/update/FoldersTask.cpp index 34e2292a..e2b1bb48 100644 --- a/api/logic/minecraft/update/FoldersTask.cpp +++ b/api/logic/minecraft/update/FoldersTask.cpp @@ -3,19 +3,19 @@ #include FoldersTask::FoldersTask(MinecraftInstance * inst) - :Task() + :Task() { - m_inst = inst; + m_inst = inst; } void FoldersTask::executeTask() { - // Make directories - QDir mcDir(m_inst->minecraftRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) - { - emitFailed(tr("Failed to create folder for minecraft binaries.")); - return; - } - emitSucceeded(); + // Make directories + QDir mcDir(m_inst->gameRoot()); + if (!mcDir.exists() && !mcDir.mkpath(".")) + { + emitFailed(tr("Failed to create folder for minecraft binaries.")); + return; + } + emitSucceeded(); } diff --git a/api/logic/minecraft/update/FoldersTask.h b/api/logic/minecraft/update/FoldersTask.h index 6e669b1e..f6ed5e6d 100644 --- a/api/logic/minecraft/update/FoldersTask.h +++ b/api/logic/minecraft/update/FoldersTask.h @@ -5,11 +5,13 @@ class MinecraftInstance; class FoldersTask : public Task { - Q_OBJECT + Q_OBJECT public: - FoldersTask(MinecraftInstance * inst); - void executeTask() override; + FoldersTask(MinecraftInstance * inst); + virtual ~FoldersTask() {}; + + void executeTask() override; private: - MinecraftInstance *m_inst; + MinecraftInstance *m_inst; }; diff --git a/api/logic/minecraft/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp index 0bec61c1..912f492b 100644 --- a/api/logic/minecraft/update/LibrariesTask.cpp +++ b/api/logic/minecraft/update/LibrariesTask.cpp @@ -5,86 +5,85 @@ LibrariesTask::LibrariesTask(MinecraftInstance * inst) { - m_inst = inst; + m_inst = inst; } void LibrariesTask::executeTask() { - setStatus(tr("Getting the library files from Mojang...")); - qDebug() << m_inst->name() << ": downloading libraries"; - MinecraftInstance *inst = (MinecraftInstance *)m_inst; + setStatus(tr("Getting the library files from Mojang...")); + qDebug() << m_inst->name() << ": downloading libraries"; + MinecraftInstance *inst = (MinecraftInstance *)m_inst; - // Build a list of URLs that will need to be downloaded. - auto components = inst->getComponentList(); - auto profile = components->getProfile(); + // Build a list of URLs that will need to be downloaded. + auto components = inst->getComponentList(); + auto profile = components->getProfile(); - auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); - downloadJob.reset(job); + auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); + downloadJob.reset(job); - auto metacache = ENV.metacache(); - QList brokenLocalLibs; - QStringList failedFiles; - auto createJob = [&](const LibraryPtr & lib) - { - if(!lib) - { - emitFailed(tr("Null jar is specified in the metadata, aborting.")); - return; - } - auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles, inst->getLocalLibraryPath()); - for(auto dl : dls) - { - downloadJob->addNetAction(dl); - } - }; - auto createJobs = [&](const QList & libs) - { - for (auto lib : libs) - { - createJob(lib); - } - }; - createJobs(profile->getLibraries()); - createJobs(profile->getNativeLibraries()); - createJobs(profile->getJarMods()); - createJob(profile->getMainJar()); + auto metacache = ENV.metacache(); - // FIXME: this is never filled!!!! - if (!brokenLocalLibs.empty()) - { - downloadJob.reset(); - QString failed_all = failedFiles.join("\n"); - emitFailed(tr("Some libraries marked as 'local' are missing their jar " - "files:\n%1\n\nYou'll have to correct this problem manually. If this is " - "an externally tracked instance, make sure to run it at least once " - "outside of MultiMC.").arg(failed_all)); - return; - } - connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); - connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); - downloadJob->start(); + auto processArtifactPool = [&](const QList & pool, QStringList & errors, const QString & localPath) + { + for (auto lib : pool) + { + if(!lib) + { + emitFailed(tr("Null jar is specified in the metadata, aborting.")); + return false; + } + auto dls = lib->getDownloads(currentSystem, metacache.get(), errors, localPath); + for(auto dl : dls) + { + downloadJob->addNetAction(dl); + } + } + return true; + }; + + QStringList failedLocalLibraries; + QList libArtifactPool; + libArtifactPool.append(profile->getLibraries()); + libArtifactPool.append(profile->getNativeLibraries()); + libArtifactPool.append(profile->getMainJar()); + processArtifactPool(libArtifactPool, failedLocalLibraries, inst->getLocalLibraryPath()); + + QStringList failedLocalJarMods; + processArtifactPool(profile->getJarMods(), failedLocalJarMods, inst->jarModsDir()); + + if (!failedLocalJarMods.empty() || !failedLocalLibraries.empty()) + { + downloadJob.reset(); + QString failed_all = (failedLocalLibraries + failedLocalJarMods).join("\n"); + emitFailed(tr("Some artifacts marked as 'local' are missing their files:\n%1\n\nYou need to either add the files, or removed the packages that require them.\nYou'll have to correct this problem manually.").arg(failed_all)); + return; + } + + connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); + connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); + connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); + downloadJob->start(); } bool LibrariesTask::canAbort() const { - return true; + return true; } void LibrariesTask::jarlibFailed(QString reason) { - emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason)); + emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason)); } bool LibrariesTask::abort() { - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted LibrariesTask"; - } - return true; + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted LibrariesTask"; + } + return true; } diff --git a/api/logic/minecraft/update/LibrariesTask.h b/api/logic/minecraft/update/LibrariesTask.h index d06a5037..49f76932 100644 --- a/api/logic/minecraft/update/LibrariesTask.h +++ b/api/logic/minecraft/update/LibrariesTask.h @@ -5,21 +5,22 @@ class MinecraftInstance; class LibrariesTask : public Task { - Q_OBJECT + Q_OBJECT public: - LibrariesTask(MinecraftInstance * inst); + LibrariesTask(MinecraftInstance * inst); + virtual ~LibrariesTask() {}; - void executeTask() override; + void executeTask() override; - bool canAbort() const override; + bool canAbort() const override; private slots: - void jarlibFailed(QString reason); + void jarlibFailed(QString reason); public slots: - bool abort() override; + bool abort() override; private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; + MinecraftInstance *m_inst; + NetJobPtr downloadJob; }; diff --git a/api/logic/modplatform/flame/FileResolvingTask.cpp b/api/logic/modplatform/flame/FileResolvingTask.cpp index efc73621..295574f0 100644 --- a/api/logic/modplatform/flame/FileResolvingTask.cpp +++ b/api/logic/modplatform/flame/FileResolvingTask.cpp @@ -1,117 +1,63 @@ #include "FileResolvingTask.h" #include "Json.h" -const char * metabase = "https://cursemeta.dries007.net"; +namespace { + const char * metabase = "https://cursemeta.dries007.net"; +} Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) - : m_toProcess(toProcess) + : m_toProcess(toProcess) { } void Flame::FileResolvingTask::executeTask() { - setStatus(tr("Resolving mod IDs...")); - setProgress(0, m_toProcess.files.size()); - m_dljob.reset(new NetJob("Mod id resolver")); - results.resize(m_toProcess.files.size()); - int index = 0; - for(auto & file: m_toProcess.files) - { - auto projectIdStr = QString::number(file.projectId); - auto fileIdStr = QString::number(file.fileId); - QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); - auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); - m_dljob->addNetAction(dl); - index ++; - } - connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); - m_dljob->start(); + setStatus(tr("Resolving mod IDs...")); + setProgress(0, m_toProcess.files.size()); + m_dljob.reset(new NetJob("Mod id resolver")); + results.resize(m_toProcess.files.size()); + int index = 0; + for(auto & file: m_toProcess.files) + { + auto projectIdStr = QString::number(file.projectId); + auto fileIdStr = QString::number(file.fileId); + QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); + auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); + m_dljob->addNetAction(dl); + index ++; + } + connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); + m_dljob->start(); } void Flame::FileResolvingTask::netJobFinished() { - bool failed = false; - int index = 0; - for(auto & bytes: results) - { - try - { - auto doc = Json::requireDocument(bytes); - auto obj = Json::requireObject(doc); - auto & out = m_toProcess.files[index]; - // result code signifies true failure. - if(obj.contains("code")) - { - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a negative result:"; - qCritical() << bytes; - failed = true; - continue; - } - out.fileName = Json::requireString(obj, "FileNameOnDisk"); - QString rawUrl = Json::requireString(obj, "DownloadURL"); - out.url = QUrl(rawUrl, QUrl::TolerantMode); - if(!out.url.isValid()) - { - throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); - } - // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience - // It is also optional - QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); - if(!projObj.isEmpty()) - { - QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); - if(strType == "singlefile") - { - out.type = File::Type::SingleFile; - } - else if(strType == "ctoc") - { - out.type = File::Type::Ctoc; - } - else if(strType == "cmod2") - { - out.type = File::Type::Cmod2; - } - else if(strType == "mod") - { - out.type = File::Type::Mod; - } - else if(strType == "folder") - { - out.type = File::Type::Folder; - } - else if(strType == "modpack") - { - out.type = File::Type::Modpack; - } - else - { - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType; - out.type = File::Type::Unknown; - failed = true; - continue; - } - out.targetFolder = Json::ensureString(projObj, "Path", "mods"); - } - out.resolved = true; - } - catch(JSONValidationError & e) - { - auto & out = m_toProcess.files[index]; - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; - qCritical() << e.cause(); - qCritical() << "JSON:"; - qCritical() << bytes; - failed = true; - } - index++; - } - if(!failed) - { - emitSucceeded(); - } - else - { - emitFailed(tr("Some mod ID resolving tasks failed.")); - } + bool failed = false; + int index = 0; + for(auto & bytes: results) + { + auto & out = m_toProcess.files[index]; + try + { + failed &= (!out.parseFromBytes(bytes)); + } + catch (const JSONValidationError &e) + { + + qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; + qCritical() << e.cause(); + qCritical() << "JSON:"; + qCritical() << bytes; + failed = true; + } + index++; + } + if(!failed) + { + emitSucceeded(); + } + else + { + emitFailed(tr("Some mod ID resolving tasks failed.")); + } } diff --git a/api/logic/modplatform/flame/FileResolvingTask.h b/api/logic/modplatform/flame/FileResolvingTask.h index 00658452..5679b907 100644 --- a/api/logic/modplatform/flame/FileResolvingTask.h +++ b/api/logic/modplatform/flame/FileResolvingTask.h @@ -10,23 +10,25 @@ namespace Flame { class MULTIMC_LOGIC_EXPORT FileResolvingTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit FileResolvingTask(Flame::Manifest &toProcess); - const Flame::Manifest &getResults() const - { - return m_toProcess; - } + explicit FileResolvingTask(Flame::Manifest &toProcess); + virtual ~FileResolvingTask() {}; + + const Flame::Manifest &getResults() const + { + return m_toProcess; + } protected: - virtual void executeTask() override; + virtual void executeTask() override; protected slots: - void netJobFinished(); + void netJobFinished(); private: /* data */ - Flame::Manifest m_toProcess; - QVector results; - NetJobPtr m_dljob; + Flame::Manifest m_toProcess; + QVector results; + NetJobPtr m_dljob; }; } diff --git a/api/logic/modplatform/flame/PackManifest.cpp b/api/logic/modplatform/flame/PackManifest.cpp index 6a9324fe..1db0a161 100644 --- a/api/logic/modplatform/flame/PackManifest.cpp +++ b/api/logic/modplatform/flame/PackManifest.cpp @@ -3,64 +3,124 @@ static void loadFileV1(Flame::File & f, QJsonObject & file) { - f.projectId = Json::requireInteger(file, "projectID"); - f.fileId = Json::requireInteger(file, "fileID"); - f.required = Json::ensureBoolean(file, QString("required"), true); + f.projectId = Json::requireInteger(file, "projectID"); + f.fileId = Json::requireInteger(file, "fileID"); + f.required = Json::ensureBoolean(file, QString("required"), true); } static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader) { - m.id = Json::requireString(modLoader, "id"); - m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); + m.id = Json::requireString(modLoader, "id"); + m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); } static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) { - m.version = Json::requireString(minecraft, "version"); - // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack - // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing - m.libraries = Json::ensureString(minecraft, QString("libraries"), QString()); - auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray()); - for (const auto & item : arr) - { - auto obj = Json::requireObject(item); - Flame::Modloader loader; - loadModloaderV1(loader, obj); - m.modLoaders.append(loader); - } + m.version = Json::requireString(minecraft, "version"); + // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack + // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing + m.libraries = Json::ensureString(minecraft, QString("libraries"), QString()); + auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray()); + for (const auto & item : arr) + { + auto obj = Json::requireObject(item); + Flame::Modloader loader; + loadModloaderV1(loader, obj); + m.modLoaders.append(loader); + } } static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) { - auto mc = Json::requireObject(manifest, "minecraft"); - loadMinecraftV1(m.minecraft, mc); - m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); - m.version = Json::ensureString(manifest, QString("version"), QString()); - m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward"); - auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (const auto & item : arr) - { - auto obj = Json::requireObject(item); - Flame::File file; - loadFileV1(file, obj); - m.files.append(file); - } - m.overrides = Json::ensureString(manifest, "overrides", "overrides"); + auto mc = Json::requireObject(manifest, "minecraft"); + loadMinecraftV1(m.minecraft, mc); + m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); + m.version = Json::ensureString(manifest, QString("version"), QString()); + m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward"); + auto arr = Json::ensureArray(manifest, "files", QJsonArray()); + for (const auto & item : arr) + { + auto obj = Json::requireObject(item); + Flame::File file; + loadFileV1(file, obj); + m.files.append(file); + } + m.overrides = Json::ensureString(manifest, "overrides", "overrides"); } void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) { - auto doc = Json::requireDocument(filepath); - auto obj = Json::requireObject(doc); - m.manifestType = Json::requireString(obj, "manifestType"); - if(m.manifestType != "minecraftModpack") - { - throw JSONValidationError("Not a modpack manifest!"); - } - m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); - if(m.manifestVersion != 1) - { - throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); - } - loadManifestV1(m, obj); + auto doc = Json::requireDocument(filepath); + auto obj = Json::requireObject(doc); + m.manifestType = Json::requireString(obj, "manifestType"); + if(m.manifestType != "minecraftModpack") + { + throw JSONValidationError("Not a modpack manifest!"); + } + m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); + if(m.manifestVersion != 1) + { + throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); + } + loadManifestV1(m, obj); +} + +bool Flame::File::parseFromBytes(const QByteArray& bytes) +{ + auto doc = Json::requireDocument(bytes); + auto obj = Json::requireObject(doc); + // result code signifies true failure. + if(obj.contains("code")) + { + qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:"; + qCritical() << bytes; + return false; + } + fileName = Json::requireString(obj, "FileNameOnDisk"); + QString rawUrl = Json::requireString(obj, "DownloadURL"); + url = QUrl(rawUrl, QUrl::TolerantMode); + if(!url.isValid()) + { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } + // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience + // It is also optional + QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); + if(!projObj.isEmpty()) + { + QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); + if(strType == "singlefile") + { + type = File::Type::SingleFile; + } + else if(strType == "ctoc") + { + type = File::Type::Ctoc; + } + else if(strType == "cmod2") + { + type = File::Type::Cmod2; + } + else if(strType == "mod") + { + type = File::Type::Mod; + } + else if(strType == "folder") + { + type = File::Type::Folder; + } + else if(strType == "modpack") + { + type = File::Type::Modpack; + } + else + { + qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType; + type = File::Type::Unknown; + return false; + } + targetFolder = Json::ensureString(projObj, "Path", "mods"); + } + resolved = true; + return true; } diff --git a/api/logic/modplatform/flame/PackManifest.h b/api/logic/modplatform/flame/PackManifest.h index 1a5254a8..02f39f0e 100644 --- a/api/logic/modplatform/flame/PackManifest.h +++ b/api/logic/modplatform/flame/PackManifest.h @@ -8,51 +8,54 @@ namespace Flame { struct File { - int projectId = 0; - int fileId = 0; - // NOTE: the opposite to 'optional'. This is at the time of writing unused. - bool required = true; - - // our - bool resolved = false; - QString fileName; - QUrl url; - QString targetFolder = QLatin1Literal("mods"); - enum class Type - { - Unknown, - Folder, - Ctoc, - SingleFile, - Cmod2, - Modpack, - Mod - } type = Type::Mod; + // NOTE: throws JSONValidationError + bool parseFromBytes(const QByteArray &bytes); + + int projectId = 0; + int fileId = 0; + // NOTE: the opposite to 'optional'. This is at the time of writing unused. + bool required = true; + + // our + bool resolved = false; + QString fileName; + QUrl url; + QString targetFolder = QLatin1Literal("mods"); + enum class Type + { + Unknown, + Folder, + Ctoc, + SingleFile, + Cmod2, + Modpack, + Mod + } type = Type::Mod; }; struct Modloader { - QString id; - bool primary = false; + QString id; + bool primary = false; }; struct Minecraft { - QString version; - QString libraries; - QVector modLoaders; + QString version; + QString libraries; + QVector modLoaders; }; struct Manifest { - QString manifestType; - int manifestVersion = 0; - Flame::Minecraft minecraft; - QString name; - QString version; - QString author; - QVector files; - QString overrides; + QString manifestType; + int manifestVersion = 0; + Flame::Minecraft minecraft; + QString name; + QString version; + QString author; + QVector files; + QString overrides; }; void loadManifest(Flame::Manifest & m, const QString &filepath); diff --git a/api/logic/modplatform/flame/UrlResolvingTask.cpp b/api/logic/modplatform/flame/UrlResolvingTask.cpp new file mode 100644 index 00000000..efea350c --- /dev/null +++ b/api/logic/modplatform/flame/UrlResolvingTask.cpp @@ -0,0 +1,175 @@ +#include "UrlResolvingTask.h" +#include +#include + + +namespace { + const char * metabase = "https://cursemeta.dries007.net"; +} + +Flame::UrlResolvingTask::UrlResolvingTask(const QString& toProcess) + : m_url(toProcess) +{ +} + +void Flame::UrlResolvingTask::executeTask() +{ + resolveUrl(); +} + +void Flame::UrlResolvingTask::resolveUrl() +{ + setStatus(tr("Resolving URL...")); + setProgress(0, 1); + QUrl actualUrl(m_url); + if(actualUrl.host() != "www.curseforge.com") { + emitFailed(tr("Not a Twitch URL.")); + return; + } + m_dljob.reset(new NetJob("URL resolver")); + + bool weAreDigging = false; + needle = QString(); + + if(m_url.startsWith("https://")) { + if(m_url.endsWith("?client=y")) { + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download?client=y + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088?client=y + m_url.chop(9); + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088 + } + if(m_url.endsWith("/download")) { + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download -> need to dig inside html... + weAreDigging = true; + needle = m_url; + needle.replace("https://", "twitch://"); + needle.replace("/download", "/download-client/"); + m_url.append("?client=y"); + } else if (m_url.contains("/download/")) { + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088 + m_url.replace("/download/", "/download-client/"); + } + } + else if(m_url.startsWith("twitch://")) { + // twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088 + m_url.replace(0, 9, "https://"); + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088 + } + auto dl = Net::Download::makeByteArray(QUrl(m_url), &results); + m_dljob->addNetAction(dl); + if(weAreDigging) { + connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processHTML); + } else { + connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCCIP); + } + m_dljob->start(); +} + +void Flame::UrlResolvingTask::processHTML() +{ + QString htmlDoc = QString::fromUtf8(results); + auto index = htmlDoc.indexOf(needle); + if(index < 0) { + emitFailed(tr("Couldn't find the needle in the haystack...")); + return; + } + auto indexStart = index; + int indexEnd = -1; + while((index + 1) < htmlDoc.size() && htmlDoc[index] != '"') { + index ++; + if(htmlDoc[index] == '"') { + indexEnd = index; + break; + } + } + if(indexEnd > 0) { + QString found = htmlDoc.mid(indexStart, indexEnd - indexStart); + qDebug() << "Found needle: " << found; + // twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088 + m_url = found; + resolveUrl(); + return; + } + emitFailed(tr("Couldn't find the end of the needle in the haystack...")); + return; +} + +void Flame::UrlResolvingTask::processCCIP() +{ + QDomDocument doc; + if (!doc.setContent(results)) { + qDebug() << results; + emitFailed(tr("Resolving failed.")); + return; + } + auto packageNode = doc.namedItem("package"); + if(!packageNode.isElement()) { + emitFailed(tr("Resolving failed: missing package root element.")); + return; + } + auto projectNode = packageNode.namedItem("project"); + if(!projectNode.isElement()) { + emitFailed(tr("Resolving failed: missing project element.")); + return; + } + auto attribs = projectNode.attributes(); + + auto projectIdNode = attribs.namedItem("id"); + if(!projectIdNode.isAttr()) { + emitFailed(tr("Resolving failed: missing id attribute.")); + return; + } + auto fileIdNode = attribs.namedItem("file"); + if(!fileIdNode.isAttr()) { + emitFailed(tr("Resolving failed: missing file attribute.")); + return; + } + + auto projectId = projectIdNode.nodeValue(); + auto fileId = fileIdNode.nodeValue(); + bool success = true; + m_result.projectId = projectId.toInt(&success); + if(!success) { + emitFailed(tr("Failed to resove projectId as a number.")); + return; + } + m_result.fileId = fileId.toInt(&success); + if(!success) { + emitFailed(tr("Failed to resove fileId as a number.")); + return; + } + qDebug() << "Resolved" << m_url << "as" << m_result.projectId << "/" << m_result.fileId; + resolveIDs(); +} + +void Flame::UrlResolvingTask::resolveIDs() +{ + setStatus(tr("Resolving mod IDs...")); + m_dljob.reset(new NetJob("Mod id resolver")); + auto projectIdStr = QString::number(m_result.projectId); + auto fileIdStr = QString::number(m_result.fileId); + QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); + auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results); + m_dljob->addNetAction(dl); + connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCursemeta); + m_dljob->start(); +} + +void Flame::UrlResolvingTask::processCursemeta() +{ + try { + if(m_result.parseFromBytes(results)) { + emitSucceeded(); + qDebug() << results; + return; + } + } catch (const JSONValidationError &e) { + + qCritical() << "Resolving of" << m_result.projectId << m_result.fileId << "failed because of a parsing error:"; + qCritical() << e.cause(); + qCritical() << "JSON:"; + qCritical() << results; + } + emitFailed(tr("Failed to resolve the modpack file.")); +} diff --git a/api/logic/modplatform/flame/UrlResolvingTask.h b/api/logic/modplatform/flame/UrlResolvingTask.h new file mode 100644 index 00000000..98b78f67 --- /dev/null +++ b/api/logic/modplatform/flame/UrlResolvingTask.h @@ -0,0 +1,43 @@ +#pragma once + +#include "tasks/Task.h" +#include "net/NetJob.h" +#include "PackManifest.h" + +#include "multimc_logic_export.h" + +namespace Flame +{ +class MULTIMC_LOGIC_EXPORT UrlResolvingTask : public Task +{ + Q_OBJECT +public: + explicit UrlResolvingTask(const QString &toProcess); + virtual ~UrlResolvingTask() {}; + + const Flame::File &getResults() const + { + return m_result; + } + +protected: + virtual void executeTask() override; + +protected slots: + void processCCIP(); + void processHTML(); + void processCursemeta(); + +private: + void resolveUrl(); + void resolveIDs(); + +private: /* data */ + QString m_url; + QString needle; + Flame::File m_result; + QByteArray results; + NetJobPtr m_dljob; +}; +} + diff --git a/api/logic/modplatform/ftb/FtbPackFetchTask.cpp b/api/logic/modplatform/ftb/FtbPackFetchTask.cpp index 5588a7fd..fe3f3fac 100644 --- a/api/logic/modplatform/ftb/FtbPackFetchTask.cpp +++ b/api/logic/modplatform/ftb/FtbPackFetchTask.cpp @@ -1,117 +1,168 @@ #include "FtbPackFetchTask.h" #include +#include "FtbPrivatePackManager.h" -FtbPackFetchTask::FtbPackFetchTask() -{ -} - -FtbPackFetchTask::~FtbPackFetchTask() -{ -} +#include "net/URLConstants.h" void FtbPackFetchTask::fetch() { - NetJob *netJob = new NetJob("FtbModpackFetch"); + publicPacks.clear(); + thirdPartyPacks.clear(); + + NetJob *netJob = new NetJob("FtbModpackFetch"); - QUrl publicPacksUrl = QUrl("https://ftb.cursecdn.com/FTB2/static/modpacks.xml"); - qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); - netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData)); + QUrl publicPacksUrl = QUrl(URLConstants::FTB_CDN_BASE_URL + "static/modpacks.xml"); + qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); + netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData)); - QUrl thirdPartyUrl = QUrl("https://ftb.cursecdn.com/FTB2/static/thirdparty.xml"); - qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); - netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData)); + QUrl thirdPartyUrl = QUrl(URLConstants::FTB_CDN_BASE_URL + "static/thirdparty.xml"); + qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); + netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData)); - QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished); - QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed); + QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished); + QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed); - jobPtr.reset(netJob); - netJob->start(); + jobPtr.reset(netJob); + netJob->start(); } -void FtbPackFetchTask::fileDownloadFinished() +void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch) { - jobPtr.reset(); - - QStringList failedLists; - - if(!parseAndAddPacks(publicModpacksXmlFileData, FtbPackType::Public, publicPacks)) { - failedLists.append(tr("Public Packs")); - } - - if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, FtbPackType::ThirdParty, thirdPartyPacks)) { - failedLists.append(tr("Third Party Packs")); - } + QString privatePackBaseUrl = URLConstants::FTB_CDN_BASE_URL + "static/%1.xml"; + + for (auto &packCode: toFetch) + { + QByteArray *data = new QByteArray(); + NetJob *job = new NetJob("Fetching private pack"); + job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data)); + + QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] + { + FtbModpackList packs; + parseAndAddPacks(*data, FtbPackType::Private, packs); + foreach(FtbModpack currentPack, packs) + { + currentPack.packCode = packCode; + emit privateFileDownloadFinished(currentPack); + } + + job->deleteLater(); + + data->clear(); + delete data; + }); + + QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason) + { + emit privateFileDownloadFailed(reason, packCode); + job->deleteLater(); + + data->clear(); + delete data; + }); + + job->start(); + } +} - if(failedLists.size() > 0) { - emit failed(QString("Failed to download some pack lists:%1").arg(failedLists.join("\n- "))); - } else { - emit finished(publicPacks, thirdPartyPacks); - } +void FtbPackFetchTask::fileDownloadFinished() +{ + jobPtr.reset(); + + QStringList failedLists; + + if(!parseAndAddPacks(publicModpacksXmlFileData, FtbPackType::Public, publicPacks)) + { + failedLists.append(tr("Public Packs")); + } + + if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, FtbPackType::ThirdParty, thirdPartyPacks)) + { + failedLists.append(tr("Third Party Packs")); + } + + if(failedLists.size() > 0) + { + emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- "))); + } + else + { + emit finished(publicPacks, thirdPartyPacks); + } } bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list) { - QDomDocument doc; - - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)){ - auto fullErrMsg = QString("Failed to fetch modpack data: %s %d:%d!").arg(errorMsg, errorLine, errorCol); - qWarning() << fullErrMsg; - data.clear(); - return false; - } - - QDomNodeList nodes = doc.elementsByTagName("modpack"); - for(int i = 0; i < nodes.length(); i++) { - QDomElement element = nodes.at(i).toElement(); - - FtbModpack modpack; - modpack.name = element.attribute("name"); - modpack.currentVersion = element.attribute("version"); - modpack.mcVersion = element.attribute("mcVersion"); - modpack.description = element.attribute("description"); - modpack.mods = element.attribute("mods"); - modpack.logo = element.attribute("logo"); - modpack.oldVersions = element.attribute("oldVersions").split(";"); - modpack.broken = false; - modpack.bugged = false; - - //remove empty if the xml is bugged - for(QString curr : modpack.oldVersions) { - if(curr.isNull() || curr.isEmpty()) { - modpack.oldVersions.removeAll(curr); - modpack.bugged = true; - qWarning() << "Removed some empty versions from" << modpack.name; - } - } - - if(modpack.oldVersions.size() < 1) { - if(!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty()) { - modpack.oldVersions.append(modpack.currentVersion); - qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")"; - } else { - modpack.broken = true; - qWarning() << "Broken pack:" << modpack.name << " => No valid version!"; - } - } - - modpack.author = element.attribute("author"); - - modpack.dir = element.attribute("dir"); - modpack.file = element.attribute("url"); - - modpack.type = packType; - - list.append(modpack); - } - - return true; + QDomDocument doc; + + QString errorMsg = "Unknown error."; + int errorLine = -1; + int errorCol = -1; + + if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) + { + auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol); + qWarning() << fullErrMsg; + data.clear(); + return false; + } + + QDomNodeList nodes = doc.elementsByTagName("modpack"); + for(int i = 0; i < nodes.length(); i++) + { + QDomElement element = nodes.at(i).toElement(); + + FtbModpack modpack; + modpack.name = element.attribute("name"); + modpack.currentVersion = element.attribute("version"); + modpack.mcVersion = element.attribute("mcVersion"); + modpack.description = element.attribute("description"); + modpack.mods = element.attribute("mods"); + modpack.logo = element.attribute("logo"); + modpack.oldVersions = element.attribute("oldVersions").split(";"); + modpack.broken = false; + modpack.bugged = false; + + //remove empty if the xml is bugged + for(QString curr : modpack.oldVersions) + { + if(curr.isNull() || curr.isEmpty()) + { + modpack.oldVersions.removeAll(curr); + modpack.bugged = true; + qWarning() << "Removed some empty versions from" << modpack.name; + } + } + + if(modpack.oldVersions.size() < 1) + { + if(!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty()) + { + modpack.oldVersions.append(modpack.currentVersion); + qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")"; + } + else + { + modpack.broken = true; + qWarning() << "Broken pack:" << modpack.name << " => No valid version!"; + } + } + + modpack.author = element.attribute("author"); + + modpack.dir = element.attribute("dir"); + modpack.file = element.attribute("url"); + + modpack.type = packType; + + list.append(modpack); + } + + return true; } -void FtbPackFetchTask::fileDownloadFailed(QString reason){ - qWarning() << "Fetching FtbPacks failed: " << reason; - emit failed(reason); +void FtbPackFetchTask::fileDownloadFailed(QString reason) +{ + qWarning() << "Fetching FtbPacks failed:" << reason; + emit failed(reason); } diff --git a/api/logic/modplatform/ftb/FtbPackFetchTask.h b/api/logic/modplatform/ftb/FtbPackFetchTask.h index b5635b12..f955fe83 100644 --- a/api/logic/modplatform/ftb/FtbPackFetchTask.h +++ b/api/logic/modplatform/ftb/FtbPackFetchTask.h @@ -8,30 +8,33 @@ class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject { - Q_OBJECT + Q_OBJECT public: - FtbPackFetchTask(); - virtual ~FtbPackFetchTask(); + FtbPackFetchTask() = default; + virtual ~FtbPackFetchTask() = default; - void fetch(); + void fetch(); + void fetchPrivate(const QStringList &toFetch); private: - NetJobPtr jobPtr; + NetJobPtr jobPtr; - QByteArray publicModpacksXmlFileData; - QByteArray thirdPartyModpacksXmlFileData; + QByteArray publicModpacksXmlFileData; + QByteArray thirdPartyModpacksXmlFileData; - bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list); - FtbModpackList publicPacks; - FtbModpackList thirdPartyPacks; + bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list); + FtbModpackList publicPacks; + FtbModpackList thirdPartyPacks; protected slots: - void fileDownloadFinished(); - void fileDownloadFailed(QString reason); + void fileDownloadFinished(); + void fileDownloadFailed(QString reason); signals: - void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); - void failed(QString reason); + void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); + void failed(QString reason); + void privateFileDownloadFinished(FtbModpack modpack); + void privateFileDownloadFailed(QString reason, QString packCode); }; diff --git a/api/logic/modplatform/ftb/FtbPackInstallTask.cpp b/api/logic/modplatform/ftb/FtbPackInstallTask.cpp index b57c2092..4962bcac 100644 --- a/api/logic/modplatform/ftb/FtbPackInstallTask.cpp +++ b/api/logic/modplatform/ftb/FtbPackInstallTask.cpp @@ -9,190 +9,199 @@ #include "minecraft/ComponentList.h" #include "minecraft/GradleSpecifier.h" +#include "net/URLConstants.h" + FtbPackInstallTask::FtbPackInstallTask(FtbModpack pack, QString version) { - m_pack = pack; - m_version = version; + m_pack = pack; + m_version = version; } void FtbPackInstallTask::executeTask() { - downloadPack(); + downloadPack(); } void FtbPackInstallTask::downloadPack() { - setStatus(tr("Downloading zip for %1").arg(m_pack.name)); - - auto packoffset = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); - auto entry = ENV.metacache()->resolveEntry("FTBPacks", packoffset); - NetJob *job = new NetJob("Download FTB Pack"); - - entry->setStale(true); - QString url = QString("http://ftb.cursecdn.com/FTB2/modpacks/%1").arg(packoffset); - job->addNetAction(Net::Download::makeCached(url, entry)); - archivePath = entry->getFullPath(); - - netJobContainer.reset(job); - connect(job, &NetJob::succeeded, this, &FtbPackInstallTask::onDownloadSucceeded); - connect(job, &NetJob::failed, this, &FtbPackInstallTask::onDownloadFailed); - connect(job, &NetJob::progress, this, &FtbPackInstallTask::onDownloadProgress); - job->start(); - - progress(1, 4); + setStatus(tr("Downloading zip for %1").arg(m_pack.name)); + + auto packoffset = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); + auto entry = ENV.metacache()->resolveEntry("FTBPacks", packoffset); + NetJob *job = new NetJob("Download FTB Pack"); + + entry->setStale(true); + QString url; + if(m_pack.type == FtbPackType::Private) + { + url = QString(URLConstants::FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset); + } + else + { + url = QString(URLConstants::FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset); + } + job->addNetAction(Net::Download::makeCached(url, entry)); + archivePath = entry->getFullPath(); + + netJobContainer.reset(job); + connect(job, &NetJob::succeeded, this, &FtbPackInstallTask::onDownloadSucceeded); + connect(job, &NetJob::failed, this, &FtbPackInstallTask::onDownloadFailed); + connect(job, &NetJob::progress, this, &FtbPackInstallTask::onDownloadProgress); + job->start(); + + progress(1, 4); } void FtbPackInstallTask::onDownloadSucceeded() { - abortable = false; - unzip(); + abortable = false; + unzip(); } void FtbPackInstallTask::onDownloadFailed(QString reason) { - emitFailed(reason); + abortable = false; + emitFailed(reason); } void FtbPackInstallTask::onDownloadProgress(qint64 current, qint64 total) { - abortable = true; - progress(current, total * 4); - setStatus(tr("Downloading zip for %1 (%2\%)").arg(m_pack.name).arg(current / 10)); + abortable = true; + progress(current, total * 4); + setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); } void FtbPackInstallTask::unzip() { - progress(2, 4); - setStatus(tr("Extracting modpack")); - QDir extractDir(m_stagingPath); - - m_packZip.reset(new QuaZip(archivePath)); - if(!m_packZip->open(QuaZip::mdUnzip)) - { - emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); - return; - } - - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); - connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &FtbPackInstallTask::onUnzipFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &FtbPackInstallTask::onUnzipCanceled); - m_extractFutureWatcher.setFuture(m_extractFuture); + progress(2, 4); + setStatus(tr("Extracting modpack")); + QDir extractDir(m_stagingPath); + + m_packZip.reset(new QuaZip(archivePath)); + if(!m_packZip->open(QuaZip::mdUnzip)) + { + emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); + return; + } + + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); + connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &FtbPackInstallTask::onUnzipFinished); + connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &FtbPackInstallTask::onUnzipCanceled); + m_extractFutureWatcher.setFuture(m_extractFuture); } void FtbPackInstallTask::onUnzipFinished() { - install(); + install(); } void FtbPackInstallTask::onUnzipCanceled() { - emitAborted(); + emitAborted(); } void FtbPackInstallTask::install() { - progress(3, 4); - setStatus(tr("Installing modpack")); - QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); - if(unzipMcDir.exists()) - { - //ok, found minecraft dir, move contents to instance dir - if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) - { - emitFailed(tr("Failed to move unzipped minecraft!")); - return; - } - } - - QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(instanceConfigPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); - - bool fallback = true; - - //handle different versions - QFile packJson(m_stagingPath + "/.minecraft/pack.json"); - QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); - if(packJson.exists()) - { - packJson.open(QIODevice::ReadOnly | QIODevice::Text); - QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); - packJson.close(); - - //we only care about the libs - QJsonArray libs = doc.object().value("libraries").toArray(); - - foreach (const QJsonValue &value, libs) - { - QString nameValue = value.toObject().value("name").toString(); - if(!nameValue.startsWith("net.minecraftforge")) - { - continue; - } - - GradleSpecifier forgeVersion(nameValue); - - components->setComponentVersion("net.minecraftforge", forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", "")); - packJson.remove(); - fallback = false; - break; - } - - } - - if(jarmodDir.exists()) - { - qDebug() << "Found jarmods, installing..."; - - QStringList jarmods; - for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << "Jarmod:" << info.fileName(); - jarmods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarmods); - fallback = false; - } - - //just nuke unzip directory, it s not needed anymore - FS::deletePath(m_stagingPath + "/unzip"); - - if(fallback) - { - //TODO: Some fallback mechanism... or just keep failing! - emitFailed(tr("No installation method found!")); - return; - } - - components->saveNow(); - - progress(4, 4); - - instance.init(); - instance.setName(m_instName); - if(m_instIcon == "default") - { - m_instIcon = "ftb_logo"; - } - instance.setIconKey(m_instIcon); - instance.setGroupInitial(m_instGroup); - instanceSettings->resumeSave(); - - emitSucceeded(); + progress(3, 4); + setStatus(tr("Installing modpack")); + QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); + if(unzipMcDir.exists()) + { + //ok, found minecraft dir, move contents to instance dir + if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) + { + emitFailed(tr("Failed to move unzipped minecraft!")); + return; + } + } + + QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(instanceConfigPath); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); + + bool fallback = true; + + //handle different versions + QFile packJson(m_stagingPath + "/.minecraft/pack.json"); + QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); + if(packJson.exists()) + { + packJson.open(QIODevice::ReadOnly | QIODevice::Text); + QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); + packJson.close(); + + //we only care about the libs + QJsonArray libs = doc.object().value("libraries").toArray(); + + foreach (const QJsonValue &value, libs) + { + QString nameValue = value.toObject().value("name").toString(); + if(!nameValue.startsWith("net.minecraftforge")) + { + continue; + } + + GradleSpecifier forgeVersion(nameValue); + + components->setComponentVersion("net.minecraftforge", forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", "")); + packJson.remove(); + fallback = false; + break; + } + + } + + if(jarmodDir.exists()) + { + qDebug() << "Found jarmods, installing..."; + + QStringList jarmods; + for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) + { + qDebug() << "Jarmod:" << info.fileName(); + jarmods.push_back(info.absoluteFilePath()); + } + + components->installJarMods(jarmods); + fallback = false; + } + + //just nuke unzip directory, it s not needed anymore + FS::deletePath(m_stagingPath + "/unzip"); + + if(fallback) + { + //TODO: Some fallback mechanism... or just keep failing! + emitFailed(tr("No installation method found!")); + return; + } + + components->saveNow(); + + progress(4, 4); + + instance.setName(m_instName); + if(m_instIcon == "default") + { + m_instIcon = "ftb_logo"; + } + instance.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + + emitSucceeded(); } bool FtbPackInstallTask::abort() { - if(abortable) - { - return netJobContainer->abort(); - } - return false; + if(abortable) + { + return netJobContainer->abort(); + } + return false; } diff --git a/api/logic/modplatform/ftb/FtbPackInstallTask.h b/api/logic/modplatform/ftb/FtbPackInstallTask.h index 0bfba3d6..3319025e 100644 --- a/api/logic/modplatform/ftb/FtbPackInstallTask.h +++ b/api/logic/modplatform/ftb/FtbPackInstallTask.h @@ -1,6 +1,5 @@ #pragma once #include "InstanceTask.h" -#include "BaseInstanceProvider.h" #include "net/NetJob.h" #include "quazip.h" #include "quazipdir.h" @@ -9,41 +8,41 @@ #include "meta/VersionList.h" #include "modplatform/ftb/PackHelpers.h" -class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public InstanceTask { - - Q_OBJECT +class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public InstanceTask +{ + Q_OBJECT public: - explicit FtbPackInstallTask(FtbModpack pack, QString version); - virtual ~FtbPackInstallTask(){} + explicit FtbPackInstallTask(FtbModpack pack, QString version); + virtual ~FtbPackInstallTask(){} - bool abort() override; + bool abort() override; protected: - //! Entry point for tasks. - virtual void executeTask() override; + //! Entry point for tasks. + virtual void executeTask() override; private: - void downloadPack(); - void unzip(); - void install(); + void downloadPack(); + void unzip(); + void install(); private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); - void onDownloadProgress(qint64 current, qint64 total); + void onDownloadSucceeded(); + void onDownloadFailed(QString reason); + void onDownloadProgress(qint64 current, qint64 total); - void onUnzipFinished(); - void onUnzipCanceled(); + void onUnzipFinished(); + void onUnzipCanceled(); private: /* data */ - bool abortable = false; - std::unique_ptr m_packZip; - QFuture m_extractFuture; - QFutureWatcher m_extractFutureWatcher; - NetJobPtr netJobContainer; - QString archivePath; - - FtbModpack m_pack; - QString m_version; + bool abortable = false; + std::unique_ptr m_packZip; + QFuture m_extractFuture; + QFutureWatcher m_extractFutureWatcher; + NetJobPtr netJobContainer; + QString archivePath; + + FtbModpack m_pack; + QString m_version; }; diff --git a/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp b/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp new file mode 100644 index 00000000..c3477cec --- /dev/null +++ b/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp @@ -0,0 +1,37 @@ +#include "FtbPrivatePackManager.h" + +#include + +#include "FileSystem.h" + +void FtbPrivatePackManager::load() +{ + try + { + currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet(); + dirty = false; + } + catch(...) + { + currentPacks = {}; + qWarning() << "Failed to read third party FTB pack codes from" << m_filename; + } +} + +void FtbPrivatePackManager::save() const +{ + if(!dirty) + { + return; + } + try + { + QStringList list = currentPacks.toList(); + FS::write(m_filename, list.join('\n').toUtf8()); + dirty = false; + } + catch(...) + { + qWarning() << "Failed to write third party FTB pack codes to" << m_filename; + } +} diff --git a/api/logic/modplatform/ftb/FtbPrivatePackManager.h b/api/logic/modplatform/ftb/FtbPrivatePackManager.h new file mode 100644 index 00000000..388224d6 --- /dev/null +++ b/api/logic/modplatform/ftb/FtbPrivatePackManager.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include "multimc_logic_export.h" + +class MULTIMC_LOGIC_EXPORT FtbPrivatePackManager +{ +public: + ~FtbPrivatePackManager() + { + save(); + } + void load(); + void save() const; + bool empty() const + { + return currentPacks.empty(); + } + const QSet &getCurrentPackCodes() const + { + return currentPacks; + } + void add(const QString &code) + { + currentPacks.insert(code); + dirty = true; + } + void remove(const QString &code) + { + currentPacks.remove(code); + dirty = true; + } + +private: + QSet currentPacks; + QString m_filename = "private_packs.txt"; + mutable bool dirty = false; +}; diff --git a/api/logic/modplatform/ftb/PackHelpers.h b/api/logic/modplatform/ftb/PackHelpers.h index 39b65927..4306caee 100644 --- a/api/logic/modplatform/ftb/PackHelpers.h +++ b/api/logic/modplatform/ftb/PackHelpers.h @@ -6,30 +6,33 @@ #include //Header for structs etc... -enum FtbPackType { - Public, - ThirdParty, - Private +enum class FtbPackType +{ + Public, + ThirdParty, + Private }; -struct FtbModpack { - QString name; - QString description; - QString author; - QStringList oldVersions; - QString currentVersion; - QString mcVersion; - QString mods; - QString logo; - - //Technical data - QString dir; - QString file; //<- Url in the xml, but doesn't make much sense - - bool bugged = false; - bool broken = false; - - FtbPackType type; +struct FtbModpack +{ + QString name; + QString description; + QString author; + QStringList oldVersions; + QString currentVersion; + QString mcVersion; + QString mods; + QString logo; + + //Technical data + QString dir; + QString file; //<- Url in the xml, but doesn't make much sense + + bool bugged = false; + bool broken = false; + + FtbPackType type; + QString packCode; }; //We need it for the proxy model diff --git a/api/logic/net/ByteArraySink.h b/api/logic/net/ByteArraySink.h index 03b77fcc..20e6764c 100644 --- a/api/logic/net/ByteArraySink.h +++ b/api/logic/net/ByteArraySink.h @@ -9,54 +9,54 @@ namespace Net { class ByteArraySink : public Sink { public: - ByteArraySink(QByteArray *output) - :m_output(output) - { - // nil - }; + ByteArraySink(QByteArray *output) + :m_output(output) + { + // nil + }; - virtual ~ByteArraySink() - { - // nil - } + virtual ~ByteArraySink() + { + // nil + } public: - JobStatus init(QNetworkRequest & request) override - { - m_output->clear(); - if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; - }; - - JobStatus write(QByteArray & data) override - { - m_output->append(data); - if(writeAllValidators(data)) - return Job_InProgress; - return Job_Failed; - } - - JobStatus abort() override - { - m_output->clear(); - failAllValidators(); - return Job_Failed; - } - - JobStatus finalize(QNetworkReply &reply) override - { - if(finalizeAllValidators(reply)) - return Job_Finished; - return Job_Failed; - } - - bool hasLocalData() override - { - return false; - } + JobStatus init(QNetworkRequest & request) override + { + m_output->clear(); + if(initAllValidators(request)) + return Job_InProgress; + return Job_Failed; + }; + + JobStatus write(QByteArray & data) override + { + m_output->append(data); + if(writeAllValidators(data)) + return Job_InProgress; + return Job_Failed; + } + + JobStatus abort() override + { + m_output->clear(); + failAllValidators(); + return Job_Failed; + } + + JobStatus finalize(QNetworkReply &reply) override + { + if(finalizeAllValidators(reply)) + return Job_Finished; + return Job_Failed; + } + + bool hasLocalData() override + { + return false; + } private: - QByteArray * m_output; + QByteArray * m_output; }; } diff --git a/api/logic/net/ChecksumValidator.h b/api/logic/net/ChecksumValidator.h index 3cd25bc3..0d6b19c2 100644 --- a/api/logic/net/ChecksumValidator.h +++ b/api/logic/net/ChecksumValidator.h @@ -9,49 +9,47 @@ namespace Net { class ChecksumValidator: public Validator { public: /* con/des */ - ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) - :m_checksum(algorithm), m_expected(expected) - { - }; - virtual ~ChecksumValidator() {}; + ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) + :m_checksum(algorithm), m_expected(expected) + { + }; + virtual ~ChecksumValidator() {}; public: /* methods */ - bool init(QNetworkRequest &) override - { - m_checksum.reset(); - return true; - } - bool write(QByteArray & data) override - { - m_checksum.addData(data); - this->data.append(data); - return true; - } - bool abort() override - { - return true; - } - bool validate(QNetworkReply &) override - { - if(m_expected.size() && m_expected != hash()) - { - qWarning() << "Checksum mismatch, download is bad."; - return false; - } - return true; - } - QByteArray hash() - { - return m_checksum.result(); - } - void setExpected(QByteArray expected) - { - m_expected = expected; - } + bool init(QNetworkRequest &) override + { + m_checksum.reset(); + return true; + } + bool write(QByteArray & data) override + { + m_checksum.addData(data); + return true; + } + bool abort() override + { + return true; + } + bool validate(QNetworkReply &) override + { + if(m_expected.size() && m_expected != hash()) + { + qWarning() << "Checksum mismatch, download is bad."; + return false; + } + return true; + } + QByteArray hash() + { + return m_checksum.result(); + } + void setExpected(QByteArray expected) + { + m_expected = expected; + } private: /* data */ - QByteArray data; - QCryptographicHash m_checksum; - QByteArray m_expected; + QCryptographicHash m_checksum; + QByteArray m_expected; }; } \ No newline at end of file diff --git a/api/logic/net/Download.cpp b/api/logic/net/Download.cpp index 631d3076..229782b0 100644 --- a/api/logic/net/Download.cpp +++ b/api/logic/net/Download.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,271 +28,281 @@ namespace Net { Download::Download():NetAction() { - m_status = Job_NotStarted; + m_status = Job_NotStarted; } Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) { - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); - auto cachedNode = new MetaCacheSink(entry, md5Node); - dl->m_sink.reset(cachedNode); - dl->m_target_path = entry->getFullPath(); - return std::shared_ptr(dl); + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); + auto cachedNode = new MetaCacheSink(entry, md5Node); + dl->m_sink.reset(cachedNode); + dl->m_target_path = entry->getFullPath(); + return std::shared_ptr(dl); } Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options options) { - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - dl->m_sink.reset(new ByteArraySink(output)); - return std::shared_ptr(dl); + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + dl->m_sink.reset(new ByteArraySink(output)); + return std::shared_ptr(dl); } Download::Ptr Download::makeFile(QUrl url, QString path, Options options) { - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - dl->m_sink.reset(new FileSink(path)); - return std::shared_ptr(dl); + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + dl->m_sink.reset(new FileSink(path)); + return std::shared_ptr(dl); } void Download::addValidator(Validator * v) { - m_sink->addValidator(v); + m_sink->addValidator(v); } void Download::start() { - if(m_status == Job_Aborted) - { - qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); - emit aborted(m_index_within_job); - return; - } - QNetworkRequest request(m_url); - m_status = m_sink->init(request); - switch(m_status) - { - case Job_Finished: - emit succeeded(m_index_within_job); - qDebug() << "Download cache hit " << m_url.toString(); - return; - case Job_InProgress: - qDebug() << "Downloading " << m_url.toString(); - break; - case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. - case Job_NotStarted: - case Job_Failed: - emit failed(m_index_within_job); - return; - case Job_Aborted: - return; - } + if(m_status == Job_Aborted) + { + qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + emit aborted(m_index_within_job); + return; + } + QNetworkRequest request(m_url); + m_status = m_sink->init(request); + switch(m_status) + { + case Job_Finished: + emit succeeded(m_index_within_job); + qDebug() << "Download cache hit " << m_url.toString(); + return; + case Job_InProgress: + qDebug() << "Downloading " << m_url.toString(); + break; + case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. + case Job_NotStarted: + case Job_Failed: + emit failed(m_index_within_job); + return; + case Job_Aborted: + return; + } - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0"); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0"); - QNetworkReply *rep = ENV.qnam().get(request); + QNetworkReply *rep = ENV.qnam().get(request); - m_reply.reset(rep); - connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); - connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); - connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); + m_reply.reset(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); } void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } void Download::downloadError(QNetworkReply::NetworkError error) { - if(error == QNetworkReply::OperationCanceledError) - { - qCritical() << "Aborted " << m_url.toString(); - m_status = Job_Aborted; - } - else - { - if(m_options & Option::AcceptLocalFiles) - { - if(m_sink->hasLocalData()) - { - m_status = Job_Failed_Proceed; - return; - } - } - // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; - m_status = Job_Failed; - } + if(error == QNetworkReply::OperationCanceledError) + { + qCritical() << "Aborted " << m_url.toString(); + m_status = Job_Aborted; + } + else + { + if(m_options & Option::AcceptLocalFiles) + { + if(m_sink->hasLocalData()) + { + m_status = Job_Failed_Proceed; + return; + } + } + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_status = Job_Failed; + } } void Download::sslErrors(const QList & errors) { - int i = 1; - for (auto error : errors) - { - qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } + int i = 1; + for (auto error : errors) + { + qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } } bool Download::handleRedirect() { - QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if(!redirect.isValid()) - { - if(!m_reply->hasRawHeader("Location")) - { - // no redirect -> it's fine to continue - return false; - } - // there is a Location header, but it's not correct. we need to apply some workarounds... - QByteArray redirectBA = m_reply->rawHeader("Location"); - if(redirectBA.size() == 0) - { - // empty, yet present redirect header? WTF? - return false; - } - QString redirectStr = QString::fromUtf8(redirectBA); + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if(!redirect.isValid()) + { + if(!m_reply->hasRawHeader("Location")) + { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if(redirectBA.size() == 0) + { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); - /* - * IF the URL begins with //, we need to insert the URL scheme. - * See: https://bugreports.qt-project.org/browse/QTBUG-41061 - */ - if(redirectStr.startsWith("//")) - { - redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } + if(redirectStr.startsWith("//")) + { + /* + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt.io/browse/QTBUG-41061 + * See: http://tools.ietf.org/html/rfc3986#section-4.2 + */ + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } + else if(redirectStr.startsWith("/")) + { + /* + * IF the URL begins with /, we need to process it as a relative URL + */ + auto url = m_reply->url(); + url.setPath(redirectStr, QUrl::TolerantMode); + redirectStr = url.toString(); + } - /* - * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. - * FIXME: report Qt bug for this - */ - redirect = QUrl(redirectStr, QUrl::TolerantMode); - if(!redirect.isValid()) - { - qWarning() << "Failed to parse redirect URL:" << redirectStr; - downloadError(QNetworkReply::ProtocolFailure); - return false; - } - qDebug() << "Fixed location header:" << redirect; - } - else - { - qDebug() << "Location header:" << redirect; - } + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if(!redirect.isValid()) + { + qWarning() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; + } + qDebug() << "Fixed location header:" << redirect; + } + else + { + qDebug() << "Location header:" << redirect; + } - m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); - start(); - return true; + m_url = QUrl(redirect.toString()); + qDebug() << "Following redirect to " << m_url.toString(); + start(); + return true; } void Download::downloadFinished() { - // handle HTTP redirection first - if(handleRedirect()) - { - qDebug() << "Download redirected:" << m_url.toString(); - return; - } + // handle HTTP redirection first + if(handleRedirect()) + { + qDebug() << "Download redirected:" << m_url.toString(); + return; + } - // if the download failed before this point ... - if (m_status == Job_Failed_Proceed) - { - qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit succeeded(m_index_within_job); - return; - } - else if (m_status == Job_Failed) - { - qDebug() << "Download failed in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - else if(m_status == Job_Aborted) - { - qDebug() << "Download aborted in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit aborted(m_index_within_job); - return; - } + // if the download failed before this point ... + if (m_status == Job_Failed_Proceed) + { + qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit succeeded(m_index_within_job); + return; + } + else if (m_status == Job_Failed) + { + qDebug() << "Download failed in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + else if(m_status == Job_Aborted) + { + qDebug() << "Download aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit aborted(m_index_within_job); + return; + } - // make sure we got all the remaining data, if any - auto data = m_reply->readAll(); - if(data.size()) - { - qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; - m_status = m_sink->write(data); - } + // make sure we got all the remaining data, if any + auto data = m_reply->readAll(); + if(data.size()) + { + qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; + m_status = m_sink->write(data); + } - // otherwise, finalize the whole graph - m_status = m_sink->finalize(*m_reply.get()); - if (m_status != Job_Finished) - { - qDebug() << "Download failed to finalize:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - m_reply.reset(); - qDebug() << "Download succeeded:" << m_url.toString(); - emit succeeded(m_index_within_job); + // otherwise, finalize the whole graph + m_status = m_sink->finalize(*m_reply.get()); + if (m_status != Job_Finished) + { + qDebug() << "Download failed to finalize:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + m_reply.reset(); + qDebug() << "Download succeeded:" << m_url.toString(); + emit succeeded(m_index_within_job); } void Download::downloadReadyRead() { - if(m_status == Job_InProgress) - { - auto data = m_reply->readAll(); - m_status = m_sink->write(data); - if(m_status == Job_Failed) - { - qCritical() << "Failed to process response chunk for " << m_target_path; - } - // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; - } - else - { - qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; - } + if(m_status == Job_InProgress) + { + auto data = m_reply->readAll(); + m_status = m_sink->write(data); + if(m_status == Job_Failed) + { + qCritical() << "Failed to process response chunk for " << m_target_path; + } + // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; + } + else + { + qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; + } } } bool Net::Download::abort() { - if(m_reply) - { - m_reply->abort(); - } - else - { - m_status = Job_Aborted; - } - return true; + if(m_reply) + { + m_reply->abort(); + } + else + { + m_status = Job_Aborted; + } + return true; } bool Net::Download::canAbort() { - return true; + return true; } diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h index 00bf108c..18472911 100644 --- a/api/logic/net/Download.h +++ b/api/logic/net/Download.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,52 +24,52 @@ namespace Net { class MULTIMC_LOGIC_EXPORT Download : public NetAction { - Q_OBJECT + Q_OBJECT public: /* types */ - typedef std::shared_ptr Ptr; - enum class Option - { - NoOptions = 0, - AcceptLocalFiles = 1 - }; - Q_DECLARE_FLAGS(Options, Option) + typedef std::shared_ptr Ptr; + enum class Option + { + NoOptions = 0, + AcceptLocalFiles = 1 + }; + Q_DECLARE_FLAGS(Options, Option) protected: /* con/des */ - explicit Download(); + explicit Download(); public: - virtual ~Download(){}; - static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions); - static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); - static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); + virtual ~Download(){}; + static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions); + static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); + static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); public: /* methods */ - QString getTargetFilepath() - { - return m_target_path; - } - void addValidator(Validator * v); - bool abort() override; - bool canAbort() override; + QString getTargetFilepath() + { + return m_target_path; + } + void addValidator(Validator * v); + bool abort() override; + bool canAbort() override; private: /* methods */ - bool handleRedirect(); + bool handleRedirect(); protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; void sslErrors(const QList & errors); - void downloadFinished() override; - void downloadReadyRead() override; + void downloadFinished() override; + void downloadReadyRead() override; public slots: - void start() override; + void start() override; private: /* data */ - // FIXME: remove this, it has no business being here. - QString m_target_path; - std::unique_ptr m_sink; - Options m_options; + // FIXME: remove this, it has no business being here. + QString m_target_path; + std::unique_ptr m_sink; + Options m_options; }; } diff --git a/api/logic/net/FileSink.cpp b/api/logic/net/FileSink.cpp index f52c5788..8b3e917d 100644 --- a/api/logic/net/FileSink.cpp +++ b/api/logic/net/FileSink.cpp @@ -7,109 +7,109 @@ namespace Net { FileSink::FileSink(QString filename) - :m_filename(filename) + :m_filename(filename) { - // nil -}; + // nil +} FileSink::~FileSink() { - // nil -}; + // nil +} JobStatus FileSink::init(QNetworkRequest& request) { - auto result = initCache(request); - if(result != Job_InProgress) - { - return result; - } - // create a new save file and open it for writing - if (!FS::ensureFilePathExists(m_filename)) - { - qCritical() << "Could not create folder for " + m_filename; - return Job_Failed; - } - wroteAnyData = false; - m_output_file.reset(new QSaveFile(m_filename)); - if (!m_output_file->open(QIODevice::WriteOnly)) - { - qCritical() << "Could not open " + m_filename + " for writing"; - return Job_Failed; - } + auto result = initCache(request); + if(result != Job_InProgress) + { + return result; + } + // create a new save file and open it for writing + if (!FS::ensureFilePathExists(m_filename)) + { + qCritical() << "Could not create folder for " + m_filename; + return Job_Failed; + } + wroteAnyData = false; + m_output_file.reset(new QSaveFile(m_filename)); + if (!m_output_file->open(QIODevice::WriteOnly)) + { + qCritical() << "Could not open " + m_filename + " for writing"; + return Job_Failed; + } - if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; + if(initAllValidators(request)) + return Job_InProgress; + return Job_Failed; } JobStatus FileSink::initCache(QNetworkRequest &) { - return Job_InProgress; + return Job_InProgress; } JobStatus FileSink::write(QByteArray& data) { - if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) - { - qCritical() << "Failed writing into " + m_filename; - m_output_file->cancelWriting(); - m_output_file.reset(); - wroteAnyData = false; - return Job_Failed; - } - wroteAnyData = true; - return Job_InProgress; + if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) + { + qCritical() << "Failed writing into " + m_filename; + m_output_file->cancelWriting(); + m_output_file.reset(); + wroteAnyData = false; + return Job_Failed; + } + wroteAnyData = true; + return Job_InProgress; } JobStatus FileSink::abort() { - m_output_file->cancelWriting(); - failAllValidators(); - return Job_Failed; + m_output_file->cancelWriting(); + failAllValidators(); + return Job_Failed; } JobStatus FileSink::finalize(QNetworkReply& reply) { - bool gotFile = false; - QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); - bool validStatus = false; - int statusCode = statusCodeV.toInt(&validStatus); - if(validStatus) - { - // this leaves out 304 Not Modified - gotFile = statusCode == 200 || statusCode == 203; - } - // if we wrote any data to the save file, we try to commit the data to the real file. - // if it actually got a proper file, we write it even if it was empty - if (gotFile || wroteAnyData) - { - // ask validators for data consistency - // we only do this for actual downloads, not 'your data is still the same' cache hits - if(!finalizeAllValidators(reply)) - return Job_Failed; - // nothing went wrong... - if (!m_output_file->commit()) - { - qCritical() << "Failed to commit changes to " << m_filename; - m_output_file->cancelWriting(); - return Job_Failed; - } - } - // then get rid of the save file - m_output_file.reset(); + bool gotFile = false; + QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); + bool validStatus = false; + int statusCode = statusCodeV.toInt(&validStatus); + if(validStatus) + { + // this leaves out 304 Not Modified + gotFile = statusCode == 200 || statusCode == 203; + } + // if we wrote any data to the save file, we try to commit the data to the real file. + // if it actually got a proper file, we write it even if it was empty + if (gotFile || wroteAnyData) + { + // ask validators for data consistency + // we only do this for actual downloads, not 'your data is still the same' cache hits + if(!finalizeAllValidators(reply)) + return Job_Failed; + // nothing went wrong... + if (!m_output_file->commit()) + { + qCritical() << "Failed to commit changes to " << m_filename; + m_output_file->cancelWriting(); + return Job_Failed; + } + } + // then get rid of the save file + m_output_file.reset(); - return finalizeCache(reply); + return finalizeCache(reply); } JobStatus FileSink::finalizeCache(QNetworkReply &) { - return Job_Finished; + return Job_Finished; } bool FileSink::hasLocalData() { - QFileInfo info(m_filename); - return info.exists() && info.size() != 0; + QFileInfo info(m_filename); + return info.exists() && info.size() != 0; } } diff --git a/api/logic/net/FileSink.h b/api/logic/net/FileSink.h index 035d5bfd..875fe511 100644 --- a/api/logic/net/FileSink.h +++ b/api/logic/net/FileSink.h @@ -6,23 +6,23 @@ namespace Net { class FileSink : public Sink { public: /* con/des */ - FileSink(QString filename); - virtual ~FileSink(); + FileSink(QString filename); + virtual ~FileSink(); public: /* methods */ - JobStatus init(QNetworkRequest & request) override; - JobStatus write(QByteArray & data) override; - JobStatus abort() override; - JobStatus finalize(QNetworkReply & reply) override; - bool hasLocalData() override; + JobStatus init(QNetworkRequest & request) override; + JobStatus write(QByteArray & data) override; + JobStatus abort() override; + JobStatus finalize(QNetworkReply & reply) override; + bool hasLocalData() override; protected: /* methods */ - virtual JobStatus initCache(QNetworkRequest &); - virtual JobStatus finalizeCache(QNetworkReply &reply); + virtual JobStatus initCache(QNetworkRequest &); + virtual JobStatus finalizeCache(QNetworkReply &reply); protected: /* data */ - QString m_filename; - bool wroteAnyData = false; - std::unique_ptr m_output_file; + QString m_filename; + bool wroteAnyData = false; + std::unique_ptr m_output_file; }; } diff --git a/api/logic/net/HttpMetaCache.cpp b/api/logic/net/HttpMetaCache.cpp index ebcb0a27..0e3d41b5 100644 --- a/api/logic/net/HttpMetaCache.cpp +++ b/api/logic/net/HttpMetaCache.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,244 +30,244 @@ QString MetaEntry::getFullPath() { - // FIXME: make local? - return FS::PathCombine(basePath, relativePath); + // FIXME: make local? + return FS::PathCombine(basePath, relativePath); } HttpMetaCache::HttpMetaCache(QString path) : QObject() { - m_index_file = path; - saveBatchingTimer.setSingleShot(true); - saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); - connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); + m_index_file = path; + saveBatchingTimer.setSingleShot(true); + saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); + connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); } HttpMetaCache::~HttpMetaCache() { - saveBatchingTimer.stop(); - SaveNow(); + saveBatchingTimer.stop(); + SaveNow(); } MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path) { - // no base. no base path. can't store - if (!m_entries.contains(base)) - { - // TODO: log problem - return MetaEntryPtr(); - } - EntryMap &map = m_entries[base]; - if (map.entry_list.contains(resource_path)) - { - return map.entry_list[resource_path]; - } - return MetaEntryPtr(); + // no base. no base path. can't store + if (!m_entries.contains(base)) + { + // TODO: log problem + return MetaEntryPtr(); + } + EntryMap &map = m_entries[base]; + if (map.entry_list.contains(resource_path)) + { + return map.entry_list[resource_path]; + } + return MetaEntryPtr(); } MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) { - auto entry = getEntry(base, resource_path); - // it's not present? generate a default stale entry - if (!entry) - { - return staleEntry(base, resource_path); - } + auto entry = getEntry(base, resource_path); + // it's not present? generate a default stale entry + if (!entry) + { + return staleEntry(base, resource_path); + } - auto &selected_base = m_entries[base]; - QString real_path = FS::PathCombine(selected_base.base_path, resource_path); - QFileInfo finfo(real_path); + auto &selected_base = m_entries[base]; + QString real_path = FS::PathCombine(selected_base.base_path, resource_path); + QFileInfo finfo(real_path); - // is the file really there? if not -> stale - if (!finfo.isFile() || !finfo.isReadable()) - { - // if the file doesn't exist, we disown the entry - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } + // is the file really there? if not -> stale + if (!finfo.isFile() || !finfo.isReadable()) + { + // if the file doesn't exist, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } - if (!expected_etag.isEmpty() && expected_etag != entry->etag) - { - // if the etag doesn't match expected, we disown the entry - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } + if (!expected_etag.isEmpty() && expected_etag != entry->etag) + { + // if the etag doesn't match expected, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } - // if the file changed, check md5sum - qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); - if (file_last_changed != entry->local_changed_timestamp) - { - QFile input(real_path); - input.open(QIODevice::ReadOnly); - QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) - .toHex() - .constData(); - if (entry->md5sum != md5sum) - { - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } - // md5sums matched... keep entry and save the new state to file - entry->local_changed_timestamp = file_last_changed; - SaveEventually(); - } + // if the file changed, check md5sum + qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); + if (file_last_changed != entry->local_changed_timestamp) + { + QFile input(real_path); + input.open(QIODevice::ReadOnly); + QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) + .toHex() + .constData(); + if (entry->md5sum != md5sum) + { + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + // md5sums matched... keep entry and save the new state to file + entry->local_changed_timestamp = file_last_changed; + SaveEventually(); + } - // entry passed all the checks we cared about. - entry->basePath = getBasePath(base); - return entry; + // entry passed all the checks we cared about. + entry->basePath = getBasePath(base); + return entry; } bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) { - if (!m_entries.contains(stale_entry->baseId)) - { - qCritical() << "Cannot add entry with unknown base: " - << stale_entry->baseId.toLocal8Bit(); - return false; - } - if (stale_entry->stale) - { - qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); - return false; - } - m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; - SaveEventually(); - return true; + if (!m_entries.contains(stale_entry->baseId)) + { + qCritical() << "Cannot add entry with unknown base: " + << stale_entry->baseId.toLocal8Bit(); + return false; + } + if (stale_entry->stale) + { + qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); + return false; + } + m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; + SaveEventually(); + return true; } bool HttpMetaCache::evictEntry(MetaEntryPtr entry) { - if(entry) - { - entry->stale = true; - SaveEventually(); - return true; - } - return false; + if(entry) + { + entry->stale = true; + SaveEventually(); + return true; + } + return false; } MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path) { - auto foo = new MetaEntry(); - foo->baseId = base; - foo->basePath = getBasePath(base); - foo->relativePath = resource_path; - foo->stale = true; - return MetaEntryPtr(foo); + auto foo = new MetaEntry(); + foo->baseId = base; + foo->basePath = getBasePath(base); + foo->relativePath = resource_path; + foo->stale = true; + return MetaEntryPtr(foo); } void HttpMetaCache::addBase(QString base, QString base_root) { - // TODO: report error - if (m_entries.contains(base)) - return; - // TODO: check if the base path is valid - EntryMap foo; - foo.base_path = base_root; - m_entries[base] = foo; + // TODO: report error + if (m_entries.contains(base)) + return; + // TODO: check if the base path is valid + EntryMap foo; + foo.base_path = base_root; + m_entries[base] = foo; } QString HttpMetaCache::getBasePath(QString base) { - if (m_entries.contains(base)) - { - return m_entries[base].base_path; - } - return QString(); + if (m_entries.contains(base)) + { + return m_entries[base].base_path; + } + return QString(); } void HttpMetaCache::Load() { - if(m_index_file.isNull()) - return; + if(m_index_file.isNull()) + return; - QFile index(m_index_file); - if (!index.open(QIODevice::ReadOnly)) - return; + QFile index(m_index_file); + if (!index.open(QIODevice::ReadOnly)) + return; - QJsonDocument json = QJsonDocument::fromJson(index.readAll()); - if (!json.isObject()) - return; - auto root = json.object(); - // check file version first - auto version_val = root.value("version"); - if (!version_val.isString()) - return; - if (version_val.toString() != "1") - return; + QJsonDocument json = QJsonDocument::fromJson(index.readAll()); + if (!json.isObject()) + return; + auto root = json.object(); + // check file version first + auto version_val = root.value("version"); + if (!version_val.isString()) + return; + if (version_val.toString() != "1") + return; - // read the entry array - auto entries_val = root.value("entries"); - if (!entries_val.isArray()) - return; - QJsonArray array = entries_val.toArray(); - for (auto element : array) - { - if (!element.isObject()) - return; - auto element_obj = element.toObject(); - QString base = element_obj.value("base").toString(); - if (!m_entries.contains(base)) - continue; - auto &entrymap = m_entries[base]; - auto foo = new MetaEntry(); - foo->baseId = base; - QString path = foo->relativePath = element_obj.value("path").toString(); - foo->md5sum = element_obj.value("md5sum").toString(); - foo->etag = element_obj.value("etag").toString(); - foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); - foo->remote_changed_timestamp = - element_obj.value("remote_changed_timestamp").toString(); - // presumed innocent until closer examination - foo->stale = false; - entrymap.entry_list[path] = MetaEntryPtr(foo); - } + // read the entry array + auto entries_val = root.value("entries"); + if (!entries_val.isArray()) + return; + QJsonArray array = entries_val.toArray(); + for (auto element : array) + { + if (!element.isObject()) + return; + auto element_obj = element.toObject(); + QString base = element_obj.value("base").toString(); + if (!m_entries.contains(base)) + continue; + auto &entrymap = m_entries[base]; + auto foo = new MetaEntry(); + foo->baseId = base; + QString path = foo->relativePath = element_obj.value("path").toString(); + foo->md5sum = element_obj.value("md5sum").toString(); + foo->etag = element_obj.value("etag").toString(); + foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); + foo->remote_changed_timestamp = + element_obj.value("remote_changed_timestamp").toString(); + // presumed innocent until closer examination + foo->stale = false; + entrymap.entry_list[path] = MetaEntryPtr(foo); + } } void HttpMetaCache::SaveEventually() { - // reset the save timer - saveBatchingTimer.stop(); - saveBatchingTimer.start(30000); + // reset the save timer + saveBatchingTimer.stop(); + saveBatchingTimer.start(30000); } void HttpMetaCache::SaveNow() { - if(m_index_file.isNull()) - return; - QJsonObject toplevel; - toplevel.insert("version", QJsonValue(QString("1"))); - QJsonArray entriesArr; - for (auto group : m_entries) - { - for (auto entry : group.entry_list) - { - // do not save stale entries. they are dead. - if(entry->stale) - { - continue; - } - QJsonObject entryObj; - entryObj.insert("base", QJsonValue(entry->baseId)); - entryObj.insert("path", QJsonValue(entry->relativePath)); - entryObj.insert("md5sum", QJsonValue(entry->md5sum)); - entryObj.insert("etag", QJsonValue(entry->etag)); - entryObj.insert("last_changed_timestamp", - QJsonValue(double(entry->local_changed_timestamp))); - if (!entry->remote_changed_timestamp.isEmpty()) - entryObj.insert("remote_changed_timestamp", - QJsonValue(entry->remote_changed_timestamp)); - entriesArr.append(entryObj); - } - } - toplevel.insert("entries", entriesArr); + if(m_index_file.isNull()) + return; + QJsonObject toplevel; + toplevel.insert("version", QJsonValue(QString("1"))); + QJsonArray entriesArr; + for (auto group : m_entries) + { + for (auto entry : group.entry_list) + { + // do not save stale entries. they are dead. + if(entry->stale) + { + continue; + } + QJsonObject entryObj; + entryObj.insert("base", QJsonValue(entry->baseId)); + entryObj.insert("path", QJsonValue(entry->relativePath)); + entryObj.insert("md5sum", QJsonValue(entry->md5sum)); + entryObj.insert("etag", QJsonValue(entry->etag)); + entryObj.insert("last_changed_timestamp", + QJsonValue(double(entry->local_changed_timestamp))); + if (!entry->remote_changed_timestamp.isEmpty()) + entryObj.insert("remote_changed_timestamp", + QJsonValue(entry->remote_changed_timestamp)); + entriesArr.append(entryObj); + } + } + toplevel.insert("entries", entriesArr); - QJsonDocument doc(toplevel); - try - { - FS::write(m_index_file, doc.toJson()); - } - catch (Exception & e) - { - qWarning() << e.what(); - } + QJsonDocument doc(toplevel); + try + { + FS::write(m_index_file, doc.toJson()); + } + catch (const Exception &e) + { + qWarning() << e.what(); + } } diff --git a/api/logic/net/HttpMetaCache.h b/api/logic/net/HttpMetaCache.h index 7ee5f735..aa92f8ff 100644 --- a/api/logic/net/HttpMetaCache.h +++ b/api/logic/net/HttpMetaCache.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,99 +27,99 @@ class MULTIMC_LOGIC_EXPORT MetaEntry { friend class HttpMetaCache; protected: - MetaEntry() {} + MetaEntry() {} public: - bool isStale() - { - return stale; - } - void setStale(bool stale) - { - this->stale = stale; - } - QString getFullPath(); - QString getRemoteChangedTimestamp() - { - return remote_changed_timestamp; - } - void setRemoteChangedTimestamp(QString remote_changed_timestamp) - { - this->remote_changed_timestamp = remote_changed_timestamp; - } - void setLocalChangedTimestamp(qint64 timestamp) - { - local_changed_timestamp = timestamp; - } - QString getETag() - { - return etag; - } - void setETag(QString etag) - { - this->etag = etag; - } - QString getMD5Sum() - { - return md5sum; - } - void setMD5Sum(QString md5sum) - { - this->md5sum = md5sum; - } + bool isStale() + { + return stale; + } + void setStale(bool stale) + { + this->stale = stale; + } + QString getFullPath(); + QString getRemoteChangedTimestamp() + { + return remote_changed_timestamp; + } + void setRemoteChangedTimestamp(QString remote_changed_timestamp) + { + this->remote_changed_timestamp = remote_changed_timestamp; + } + void setLocalChangedTimestamp(qint64 timestamp) + { + local_changed_timestamp = timestamp; + } + QString getETag() + { + return etag; + } + void setETag(QString etag) + { + this->etag = etag; + } + QString getMD5Sum() + { + return md5sum; + } + void setMD5Sum(QString md5sum) + { + this->md5sum = md5sum; + } protected: - QString baseId; - QString basePath; - QString relativePath; - QString md5sum; - QString etag; - qint64 local_changed_timestamp = 0; - QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time - bool stale = true; + QString baseId; + QString basePath; + QString relativePath; + QString md5sum; + QString etag; + qint64 local_changed_timestamp = 0; + QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time + bool stale = true; }; typedef std::shared_ptr MetaEntryPtr; class MULTIMC_LOGIC_EXPORT HttpMetaCache : public QObject { - Q_OBJECT + Q_OBJECT public: - // supply path to the cache index file - HttpMetaCache(QString path = QString()); - ~HttpMetaCache(); + // supply path to the cache index file + HttpMetaCache(QString path = QString()); + ~HttpMetaCache(); - // get the entry solely from the cache - // you probably don't want this, unless you have some specific caching needs. - MetaEntryPtr getEntry(QString base, QString resource_path); + // get the entry solely from the cache + // you probably don't want this, unless you have some specific caching needs. + MetaEntryPtr getEntry(QString base, QString resource_path); - // get the entry from cache and verify that it isn't stale (within reason) - MetaEntryPtr resolveEntry(QString base, QString resource_path, - QString expected_etag = QString()); + // get the entry from cache and verify that it isn't stale (within reason) + MetaEntryPtr resolveEntry(QString base, QString resource_path, + QString expected_etag = QString()); - // add a previously resolved stale entry - bool updateEntry(MetaEntryPtr stale_entry); + // add a previously resolved stale entry + bool updateEntry(MetaEntryPtr stale_entry); - // evict selected entry from cache - bool evictEntry(MetaEntryPtr entry); + // evict selected entry from cache + bool evictEntry(MetaEntryPtr entry); - void addBase(QString base, QString base_root); + void addBase(QString base, QString base_root); - // (re)start a timer that calls SaveNow later. - void SaveEventually(); - void Load(); - QString getBasePath(QString base); + // (re)start a timer that calls SaveNow later. + void SaveEventually(); + void Load(); + QString getBasePath(QString base); public slots: - void SaveNow(); + void SaveNow(); private: - // create a new stale entry, given the parameters - MetaEntryPtr staleEntry(QString base, QString resource_path); - struct EntryMap - { - QString base_path; - QMap entry_list; - }; - QMap m_entries; - QString m_index_file; - QTimer saveBatchingTimer; + // create a new stale entry, given the parameters + MetaEntryPtr staleEntry(QString base, QString resource_path); + struct EntryMap + { + QString base_path; + QMap entry_list; + }; + QMap m_entries; + QString m_index_file; + QTimer saveBatchingTimer; }; diff --git a/api/logic/net/MetaCacheSink.cpp b/api/logic/net/MetaCacheSink.cpp index c9d75310..d7f18533 100644 --- a/api/logic/net/MetaCacheSink.cpp +++ b/api/logic/net/MetaCacheSink.cpp @@ -7,59 +7,59 @@ namespace Net { MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum) - :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) + :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) { - addValidator(md5sum); -}; + addValidator(md5sum); +} MetaCacheSink::~MetaCacheSink() { - // nil -}; + // nil +} JobStatus MetaCacheSink::initCache(QNetworkRequest& request) { - if (!m_entry->isStale()) - { - return Job_Finished; - } - // check if file exists, if it does, use its information for the request - QFile current(m_filename); - if(current.exists() && current.size() != 0) - { - if (m_entry->getRemoteChangedTimestamp().size()) - { - request.setRawHeader(QString("If-Modified-Since").toLatin1(), m_entry->getRemoteChangedTimestamp().toLatin1()); - } - if (m_entry->getETag().size()) - { - request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); - } - } - return Job_InProgress; + if (!m_entry->isStale()) + { + return Job_Finished; + } + // check if file exists, if it does, use its information for the request + QFile current(m_filename); + if(current.exists() && current.size() != 0) + { + if (m_entry->getRemoteChangedTimestamp().size()) + { + request.setRawHeader(QString("If-Modified-Since").toLatin1(), m_entry->getRemoteChangedTimestamp().toLatin1()); + } + if (m_entry->getETag().size()) + { + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); + } + } + return Job_InProgress; } JobStatus MetaCacheSink::finalizeCache(QNetworkReply & reply) { - QFileInfo output_file_info(m_filename); - if(wroteAnyData) - { - m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); - } - m_entry->setETag(reply.rawHeader("ETag").constData()); - if (reply.hasRawHeader("Last-Modified")) - { - m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData()); - } - m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); - m_entry->setStale(false); - ENV.metacache()->updateEntry(m_entry); - return Job_Finished; + QFileInfo output_file_info(m_filename); + if(wroteAnyData) + { + m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); + } + m_entry->setETag(reply.rawHeader("ETag").constData()); + if (reply.hasRawHeader("Last-Modified")) + { + m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData()); + } + m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); + m_entry->setStale(false); + ENV.metacache()->updateEntry(m_entry); + return Job_Finished; } bool MetaCacheSink::hasLocalData() { - QFileInfo info(m_filename); - return info.exists() && info.size() != 0; + QFileInfo info(m_filename); + return info.exists() && info.size() != 0; } } diff --git a/api/logic/net/MetaCacheSink.h b/api/logic/net/MetaCacheSink.h index 0f3bbdf6..edcf7ad1 100644 --- a/api/logic/net/MetaCacheSink.h +++ b/api/logic/net/MetaCacheSink.h @@ -7,16 +7,16 @@ namespace Net { class MetaCacheSink : public FileSink { public: /* con/des */ - MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum); - virtual ~MetaCacheSink(); - bool hasLocalData() override; + MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum); + virtual ~MetaCacheSink(); + bool hasLocalData() override; protected: /* methods */ - JobStatus initCache(QNetworkRequest & request) override; - JobStatus finalizeCache(QNetworkReply & reply) override; + JobStatus initCache(QNetworkRequest & request) override; + JobStatus finalizeCache(QNetworkReply & reply) override; private: /* data */ - MetaEntryPtr m_entry; - ChecksumValidator * m_md5Node; + MetaEntryPtr m_entry; + ChecksumValidator * m_md5Node; }; } diff --git a/api/logic/net/Mode.h b/api/logic/net/Mode.h index 62e26d92..9a95f5ad 100644 --- a/api/logic/net/Mode.h +++ b/api/logic/net/Mode.h @@ -4,7 +4,7 @@ namespace Net { enum class Mode { - Offline, - Online + Offline, + Online }; } diff --git a/api/logic/net/NetAction.h b/api/logic/net/NetAction.h index 08e40a29..da34a04e 100644 --- a/api/logic/net/NetAction.h +++ b/api/logic/net/NetAction.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,91 +25,91 @@ enum JobStatus { - Job_NotStarted, - Job_InProgress, - Job_Finished, - Job_Failed, - Job_Aborted, - /* - * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. - * Same could be true for aborted task - the presence of pre-existing result is a separate concern - */ - Job_Failed_Proceed + Job_NotStarted, + Job_InProgress, + Job_Finished, + Job_Failed, + Job_Aborted, + /* + * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. + * Same could be true for aborted task - the presence of pre-existing result is a separate concern + */ + Job_Failed_Proceed }; typedef std::shared_ptr NetActionPtr; class MULTIMC_LOGIC_EXPORT NetAction : public QObject { - Q_OBJECT + Q_OBJECT protected: - explicit NetAction() : QObject(0) {}; + explicit NetAction() : QObject(0) {}; public: - virtual ~NetAction() {}; - - bool isRunning() const - { - return m_status == Job_InProgress; - } - bool isFinished() const - { - return m_status >= Job_Finished; - } - bool wasSuccessful() const - { - return m_status == Job_Finished || m_status == Job_Failed_Proceed; - } - - qint64 totalProgress() const - { - return m_total_progress; - } - qint64 currentProgress() const - { - return m_progress; - } - virtual bool abort() - { - return false; - } - virtual bool canAbort() - { - return false; - } - QUrl url() - { - return m_url; - } + virtual ~NetAction() {}; + + bool isRunning() const + { + return m_status == Job_InProgress; + } + bool isFinished() const + { + return m_status >= Job_Finished; + } + bool wasSuccessful() const + { + return m_status == Job_Finished || m_status == Job_Failed_Proceed; + } + + qint64 totalProgress() const + { + return m_total_progress; + } + qint64 currentProgress() const + { + return m_progress; + } + virtual bool abort() + { + return false; + } + virtual bool canAbort() + { + return false; + } + QUrl url() + { + return m_url; + } signals: - void started(int index); - void netActionProgress(int index, qint64 current, qint64 total); - void succeeded(int index); - void failed(int index); - void aborted(int index); + void started(int index); + void netActionProgress(int index, qint64 current, qint64 total); + void succeeded(int index); + void failed(int index); + void aborted(int index); protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; - virtual void downloadError(QNetworkReply::NetworkError error) = 0; - virtual void downloadFinished() = 0; - virtual void downloadReadyRead() = 0; + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; + virtual void downloadError(QNetworkReply::NetworkError error) = 0; + virtual void downloadFinished() = 0; + virtual void downloadReadyRead() = 0; public slots: - virtual void start() = 0; + virtual void start() = 0; public: - /// index within the parent job, FIXME: nuke - int m_index_within_job = 0; + /// index within the parent job, FIXME: nuke + int m_index_within_job = 0; - /// the network reply - unique_qobject_ptr m_reply; + /// the network reply + unique_qobject_ptr m_reply; - /// source URL - QUrl m_url; + /// source URL + QUrl m_url; - qint64 m_progress = 0; - qint64 m_total_progress = 1; + qint64 m_progress = 0; + qint64 m_total_progress = 1; protected: - JobStatus m_status = Job_NotStarted; + JobStatus m_status = Job_NotStarted; }; diff --git a/api/logic/net/NetJob.cpp b/api/logic/net/NetJob.cpp index 1ff8e28f..71e31736 100644 --- a/api/logic/net/NetJob.cpp +++ b/api/logic/net/NetJob.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,197 +20,197 @@ void NetJob::partSucceeded(int index) { - // do progress. all slots are 1 in size at least - auto &slot = parts_progress[index]; - partProgress(index, slot.total_progress, slot.total_progress); - - m_doing.remove(index); - m_done.insert(index); - downloads[index].get()->disconnect(this); - startMoreParts(); + // do progress. all slots are 1 in size at least + auto &slot = parts_progress[index]; + partProgress(index, slot.total_progress, slot.total_progress); + + m_doing.remove(index); + m_done.insert(index); + downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partFailed(int index) { - m_doing.remove(index); - auto &slot = parts_progress[index]; - if (slot.failures == 3) - { - m_failed.insert(index); - } - else - { - slot.failures++; - m_todo.enqueue(index); - } - downloads[index].get()->disconnect(this); - startMoreParts(); + m_doing.remove(index); + auto &slot = parts_progress[index]; + if (slot.failures == 3) + { + m_failed.insert(index); + } + else + { + slot.failures++; + m_todo.enqueue(index); + } + downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partAborted(int index) { - m_aborted = true; - m_doing.remove(index); - m_failed.insert(index); - downloads[index].get()->disconnect(this); - startMoreParts(); + m_aborted = true; + m_doing.remove(index); + m_failed.insert(index); + downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) { - auto &slot = parts_progress[index]; - slot.current_progress = bytesReceived; - slot.total_progress = bytesTotal; - - int done = m_done.size(); - int doing = m_doing.size(); - int all = parts_progress.size(); - - qint64 bytesAll = 0; - qint64 bytesTotalAll = 0; - for(auto & partIdx: m_doing) - { - auto part = parts_progress[partIdx]; - // do not count parts with unknown/nonsensical total size - if(part.total_progress <= 0) - { - continue; - } - bytesAll += part.current_progress; - bytesTotalAll += part.total_progress; - } - - qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; - auto current = done * 1000 + doing * inprogress; - auto current_total = all * 1000; - // HACK: make sure it never jumps backwards. - // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress - if(m_current_progress == 1000) { - m_current_progress = inprogress; - } - if(m_current_progress > current) - { - current = m_current_progress; - } - m_current_progress = current; - setProgress(current, current_total); + auto &slot = parts_progress[index]; + slot.current_progress = bytesReceived; + slot.total_progress = bytesTotal; + + int done = m_done.size(); + int doing = m_doing.size(); + int all = parts_progress.size(); + + qint64 bytesAll = 0; + qint64 bytesTotalAll = 0; + for(auto & partIdx: m_doing) + { + auto part = parts_progress[partIdx]; + // do not count parts with unknown/nonsensical total size + if(part.total_progress <= 0) + { + continue; + } + bytesAll += part.current_progress; + bytesTotalAll += part.total_progress; + } + + qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; + auto current = done * 1000 + doing * inprogress; + auto current_total = all * 1000; + // HACK: make sure it never jumps backwards. + // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress + if(m_current_progress == 1000) { + m_current_progress = inprogress; + } + if(m_current_progress > current) + { + current = m_current_progress; + } + m_current_progress = current; + setProgress(current, current_total); } void NetJob::executeTask() { - // hack that delays early failures so they can be caught easier - QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); + // hack that delays early failures so they can be caught easier + QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); } void NetJob::startMoreParts() { - if(!isRunning()) - { - // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. - return; - } - // OK. We are actively processing tasks, proceed. - // Check for final conditions if there's nothing in the queue. - if(!m_todo.size()) - { - if(!m_doing.size()) - { - if(!m_failed.size()) - { - emitSucceeded(); - } - else if(m_aborted) - { - emitAborted(); - } - else - { - emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); - } - } - return; - } - // There's work to do, try to start more parts. - while (m_doing.size() < 6) - { - if(!m_todo.size()) - return; - int doThis = m_todo.dequeue(); - m_doing.insert(doThis); - auto part = downloads[doThis]; - // connect signals :D - connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); - connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), - SLOT(partProgress(int, qint64, qint64))); - part->start(); - } + if(!isRunning()) + { + // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. + return; + } + // OK. We are actively processing tasks, proceed. + // Check for final conditions if there's nothing in the queue. + if(!m_todo.size()) + { + if(!m_doing.size()) + { + if(!m_failed.size()) + { + emitSucceeded(); + } + else if(m_aborted) + { + emitAborted(); + } + else + { + emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); + } + } + return; + } + // There's work to do, try to start more parts. + while (m_doing.size() < 6) + { + if(!m_todo.size()) + return; + int doThis = m_todo.dequeue(); + m_doing.insert(doThis); + auto part = downloads[doThis]; + // connect signals :D + connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); + connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), + SLOT(partProgress(int, qint64, qint64))); + part->start(); + } } QStringList NetJob::getFailedFiles() { - QStringList failed; - for (auto index: m_failed) - { - failed.push_back(downloads[index]->url().toString()); - } - failed.sort(); - return failed; + QStringList failed; + for (auto index: m_failed) + { + failed.push_back(downloads[index]->url().toString()); + } + failed.sort(); + return failed; } bool NetJob::canAbort() const { - bool canFullyAbort = true; - // can abort the waiting? - for(auto index: m_todo) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - // can abort the active? - for(auto index: m_doing) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - return canFullyAbort; + bool canFullyAbort = true; + // can abort the waiting? + for(auto index: m_todo) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + // can abort the active? + for(auto index: m_doing) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + return canFullyAbort; } bool NetJob::abort() { - bool fullyAborted = true; - // fail all waiting - m_failed.unite(m_todo.toSet()); - m_todo.clear(); - // abort active - auto toKill = m_doing.toList(); - for(auto index: toKill) - { - auto part = downloads[index]; - fullyAborted &= part->abort(); - } - return fullyAborted; + bool fullyAborted = true; + // fail all waiting + m_failed.unite(m_todo.toSet()); + m_todo.clear(); + // abort active + auto toKill = m_doing.toList(); + for(auto index: toKill) + { + auto part = downloads[index]; + fullyAborted &= part->abort(); + } + return fullyAborted; } bool NetJob::addNetAction(NetActionPtr action) { - action->m_index_within_job = downloads.size(); - downloads.append(action); - part_info pi; - parts_progress.append(pi); - partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); - - if(action->isRunning()) - { - connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); - } - else - { - m_todo.append(parts_progress.size() - 1); - } - return true; + action->m_index_within_job = downloads.size(); + downloads.append(action); + part_info pi; + parts_progress.append(pi); + partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); + + if(action->isRunning()) + { + connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); + } + else + { + m_todo.append(parts_progress.size() - 1); + } + return true; } diff --git a/api/logic/net/NetJob.h b/api/logic/net/NetJob.h index be58c61a..0b56bdaa 100644 --- a/api/logic/net/NetJob.h +++ b/api/logic/net/NetJob.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,64 +28,64 @@ typedef shared_qobject_ptr NetJobPtr; class MULTIMC_LOGIC_EXPORT NetJob : public Task { - Q_OBJECT + Q_OBJECT public: - explicit NetJob(QString job_name) : Task() - { - setObjectName(job_name); - } - virtual ~NetJob() {} + explicit NetJob(QString job_name) : Task() + { + setObjectName(job_name); + } + virtual ~NetJob() {} - bool addNetAction(NetActionPtr action); + bool addNetAction(NetActionPtr action); - NetActionPtr operator[](int index) - { - return downloads[index]; - } - const NetActionPtr at(const int index) - { - return downloads.at(index); - } - NetActionPtr first() - { - if (downloads.size()) - return downloads[0]; - return NetActionPtr(); - } - int size() const - { - return downloads.size(); - } - QStringList getFailedFiles(); + NetActionPtr operator[](int index) + { + return downloads[index]; + } + const NetActionPtr at(const int index) + { + return downloads.at(index); + } + NetActionPtr first() + { + if (downloads.size()) + return downloads[0]; + return NetActionPtr(); + } + int size() const + { + return downloads.size(); + } + QStringList getFailedFiles(); - bool canAbort() const override; + bool canAbort() const override; private slots: - void startMoreParts(); + void startMoreParts(); public slots: - virtual void executeTask() override; - virtual bool abort() override; + virtual void executeTask() override; + virtual bool abort() override; private slots: - void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); - void partSucceeded(int index); - void partFailed(int index); - void partAborted(int index); + void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); + void partSucceeded(int index); + void partFailed(int index); + void partAborted(int index); private: - struct part_info - { - qint64 current_progress = 0; - qint64 total_progress = 1; - int failures = 0; - }; - QList downloads; - QList parts_progress; - QQueue m_todo; - QSet m_doing; - QSet m_done; - QSet m_failed; - qint64 m_current_progress = 0; - bool m_aborted = false; + struct part_info + { + qint64 current_progress = 0; + qint64 total_progress = 1; + int failures = 0; + }; + QList downloads; + QList parts_progress; + QQueue m_todo; + QSet m_doing; + QSet m_done; + QSet m_failed; + qint64 m_current_progress = 0; + bool m_aborted = false; }; diff --git a/api/logic/net/PasteUpload.cpp b/api/logic/net/PasteUpload.cpp index d1ddf39d..3526e207 100644 --- a/api/logic/net/PasteUpload.cpp +++ b/api/logic/net/PasteUpload.cpp @@ -8,18 +8,18 @@ PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window) { - m_key = key; - QByteArray temp; - QJsonObject topLevelObj; - QJsonObject sectionObject; - sectionObject.insert("contents", text); - QJsonArray sectionArray; - sectionArray.append(sectionObject); - topLevelObj.insert("description", "MultiMC Log Upload"); - topLevelObj.insert("sections", sectionArray); - QJsonDocument docOut; - docOut.setObject(topLevelObj); - m_jsonContent = docOut.toJson(); + m_key = key; + QByteArray temp; + QJsonObject topLevelObj; + QJsonObject sectionObject; + sectionObject.insert("contents", text); + QJsonArray sectionArray; + sectionArray.append(sectionObject); + topLevelObj.insert("description", "MultiMC Log Upload"); + topLevelObj.insert("sections", sectionArray); + QJsonDocument docOut; + docOut.setObject(topLevelObj); + m_jsonContent = docOut.toJson(); } PasteUpload::~PasteUpload() @@ -28,76 +28,76 @@ PasteUpload::~PasteUpload() bool PasteUpload::validateText() { - return m_jsonContent.size() <= maxSize(); + return m_jsonContent.size() <= maxSize(); } void PasteUpload::executeTask() { - QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); - request.setRawHeader("Content-Type", "application/json"); - request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); - request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); + request.setRawHeader("Content-Type", "application/json"); + request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); + request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); - QNetworkReply *rep = ENV.qnam().post(request, m_jsonContent); + QNetworkReply *rep = ENV.qnam().post(request, m_jsonContent); - m_reply = std::shared_ptr(rep); - setStatus(tr("Uploading to paste.ee")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + m_reply = std::shared_ptr(rep); + setStatus(tr("Uploading to paste.ee")); + connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } void PasteUpload::downloadError(QNetworkReply::NetworkError error) { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + // error happened during download. + qCritical() << "Network error: " << error; + emitFailed(m_reply->errorString()); } void PasteUpload::downloadFinished() { - QByteArray data = m_reply->readAll(); - // if the download succeeded - if (m_reply->error() == QNetworkReply::NetworkError::NoError) - { - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - emitFailed(jsonError.errorString()); - return; - } - if (!parseResult(doc)) - { - emitFailed(tr("paste.ee returned an error. Please consult the logs for more information")); - return; - } - } - // else the download failed - else - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + QByteArray data = m_reply->readAll(); + // if the download succeeded + if (m_reply->error() == QNetworkReply::NetworkError::NoError) + { + m_reply.reset(); + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed(jsonError.errorString()); + return; + } + if (!parseResult(doc)) + { + emitFailed(tr("paste.ee returned an error. Please consult the logs for more information")); + return; + } + } + // else the download failed + else + { + emitFailed(QString("Network error: %1").arg(m_reply->errorString())); + m_reply.reset(); + return; + } + emitSucceeded(); } bool PasteUpload::parseResult(QJsonDocument doc) { - auto object = doc.object(); - auto status = object.value("success").toBool(); - if (!status) - { - qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); - return false; - } - m_pasteLink = object.value("link").toString(); - m_pasteID = object.value("id").toString(); - qDebug() << m_pasteLink; - return true; + auto object = doc.object(); + auto status = object.value("success").toBool(); + if (!status) + { + qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); + return false; + } + m_pasteLink = object.value("link").toString(); + m_pasteID = object.value("id").toString(); + qDebug() << m_pasteLink; + return true; } diff --git a/api/logic/net/PasteUpload.h b/api/logic/net/PasteUpload.h index 62d2e766..11e05c2e 100644 --- a/api/logic/net/PasteUpload.h +++ b/api/logic/net/PasteUpload.h @@ -8,41 +8,42 @@ class MULTIMC_LOGIC_EXPORT PasteUpload : public Task { - Q_OBJECT + Q_OBJECT public: - PasteUpload(QWidget *window, QString text, QString key = "public"); - virtual ~PasteUpload(); - QString pasteLink() - { - return m_pasteLink; - } - QString pasteID() - { - return m_pasteID; - } - int maxSize() - { - // 2MB for paste.ee - public - if(m_key == "public") - return 1024*1024*2; - // 12MB for paste.ee - with actual key - return 1024*1024*12; - } - bool validateText(); + PasteUpload(QWidget *window, QString text, QString key = "public"); + virtual ~PasteUpload(); + + QString pasteLink() + { + return m_pasteLink; + } + QString pasteID() + { + return m_pasteID; + } + int maxSize() + { + // 2MB for paste.ee - public + if(m_key == "public") + return 1024*1024*2; + // 12MB for paste.ee - with actual key + return 1024*1024*12; + } + bool validateText(); protected: - virtual void executeTask(); + virtual void executeTask(); private: - bool parseResult(QJsonDocument doc); - QString m_error; - QWidget *m_window; - QString m_pasteID; - QString m_pasteLink; - QString m_key; - QByteArray m_jsonContent; - std::shared_ptr m_reply; + bool parseResult(QJsonDocument doc); + QString m_error; + QWidget *m_window; + QString m_pasteID; + QString m_pasteLink; + QString m_key; + QByteArray m_jsonContent; + std::shared_ptr m_reply; public slots: - void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); + void downloadError(QNetworkReply::NetworkError); + void downloadFinished(); }; diff --git a/api/logic/net/Sink.h b/api/logic/net/Sink.h index de9b1722..d526895c 100644 --- a/api/logic/net/Sink.h +++ b/api/logic/net/Sink.h @@ -9,63 +9,63 @@ namespace Net { class MULTIMC_LOGIC_EXPORT Sink { public: /* con/des */ - Sink() {}; - virtual ~Sink() {}; + Sink() {}; + virtual ~Sink() {}; public: /* methods */ - virtual JobStatus init(QNetworkRequest & request) = 0; - virtual JobStatus write(QByteArray & data) = 0; - virtual JobStatus abort() = 0; - virtual JobStatus finalize(QNetworkReply & reply) = 0; - virtual bool hasLocalData() = 0; + virtual JobStatus init(QNetworkRequest & request) = 0; + virtual JobStatus write(QByteArray & data) = 0; + virtual JobStatus abort() = 0; + virtual JobStatus finalize(QNetworkReply & reply) = 0; + virtual bool hasLocalData() = 0; - void addValidator(Validator * validator) - { - if(validator) - { - validators.push_back(std::shared_ptr(validator)); - } - } + void addValidator(Validator * validator) + { + if(validator) + { + validators.push_back(std::shared_ptr(validator)); + } + } protected: /* methods */ - bool finalizeAllValidators(QNetworkReply & reply) - { - for(auto & validator: validators) - { - if(!validator->validate(reply)) - return false; - } - return true; - } - bool failAllValidators() - { - bool success = true; - for(auto & validator: validators) - { - success &= validator->abort(); - } - return success; - } - bool initAllValidators(QNetworkRequest & request) - { - for(auto & validator: validators) - { - if(!validator->init(request)) - return false; - } - return true; - } - bool writeAllValidators(QByteArray & data) - { - for(auto & validator: validators) - { - if(!validator->write(data)) - return false; - } - return true; - } + bool finalizeAllValidators(QNetworkReply & reply) + { + for(auto & validator: validators) + { + if(!validator->validate(reply)) + return false; + } + return true; + } + bool failAllValidators() + { + bool success = true; + for(auto & validator: validators) + { + success &= validator->abort(); + } + return success; + } + bool initAllValidators(QNetworkRequest & request) + { + for(auto & validator: validators) + { + if(!validator->init(request)) + return false; + } + return true; + } + bool writeAllValidators(QByteArray & data) + { + for(auto & validator: validators) + { + if(!validator->write(data)) + return false; + } + return true; + } protected: /* data */ - std::vector> validators; + std::vector> validators; }; } diff --git a/api/logic/net/URLConstants.cpp b/api/logic/net/URLConstants.cpp index bd476b2c..9a4d920b 100644 --- a/api/logic/net/URLConstants.cpp +++ b/api/logic/net/URLConstants.cpp @@ -4,12 +4,12 @@ namespace URLConstants { QString getLegacyJarUrl(QString version) { - return "http://" + AWS_DOWNLOAD_VERSIONS + getJarPath(version); + return AWS_DOWNLOAD_VERSIONS + getJarPath(version); } QString getJarPath(QString version) { - return version + "/" + version + ".jar"; + return version + "/" + version + ".jar"; } diff --git a/api/logic/net/URLConstants.h b/api/logic/net/URLConstants.h index 22d128b2..5ff0f794 100644 --- a/api/logic/net/URLConstants.h +++ b/api/logic/net/URLConstants.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,21 +19,17 @@ namespace URLConstants { -const QString AWS_DOWNLOAD_VERSIONS("s3.amazonaws.com/Minecraft.Download/versions/"); -const QString RESOURCE_BASE("resources.download.minecraft.net/"); -const QString LIBRARY_BASE("libraries.minecraft.net/"); -//const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/"); -const QString SKINS_BASE("crafatar.com/skins/"); -const QString AUTH_BASE("authserver.mojang.com/"); -const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json"); -const QString FORGE_GRADLE_URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/json"); -const QString MOJANG_STATUS_URL("http://status.mojang.com/check"); -const QString MOJANG_STATUS_NEWS_URL("http://status.mojang.com/news"); -const QString LITELOADER_URL("http://dl.liteloader.com/versions/versions.json"); +const QString AWS_DOWNLOAD_VERSIONS("https://s3.amazonaws.com/Minecraft.Download/versions/"); +const QString RESOURCE_BASE("https://resources.download.minecraft.net/"); +const QString LIBRARY_BASE("https://libraries.minecraft.net/"); +const QString SKINS_BASE("https://crafatar.com/skins/"); +const QString AUTH_BASE("https://authserver.mojang.com/"); +const QString MOJANG_STATUS_URL("https://status.mojang.com/check"); const QString IMGUR_BASE_URL("https://api.imgur.com/3/"); -const QString FMLLIBS_OUR_BASE_URL("http://files.multimc.org/fmllibs/"); -const QString FMLLIBS_FORGE_BASE_URL("http://files.minecraftforge.net/fmllibs/"); -const QString TRANSLATIONS_BASE_URL("http://files.multimc.org/translations/"); +const QString FMLLIBS_OUR_BASE_URL("https://files.multimc.org/fmllibs/"); +const QString FMLLIBS_FORGE_BASE_URL("https://files.minecraftforge.net/fmllibs/"); +const QString TRANSLATIONS_BASE_URL("https://files.multimc.org/translations/"); +const QString FTB_CDN_BASE_URL("https://ftb.forgecdn.net/FTB2/"); QString getJarPath(QString version); QString getLegacyJarUrl(QString version); diff --git a/api/logic/net/Validator.h b/api/logic/net/Validator.h index a390ab28..955412ce 100644 --- a/api/logic/net/Validator.h +++ b/api/logic/net/Validator.h @@ -8,13 +8,13 @@ namespace Net { class MULTIMC_LOGIC_EXPORT Validator { public: /* con/des */ - Validator() {}; - virtual ~Validator() {}; + Validator() {}; + virtual ~Validator() {}; public: /* methods */ - virtual bool init(QNetworkRequest & request) = 0; - virtual bool write(QByteArray & data) = 0; - virtual bool abort() = 0; - virtual bool validate(QNetworkReply & reply) = 0; + virtual bool init(QNetworkRequest & request) = 0; + virtual bool write(QByteArray & data) = 0; + virtual bool abort() = 0; + virtual bool validate(QNetworkReply & reply) = 0; }; } \ No newline at end of file diff --git a/api/logic/news/NewsChecker.cpp b/api/logic/news/NewsChecker.cpp index 0ee3f9da..a68022e8 100644 --- a/api/logic/news/NewsChecker.cpp +++ b/api/logic/news/NewsChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,110 +22,110 @@ NewsChecker::NewsChecker(const QString& feedUrl) { - m_feedUrl = feedUrl; + m_feedUrl = feedUrl; } void NewsChecker::reloadNews() { - // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done. - if (isLoadingNews()) - { - qDebug() << "Ignored request to reload news. Currently reloading already."; - return; - } - - qDebug() << "Reloading news."; - - NetJob* job = new NetJob("News RSS Feed"); - job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); - QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); - m_newsNetJob.reset(job); - job->start(); + // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done. + if (isLoadingNews()) + { + qDebug() << "Ignored request to reload news. Currently reloading already."; + return; + } + + qDebug() << "Reloading news."; + + NetJob* job = new NetJob("News RSS Feed"); + job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); + QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); + QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); + m_newsNetJob.reset(job); + job->start(); } void NewsChecker::rssDownloadFinished() { - // Parse the XML file and process the RSS feed entries. - qDebug() << "Finished loading RSS feed."; - - m_newsNetJob.reset(); - QDomDocument doc; - { - // Stuff to store error info in. - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - // Parse the XML. - if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) - { - QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); - fail(fullErrorMsg); - newsData.clear(); - return; - } - newsData.clear(); - } - - // If the parsing succeeded, read it. - QDomNodeList items = doc.elementsByTagName("item"); - m_newsEntries.clear(); - for (int i = 0; i < items.length(); i++) - { - QDomElement element = items.at(i).toElement(); - NewsEntryPtr entry; - entry.reset(new NewsEntry()); - QString errorMsg = "An unknown error occurred."; - if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) - { - qDebug() << "Loaded news entry" << entry->title; - m_newsEntries.append(entry); - } - else - { - qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; - } - } - - succeed(); + // Parse the XML file and process the RSS feed entries. + qDebug() << "Finished loading RSS feed."; + + m_newsNetJob.reset(); + QDomDocument doc; + { + // Stuff to store error info in. + QString errorMsg = "Unknown error."; + int errorLine = -1; + int errorCol = -1; + + // Parse the XML. + if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) + { + QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); + fail(fullErrorMsg); + newsData.clear(); + return; + } + newsData.clear(); + } + + // If the parsing succeeded, read it. + QDomNodeList items = doc.elementsByTagName("item"); + m_newsEntries.clear(); + for (int i = 0; i < items.length(); i++) + { + QDomElement element = items.at(i).toElement(); + NewsEntryPtr entry; + entry.reset(new NewsEntry()); + QString errorMsg = "An unknown error occurred."; + if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) + { + qDebug() << "Loaded news entry" << entry->title; + m_newsEntries.append(entry); + } + else + { + qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; + } + } + + succeed(); } void NewsChecker::rssDownloadFailed(QString reason) { - // Set an error message and fail. - fail(tr("Failed to load news RSS feed:\n%1").arg(reason)); + // Set an error message and fail. + fail(tr("Failed to load news RSS feed:\n%1").arg(reason)); } QList NewsChecker::getNewsEntries() const { - return m_newsEntries; + return m_newsEntries; } bool NewsChecker::isLoadingNews() const { - return m_newsNetJob.get() != nullptr; + return m_newsNetJob.get() != nullptr; } QString NewsChecker::getLastLoadErrorMsg() const { - return m_lastLoadError; + return m_lastLoadError; } void NewsChecker::succeed() { - m_lastLoadError = ""; - qDebug() << "News loading succeeded."; - m_newsNetJob.reset(); - emit newsLoaded(); + m_lastLoadError = ""; + qDebug() << "News loading succeeded."; + m_newsNetJob.reset(); + emit newsLoaded(); } void NewsChecker::fail(const QString& errorMsg) { - m_lastLoadError = errorMsg; - qDebug() << "Failed to load news:" << errorMsg; - m_newsNetJob.reset(); - emit newsLoadingFailed(errorMsg); + m_lastLoadError = errorMsg; + qDebug() << "Failed to load news:" << errorMsg; + m_newsNetJob.reset(); + emit newsLoadingFailed(errorMsg); } diff --git a/api/logic/news/NewsChecker.h b/api/logic/news/NewsChecker.h index 44f2534a..5983eeb3 100644 --- a/api/logic/news/NewsChecker.h +++ b/api/logic/news/NewsChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,79 +27,79 @@ class MULTIMC_LOGIC_EXPORT NewsChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - /*! - * Constructs a news reader to read from the given RSS feed URL. - */ - NewsChecker(const QString& feedUrl); - - /*! - * Returns the error message for the last time the news was loaded. - * Empty string if the last load was successful. - */ - QString getLastLoadErrorMsg() const; - - /*! - * Returns true if the news has been loaded successfully. - */ - bool isNewsLoaded() const; - - //! True if the news is currently loading. If true, reloadNews() will do nothing. - bool isLoadingNews() const; - - /*! - * Returns a list of news entries. - */ - QList getNewsEntries() const; - - /*! - * Reloads the news from the website's RSS feed. - * If the news is already loading, this does nothing. - */ - void Q_SLOT reloadNews(); + /*! + * Constructs a news reader to read from the given RSS feed URL. + */ + NewsChecker(const QString& feedUrl); + + /*! + * Returns the error message for the last time the news was loaded. + * Empty string if the last load was successful. + */ + QString getLastLoadErrorMsg() const; + + /*! + * Returns true if the news has been loaded successfully. + */ + bool isNewsLoaded() const; + + //! True if the news is currently loading. If true, reloadNews() will do nothing. + bool isLoadingNews() const; + + /*! + * Returns a list of news entries. + */ + QList getNewsEntries() const; + + /*! + * Reloads the news from the website's RSS feed. + * If the news is already loading, this does nothing. + */ + void Q_SLOT reloadNews(); signals: - /*! - * Signal fired after the news has finished loading. - */ - void newsLoaded(); + /*! + * Signal fired after the news has finished loading. + */ + void newsLoaded(); - /*! - * Signal fired after the news fails to load. - */ - void newsLoadingFailed(QString errorMsg); + /*! + * Signal fired after the news fails to load. + */ + void newsLoadingFailed(QString errorMsg); protected slots: - void rssDownloadFinished(); - void rssDownloadFailed(QString reason); + void rssDownloadFinished(); + void rssDownloadFailed(QString reason); protected: /* data */ - //! The URL for the RSS feed to fetch. - QString m_feedUrl; + //! The URL for the RSS feed to fetch. + QString m_feedUrl; - //! List of news entries. - QList m_newsEntries; + //! List of news entries. + QList m_newsEntries; - //! The network job to use to load the news. - NetJobPtr m_newsNetJob; + //! The network job to use to load the news. + NetJobPtr m_newsNetJob; - //! True if news has been loaded. - bool m_loadedNews; + //! True if news has been loaded. + bool m_loadedNews; - QByteArray newsData; + QByteArray newsData; - /*! - * Gets the error message that was given last time the news was loaded. - * If the last news load succeeded, this will be an empty string. - */ - QString m_lastLoadError; + /*! + * Gets the error message that was given last time the news was loaded. + * If the last news load succeeded, this will be an empty string. + */ + QString m_lastLoadError; protected slots: - /// Emits newsLoaded() and sets m_lastLoadError to empty string. - void succeed(); + /// Emits newsLoaded() and sets m_lastLoadError to empty string. + void succeed(); - /// Emits newsLoadingFailed() and sets m_lastLoadError to the given message. - void fail(const QString& errorMsg); + /// Emits newsLoadingFailed() and sets m_lastLoadError to the given message. + void fail(const QString& errorMsg); }; diff --git a/api/logic/news/NewsEntry.cpp b/api/logic/news/NewsEntry.cpp index 4377f766..2edffb1b 100644 --- a/api/logic/news/NewsEntry.cpp +++ b/api/logic/news/NewsEntry.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,23 +19,23 @@ #include NewsEntry::NewsEntry(QObject* parent) : - QObject(parent) + QObject(parent) { - this->title = tr("Untitled"); - this->content = tr("No content."); - this->link = ""; - this->author = tr("Unknown Author"); - this->pubDate = QDateTime::currentDateTime(); + this->title = tr("Untitled"); + this->content = tr("No content."); + this->link = ""; + this->author = tr("Unknown Author"); + this->pubDate = QDateTime::currentDateTime(); } NewsEntry::NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent) : - QObject(parent) + QObject(parent) { - this->title = title; - this->content = content; - this->link = link; - this->author = author; - this->pubDate = pubDate; + this->title = title; + this->content = content; + this->link = link; + this->author = author; + this->pubDate = pubDate; } /*! @@ -43,35 +43,35 @@ NewsEntry::NewsEntry(const QString& title, const QString& content, const QString */ inline QString childValue(const QDomElement& element, const QString& childName, QString defaultVal="") { - QDomNodeList nodes = element.elementsByTagName(childName); - if (nodes.count() > 0) - { - QDomElement element = nodes.at(0).toElement(); - return element.text(); - } - else - { - return defaultVal; - } + QDomNodeList nodes = element.elementsByTagName(childName); + if (nodes.count() > 0) + { + QDomElement element = nodes.at(0).toElement(); + return element.text(); + } + else + { + return defaultVal; + } } bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg) { - QString title = childValue(element, "title", tr("Untitled")); - QString content = childValue(element, "description", tr("No content.")); - QString link = childValue(element, "link"); - QString author = childValue(element, "dc:creator", tr("Unknown Author")); - QString pubDateStr = childValue(element, "pubDate"); + QString title = childValue(element, "title", tr("Untitled")); + QString content = childValue(element, "description", tr("No content.")); + QString link = childValue(element, "link"); + QString author = childValue(element, "dc:creator", tr("Unknown Author")); + QString pubDateStr = childValue(element, "pubDate"); - // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same. - QString dateFormat("ddd, dd MMM yyyy hh:mm:ss"); - QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat); + // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same. + QString dateFormat("ddd, dd MMM yyyy hh:mm:ss"); + QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat); - entry->title = title; - entry->content = content; - entry->link = link; - entry->author = author; - entry->pubDate = pubDate; - return true; + entry->title = title; + entry->content = content; + entry->link = link; + entry->author = author; + entry->pubDate = pubDate; + return true; } diff --git a/api/logic/news/NewsEntry.h b/api/logic/news/NewsEntry.h index 16a17f9c..94a9e761 100644 --- a/api/logic/news/NewsEntry.h +++ b/api/logic/news/NewsEntry.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,41 +24,41 @@ class NewsEntry : public QObject { - Q_OBJECT + Q_OBJECT public: - /*! - * Constructs an empty news entry. - */ - explicit NewsEntry(QObject* parent=0); + /*! + * Constructs an empty news entry. + */ + explicit NewsEntry(QObject* parent=0); - /*! - * Constructs a new news entry. - * Note that content may contain HTML. - */ - NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0); + /*! + * Constructs a new news entry. + * Note that content may contain HTML. + */ + NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0); - /*! - * Attempts to load information from the given XML element into the given news entry pointer. - * If this fails, the function will return false and store an error message in the errorMsg pointer. - */ - static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0); + /*! + * Attempts to load information from the given XML element into the given news entry pointer. + * If this fails, the function will return false and store an error message in the errorMsg pointer. + */ + static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0); - //! The post title. - QString title; + //! The post title. + QString title; - //! The post's content. May contain HTML. - QString content; + //! The post's content. May contain HTML. + QString content; - //! URL to the post. - QString link; + //! URL to the post. + QString link; - //! The post's author. - QString author; - - //! The date and time that this post was published. - QDateTime pubDate; + //! The post's author. + QString author; + + //! The date and time that this post was published. + QDateTime pubDate; }; typedef std::shared_ptr NewsEntryPtr; diff --git a/api/logic/notifications/NotificationChecker.cpp b/api/logic/notifications/NotificationChecker.cpp index 6d006c31..8209c28b 100644 --- a/api/logic/notifications/NotificationChecker.cpp +++ b/api/logic/notifications/NotificationChecker.cpp @@ -10,120 +10,120 @@ NotificationChecker::NotificationChecker(QObject *parent) - : QObject(parent) + : QObject(parent) { } void NotificationChecker::setNotificationsUrl(const QUrl ¬ificationsUrl) { - m_notificationsUrl = notificationsUrl; + m_notificationsUrl = notificationsUrl; } void NotificationChecker::setApplicationChannel(QString channel) { - m_appVersionChannel = channel; + m_appVersionChannel = channel; } void NotificationChecker::setApplicationFullVersion(QString version) { - m_appFullVersion = version; + m_appFullVersion = version; } void NotificationChecker::setApplicationPlatform(QString platform) { - m_appPlatform = platform; + m_appPlatform = platform; } QList NotificationChecker::notificationEntries() const { - return m_entries; + return m_entries; } void NotificationChecker::checkForNotifications() { - if (!m_notificationsUrl.isValid()) - { - qCritical() << "Failed to check for notifications. No notifications URL set." - << "If you'd like to use MultiMC's notification system, please pass the " - "URL to CMake at compile time."; - return; - } - if (m_checkJob) - { - return; - } - m_checkJob.reset(new NetJob("Checking for notifications")); - auto entry = ENV.metacache()->resolveEntry("root", "notifications.json"); - entry->setStale(true); - m_checkJob->addNetAction(m_download = Net::Download::makeCached(m_notificationsUrl, entry)); - connect(m_download.get(), &Net::Download::succeeded, this, &NotificationChecker::downloadSucceeded); - m_checkJob->start(); + if (!m_notificationsUrl.isValid()) + { + qCritical() << "Failed to check for notifications. No notifications URL set." + << "If you'd like to use MultiMC's notification system, please pass the " + "URL to CMake at compile time."; + return; + } + if (m_checkJob) + { + return; + } + m_checkJob.reset(new NetJob("Checking for notifications")); + auto entry = ENV.metacache()->resolveEntry("root", "notifications.json"); + entry->setStale(true); + m_checkJob->addNetAction(m_download = Net::Download::makeCached(m_notificationsUrl, entry)); + connect(m_download.get(), &Net::Download::succeeded, this, &NotificationChecker::downloadSucceeded); + m_checkJob->start(); } void NotificationChecker::downloadSucceeded(int) { - m_entries.clear(); - - QFile file(m_download->getTargetFilepath()); - if (file.open(QFile::ReadOnly)) - { - QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); - for (auto it = root.begin(); it != root.end(); ++it) - { - QJsonObject obj = (*it).toObject(); - NotificationEntry entry; - entry.id = obj.value("id").toDouble(); - entry.message = obj.value("message").toString(); - entry.channel = obj.value("channel").toString(); - entry.platform = obj.value("platform").toString(); - entry.from = obj.value("from").toString(); - entry.to = obj.value("to").toString(); - const QString type = obj.value("type").toString("critical"); - if (type == "critical") - { - entry.type = NotificationEntry::Critical; - } - else if (type == "warning") - { - entry.type = NotificationEntry::Warning; - } - else if (type == "information") - { - entry.type = NotificationEntry::Information; - } - if(entryApplies(entry)) - m_entries.append(entry); - } - } - - m_checkJob.reset(); - - emit notificationCheckFinished(); + m_entries.clear(); + + QFile file(m_download->getTargetFilepath()); + if (file.open(QFile::ReadOnly)) + { + QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); + for (auto it = root.begin(); it != root.end(); ++it) + { + QJsonObject obj = (*it).toObject(); + NotificationEntry entry; + entry.id = obj.value("id").toDouble(); + entry.message = obj.value("message").toString(); + entry.channel = obj.value("channel").toString(); + entry.platform = obj.value("platform").toString(); + entry.from = obj.value("from").toString(); + entry.to = obj.value("to").toString(); + const QString type = obj.value("type").toString("critical"); + if (type == "critical") + { + entry.type = NotificationEntry::Critical; + } + else if (type == "warning") + { + entry.type = NotificationEntry::Warning; + } + else if (type == "information") + { + entry.type = NotificationEntry::Information; + } + if(entryApplies(entry)) + m_entries.append(entry); + } + } + + m_checkJob.reset(); + + emit notificationCheckFinished(); } bool versionLessThan(const QString &v1, const QString &v2) { - QStringList l1 = v1.split('.'); - QStringList l2 = v2.split('.'); - while (!l1.isEmpty() && !l2.isEmpty()) - { - int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); - int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); - if (one != two) - { - return one < two; - } - } - return false; + QStringList l1 = v1.split('.'); + QStringList l2 = v2.split('.'); + while (!l1.isEmpty() && !l2.isEmpty()) + { + int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); + int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); + if (one != two) + { + return one < two; + } + } + return false; } bool NotificationChecker::entryApplies(const NotificationChecker::NotificationEntry& entry) const { - bool channelApplies = entry.channel.isEmpty() || entry.channel == m_appVersionChannel; - bool platformApplies = entry.platform.isEmpty() || entry.platform == m_appPlatform; - bool fromApplies = - entry.from.isEmpty() || entry.from == m_appFullVersion || !versionLessThan(m_appFullVersion, entry.from); - bool toApplies = - entry.to.isEmpty() || entry.to == m_appFullVersion || !versionLessThan(entry.to, m_appFullVersion); - return channelApplies && platformApplies && fromApplies && toApplies; + bool channelApplies = entry.channel.isEmpty() || entry.channel == m_appVersionChannel; + bool platformApplies = entry.platform.isEmpty() || entry.platform == m_appPlatform; + bool fromApplies = + entry.from.isEmpty() || entry.from == m_appFullVersion || !versionLessThan(m_appFullVersion, entry.from); + bool toApplies = + entry.to.isEmpty() || entry.to == m_appFullVersion || !versionLessThan(entry.to, m_appFullVersion); + return channelApplies && platformApplies && fromApplies && toApplies; } diff --git a/api/logic/notifications/NotificationChecker.h b/api/logic/notifications/NotificationChecker.h index c8e831d5..4b1b893d 100644 --- a/api/logic/notifications/NotificationChecker.h +++ b/api/logic/notifications/NotificationChecker.h @@ -9,55 +9,55 @@ class MULTIMC_LOGIC_EXPORT NotificationChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit NotificationChecker(QObject *parent = 0); - - void setNotificationsUrl(const QUrl ¬ificationsUrl); - void setApplicationPlatform(QString platform); - void setApplicationChannel(QString channel); - void setApplicationFullVersion(QString version); - - struct NotificationEntry - { - int id; - QString message; - enum - { - Critical, - Warning, - Information - } type; - QString channel; - QString platform; - QString from; - QString to; - }; - - QList notificationEntries() const; + explicit NotificationChecker(QObject *parent = 0); + + void setNotificationsUrl(const QUrl ¬ificationsUrl); + void setApplicationPlatform(QString platform); + void setApplicationChannel(QString channel); + void setApplicationFullVersion(QString version); + + struct NotificationEntry + { + int id; + QString message; + enum + { + Critical, + Warning, + Information + } type; + QString channel; + QString platform; + QString from; + QString to; + }; + + QList notificationEntries() const; public slots: - void checkForNotifications(); + void checkForNotifications(); private slots: - void downloadSucceeded(int); + void downloadSucceeded(int); signals: - void notificationCheckFinished(); + void notificationCheckFinished(); private: - bool entryApplies(const NotificationEntry &entry) const; + bool entryApplies(const NotificationEntry &entry) const; private: - QList m_entries; - QUrl m_notificationsUrl; - NetJobPtr m_checkJob; - Net::Download::Ptr m_download; - - QString m_appVersionChannel; - QString m_appPlatform; - QString m_appFullVersion; + QList m_entries; + QUrl m_notificationsUrl; + NetJobPtr m_checkJob; + Net::Download::Ptr m_download; + + QString m_appVersionChannel; + QString m_appPlatform; + QString m_appFullVersion; }; diff --git a/api/logic/pathmatcher/FSTreeMatcher.h b/api/logic/pathmatcher/FSTreeMatcher.h index a5bed57c..361924af 100644 --- a/api/logic/pathmatcher/FSTreeMatcher.h +++ b/api/logic/pathmatcher/FSTreeMatcher.h @@ -7,15 +7,15 @@ class FSTreeMatcher : public IPathMatcher { public: - virtual ~FSTreeMatcher() {}; - FSTreeMatcher(SeparatorPrefixTree<'/'> & tree) : m_fsTree(tree) - { - } + virtual ~FSTreeMatcher() {}; + FSTreeMatcher(SeparatorPrefixTree<'/'> & tree) : m_fsTree(tree) + { + } - virtual bool matches(const QString &string) const override - { - return m_fsTree.covers(string); - } + virtual bool matches(const QString &string) const override + { + return m_fsTree.covers(string); + } - SeparatorPrefixTree<'/'> & m_fsTree; + SeparatorPrefixTree<'/'> & m_fsTree; }; diff --git a/api/logic/pathmatcher/IPathMatcher.h b/api/logic/pathmatcher/IPathMatcher.h index 1d410947..b60621c9 100644 --- a/api/logic/pathmatcher/IPathMatcher.h +++ b/api/logic/pathmatcher/IPathMatcher.h @@ -4,9 +4,9 @@ class IPathMatcher { public: - typedef std::shared_ptr Ptr; + typedef std::shared_ptr Ptr; public: - virtual ~IPathMatcher(){}; - virtual bool matches(const QString &string) const = 0; + virtual ~IPathMatcher(){}; + virtual bool matches(const QString &string) const = 0; }; diff --git a/api/logic/pathmatcher/MultiMatcher.h b/api/logic/pathmatcher/MultiMatcher.h index 91f70aa4..8bc1b6ee 100644 --- a/api/logic/pathmatcher/MultiMatcher.h +++ b/api/logic/pathmatcher/MultiMatcher.h @@ -5,27 +5,27 @@ class MultiMatcher : public IPathMatcher { public: - virtual ~MultiMatcher() {}; - MultiMatcher() - { - } - MultiMatcher &add(Ptr add) - { - m_matchers.append(add); - return *this; - } + virtual ~MultiMatcher() {}; + MultiMatcher() + { + } + MultiMatcher &add(Ptr add) + { + m_matchers.append(add); + return *this; + } - virtual bool matches(const QString &string) const override - { - for(auto iter: m_matchers) - { - if(iter->matches(string)) - { - return true; - } - } - return false; - } + virtual bool matches(const QString &string) const override + { + for(auto iter: m_matchers) + { + if(iter->matches(string)) + { + return true; + } + } + return false; + } - QList m_matchers; + QList m_matchers; }; diff --git a/api/logic/pathmatcher/RegexpMatcher.h b/api/logic/pathmatcher/RegexpMatcher.h index da552123..825d488c 100644 --- a/api/logic/pathmatcher/RegexpMatcher.h +++ b/api/logic/pathmatcher/RegexpMatcher.h @@ -4,39 +4,39 @@ class RegexpMatcher : public IPathMatcher { public: - virtual ~RegexpMatcher() {}; - RegexpMatcher(const QString ®exp) - { - m_regexp.setPattern(regexp); - m_onlyFilenamePart = !regexp.contains('/'); - } + virtual ~RegexpMatcher() {}; + RegexpMatcher(const QString ®exp) + { + m_regexp.setPattern(regexp); + m_onlyFilenamePart = !regexp.contains('/'); + } - RegexpMatcher &caseSensitive(bool cs = true) - { - if(cs) - { - m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - } - else - { - m_regexp.setPatternOptions(QRegularExpression::NoPatternOption); - } - return *this; - } + RegexpMatcher &caseSensitive(bool cs = true) + { + if(cs) + { + m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + } + else + { + m_regexp.setPatternOptions(QRegularExpression::NoPatternOption); + } + return *this; + } - virtual bool matches(const QString &string) const override - { - if(m_onlyFilenamePart) - { - auto slash = string.lastIndexOf('/'); - if(slash != -1) - { - auto part = string.mid(slash + 1); - return m_regexp.match(part).hasMatch(); - } - } - return m_regexp.match(string).hasMatch(); - } - QRegularExpression m_regexp; - bool m_onlyFilenamePart = false; + virtual bool matches(const QString &string) const override + { + if(m_onlyFilenamePart) + { + auto slash = string.lastIndexOf('/'); + if(slash != -1) + { + auto part = string.mid(slash + 1); + return m_regexp.match(part).hasMatch(); + } + } + return m_regexp.match(string).hasMatch(); + } + QRegularExpression m_regexp; + bool m_onlyFilenamePart = false; }; diff --git a/api/logic/screenshots/ImgurAlbumCreation.cpp b/api/logic/screenshots/ImgurAlbumCreation.cpp index 3724e3df..3d32f597 100644 --- a/api/logic/screenshots/ImgurAlbumCreation.cpp +++ b/api/logic/screenshots/ImgurAlbumCreation.cpp @@ -12,77 +12,77 @@ ImgurAlbumCreation::ImgurAlbumCreation(QList screenshots) : NetAction(), m_screenshots(screenshots) { - m_url = URLConstants::IMGUR_BASE_URL + "album.json"; - m_status = Job_NotStarted; + m_url = URLConstants::IMGUR_BASE_URL + "album.json"; + m_status = Job_NotStarted; } void ImgurAlbumCreation::start() { - m_status = Job_InProgress; - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); - request.setRawHeader("Accept", "application/json"); + m_status = Job_InProgress; + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); + request.setRawHeader("Accept", "application/json"); - QStringList hashes; - for (auto shot : m_screenshots) - { - hashes.append(shot->m_imgurDeleteHash); - } + QStringList hashes; + for (auto shot : m_screenshots) + { + hashes.append(shot->m_imgurDeleteHash); + } - const QByteArray data = "deletehashes=" + hashes.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; + const QByteArray data = "deletehashes=" + hashes.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; - QNetworkReply *rep = ENV.qnam().post(request, data); + QNetworkReply *rep = ENV.qnam().post(request, data); - m_reply.reset(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + m_reply.reset(rep); + connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); } void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error) { - qDebug() << m_reply->errorString(); - m_status = Job_Failed; + qDebug() << m_reply->errorString(); + m_status = Job_Failed; } void ImgurAlbumCreation::downloadFinished() { - if (m_status != Job_Failed) - { - QByteArray data = m_reply->readAll(); - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - qDebug() << jsonError.errorString(); - emit failed(m_index_within_job); - return; - } - auto object = doc.object(); - if (!object.value("success").toBool()) - { - qDebug() << doc.toJson(); - emit failed(m_index_within_job); - return; - } - m_deleteHash = object.value("data").toObject().value("deletehash").toString(); - m_id = object.value("data").toObject().value("id").toString(); - m_status = Job_Finished; - emit succeeded(m_index_within_job); - return; - } - else - { - qDebug() << m_reply->readAll(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } + if (m_status != Job_Failed) + { + QByteArray data = m_reply->readAll(); + m_reply.reset(); + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + qDebug() << jsonError.errorString(); + emit failed(m_index_within_job); + return; + } + auto object = doc.object(); + if (!object.value("success").toBool()) + { + qDebug() << doc.toJson(); + emit failed(m_index_within_job); + return; + } + m_deleteHash = object.value("data").toObject().value("deletehash").toString(); + m_id = object.value("data").toObject().value("id").toString(); + m_status = Job_Finished; + emit succeeded(m_index_within_job); + return; + } + else + { + qDebug() << m_reply->readAll(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } } void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } diff --git a/api/logic/screenshots/ImgurAlbumCreation.h b/api/logic/screenshots/ImgurAlbumCreation.h index 469174e4..55478021 100644 --- a/api/logic/screenshots/ImgurAlbumCreation.h +++ b/api/logic/screenshots/ImgurAlbumCreation.h @@ -8,37 +8,37 @@ typedef std::shared_ptr ImgurAlbumCreationPtr; class MULTIMC_LOGIC_EXPORT ImgurAlbumCreation : public NetAction { public: - explicit ImgurAlbumCreation(QList screenshots); - static ImgurAlbumCreationPtr make(QList screenshots) - { - return ImgurAlbumCreationPtr(new ImgurAlbumCreation(screenshots)); - } + explicit ImgurAlbumCreation(QList screenshots); + static ImgurAlbumCreationPtr make(QList screenshots) + { + return ImgurAlbumCreationPtr(new ImgurAlbumCreation(screenshots)); + } - QString deleteHash() const - { - return m_deleteHash; - } - QString id() const - { - return m_id; - } + QString deleteHash() const + { + return m_deleteHash; + } + QString id() const + { + return m_id; + } protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead() - { - } + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead() + { + } public slots: - virtual void start(); + virtual void start(); private: - QList m_screenshots; + QList m_screenshots; - QString m_deleteHash; - QString m_id; + QString m_deleteHash; + QString m_id; }; diff --git a/api/logic/screenshots/ImgurUpload.cpp b/api/logic/screenshots/ImgurUpload.cpp index 659879f6..74165869 100644 --- a/api/logic/screenshots/ImgurUpload.cpp +++ b/api/logic/screenshots/ImgurUpload.cpp @@ -14,101 +14,101 @@ ImgurUpload::ImgurUpload(ScreenshotPtr shot) : NetAction(), m_shot(shot) { - m_url = URLConstants::IMGUR_BASE_URL + "upload.json"; - m_status = Job_NotStarted; + m_url = URLConstants::IMGUR_BASE_URL + "upload.json"; + m_status = Job_NotStarted; } void ImgurUpload::start() { - finished = false; - m_status = Job_InProgress; - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); - request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); - request.setRawHeader("Accept", "application/json"); + finished = false; + m_status = Job_InProgress; + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); + request.setRawHeader("Accept", "application/json"); - QFile f(m_shot->m_file.absoluteFilePath()); - if (!f.open(QFile::ReadOnly)) - { - emit failed(m_index_within_job); - return; - } + QFile f(m_shot->m_file.absoluteFilePath()); + if (!f.open(QFile::ReadOnly)) + { + emit failed(m_index_within_job); + return; + } - QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart filePart; - filePart.setBody(f.readAll().toBase64()); - filePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); - filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"image\""); - multipart->append(filePart); - QHttpPart typePart; - typePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"type\""); - typePart.setBody("base64"); - multipart->append(typePart); - QHttpPart namePart; - namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"name\""); - namePart.setBody(m_shot->m_file.baseName().toUtf8()); - multipart->append(namePart); + QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpPart filePart; + filePart.setBody(f.readAll().toBase64()); + filePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); + filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"image\""); + multipart->append(filePart); + QHttpPart typePart; + typePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"type\""); + typePart.setBody("base64"); + multipart->append(typePart); + QHttpPart namePart; + namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"name\""); + namePart.setBody(m_shot->m_file.baseName().toUtf8()); + multipart->append(namePart); - QNetworkReply *rep = ENV.qnam().post(request, multipart); + QNetworkReply *rep = ENV.qnam().post(request, multipart); - m_reply.reset(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), - SLOT(downloadError(QNetworkReply::NetworkError))); + m_reply.reset(rep); + connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); } void ImgurUpload::downloadError(QNetworkReply::NetworkError error) { - qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll(); - if(finished) - { - qCritical() << "Double finished ImgurUpload!"; - return; - } - m_status = Job_Failed; - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); + qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll(); + if(finished) + { + qCritical() << "Double finished ImgurUpload!"; + return; + } + m_status = Job_Failed; + finished = true; + m_reply.reset(); + emit failed(m_index_within_job); } void ImgurUpload::downloadFinished() { - if(finished) - { - qCritical() << "Double finished ImgurUpload!"; - return; - } - QByteArray data = m_reply->readAll(); - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - auto object = doc.object(); - if (!object.value("success").toBool()) - { - qDebug() << "Screenshot upload not successful:" << doc.toJson(); - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); - m_shot->m_url = object.value("data").toObject().value("link").toString(); - m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString(); - m_status = Job_Finished; - finished = true; - emit succeeded(m_index_within_job); - return; + if(finished) + { + qCritical() << "Double finished ImgurUpload!"; + return; + } + QByteArray data = m_reply->readAll(); + m_reply.reset(); + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); + finished = true; + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + auto object = doc.object(); + if (!object.value("success").toBool()) + { + qDebug() << "Screenshot upload not successful:" << doc.toJson(); + finished = true; + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); + m_shot->m_url = object.value("data").toObject().value("link").toString(); + m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString(); + m_status = Job_Finished; + finished = true; + emit succeeded(m_index_within_job); + return; } void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } diff --git a/api/logic/screenshots/ImgurUpload.h b/api/logic/screenshots/ImgurUpload.h index 0a766b8f..d79807f2 100644 --- a/api/logic/screenshots/ImgurUpload.h +++ b/api/logic/screenshots/ImgurUpload.h @@ -8,26 +8,26 @@ typedef std::shared_ptr ImgurUploadPtr; class MULTIMC_LOGIC_EXPORT ImgurUpload : public NetAction { public: - explicit ImgurUpload(ScreenshotPtr shot); - static ImgurUploadPtr make(ScreenshotPtr shot) - { - return ImgurUploadPtr(new ImgurUpload(shot)); - } + explicit ImgurUpload(ScreenshotPtr shot); + static ImgurUploadPtr make(ScreenshotPtr shot) + { + return ImgurUploadPtr(new ImgurUpload(shot)); + } protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead() - { - } + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead() + { + } public slots: - virtual void start(); + virtual void start(); private: - ScreenshotPtr m_shot; - bool finished = true; + ScreenshotPtr m_shot; + bool finished = true; }; diff --git a/api/logic/screenshots/Screenshot.h b/api/logic/screenshots/Screenshot.h index 2c70ecf5..9db3a8a1 100644 --- a/api/logic/screenshots/Screenshot.h +++ b/api/logic/screenshots/Screenshot.h @@ -7,14 +7,14 @@ struct ScreenShot { - ScreenShot(QFileInfo file) - { - m_file = file; - } - QFileInfo m_file; - QString m_url; - QString m_imgurId; - QString m_imgurDeleteHash; + ScreenShot(QFileInfo file) + { + m_file = file; + } + QFileInfo m_file; + QString m_url; + QString m_imgurId; + QString m_imgurDeleteHash; }; typedef std::shared_ptr ScreenshotPtr; diff --git a/api/logic/settings/INIFile.cpp b/api/logic/settings/INIFile.cpp index 9e97f861..ff6d5cf3 100644 --- a/api/logic/settings/INIFile.cpp +++ b/api/logic/settings/INIFile.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,124 +28,136 @@ INIFile::INIFile() QString INIFile::unescape(QString orig) { - QString out; - QChar prev = 0; - for(auto c: orig) - { - if(prev == '\\') - { - if(c == 'n') - out += '\n'; - else if (c == 't') - out += '\t'; - else - out += c; - prev = 0; - } - else - { - if(c == '\\') - { - prev = c; - continue; - } - out += c; - prev = 0; - } - } - return out; + QString out; + QChar prev = 0; + for(auto c: orig) + { + if(prev == '\\') + { + if(c == 'n') + out += '\n'; + else if(c == 't') + out += '\t'; + else if(c == '#') + out += '#'; + else + out += c; + prev = 0; + } + else + { + if(c == '\\') + { + prev = c; + continue; + } + out += c; + prev = 0; + } + } + return out; } QString INIFile::escape(QString orig) { - QString out; - for(auto c: orig) - { - if(c == '\n') - out += "\\n"; - else if (c == '\t') - out += "\\t"; - else if(c == '\\') - out += "\\\\"; - else - out += c; - } - return out; + QString out; + for(auto c: orig) + { + if(c == '\n') + out += "\\n"; + else if (c == '\t') + out += "\\t"; + else if(c == '\\') + out += "\\\\"; + else if(c == '#') + out += "\\#"; + else + out += c; + } + return out; } bool INIFile::saveFile(QString fileName) { - QByteArray outArray; - for (Iterator iter = begin(); iter != end(); iter++) - { - QString value = iter.value().toString(); - value = escape(value); - outArray.append(iter.key().toUtf8()); - outArray.append('='); - outArray.append(value.toUtf8()); - outArray.append('\n'); - } - - try - { - FS::write(fileName, outArray); - } - catch (Exception & e) - { - qCritical() << e.what(); - return false; - } - - return true; + QByteArray outArray; + for (Iterator iter = begin(); iter != end(); iter++) + { + QString value = iter.value().toString(); + value = escape(value); + outArray.append(iter.key().toUtf8()); + outArray.append('='); + outArray.append(value.toUtf8()); + outArray.append('\n'); + } + + try + { + FS::write(fileName, outArray); + } + catch (const Exception &e) + { + qCritical() << e.what(); + return false; + } + + return true; } bool INIFile::loadFile(QString fileName) { - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) - return false; - bool success = loadFile(file.readAll()); - file.close(); - return success; + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return false; + bool success = loadFile(file.readAll()); + file.close(); + return success; } bool INIFile::loadFile(QByteArray file) { - QTextStream in(file); - in.setCodec("UTF-8"); - - QStringList lines = in.readAll().split('\n'); - for (int i = 0; i < lines.count(); i++) - { - QString &lineRaw = lines[i]; - // Ignore comments. - QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed(); - - int eqPos = line.indexOf('='); - if (eqPos == -1) - continue; - QString key = line.left(eqPos).trimmed(); - QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); - - valueStr = unescape(valueStr); - - QVariant value(valueStr); - this->operator[](key) = value; - } - - return true; + QTextStream in(file); + in.setCodec("UTF-8"); + + QStringList lines = in.readAll().split('\n'); + for (int i = 0; i < lines.count(); i++) + { + QString &lineRaw = lines[i]; + // Ignore comments. + int commentIndex = 0; + QString line = lineRaw; + // Search for comments until no more escaped # are available + while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) { + if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') { + continue; + } + line = line.left(lineRaw.indexOf('#')).trimmed(); + } + + int eqPos = line.indexOf('='); + if (eqPos == -1) + continue; + QString key = line.left(eqPos).trimmed(); + QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); + + valueStr = unescape(valueStr); + + QVariant value(valueStr); + this->operator[](key) = value; + } + + return true; } QVariant INIFile::get(QString key, QVariant def) const { - if (!this->contains(key)) - return def; - else - return this->operator[](key); + if (!this->contains(key)) + return def; + else + return this->operator[](key); } void INIFile::set(QString key, QVariant val) { - this->operator[](key) = val; + this->operator[](key) = val; } diff --git a/api/logic/settings/INIFile.h b/api/logic/settings/INIFile.h index f0c63d3c..9406f7bd 100644 --- a/api/logic/settings/INIFile.h +++ b/api/logic/settings/INIFile.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,14 +25,14 @@ class MULTIMC_LOGIC_EXPORT INIFile : public QMap { public: - explicit INIFile(); + explicit INIFile(); - bool loadFile(QByteArray file); - bool loadFile(QString fileName); - bool saveFile(QString fileName); + bool loadFile(QByteArray file); + bool loadFile(QString fileName); + bool saveFile(QString fileName); - QVariant get(QString key, QVariant def) const; - void set(QString key, QVariant val); - static QString unescape(QString orig); - static QString escape(QString orig); + QVariant get(QString key, QVariant def) const; + void set(QString key, QVariant val); + static QString unescape(QString orig); + static QString escape(QString orig); }; diff --git a/api/logic/settings/INIFile_test.cpp b/api/logic/settings/INIFile_test.cpp index b3ae7375..08c2155e 100644 --- a/api/logic/settings/INIFile_test.cpp +++ b/api/logic/settings/INIFile_test.cpp @@ -5,56 +5,57 @@ class IniFileTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_Escape_data() - { - QTest::addColumn("through"); - - QTest::newRow("unix path") << "/abc/def/ghi/jkl"; - QTest::newRow("windows path") << "C:\\Program files\\terrible\\name\\of something\\"; - QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet."; - QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet."; - QTest::newRow("Escape sequences 2") << "\"\n\n\""; - } - void test_Escape() - { - QFETCH(QString, through); - - QString there = INIFile::escape(through); - QString back = INIFile::unescape(there); - - QCOMPARE(back, through); - } - - void test_SaveLoad() - { - QString a = "a"; - QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\"; - QString filename = "test_SaveLoad.ini"; - - // save - INIFile f; - f.set("a", a); - f.set("b", b); - f.saveFile(filename); - - // load - INIFile f2; - f2.loadFile(filename); - QCOMPARE(a, f2.get("a","NOT SET").toString()); - QCOMPARE(b, f2.get("b","NOT SET").toString()); - } + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void test_Escape_data() + { + QTest::addColumn("through"); + + QTest::newRow("unix path") << "/abc/def/ghi/jkl"; + QTest::newRow("windows path") << "C:\\Program files\\terrible\\name\\of something\\"; + QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet."; + QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet."; + QTest::newRow("Escape sequences 2") << "\"\n\n\""; + QTest::newRow("Hashtags") << "some data#something"; + } + void test_Escape() + { + QFETCH(QString, through); + + QString there = INIFile::escape(through); + QString back = INIFile::unescape(there); + + QCOMPARE(back, through); + } + + void test_SaveLoad() + { + QString a = "a"; + QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment"; + QString filename = "test_SaveLoad.ini"; + + // save + INIFile f; + f.set("a", a); + f.set("b", b); + f.saveFile(filename); + + // load + INIFile f2; + f2.loadFile(filename); + QCOMPARE(a, f2.get("a","NOT SET").toString()); + QCOMPARE(b, f2.get("b","NOT SET").toString()); + } }; QTEST_GUILESS_MAIN(IniFileTest) diff --git a/api/logic/settings/INISettingsObject.cpp b/api/logic/settings/INISettingsObject.cpp index ff2cee31..f04a66b5 100644 --- a/api/logic/settings/INISettingsObject.cpp +++ b/api/logic/settings/INISettingsObject.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,91 +17,91 @@ #include "Setting.h" INISettingsObject::INISettingsObject(const QString &path, QObject *parent) - : SettingsObject(parent) + : SettingsObject(parent) { - m_filePath = path; - m_ini.loadFile(path); + m_filePath = path; + m_ini.loadFile(path); } void INISettingsObject::setFilePath(const QString &filePath) { - m_filePath = filePath; + m_filePath = filePath; } bool INISettingsObject::reload() { - return m_ini.loadFile(m_filePath) && SettingsObject::reload(); + return m_ini.loadFile(m_filePath) && SettingsObject::reload(); } void INISettingsObject::suspendSave() { - m_suspendSave = true; + m_suspendSave = true; } void INISettingsObject::resumeSave() { - m_suspendSave = false; - if(m_doSave) - { - m_ini.saveFile(m_filePath); - } + m_suspendSave = false; + if(m_doSave) + { + m_ini.saveFile(m_filePath); + } } void INISettingsObject::changeSetting(const Setting &setting, QVariant value) { - if (contains(setting.id())) - { - // valid value -> set the main config, remove all the sysnonyms - if (value.isValid()) - { - auto list = setting.configKeys(); - m_ini.set(list.takeFirst(), value); - for(auto iter: list) - m_ini.remove(iter); - } - // invalid -> remove all (just like resetSetting) - else - { - for(auto iter: setting.configKeys()) - m_ini.remove(iter); - } - doSave(); - } + if (contains(setting.id())) + { + // valid value -> set the main config, remove all the sysnonyms + if (value.isValid()) + { + auto list = setting.configKeys(); + m_ini.set(list.takeFirst(), value); + for(auto iter: list) + m_ini.remove(iter); + } + // invalid -> remove all (just like resetSetting) + else + { + for(auto iter: setting.configKeys()) + m_ini.remove(iter); + } + doSave(); + } } void INISettingsObject::doSave() { - if(m_suspendSave) - { - m_doSave = true; - } - else - { - m_ini.saveFile(m_filePath); - } + if(m_suspendSave) + { + m_doSave = true; + } + else + { + m_ini.saveFile(m_filePath); + } } void INISettingsObject::resetSetting(const Setting &setting) { - // if we have the setting, remove all the synonyms. ALL OF THEM - if (contains(setting.id())) - { - for(auto iter: setting.configKeys()) - m_ini.remove(iter); - doSave(); - } + // if we have the setting, remove all the synonyms. ALL OF THEM + if (contains(setting.id())) + { + for(auto iter: setting.configKeys()) + m_ini.remove(iter); + doSave(); + } } QVariant INISettingsObject::retrieveValue(const Setting &setting) { - // if we have the setting, return value of the first matching synonym - if (contains(setting.id())) - { - for(auto iter: setting.configKeys()) - { - if(m_ini.contains(iter)) - return m_ini[iter]; - } - } - return QVariant(); + // if we have the setting, return value of the first matching synonym + if (contains(setting.id())) + { + for(auto iter: setting.configKeys()) + { + if(m_ini.contains(iter)) + return m_ini[iter]; + } + } + return QVariant(); } diff --git a/api/logic/settings/INISettingsObject.h b/api/logic/settings/INISettingsObject.h index 111215e6..c8ea99ca 100644 --- a/api/logic/settings/INISettingsObject.h +++ b/api/logic/settings/INISettingsObject.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,39 +28,39 @@ */ class MULTIMC_LOGIC_EXPORT INISettingsObject : public SettingsObject { - Q_OBJECT + Q_OBJECT public: - explicit INISettingsObject(const QString &path, QObject *parent = 0); + explicit INISettingsObject(const QString &path, QObject *parent = 0); - /*! - * \brief Gets the path to the INI file. - * \return The path to the INI file. - */ - virtual QString filePath() const - { - return m_filePath; - } + /*! + * \brief Gets the path to the INI file. + * \return The path to the INI file. + */ + virtual QString filePath() const + { + return m_filePath; + } - /*! - * \brief Sets the path to the INI file and reloads it. - * \param filePath The INI file's new path. - */ - virtual void setFilePath(const QString &filePath); + /*! + * \brief Sets the path to the INI file and reloads it. + * \param filePath The INI file's new path. + */ + virtual void setFilePath(const QString &filePath); - bool reload() override; + bool reload() override; - void suspendSave() override; - void resumeSave() override; + void suspendSave() override; + void resumeSave() override; protected slots: - virtual void changeSetting(const Setting &setting, QVariant value) override; - virtual void resetSetting(const Setting &setting) override; + virtual void changeSetting(const Setting &setting, QVariant value) override; + virtual void resetSetting(const Setting &setting) override; protected: - virtual QVariant retrieveValue(const Setting &setting) override; - void doSave(); + virtual QVariant retrieveValue(const Setting &setting) override; + void doSave(); protected: - INIFile m_ini; - QString m_filePath; + INIFile m_ini; + QString m_filePath; }; diff --git a/api/logic/settings/OverrideSetting.cpp b/api/logic/settings/OverrideSetting.cpp index a3d48e03..1efdebac 100644 --- a/api/logic/settings/OverrideSetting.cpp +++ b/api/logic/settings/OverrideSetting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,39 +16,39 @@ #include "OverrideSetting.h" OverrideSetting::OverrideSetting(std::shared_ptr other, std::shared_ptr gate) - : Setting(other->configKeys(), QVariant()) + : Setting(other->configKeys(), QVariant()) { - Q_ASSERT(other); - Q_ASSERT(gate); - m_other = other; - m_gate = gate; + Q_ASSERT(other); + Q_ASSERT(gate); + m_other = other; + m_gate = gate; } bool OverrideSetting::isOverriding() const { - return m_gate->get().toBool(); + return m_gate->get().toBool(); } QVariant OverrideSetting::defValue() const { - return m_other->get(); + return m_other->get(); } QVariant OverrideSetting::get() const { - if(isOverriding()) - { - return Setting::get(); - } - return m_other->get(); + if(isOverriding()) + { + return Setting::get(); + } + return m_other->get(); } void OverrideSetting::reset() { - Setting::reset(); + Setting::reset(); } void OverrideSetting::set(QVariant value) { - Setting::set(value); + Setting::set(value); } diff --git a/api/logic/settings/OverrideSetting.h b/api/logic/settings/OverrideSetting.h index f2cbc5dc..f99caff1 100644 --- a/api/logic/settings/OverrideSetting.h +++ b/api/logic/settings/OverrideSetting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,19 +28,19 @@ */ class OverrideSetting : public Setting { - Q_OBJECT + Q_OBJECT public: - explicit OverrideSetting(std::shared_ptr overriden, std::shared_ptr gate); + explicit OverrideSetting(std::shared_ptr overriden, std::shared_ptr gate); - virtual QVariant defValue() const; - virtual QVariant get() const; - virtual void set (QVariant value); - virtual void reset(); + virtual QVariant defValue() const; + virtual QVariant get() const; + virtual void set (QVariant value); + virtual void reset(); private: - bool isOverriding() const; + bool isOverriding() const; protected: - std::shared_ptr m_other; - std::shared_ptr m_gate; + std::shared_ptr m_other; + std::shared_ptr m_gate; }; diff --git a/api/logic/settings/PassthroughSetting.cpp b/api/logic/settings/PassthroughSetting.cpp index 5da5d11c..beee4152 100644 --- a/api/logic/settings/PassthroughSetting.cpp +++ b/api/logic/settings/PassthroughSetting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,54 +16,54 @@ #include "PassthroughSetting.h" PassthroughSetting::PassthroughSetting(std::shared_ptr other, std::shared_ptr gate) - : Setting(other->configKeys(), QVariant()) + : Setting(other->configKeys(), QVariant()) { - Q_ASSERT(other); - m_other = other; - m_gate = gate; + Q_ASSERT(other); + m_other = other; + m_gate = gate; } bool PassthroughSetting::isOverriding() const { - if(!m_gate) - { - return false; - } - return m_gate->get().toBool(); + if(!m_gate) + { + return false; + } + return m_gate->get().toBool(); } QVariant PassthroughSetting::defValue() const { - if(isOverriding()) - { - return m_other->get(); - } - return m_other->defValue(); + if(isOverriding()) + { + return m_other->get(); + } + return m_other->defValue(); } QVariant PassthroughSetting::get() const { - if(isOverriding()) - { - return Setting::get(); - } - return m_other->get(); + if(isOverriding()) + { + return Setting::get(); + } + return m_other->get(); } void PassthroughSetting::reset() { - if(isOverriding()) - { - Setting::reset(); - } - m_other->reset(); + if(isOverriding()) + { + Setting::reset(); + } + m_other->reset(); } void PassthroughSetting::set(QVariant value) { - if(isOverriding()) - { - Setting::set(value); - } - m_other->set(value); + if(isOverriding()) + { + Setting::set(value); + } + m_other->set(value); } diff --git a/api/logic/settings/PassthroughSetting.h b/api/logic/settings/PassthroughSetting.h index ee844da4..00212072 100644 --- a/api/logic/settings/PassthroughSetting.h +++ b/api/logic/settings/PassthroughSetting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,19 +27,19 @@ */ class PassthroughSetting : public Setting { - Q_OBJECT + Q_OBJECT public: - explicit PassthroughSetting(std::shared_ptr overriden, std::shared_ptr gate); + explicit PassthroughSetting(std::shared_ptr overriden, std::shared_ptr gate); - virtual QVariant defValue() const; - virtual QVariant get() const; - virtual void set (QVariant value); - virtual void reset(); + virtual QVariant defValue() const; + virtual QVariant get() const; + virtual void set (QVariant value); + virtual void reset(); private: - bool isOverriding() const; + bool isOverriding() const; protected: - std::shared_ptr m_other; - std::shared_ptr m_gate; + std::shared_ptr m_other; + std::shared_ptr m_gate; }; diff --git a/api/logic/settings/Setting.cpp b/api/logic/settings/Setting.cpp index fa0041e0..419dfa2f 100644 --- a/api/logic/settings/Setting.cpp +++ b/api/logic/settings/Setting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,37 +17,37 @@ #include "settings/SettingsObject.h" Setting::Setting(QStringList synonyms, QVariant defVal) - : QObject(), m_synonyms(synonyms), m_defVal(defVal) + : QObject(), m_synonyms(synonyms), m_defVal(defVal) { } QVariant Setting::get() const { - SettingsObject *sbase = m_storage; - if (!sbase) - { - return defValue(); - } - else - { - QVariant test = sbase->retrieveValue(*this); - if (!test.isValid()) - return defValue(); - return test; - } + SettingsObject *sbase = m_storage; + if (!sbase) + { + return defValue(); + } + else + { + QVariant test = sbase->retrieveValue(*this); + if (!test.isValid()) + return defValue(); + return test; + } } QVariant Setting::defValue() const { - return m_defVal; + return m_defVal; } void Setting::set(QVariant value) { - emit SettingChanged(*this, value); + emit SettingChanged(*this, value); } void Setting::reset() { - emit settingReset(*this); + emit settingReset(*this); } diff --git a/api/logic/settings/Setting.h b/api/logic/settings/Setting.h index 3edea7be..9d827c72 100644 --- a/api/logic/settings/Setting.h +++ b/api/logic/settings/Setting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,91 +29,91 @@ class SettingsObject; */ class MULTIMC_LOGIC_EXPORT Setting : public QObject { - Q_OBJECT + Q_OBJECT public: - /** - * Construct a Setting - * - * Synonyms are all the possible names used in the settings object, in order of preference. - * First synonym is the ID, which identifies the setting in MultiMC. - * - * defVal is the default value that will be returned when the settings object - * doesn't have any value for this setting. - */ - explicit Setting(QStringList synonyms, QVariant defVal = QVariant()); + /** + * Construct a Setting + * + * Synonyms are all the possible names used in the settings object, in order of preference. + * First synonym is the ID, which identifies the setting in MultiMC. + * + * defVal is the default value that will be returned when the settings object + * doesn't have any value for this setting. + */ + explicit Setting(QStringList synonyms, QVariant defVal = QVariant()); - /*! - * \brief Gets this setting's ID. - * This is used to refer to the setting within the application. - * \warning Changing the ID while the setting is registered with a SettingsObject results in - * undefined behavior. - * \return The ID of the setting. - */ - virtual QString id() const - { - return m_synonyms.first(); - } + /*! + * \brief Gets this setting's ID. + * This is used to refer to the setting within the application. + * \warning Changing the ID while the setting is registered with a SettingsObject results in + * undefined behavior. + * \return The ID of the setting. + */ + virtual QString id() const + { + return m_synonyms.first(); + } - /*! - * \brief Gets this setting's config file key. - * This is used to store the setting's value in the config file. It is usually - * the same as the setting's ID, but it can be different. - * \return The setting's config file key. - */ - virtual QStringList configKeys() const - { - return m_synonyms; - } + /*! + * \brief Gets this setting's config file key. + * This is used to store the setting's value in the config file. It is usually + * the same as the setting's ID, but it can be different. + * \return The setting's config file key. + */ + virtual QStringList configKeys() const + { + return m_synonyms; + } - /*! - * \brief Gets this setting's value as a QVariant. - * This is done by calling the SettingsObject's retrieveValue() function. - * If this Setting doesn't have a SettingsObject, this returns an invalid QVariant. - * \return QVariant containing this setting's value. - * \sa value() - */ - virtual QVariant get() const; + /*! + * \brief Gets this setting's value as a QVariant. + * This is done by calling the SettingsObject's retrieveValue() function. + * If this Setting doesn't have a SettingsObject, this returns an invalid QVariant. + * \return QVariant containing this setting's value. + * \sa value() + */ + virtual QVariant get() const; - /*! - * \brief Gets this setting's default value. - * \return The default value of this setting. - */ - virtual QVariant defValue() const; + /*! + * \brief Gets this setting's default value. + * \return The default value of this setting. + */ + virtual QVariant defValue() const; signals: - /*! - * \brief Signal emitted when this Setting object's value changes. - * \param setting A reference to the Setting that changed. - * \param value This Setting object's new value. - */ - void SettingChanged(const Setting &setting, QVariant value); + /*! + * \brief Signal emitted when this Setting object's value changes. + * \param setting A reference to the Setting that changed. + * \param value This Setting object's new value. + */ + void SettingChanged(const Setting &setting, QVariant value); - /*! - * \brief Signal emitted when this Setting object's value resets to default. - * \param setting A reference to the Setting that changed. - */ - void settingReset(const Setting &setting); + /*! + * \brief Signal emitted when this Setting object's value resets to default. + * \param setting A reference to the Setting that changed. + */ + void settingReset(const Setting &setting); public slots: - /*! - * \brief Changes the setting's value. - * This is done by emitting the SettingChanged() signal which will then be - * handled by the SettingsObject object and cause the setting to change. - * \param value The new value. - */ - virtual void set(QVariant value); + /*! + * \brief Changes the setting's value. + * This is done by emitting the SettingChanged() signal which will then be + * handled by the SettingsObject object and cause the setting to change. + * \param value The new value. + */ + virtual void set(QVariant value); - /*! - * \brief Reset the setting to default - * This is done by emitting the settingReset() signal which will then be - * handled by the SettingsObject object and cause the setting to change. - */ - virtual void reset(); + /*! + * \brief Reset the setting to default + * This is done by emitting the settingReset() signal which will then be + * handled by the SettingsObject object and cause the setting to change. + */ + virtual void reset(); protected: - friend class SettingsObject; - SettingsObject * m_storage; - QStringList m_synonyms; - QVariant m_defVal; + friend class SettingsObject; + SettingsObject * m_storage; + QStringList m_synonyms; + QVariant m_defVal; }; diff --git a/api/logic/settings/SettingsObject.cpp b/api/logic/settings/SettingsObject.cpp index 87a8c2a8..b4fc5a68 100644 --- a/api/logic/settings/SettingsObject.cpp +++ b/api/logic/settings/SettingsObject.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,116 +27,116 @@ SettingsObject::SettingsObject(QObject *parent) : QObject(parent) SettingsObject::~SettingsObject() { - m_settings.clear(); + m_settings.clear(); } std::shared_ptr SettingsObject::registerOverride(std::shared_ptr original, - std::shared_ptr gate) + std::shared_ptr gate) { - if (contains(original->id())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(original->id()); - return nullptr; // Fail - } - auto override = std::make_shared(original, gate); - override->m_storage = this; - connectSignals(*override); - m_settings.insert(override->id(), override); - return override; + if (contains(original->id())) + { + qCritical() << QString("Failed to register setting %1. ID already exists.") + .arg(original->id()); + return nullptr; // Fail + } + auto override = std::make_shared(original, gate); + override->m_storage = this; + connectSignals(*override); + m_settings.insert(override->id(), override); + return override; } std::shared_ptr SettingsObject::registerPassthrough(std::shared_ptr original, - std::shared_ptr gate) + std::shared_ptr gate) { - if (contains(original->id())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(original->id()); - return nullptr; // Fail - } - auto passthrough = std::make_shared(original, gate); - passthrough->m_storage = this; - connectSignals(*passthrough); - m_settings.insert(passthrough->id(), passthrough); - return passthrough; + if (contains(original->id())) + { + qCritical() << QString("Failed to register setting %1. ID already exists.") + .arg(original->id()); + return nullptr; // Fail + } + auto passthrough = std::make_shared(original, gate); + passthrough->m_storage = this; + connectSignals(*passthrough); + m_settings.insert(passthrough->id(), passthrough); + return passthrough; } std::shared_ptr SettingsObject::registerSetting(QStringList synonyms, QVariant defVal) { - if (synonyms.empty()) - return nullptr; - if (contains(synonyms.first())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(synonyms.first()); - return nullptr; // Fail - } - auto setting = std::make_shared(synonyms, defVal); - setting->m_storage = this; - connectSignals(*setting); - m_settings.insert(setting->id(), setting); - return setting; + if (synonyms.empty()) + return nullptr; + if (contains(synonyms.first())) + { + qCritical() << QString("Failed to register setting %1. ID already exists.") + .arg(synonyms.first()); + return nullptr; // Fail + } + auto setting = std::make_shared(synonyms, defVal); + setting->m_storage = this; + connectSignals(*setting); + m_settings.insert(setting->id(), setting); + return setting; } std::shared_ptr SettingsObject::getSetting(const QString &id) const { - // Make sure there is a setting with the given ID. - if (!m_settings.contains(id)) - return NULL; + // Make sure there is a setting with the given ID. + if (!m_settings.contains(id)) + return NULL; - return m_settings[id]; + return m_settings[id]; } QVariant SettingsObject::get(const QString &id) const { - auto setting = getSetting(id); - return (setting ? setting->get() : QVariant()); + auto setting = getSetting(id); + return (setting ? setting->get() : QVariant()); } bool SettingsObject::set(const QString &id, QVariant value) { - auto setting = getSetting(id); - if (!setting) - { - qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id); - return false; - } - else - { - setting->set(value); - return true; - } + auto setting = getSetting(id); + if (!setting) + { + qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id); + return false; + } + else + { + setting->set(value); + return true; + } } void SettingsObject::reset(const QString &id) const { - auto setting = getSetting(id); - if (setting) - setting->reset(); + auto setting = getSetting(id); + if (setting) + setting->reset(); } bool SettingsObject::contains(const QString &id) { - return m_settings.contains(id); + return m_settings.contains(id); } bool SettingsObject::reload() { - for (auto setting : m_settings.values()) - { - setting->set(setting->get()); - } - return true; + for (auto setting : m_settings.values()) + { + setting->set(setting->get()); + } + return true; } void SettingsObject::connectSignals(const Setting &setting) { - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), - SLOT(changeSetting(const Setting &, QVariant))); - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), - SIGNAL(SettingChanged(const Setting &, QVariant))); + connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), + SLOT(changeSetting(const Setting &, QVariant))); + connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), + SIGNAL(SettingChanged(const Setting &, QVariant))); - connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); - connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); + connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); + connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); } diff --git a/api/logic/settings/SettingsObject.h b/api/logic/settings/SettingsObject.h index 8582d8ad..11dc50ca 100644 --- a/api/logic/settings/SettingsObject.h +++ b/api/logic/settings/SettingsObject.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,173 +42,173 @@ typedef std::shared_ptr SettingsObjectPtr; */ class MULTIMC_LOGIC_EXPORT SettingsObject : public QObject { - Q_OBJECT + Q_OBJECT public: - class Lock - { - public: - Lock(SettingsObjectPtr locked) - :m_locked(locked) - { - m_locked->suspendSave(); - } - ~Lock() - { - m_locked->resumeSave(); - } - private: - SettingsObjectPtr m_locked; - }; + class Lock + { + public: + Lock(SettingsObjectPtr locked) + :m_locked(locked) + { + m_locked->suspendSave(); + } + ~Lock() + { + m_locked->resumeSave(); + } + private: + SettingsObjectPtr m_locked; + }; public: - explicit SettingsObject(QObject *parent = 0); - virtual ~SettingsObject(); - /*! - * Registers an override setting for the given original setting in this settings object - * gate decides if the passthrough (true) or the original (false) is used for value - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerOverride(std::shared_ptr original, std::shared_ptr gate); - - /*! - * Registers a passthorugh setting for the given original setting in this settings object - * gate decides if the passthrough (true) or the original (false) is used for value - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerPassthrough(std::shared_ptr original, std::shared_ptr gate); - - /*! - * Registers the given setting with this SettingsObject and connects the necessary signals. - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerSetting(QStringList synonyms, - QVariant defVal = QVariant()); - - /*! - * Registers the given setting with this SettingsObject and connects the necessary signals. - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerSetting(QString id, QVariant defVal = QVariant()) - { - return registerSetting(QStringList(id), defVal); - } - - /*! - * \brief Gets the setting with the given ID. - * \param id The ID of the setting to get. - * \return A pointer to the setting with the given ID. - * Returns null if there is no setting with the given ID. - * \sa operator []() - */ - std::shared_ptr getSetting(const QString &id) const; - - /*! - * \brief Gets the value of the setting with the given ID. - * \param id The ID of the setting to get. - * \return The setting's value as a QVariant. - * If no setting with the given ID exists, returns an invalid QVariant. - */ - QVariant get(const QString &id) const; - - /*! - * \brief Sets the value of the setting with the given ID. - * If no setting with the given ID exists, returns false - * \param id The ID of the setting to change. - * \param value The new value of the setting. - * \return True if successful, false if it failed. - */ - bool set(const QString &id, QVariant value); - - /*! - * \brief Reverts the setting with the given ID to default. - * \param id The ID of the setting to reset. - */ - void reset(const QString &id) const; - - /*! - * \brief Checks if this SettingsObject contains a setting with the given ID. - * \param id The ID to check for. - * \return True if the SettingsObject has a setting with the given ID. - */ - bool contains(const QString &id); - - /*! - * \brief Reloads the settings and emit signals for changed settings - * \return True if reloading was successful - */ - virtual bool reload(); - - virtual void suspendSave() = 0; - virtual void resumeSave() = 0; + explicit SettingsObject(QObject *parent = 0); + virtual ~SettingsObject(); + /*! + * Registers an override setting for the given original setting in this settings object + * gate decides if the passthrough (true) or the original (false) is used for value + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerOverride(std::shared_ptr original, std::shared_ptr gate); + + /*! + * Registers a passthorugh setting for the given original setting in this settings object + * gate decides if the passthrough (true) or the original (false) is used for value + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerPassthrough(std::shared_ptr original, std::shared_ptr gate); + + /*! + * Registers the given setting with this SettingsObject and connects the necessary signals. + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerSetting(QStringList synonyms, + QVariant defVal = QVariant()); + + /*! + * Registers the given setting with this SettingsObject and connects the necessary signals. + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerSetting(QString id, QVariant defVal = QVariant()) + { + return registerSetting(QStringList(id), defVal); + } + + /*! + * \brief Gets the setting with the given ID. + * \param id The ID of the setting to get. + * \return A pointer to the setting with the given ID. + * Returns null if there is no setting with the given ID. + * \sa operator []() + */ + std::shared_ptr getSetting(const QString &id) const; + + /*! + * \brief Gets the value of the setting with the given ID. + * \param id The ID of the setting to get. + * \return The setting's value as a QVariant. + * If no setting with the given ID exists, returns an invalid QVariant. + */ + QVariant get(const QString &id) const; + + /*! + * \brief Sets the value of the setting with the given ID. + * If no setting with the given ID exists, returns false + * \param id The ID of the setting to change. + * \param value The new value of the setting. + * \return True if successful, false if it failed. + */ + bool set(const QString &id, QVariant value); + + /*! + * \brief Reverts the setting with the given ID to default. + * \param id The ID of the setting to reset. + */ + void reset(const QString &id) const; + + /*! + * \brief Checks if this SettingsObject contains a setting with the given ID. + * \param id The ID to check for. + * \return True if the SettingsObject has a setting with the given ID. + */ + bool contains(const QString &id); + + /*! + * \brief Reloads the settings and emit signals for changed settings + * \return True if reloading was successful + */ + virtual bool reload(); + + virtual void suspendSave() = 0; + virtual void resumeSave() = 0; signals: - /*! - * \brief Signal emitted when one of this SettingsObject object's settings changes. - * This is usually just connected directly to each Setting object's - * SettingChanged() signals. - * \param setting A reference to the Setting object that changed. - * \param value The Setting object's new value. - */ - void SettingChanged(const Setting &setting, QVariant value); - - /*! - * \brief Signal emitted when one of this SettingsObject object's settings resets. - * This is usually just connected directly to each Setting object's - * settingReset() signals. - * \param setting A reference to the Setting object that changed. - */ - void settingReset(const Setting &setting); + /*! + * \brief Signal emitted when one of this SettingsObject object's settings changes. + * This is usually just connected directly to each Setting object's + * SettingChanged() signals. + * \param setting A reference to the Setting object that changed. + * \param value The Setting object's new value. + */ + void SettingChanged(const Setting &setting, QVariant value); + + /*! + * \brief Signal emitted when one of this SettingsObject object's settings resets. + * This is usually just connected directly to each Setting object's + * settingReset() signals. + * \param setting A reference to the Setting object that changed. + */ + void settingReset(const Setting &setting); protected slots: - /*! - * \brief Changes a setting. - * This slot is usually connected to each Setting object's - * SettingChanged() signal. The signal is emitted, causing this slot - * to update the setting's value in the config file. - * \param setting A reference to the Setting object that changed. - * \param value The setting's new value. - */ - virtual void changeSetting(const Setting &setting, QVariant value) = 0; - - /*! - * \brief Resets a setting. - * This slot is usually connected to each Setting object's - * settingReset() signal. The signal is emitted, causing this slot - * to update the setting's value in the config file. - * \param setting A reference to the Setting object that changed. - */ - virtual void resetSetting(const Setting &setting) = 0; + /*! + * \brief Changes a setting. + * This slot is usually connected to each Setting object's + * SettingChanged() signal. The signal is emitted, causing this slot + * to update the setting's value in the config file. + * \param setting A reference to the Setting object that changed. + * \param value The setting's new value. + */ + virtual void changeSetting(const Setting &setting, QVariant value) = 0; + + /*! + * \brief Resets a setting. + * This slot is usually connected to each Setting object's + * settingReset() signal. The signal is emitted, causing this slot + * to update the setting's value in the config file. + * \param setting A reference to the Setting object that changed. + */ + virtual void resetSetting(const Setting &setting) = 0; protected: - /*! - * \brief Connects the necessary signals to the given Setting. - * \param setting The setting to connect. - */ - void connectSignals(const Setting &setting); + /*! + * \brief Connects the necessary signals to the given Setting. + * \param setting The setting to connect. + */ + void connectSignals(const Setting &setting); - /*! - * \brief Function used by Setting objects to get their values from the SettingsObject. - * \param setting The - * \return - */ - virtual QVariant retrieveValue(const Setting &setting) = 0; + /*! + * \brief Function used by Setting objects to get their values from the SettingsObject. + * \param setting The + * \return + */ + virtual QVariant retrieveValue(const Setting &setting) = 0; - friend class Setting; + friend class Setting; private: - QMap> m_settings; + QMap> m_settings; protected: - bool m_suspendSave = false; - bool m_doSave = false; + bool m_suspendSave = false; + bool m_doSave = false; }; diff --git a/api/logic/status/StatusChecker.cpp b/api/logic/status/StatusChecker.cpp index bff9fda9..f18ed364 100644 --- a/api/logic/status/StatusChecker.cpp +++ b/api/logic/status/StatusChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,121 +28,121 @@ StatusChecker::StatusChecker() void StatusChecker::timerEvent(QTimerEvent *e) { - QObject::timerEvent(e); - reloadStatus(); + QObject::timerEvent(e); + reloadStatus(); } void StatusChecker::reloadStatus() { - if (isLoadingStatus()) - { - // qDebug() << "Ignored request to reload status. Currently reloading already."; - return; - } - - // qDebug() << "Reloading status."; - - NetJob* job = new NetJob("Status JSON"); - job->addNetAction(Net::Download::makeByteArray(URLConstants::MOJANG_STATUS_URL, &dataSink)); - QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed); - m_statusNetJob.reset(job); - emit statusLoading(true); - job->start(); + if (isLoadingStatus()) + { + // qDebug() << "Ignored request to reload status. Currently reloading already."; + return; + } + + // qDebug() << "Reloading status."; + + NetJob* job = new NetJob("Status JSON"); + job->addNetAction(Net::Download::makeByteArray(URLConstants::MOJANG_STATUS_URL, &dataSink)); + QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished); + QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed); + m_statusNetJob.reset(job); + emit statusLoading(true); + job->start(); } void StatusChecker::statusDownloadFinished() { - qDebug() << "Finished loading status JSON."; - m_statusEntries.clear(); - m_statusNetJob.reset(); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(dataSink, &jsonError); - - if (jsonError.error != QJsonParseError::NoError) - { - fail("Error parsing status JSON:" + jsonError.errorString()); - return; - } - - if (!jsonDoc.isArray()) - { - fail("Error parsing status JSON: JSON root is not an array"); - return; - } - - QJsonArray root = jsonDoc.array(); - - for(auto status = root.begin(); status != root.end(); ++status) - { - QVariantMap map = (*status).toObject().toVariantMap(); - - for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) - { - QString key = iter.key(); - QVariant value = iter.value(); - - if(value.type() == QVariant::Type::String) - { - m_statusEntries.insert(key, value.toString()); - //qDebug() << "Status JSON object: " << key << m_statusEntries[key]; - } - else - { - fail("Malformed status JSON: expected status type to be a string."); - return; - } - } - } - - succeed(); + qDebug() << "Finished loading status JSON."; + m_statusEntries.clear(); + m_statusNetJob.reset(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(dataSink, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + fail("Error parsing status JSON:" + jsonError.errorString()); + return; + } + + if (!jsonDoc.isArray()) + { + fail("Error parsing status JSON: JSON root is not an array"); + return; + } + + QJsonArray root = jsonDoc.array(); + + for(auto status = root.begin(); status != root.end(); ++status) + { + QVariantMap map = (*status).toObject().toVariantMap(); + + for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) + { + QString key = iter.key(); + QVariant value = iter.value(); + + if(value.type() == QVariant::Type::String) + { + m_statusEntries.insert(key, value.toString()); + //qDebug() << "Status JSON object: " << key << m_statusEntries[key]; + } + else + { + fail("Malformed status JSON: expected status type to be a string."); + return; + } + } + } + + succeed(); } void StatusChecker::statusDownloadFailed(QString reason) { - fail(tr("Failed to load status JSON:\n%1").arg(reason)); + fail(tr("Failed to load status JSON:\n%1").arg(reason)); } QMap StatusChecker::getStatusEntries() const { - return m_statusEntries; + return m_statusEntries; } bool StatusChecker::isLoadingStatus() const { - return m_statusNetJob.get() != nullptr; + return m_statusNetJob.get() != nullptr; } QString StatusChecker::getLastLoadErrorMsg() const { - return m_lastLoadError; + return m_lastLoadError; } void StatusChecker::succeed() { - if(m_prevEntries != m_statusEntries) - { - emit statusChanged(m_statusEntries); - m_prevEntries = m_statusEntries; - } - m_lastLoadError = ""; - qDebug() << "Status loading succeeded."; - m_statusNetJob.reset(); - emit statusLoading(false); + if(m_prevEntries != m_statusEntries) + { + emit statusChanged(m_statusEntries); + m_prevEntries = m_statusEntries; + } + m_lastLoadError = ""; + qDebug() << "Status loading succeeded."; + m_statusNetJob.reset(); + emit statusLoading(false); } void StatusChecker::fail(const QString& errorMsg) { - if(m_prevEntries != m_statusEntries) - { - emit statusChanged(m_statusEntries); - m_prevEntries = m_statusEntries; - } - m_lastLoadError = errorMsg; - qDebug() << "Failed to load status:" << errorMsg; - m_statusNetJob.reset(); - emit statusLoading(false); + if(m_prevEntries != m_statusEntries) + { + emit statusChanged(m_statusEntries); + m_prevEntries = m_statusEntries; + } + m_lastLoadError = errorMsg; + qDebug() << "Failed to load status:" << errorMsg; + m_statusNetJob.reset(); + emit statusLoading(false); } diff --git a/api/logic/status/StatusChecker.h b/api/logic/status/StatusChecker.h index f19aba9a..829b139d 100644 --- a/api/logic/status/StatusChecker.h +++ b/api/logic/status/StatusChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,36 +25,36 @@ class MULTIMC_LOGIC_EXPORT StatusChecker : public QObject { - Q_OBJECT + Q_OBJECT public: /* con/des */ - StatusChecker(); + StatusChecker(); public: /* methods */ - QString getLastLoadErrorMsg() const; - bool isLoadingStatus() const; - QMap getStatusEntries() const; + QString getLastLoadErrorMsg() const; + bool isLoadingStatus() const; + QMap getStatusEntries() const; signals: - void statusLoading(bool loading); - void statusChanged(QMap newStatus); + void statusLoading(bool loading); + void statusChanged(QMap newStatus); public slots: - void reloadStatus(); + void reloadStatus(); protected: /* methods */ - virtual void timerEvent(QTimerEvent *); + virtual void timerEvent(QTimerEvent *); protected slots: - void statusDownloadFinished(); - void statusDownloadFailed(QString reason); - void succeed(); - void fail(const QString& errorMsg); + void statusDownloadFinished(); + void statusDownloadFailed(QString reason); + void succeed(); + void fail(const QString& errorMsg); protected: /* data */ - QMap m_prevEntries; - QMap m_statusEntries; - NetJobPtr m_statusNetJob; - QString m_lastLoadError; - QByteArray dataSink; + QMap m_prevEntries; + QMap m_statusEntries; + NetJobPtr m_statusNetJob; + QString m_lastLoadError; + QByteArray dataSink; }; diff --git a/api/logic/tasks/SequentialTask.cpp b/api/logic/tasks/SequentialTask.cpp index ac0e7820..d0777132 100644 --- a/api/logic/tasks/SequentialTask.cpp +++ b/api/logic/tasks/SequentialTask.cpp @@ -6,50 +6,50 @@ SequentialTask::SequentialTask(QObject *parent) : Task(parent), m_currentIndex(- void SequentialTask::addTask(std::shared_ptr task) { - m_queue.append(task); + m_queue.append(task); } void SequentialTask::executeTask() { - m_currentIndex = -1; - startNext(); + m_currentIndex = -1; + startNext(); } void SequentialTask::startNext() { - if (m_currentIndex != -1) - { - std::shared_ptr previous = m_queue[m_currentIndex]; - disconnect(previous.get(), 0, this, 0); - } - m_currentIndex++; - if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) - { - emitSucceeded(); - return; - } - std::shared_ptr next = m_queue[m_currentIndex]; - connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); - connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); - connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); - connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); - next->start(); + if (m_currentIndex != -1) + { + std::shared_ptr previous = m_queue[m_currentIndex]; + disconnect(previous.get(), 0, this, 0); + } + m_currentIndex++; + if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) + { + emitSucceeded(); + return; + } + std::shared_ptr next = m_queue[m_currentIndex]; + connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); + connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); + connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); + connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); + next->start(); } void SequentialTask::subTaskFailed(const QString &msg) { - emitFailed(msg); + emitFailed(msg); } void SequentialTask::subTaskStatus(const QString &msg) { - setStatus(msg); + setStatus(msg); } void SequentialTask::subTaskProgress(qint64 current, qint64 total) { - if(total == 0) - { - setProgress(0, 100); - return; - } - setProgress(current, total); + if(total == 0) + { + setProgress(0, 100); + return; + } + setProgress(current, total); } diff --git a/api/logic/tasks/SequentialTask.h b/api/logic/tasks/SequentialTask.h index 69031095..2ca77c00 100644 --- a/api/logic/tasks/SequentialTask.h +++ b/api/logic/tasks/SequentialTask.h @@ -9,23 +9,24 @@ class MULTIMC_LOGIC_EXPORT SequentialTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit SequentialTask(QObject *parent = 0); + explicit SequentialTask(QObject *parent = 0); + virtual ~SequentialTask() {}; - void addTask(std::shared_ptr task); + void addTask(std::shared_ptr task); protected: - void executeTask(); + void executeTask(); private slots: - void startNext(); - void subTaskFailed(const QString &msg); - void subTaskStatus(const QString &msg); - void subTaskProgress(qint64 current, qint64 total); + void startNext(); + void subTaskFailed(const QString &msg); + void subTaskStatus(const QString &msg); + void subTaskProgress(qint64 current, qint64 total); private: - QQueue > m_queue; - int m_currentIndex; + QQueue > m_queue; + int m_currentIndex; }; diff --git a/api/logic/tasks/Task.cpp b/api/logic/tasks/Task.cpp index 2523aeb2..accb39b6 100644 --- a/api/logic/tasks/Task.cpp +++ b/api/logic/tasks/Task.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,124 +23,146 @@ Task::Task(QObject *parent) : QObject(parent) void Task::setStatus(const QString &new_status) { - if(m_status != new_status) - { - m_status = new_status; - emit status(m_status); - } + if(m_status != new_status) + { + m_status = new_status; + emit status(m_status); + } } void Task::setProgress(qint64 current, qint64 total) { - m_progress = current; - m_progressTotal = total; - emit progress(m_progress, m_progressTotal); + m_progress = current; + m_progressTotal = total; + emit progress(m_progress, m_progressTotal); } void Task::start() { - m_running = true; - emit started(); - qDebug() << "Task" << describe() << "started"; - executeTask(); + switch(m_state) + { + case State::Inactive: + { + qDebug() << "Task" << describe() << "starting for the first time"; + break; + } + case State::AbortedByUser: + { + qDebug() << "Task" << describe() << "restarting for after being aborted by user"; + break; + } + case State::Failed: + { + qDebug() << "Task" << describe() << "restarting for after failing at first"; + break; + } + case State::Succeeded: + { + qDebug() << "Task" << describe() << "restarting for after succeeding at first"; + break; + } + case State::Running: + { + qWarning() << "MultiMC tried to start task" << describe() << "while it was already running!"; + return; + } + } + // NOTE: only fall thorugh to here in end states + m_state = State::Running; + emit started(); + executeTask(); } void Task::emitFailed(QString reason) { - // Don't fail twice. - if (!m_running) - { - qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; - return; - } - m_running = false; - m_finished = true; - m_succeeded = false; - m_failReason = reason; - qCritical() << "Task" << describe() << "failed: " << reason; - emit failed(reason); - emit finished(); + // Don't fail twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; + return; + } + m_state = State::Failed; + m_failReason = reason; + qCritical() << "Task" << describe() << "failed: " << reason; + emit failed(reason); + emit finished(); } void Task::emitAborted() { - // Don't abort twice. - if (!m_running) - { - qCritical() << "Task" << describe() << "aborted while not running!!!!"; - return; - } - m_running = false; - m_finished = true; - m_succeeded = false; - m_failReason = "Aborted."; - qDebug() << "Task" << describe() << "aborted."; - emit failed(m_failReason); - emit finished(); + // Don't abort twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "aborted while not running!!!!"; + return; + } + m_state = State::AbortedByUser; + m_failReason = "Aborted."; + qDebug() << "Task" << describe() << "aborted."; + emit failed(m_failReason); + emit finished(); } void Task::emitSucceeded() { - // Don't succeed twice. - if (!m_running) - { - qCritical() << "Task" << describe() << "succeeded while not running!!!!"; - return; - } - m_running = false; - m_finished = true; - m_succeeded = true; - qDebug() << "Task" << describe() << "succeeded"; - emit succeeded(); - emit finished(); + // Don't succeed twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "succeeded while not running!!!!"; + return; + } + m_state = State::Succeeded; + qDebug() << "Task" << describe() << "succeeded"; + emit succeeded(); + emit finished(); } QString Task::describe() { - QString outStr; - QTextStream out(&outStr); - out << metaObject()->className() << QChar('('); - auto name = objectName(); - if(name.isEmpty()) - { - out << QString("0x%1").arg((quintptr)this, 0, 16); - } - else - { - out << name; - } - out << QChar(')'); - out.flush(); - return outStr; + QString outStr; + QTextStream out(&outStr); + out << metaObject()->className() << QChar('('); + auto name = objectName(); + if(name.isEmpty()) + { + out << QString("0x%1").arg((quintptr)this, 0, 16); + } + else + { + out << name; + } + out << QChar(')'); + out.flush(); + return outStr; } bool Task::isRunning() const { - return m_running; + return m_state == State::Running; } bool Task::isFinished() const { - return m_finished; + return m_state != State::Running && m_state != State::Inactive; } bool Task::wasSuccessful() const { - return m_succeeded; + return m_state == State::Succeeded; } QString Task::failReason() const { - return m_failReason; + return m_failReason; } void Task::logWarning(const QString& line) { - qWarning() << line; - m_Warnings.append(line); + qWarning() << line; + m_Warnings.append(line); } QStringList Task::warnings() const { - return m_Warnings; + return m_Warnings; } diff --git a/api/logic/tasks/Task.h b/api/logic/tasks/Task.h index 643f8510..72bfeca8 100644 --- a/api/logic/tasks/Task.h +++ b/api/logic/tasks/Task.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,78 +23,86 @@ class MULTIMC_LOGIC_EXPORT Task : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit Task(QObject *parent = 0); - virtual ~Task() {}; + enum class State + { + Inactive, + Running, + Succeeded, + Failed, + AbortedByUser + }; - bool isRunning() const; - bool isFinished() const; - bool wasSuccessful() const; +public: + explicit Task(QObject *parent = 0); + virtual ~Task() {}; + + bool isRunning() const; + bool isFinished() const; + bool wasSuccessful() const; - /*! - * Returns the string that was passed to emitFailed as the error message when the task failed. - * If the task hasn't failed, returns an empty string. - */ - QString failReason() const; + /*! + * Returns the string that was passed to emitFailed as the error message when the task failed. + * If the task hasn't failed, returns an empty string. + */ + QString failReason() const; - virtual QStringList warnings() const; + virtual QStringList warnings() const; - virtual bool canAbort() const { return false; } + virtual bool canAbort() const { return false; } - QString getStatus() - { - return m_status; - } + QString getStatus() + { + return m_status; + } - qint64 getProgress() - { - return m_progress; - } + qint64 getProgress() + { + return m_progress; + } - qint64 getTotalProgress() - { - return m_progressTotal; - } + qint64 getTotalProgress() + { + return m_progressTotal; + } protected: - void logWarning(const QString & line); + void logWarning(const QString & line); private: - QString describe(); + QString describe(); signals: - void started(); - void progress(qint64 current, qint64 total); - void finished(); - void succeeded(); - void failed(QString reason); - void status(QString status); + void started(); + void progress(qint64 current, qint64 total); + void finished(); + void succeeded(); + void failed(QString reason); + void status(QString status); public slots: - virtual void start(); - virtual bool abort() { return false; }; + virtual void start(); + virtual bool abort() { return false; }; protected: - virtual void executeTask() = 0; + virtual void executeTask() = 0; protected slots: - virtual void emitSucceeded(); - virtual void emitAborted(); - virtual void emitFailed(QString reason); + virtual void emitSucceeded(); + virtual void emitAborted(); + virtual void emitFailed(QString reason); public slots: - void setStatus(const QString &status); - void setProgress(qint64 current, qint64 total); + void setStatus(const QString &status); + void setProgress(qint64 current, qint64 total); private: - bool m_running = false; - bool m_finished = false; - bool m_succeeded = false; - QStringList m_Warnings; - QString m_failReason = ""; - QString m_status; - int m_progress = 0; - int m_progressTotal = 100; + State m_state = State::Inactive; + QStringList m_Warnings; + QString m_failReason = ""; + QString m_status; + int m_progress = 0; + int m_progressTotal = 100; }; diff --git a/api/logic/tools/BaseExternalTool.cpp b/api/logic/tools/BaseExternalTool.cpp index 2b97c3c9..38d81788 100644 --- a/api/logic/tools/BaseExternalTool.cpp +++ b/api/logic/tools/BaseExternalTool.cpp @@ -10,7 +10,7 @@ #include "BaseInstance.h" BaseExternalTool::BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : QObject(parent), m_instance(instance), globalSettings(settings) + : QObject(parent), m_instance(instance), globalSettings(settings) { } @@ -19,14 +19,14 @@ BaseExternalTool::~BaseExternalTool() } BaseDetachedTool::BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseExternalTool(settings, instance, parent) + : BaseExternalTool(settings, instance, parent) { } void BaseDetachedTool::run() { - runImpl(); + runImpl(); } @@ -35,7 +35,7 @@ BaseExternalToolFactory::~BaseExternalToolFactory() } BaseDetachedTool *BaseDetachedToolFactory::createDetachedTool(InstancePtr instance, - QObject *parent) + QObject *parent) { - return qobject_cast(createTool(instance, parent)); + return qobject_cast(createTool(instance, parent)); } diff --git a/api/logic/tools/BaseExternalTool.h b/api/logic/tools/BaseExternalTool.h index fe1b5dc6..b393b9ef 100644 --- a/api/logic/tools/BaseExternalTool.h +++ b/api/logic/tools/BaseExternalTool.h @@ -11,50 +11,50 @@ class QProcess; class MULTIMC_LOGIC_EXPORT BaseExternalTool : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); - virtual ~BaseExternalTool(); + explicit BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + virtual ~BaseExternalTool(); protected: - InstancePtr m_instance; - SettingsObjectPtr globalSettings; + InstancePtr m_instance; + SettingsObjectPtr globalSettings; }; class MULTIMC_LOGIC_EXPORT BaseDetachedTool : public BaseExternalTool { - Q_OBJECT + Q_OBJECT public: - explicit BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + explicit BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); public slots: - void run(); + void run(); protected: - virtual void runImpl() = 0; + virtual void runImpl() = 0; }; class MULTIMC_LOGIC_EXPORT BaseExternalToolFactory { public: - virtual ~BaseExternalToolFactory(); + virtual ~BaseExternalToolFactory(); - virtual QString name() const = 0; + virtual QString name() const = 0; - virtual void registerSettings(SettingsObjectPtr settings) = 0; + virtual void registerSettings(SettingsObjectPtr settings) = 0; - virtual BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) = 0; + virtual BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) = 0; - virtual bool check(QString *error) = 0; - virtual bool check(const QString &path, QString *error) = 0; + virtual bool check(QString *error) = 0; + virtual bool check(const QString &path, QString *error) = 0; protected: - SettingsObjectPtr globalSettings; + SettingsObjectPtr globalSettings; }; class MULTIMC_LOGIC_EXPORT BaseDetachedToolFactory : public BaseExternalToolFactory { public: - virtual BaseDetachedTool *createDetachedTool(InstancePtr instance, QObject *parent = 0); + virtual BaseDetachedTool *createDetachedTool(InstancePtr instance, QObject *parent = 0); }; diff --git a/api/logic/tools/BaseProfiler.cpp b/api/logic/tools/BaseProfiler.cpp index 5ff0fa44..300d1a73 100644 --- a/api/logic/tools/BaseProfiler.cpp +++ b/api/logic/tools/BaseProfiler.cpp @@ -1,35 +1,36 @@ #include "BaseProfiler.h" +#include "QObjectPtr.h" #include BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseExternalTool(settings, instance, parent) + : BaseExternalTool(settings, instance, parent) { } -void BaseProfiler::beginProfiling(std::shared_ptr process) +void BaseProfiler::beginProfiling(shared_qobject_ptr process) { - beginProfilingImpl(process); + beginProfilingImpl(process); } void BaseProfiler::abortProfiling() { - abortProfilingImpl(); + abortProfilingImpl(); } void BaseProfiler::abortProfilingImpl() { - if (!m_profilerProcess) - { - return; - } - m_profilerProcess->terminate(); - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - emit abortLaunch(tr("Profiler aborted")); + if (!m_profilerProcess) + { + return; + } + m_profilerProcess->terminate(); + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + emit abortLaunch(tr("Profiler aborted")); } BaseProfiler *BaseProfilerFactory::createProfiler(InstancePtr instance, QObject *parent) { - return qobject_cast(createTool(instance, parent)); + return qobject_cast(createTool(instance, parent)); } diff --git a/api/logic/tools/BaseProfiler.h b/api/logic/tools/BaseProfiler.h index 3340b7e4..da817f52 100644 --- a/api/logic/tools/BaseProfiler.h +++ b/api/logic/tools/BaseProfiler.h @@ -1,6 +1,7 @@ #pragma once #include "BaseExternalTool.h" +#include "QObjectPtr.h" #include "multimc_logic_export.h" @@ -11,28 +12,28 @@ class QProcess; class MULTIMC_LOGIC_EXPORT BaseProfiler : public BaseExternalTool { - Q_OBJECT + Q_OBJECT public: - explicit BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + explicit BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); public slots: - void beginProfiling(std::shared_ptr process); - void abortProfiling(); + void beginProfiling(shared_qobject_ptr process); + void abortProfiling(); protected: - QProcess *m_profilerProcess; + QProcess *m_profilerProcess; - virtual void beginProfilingImpl(std::shared_ptr process) = 0; - virtual void abortProfilingImpl(); + virtual void beginProfilingImpl(shared_qobject_ptr process) = 0; + virtual void abortProfilingImpl(); signals: - void readyToLaunch(const QString &message); - void abortLaunch(const QString &message); + void readyToLaunch(const QString &message); + void abortLaunch(const QString &message); }; class MULTIMC_LOGIC_EXPORT BaseProfilerFactory : public BaseExternalToolFactory { public: - virtual BaseProfiler *createProfiler(InstancePtr instance, QObject *parent = 0); + virtual BaseProfiler *createProfiler(InstancePtr instance, QObject *parent = 0); }; diff --git a/api/logic/tools/JProfiler.cpp b/api/logic/tools/JProfiler.cpp index a0e3c895..1dc0d109 100644 --- a/api/logic/tools/JProfiler.cpp +++ b/api/logic/tools/JProfiler.cpp @@ -8,109 +8,109 @@ class JProfiler : public BaseProfiler { - Q_OBJECT + Q_OBJECT public: - JProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + JProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); private slots: - void profilerStarted(); - void profilerFinished(int exit, QProcess::ExitStatus status); + void profilerStarted(); + void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(std::shared_ptr process); + void beginProfilingImpl(shared_qobject_ptr process); private: - int listeningPort = 0; + int listeningPort = 0; }; JProfiler::JProfiler(SettingsObjectPtr settings, InstancePtr instance, - QObject *parent) - : BaseProfiler(settings, instance, parent) + QObject *parent) + : BaseProfiler(settings, instance, parent) { } void JProfiler::profilerStarted() { - emit readyToLaunch(tr("Listening on port: %1").arg(listeningPort)); + emit readyToLaunch(tr("Listening on port: %1").arg(listeningPort)); } void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status) { - if (status == QProcess::CrashExit) - { - emit abortLaunch(tr("Profiler aborted")); - } - if (m_profilerProcess) - { - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - } + if (status == QProcess::CrashExit) + { + emit abortLaunch(tr("Profiler aborted")); + } + if (m_profilerProcess) + { + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + } } -void JProfiler::beginProfilingImpl(std::shared_ptr process) +void JProfiler::beginProfilingImpl(shared_qobject_ptr process) { - listeningPort = globalSettings->get("JProfilerPort").toInt(); - QProcess *profiler = new QProcess(this); - QStringList profilerArgs = - { - "-d", QString::number(process->pid()), - "--gui", - "-p", QString::number(listeningPort) - }; - auto basePath = globalSettings->get("JProfilerPath").toString(); + listeningPort = globalSettings->get("JProfilerPort").toInt(); + QProcess *profiler = new QProcess(this); + QStringList profilerArgs = + { + "-d", QString::number(process->pid()), + "--gui", + "-p", QString::number(listeningPort) + }; + auto basePath = globalSettings->get("JProfilerPath").toString(); #ifdef Q_OS_WIN - QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable.exe"); + QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable.exe"); #else - QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable"); + QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable"); #endif - profiler->setArguments(profilerArgs); - profiler->setProgram(profilerProgram); + profiler->setArguments(profilerArgs); + profiler->setProgram(profilerProgram); - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); + connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); + connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); - m_profilerProcess = profiler; - profiler->start(); + m_profilerProcess = profiler; + profiler->start(); } void JProfilerFactory::registerSettings(SettingsObjectPtr settings) { - settings->registerSetting("JProfilerPath"); - settings->registerSetting("JProfilerPort", 42042); - globalSettings = settings; + settings->registerSetting("JProfilerPath"); + settings->registerSetting("JProfilerPort", 42042); + globalSettings = settings; } BaseExternalTool *JProfilerFactory::createTool(InstancePtr instance, QObject *parent) { - return new JProfiler(globalSettings, instance, parent); + return new JProfiler(globalSettings, instance, parent); } bool JProfilerFactory::check(QString *error) { - return check(globalSettings->get("JProfilerPath").toString(), error); + return check(globalSettings->get("JProfilerPath").toString(), error); } bool JProfilerFactory::check(const QString &path, QString *error) { - if (path.isEmpty()) - { - *error = QObject::tr("Empty path"); - return false; - } - QDir dir(path); - if (!dir.exists()) - { - *error = QObject::tr("Path does not exist"); - return false; - } - if (!dir.exists("bin") || !(dir.exists("bin/jprofiler") || dir.exists("bin/jprofiler.exe")) || !dir.exists("bin/agent.jar")) - { - *error = QObject::tr("Invalid JProfiler install"); - return false; - } - return true; + if (path.isEmpty()) + { + *error = QObject::tr("Empty path"); + return false; + } + QDir dir(path); + if (!dir.exists()) + { + *error = QObject::tr("Path does not exist"); + return false; + } + if (!dir.exists("bin") || !(dir.exists("bin/jprofiler") || dir.exists("bin/jprofiler.exe")) || !dir.exists("bin/agent.jar")) + { + *error = QObject::tr("Invalid JProfiler install"); + return false; + } + return true; } #include "JProfiler.moc" diff --git a/api/logic/tools/JProfiler.h b/api/logic/tools/JProfiler.h index d658d6c2..f211ddbf 100644 --- a/api/logic/tools/JProfiler.h +++ b/api/logic/tools/JProfiler.h @@ -7,9 +7,9 @@ class MULTIMC_LOGIC_EXPORT JProfilerFactory : public BaseProfilerFactory { public: - QString name() const override { return "JProfiler"; } - void registerSettings(SettingsObjectPtr settings) override; - BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; - bool check(QString *error) override; - bool check(const QString &path, QString *error) override; + QString name() const override { return "JProfiler"; } + void registerSettings(SettingsObjectPtr settings) override; + BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; + bool check(QString *error) override; + bool check(const QString &path, QString *error) override; }; diff --git a/api/logic/tools/JVisualVM.cpp b/api/logic/tools/JVisualVM.cpp index 8fdb594f..b1acc3c0 100644 --- a/api/logic/tools/JVisualVM.cpp +++ b/api/logic/tools/JVisualVM.cpp @@ -9,96 +9,96 @@ class JVisualVM : public BaseProfiler { - Q_OBJECT + Q_OBJECT public: - JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); private slots: - void profilerStarted(); - void profilerFinished(int exit, QProcess::ExitStatus status); + void profilerStarted(); + void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(std::shared_ptr process); + void beginProfilingImpl(shared_qobject_ptr process); }; JVisualVM::JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseProfiler(settings, instance, parent) + : BaseProfiler(settings, instance, parent) { } void JVisualVM::profilerStarted() { - emit readyToLaunch(tr("JVisualVM started")); + emit readyToLaunch(tr("JVisualVM started")); } void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status) { - if (status == QProcess::CrashExit) - { - emit abortLaunch(tr("Profiler aborted")); - } - if (m_profilerProcess) - { - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - } + if (status == QProcess::CrashExit) + { + emit abortLaunch(tr("Profiler aborted")); + } + if (m_profilerProcess) + { + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + } } -void JVisualVM::beginProfilingImpl(std::shared_ptr process) +void JVisualVM::beginProfilingImpl(shared_qobject_ptr process) { - QProcess *profiler = new QProcess(this); - QStringList profilerArgs = - { - "--openpid", QString::number(process->pid()) - }; - auto programPath = globalSettings->get("JVisualVMPath").toString(); + QProcess *profiler = new QProcess(this); + QStringList profilerArgs = + { + "--openpid", QString::number(process->pid()) + }; + auto programPath = globalSettings->get("JVisualVMPath").toString(); - profiler->setArguments(profilerArgs); - profiler->setProgram(programPath); + profiler->setArguments(profilerArgs); + profiler->setProgram(programPath); - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); + connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); + connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); - profiler->start(); - m_profilerProcess = profiler; + profiler->start(); + m_profilerProcess = profiler; } void JVisualVMFactory::registerSettings(SettingsObjectPtr settings) { - QString defaultValue = QStandardPaths::findExecutable("jvisualvm"); - if (defaultValue.isNull()) - { - defaultValue = QStandardPaths::findExecutable("visualvm"); - } - settings->registerSetting("JVisualVMPath", defaultValue); - globalSettings = settings; + QString defaultValue = QStandardPaths::findExecutable("jvisualvm"); + if (defaultValue.isNull()) + { + defaultValue = QStandardPaths::findExecutable("visualvm"); + } + settings->registerSetting("JVisualVMPath", defaultValue); + globalSettings = settings; } BaseExternalTool *JVisualVMFactory::createTool(InstancePtr instance, QObject *parent) { - return new JVisualVM(globalSettings, instance, parent); + return new JVisualVM(globalSettings, instance, parent); } bool JVisualVMFactory::check(QString *error) { - return check(globalSettings->get("JVisualVMPath").toString(), error); + return check(globalSettings->get("JVisualVMPath").toString(), error); } bool JVisualVMFactory::check(const QString &path, QString *error) { - if (path.isEmpty()) - { - *error = QObject::tr("Empty path"); - return false; - } - QFileInfo finfo(path); - if (!finfo.isExecutable() || !finfo.fileName().contains("visualvm")) - { - *error = QObject::tr("Invalid path to JVisualVM"); - return false; - } - return true; + if (path.isEmpty()) + { + *error = QObject::tr("Empty path"); + return false; + } + QFileInfo finfo(path); + if (!finfo.isExecutable() || !finfo.fileName().contains("visualvm")) + { + *error = QObject::tr("Invalid path to JVisualVM"); + return false; + } + return true; } #include "JVisualVM.moc" diff --git a/api/logic/tools/JVisualVM.h b/api/logic/tools/JVisualVM.h index 0674da13..91d48d94 100644 --- a/api/logic/tools/JVisualVM.h +++ b/api/logic/tools/JVisualVM.h @@ -7,9 +7,9 @@ class MULTIMC_LOGIC_EXPORT JVisualVMFactory : public BaseProfilerFactory { public: - QString name() const override { return "JVisualVM"; } - void registerSettings(SettingsObjectPtr settings) override; - BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; - bool check(QString *error) override; - bool check(const QString &path, QString *error) override; + QString name() const override { return "JVisualVM"; } + void registerSettings(SettingsObjectPtr settings) override; + BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; + bool check(QString *error) override; + bool check(const QString &path, QString *error) override; }; diff --git a/api/logic/tools/MCEditTool.cpp b/api/logic/tools/MCEditTool.cpp index 74715d3f..880327c7 100644 --- a/api/logic/tools/MCEditTool.cpp +++ b/api/logic/tools/MCEditTool.cpp @@ -10,68 +10,68 @@ MCEditTool::MCEditTool(SettingsObjectPtr settings) { - settings->registerSetting("MCEditPath"); - m_settings = settings; + settings->registerSetting("MCEditPath"); + m_settings = settings; } void MCEditTool::setPath(QString& path) { - m_settings->set("MCEditPath", path); + m_settings->set("MCEditPath", path); } QString MCEditTool::path() const { - return m_settings->get("MCEditPath").toString(); + return m_settings->get("MCEditPath").toString(); } bool MCEditTool::check(const QString& toolPath, QString& error) { - if (toolPath.isEmpty()) - { - error = QObject::tr("Path is empty"); - return false; - } - const QDir dir(toolPath); - if (!dir.exists()) - { - error = QObject::tr("Path does not exist"); - return false; - } - if (!dir.exists("mcedit.sh") && !dir.exists("mcedit.py") && !dir.exists("mcedit.exe") && !dir.exists("Contents") && !dir.exists("mcedit2.exe")) - { - error = QObject::tr("Path does not seem to be a MCEdit path"); - return false; - } - return true; + if (toolPath.isEmpty()) + { + error = QObject::tr("Path is empty"); + return false; + } + const QDir dir(toolPath); + if (!dir.exists()) + { + error = QObject::tr("Path does not exist"); + return false; + } + if (!dir.exists("mcedit.sh") && !dir.exists("mcedit.py") && !dir.exists("mcedit.exe") && !dir.exists("Contents") && !dir.exists("mcedit2.exe")) + { + error = QObject::tr("Path does not seem to be a MCEdit path"); + return false; + } + return true; } QString MCEditTool::getProgramPath() { #ifdef Q_OS_OSX - return path(); + return path(); #else - const QString mceditPath = path(); - QDir mceditDir(mceditPath); + const QString mceditPath = path(); + QDir mceditDir(mceditPath); #ifdef Q_OS_LINUX - if (mceditDir.exists("mcedit.sh")) - { - return mceditDir.absoluteFilePath("mcedit.sh"); - } - else if (mceditDir.exists("mcedit.py")) - { - return mceditDir.absoluteFilePath("mcedit.py"); - } - return QString(); + if (mceditDir.exists("mcedit.sh")) + { + return mceditDir.absoluteFilePath("mcedit.sh"); + } + else if (mceditDir.exists("mcedit.py")) + { + return mceditDir.absoluteFilePath("mcedit.py"); + } + return QString(); #elif defined(Q_OS_WIN32) - if (mceditDir.exists("mcedit.exe")) - { - return mceditDir.absoluteFilePath("mcedit.exe"); - } - else if (mceditDir.exists("mcedit2.exe")) - { - return mceditDir.absoluteFilePath("mcedit2.exe"); - } - return QString(); + if (mceditDir.exists("mcedit.exe")) + { + return mceditDir.absoluteFilePath("mcedit.exe"); + } + else if (mceditDir.exists("mcedit2.exe")) + { + return mceditDir.absoluteFilePath("mcedit2.exe"); + } + return QString(); #endif #endif } diff --git a/api/logic/tools/MCEditTool.h b/api/logic/tools/MCEditTool.h index 51feb1fe..1465494e 100644 --- a/api/logic/tools/MCEditTool.h +++ b/api/logic/tools/MCEditTool.h @@ -7,11 +7,11 @@ class MULTIMC_LOGIC_EXPORT MCEditTool { public: - MCEditTool(SettingsObjectPtr settings); - void setPath(QString & path); - QString path() const; - bool check(const QString &toolPath, QString &error); - QString getProgramPath(); + MCEditTool(SettingsObjectPtr settings); + void setPath(QString & path); + QString path() const; + bool check(const QString &toolPath, QString &error); + QString getProgramPath(); private: - SettingsObjectPtr m_settings; + SettingsObjectPtr m_settings; }; diff --git a/api/logic/translations/POTranslator.cpp b/api/logic/translations/POTranslator.cpp new file mode 100644 index 00000000..1ffcb9a4 --- /dev/null +++ b/api/logic/translations/POTranslator.cpp @@ -0,0 +1,373 @@ +#include "POTranslator.h" + +#include +#include "FileSystem.h" + +struct POEntry +{ + QString text; + bool fuzzy; +}; + +struct POTranslatorPrivate +{ + QString filename; + QHash mapping; + QHash mapping_disambiguatrion; + bool loaded = false; + + void reload(); +}; + +class ParserArray : public QByteArray +{ +public: + ParserArray(const QByteArray &in) : QByteArray(in) + { + } + bool chomp(const char * data, int length) + { + if(startsWith(data)) + { + remove(0, length); + return true; + } + return false; + } + bool chompString(QByteArray & appendHere) + { + QByteArray msg; + bool escape = false; + if(size() < 2) + { + qDebug() << "String fragment is too short"; + return false; + } + if(!startsWith('"')) + { + qDebug() << "String fragment does not start with \""; + return false; + } + if(!endsWith('"')) + { + qDebug() << "String fragment does not end with \", instead, there is" << at(size() - 1); + return false; + } + for(int i = 1; i < size() - 1; i++) + { + char c = operator[](i); + if(escape) + { + switch(c) + { + case 'r': + msg += '\r'; + break; + case 'n': + msg += '\n'; + break; + case 't': + msg += '\t'; + break; + case 'v': + msg += '\v'; + break; + case 'a': + msg += '\a'; + break; + case 'b': + msg += '\b'; + break; + case 'f': + msg += '\f'; + break; + case '"': + msg += '"'; + break; + case '\\': + msg.append('\\'); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int octal_start = i; + while ((c = operator[](i)) >= '0' && c <= '7') + { + i++; + if (i == length() - 1) + { + qDebug() << "Something went bad while parsing an octal escape string..."; + return false; + } + } + msg += mid(octal_start, i - octal_start).toUInt(0, 8); + break; + } + case 'x': + { + // chomp the 'x' + i++; + int hex_start = i; + while (isxdigit(operator[](i))) + { + i++; + if (i == length() - 1) + { + qDebug() << "Something went bad while parsing a hex escape string..."; + return false; + } + } + msg += mid(hex_start, i - hex_start).toUInt(0, 16); + break; + } + default: + { + qDebug() << "Invalid escape sequence character:" << c; + return false; + } + } + escape = false; + } + else if(c == '\\') + { + escape = true; + } + else + { + msg += c; + } + } + if(escape) + { + qDebug() << "Unterminated escape sequence..."; + return false; + } + appendHere += msg; + return true; + } +}; + +void POTranslatorPrivate::reload() +{ + QFile file(filename); + if(!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text)) + { + qDebug() << "Failed to open PO file:" << filename; + return; + } + + QByteArray context; + QByteArray disambiguation; + QByteArray id; + QByteArray str; + bool fuzzy = false; + bool nextFuzzy = false; + + enum class Mode + { + First, + MessageContext, + MessageId, + MessageString + } mode = Mode::First; + + int lineNumber = 0; + QHash newMapping; + QHash newMapping_disambiguation; + auto endEntry = [&]() { + auto strStr = QString::fromUtf8(str); + // NOTE: PO header has empty id. We skip it. + if(!id.isEmpty()) + { + auto normalKey = context + "|" + id; + newMapping.insert(normalKey, {strStr, fuzzy}); + if(!disambiguation.isEmpty()) + { + auto disambiguationKey = context + "|" + id + "@" + disambiguation; + newMapping_disambiguation.insert(disambiguationKey, {strStr, fuzzy}); + } + } + context.clear(); + disambiguation.clear(); + id.clear(); + str.clear(); + fuzzy = nextFuzzy; + nextFuzzy = false; + }; + while (!file.atEnd()) + { + ParserArray line = file.readLine(); + if(line.endsWith('\n')) + { + line.resize(line.size() - 1); + } + if(line.endsWith('\r')) + { + line.resize(line.size() - 1); + } + + if(!line.size()) + { + // NIL + } + else if(line[0] == '#') + { + if(line.contains(", fuzzy")) + { + nextFuzzy = true; + } + } + else if(line.startsWith('"')) + { + QByteArray temp; + QByteArray *out = &temp; + + switch(mode) + { + case Mode::First: + qDebug() << "Unexpected escaped string during initial state... line:" << lineNumber; + return; + case Mode::MessageString: + out = &str; + break; + case Mode::MessageContext: + out = &context; + break; + case Mode::MessageId: + out = &id; + break; + } + if(!line.chompString(*out)) + { + qDebug() << "Badly formatted string on line:" << lineNumber; + return; + } + } + else if(line.chomp("msgctxt ", 8)) + { + switch(mode) + { + case Mode::First: + break; + case Mode::MessageString: + endEntry(); + break; + case Mode::MessageContext: + case Mode::MessageId: + qDebug() << "Unexpected msgctxt line:" << lineNumber; + return; + } + if(line.chompString(context)) + { + auto parts = context.split('|'); + context = parts[0]; + if(parts.size() > 1 && !parts[1].isEmpty()) + { + disambiguation = parts[1]; + } + mode = Mode::MessageContext; + } + } + else if (line.chomp("msgid ", 6)) + { + switch(mode) + { + case Mode::MessageContext: + case Mode::First: + break; + case Mode::MessageString: + endEntry(); + break; + case Mode::MessageId: + qDebug() << "Unexpected msgid line:" << lineNumber; + return; + } + if(line.chompString(id)) + { + mode = Mode::MessageId; + } + } + else if (line.chomp("msgstr ", 7)) + { + switch(mode) + { + case Mode::First: + case Mode::MessageString: + case Mode::MessageContext: + qDebug() << "Unexpected msgstr line:" << lineNumber; + return; + case Mode::MessageId: + break; + } + if(line.chompString(str)) + { + mode = Mode::MessageString; + } + } + else + { + qDebug() << "I did not understand line: " << lineNumber << ":" << QString::fromUtf8(line); + } + lineNumber++; + } + endEntry(); + mapping = std::move(newMapping); + mapping_disambiguatrion = std::move(newMapping_disambiguation); + loaded = true; +} + +POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslator(parent) +{ + d = new POTranslatorPrivate; + d->filename = filename; + d->reload(); +} + +QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const +{ + if(disambiguation) + { + auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation); + auto iter = d->mapping_disambiguatrion.find(disambiguationKey); + if(iter != d->mapping_disambiguatrion.end()) + { + auto & entry = *iter; + if(entry.text.isEmpty()) + { + qDebug() << "Translation entry has no content:" << disambiguationKey; + } + if(entry.fuzzy) + { + qDebug() << "Translation entry is fuzzy:" << disambiguationKey << "->" << entry.text; + } + return entry.text; + } + } + auto key = QByteArray(context) + "|" + QByteArray(sourceText); + auto iter = d->mapping.find(key); + if(iter != d->mapping.end()) + { + auto & entry = *iter; + if(entry.text.isEmpty()) + { + qDebug() << "Translation entry has no content:" << key; + } + if(entry.fuzzy) + { + qDebug() << "Translation entry is fuzzy:" << key << "->" << entry.text; + } + return entry.text; + } + return QString(); +} + +bool POTranslator::isEmpty() const +{ + return !d->loaded; +} diff --git a/api/logic/translations/POTranslator.h b/api/logic/translations/POTranslator.h new file mode 100644 index 00000000..6d518560 --- /dev/null +++ b/api/logic/translations/POTranslator.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct POTranslatorPrivate; + +class POTranslator : public QTranslator +{ + Q_OBJECT +public: + explicit POTranslator(const QString& filename, QObject * parent = nullptr); + QString translate(const char * context, const char * sourceText, const char * disambiguation, int n) const override; + bool isEmpty() const override; +private: + POTranslatorPrivate * d; +}; diff --git a/api/logic/translations/TranslationsModel.cpp b/api/logic/translations/TranslationsModel.cpp index 868aa98f..0d9eccbf 100644 --- a/api/logic/translations/TranslationsModel.cpp +++ b/api/logic/translations/TranslationsModel.cpp @@ -8,312 +8,642 @@ #include #include #include +#include #include #include +#include "Json.h" + +#include "POTranslator.h" const static QLatin1Literal defaultLangCode("en"); +enum class FileType +{ + NONE, + QM, + PO +}; + struct Language { - QString key; - QLocale locale; - bool updated; + Language() + { + updated = true; + } + Language(const QString & _key) + { + key = _key; + locale = QLocale(key); + updated = (key == defaultLangCode); + } + + float percentTranslated() const + { + if (total == 0) + { + return 100.0f; + } + return 100.0f * float(translated) / float(total); + } + + void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy) + { + translated = _translated; + untranslated = _untranslated; + fuzzy = _fuzzy; + total = translated + untranslated + fuzzy; + } + + bool isOfSameNameAs(const Language& other) const + { + return key == other.key; + } + + bool isIdenticalTo(const Language& other) const + { + return + ( + key == other.key && + file_name == other.file_name && + file_size == other.file_size && + file_sha1 == other.file_sha1 && + translated == other.translated && + fuzzy == other.fuzzy && + total == other.fuzzy && + localFileType == other.localFileType + ); + } + + Language & apply(Language & other) + { + if(!isOfSameNameAs(other)) + { + return *this; + } + file_name = other.file_name; + file_size = other.file_size; + file_sha1 = other.file_sha1; + translated = other.translated; + fuzzy = other.fuzzy; + total = other.total; + localFileType = other.localFileType; + return *this; + } + + QString key; + QLocale locale; + bool updated; + + QString file_name = QString(); + std::size_t file_size = 0; + QString file_sha1 = QString(); + + unsigned translated = 0; + unsigned untranslated = 0; + unsigned fuzzy = 0; + unsigned total = 0; + + FileType localFileType = FileType::NONE; }; + + struct TranslationsModel::Private { - QDir m_dir; - - // initial state is just english - QVector m_languages = {{defaultLangCode, QLocale(defaultLangCode), false}}; - QString m_selectedLanguage = defaultLangCode; - std::unique_ptr m_qt_translator; - std::unique_ptr m_app_translator; - - std::shared_ptr m_index_task; - QString m_downloadingTranslation; - NetJobPtr m_dl_job; - NetJobPtr m_index_job; - QString m_nextDownload; + QDir m_dir; + + // initial state is just english + QVector m_languages = {Language (defaultLangCode)}; + + QString m_selectedLanguage = defaultLangCode; + std::unique_ptr m_qt_translator; + std::unique_ptr m_app_translator; + + std::shared_ptr m_index_task; + QString m_downloadingTranslation; + NetJobPtr m_dl_job; + NetJobPtr m_index_job; + QString m_nextDownload; + + std::unique_ptr m_po_translator; + QFileSystemWatcher *watcher; }; TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent) { - d.reset(new Private); - d->m_dir.setPath(path); - loadLocalIndex(); + d.reset(new Private); + d->m_dir.setPath(path); + FS::ensureFolderPathExists(path); + reloadLocalFiles(); + + d->watcher = new QFileSystemWatcher(this); + connect(d->watcher, &QFileSystemWatcher::directoryChanged, this, &TranslationsModel::translationDirChanged); + d->watcher->addPath(d->m_dir.canonicalPath()); } TranslationsModel::~TranslationsModel() { } +void TranslationsModel::translationDirChanged(const QString& path) +{ + qDebug() << "Dir changed:" << path; + reloadLocalFiles(); + selectLanguage(selectedLanguage()); +} + +void TranslationsModel::indexReceived() +{ + qDebug() << "Got translations index!"; + d->m_index_job.reset(); + if(d->m_selectedLanguage != defaultLangCode) + { + downloadTranslation(d->m_selectedLanguage); + } +} + +namespace { +void readIndex(const QString & path, QMap& languages) +{ + QByteArray data; + try + { + data = FS::read(path); + } + catch (const Exception &e) + { + qCritical() << "Translations Download Failed: index file not readable"; + return; + } + + int index = 1; + try + { + auto doc = Json::requireObject(Json::requireDocument(data)); + auto file_type = Json::requireString(doc, "file_type"); + if(file_type != "MMC-TRANSLATION-INDEX") + { + qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type; + return; + } + auto version = Json::requireInteger(doc, "version"); + if(version > 2) + { + qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type; + return; + } + auto langObjs = Json::requireObject(doc, "languages"); + for(auto iter = langObjs.begin(); iter != langObjs.end(); iter++) + { + Language lang(iter.key()); + + auto langObj = Json::requireObject(iter.value()); + lang.setTranslationStats( + Json::ensureInteger(langObj, "translated", 0), + Json::ensureInteger(langObj, "untranslated", 0), + Json::ensureInteger(langObj, "fuzzy", 0) + ); + lang.file_name = Json::requireString(langObj, "file"); + lang.file_sha1 = Json::requireString(langObj, "sha1"); + lang.file_size = Json::requireInteger(langObj, "size"); + + languages.insert(lang.key, lang); + index++; + } + } + catch (Json::JsonException & e) + { + qCritical() << "Translations Download Failed: index file could not be parsed as json"; + } +} +} + +void TranslationsModel::reloadLocalFiles() +{ + QMap languages = {{defaultLangCode, Language(defaultLangCode)}}; + + readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages); + auto entries = d->m_dir.entryInfoList({"mmc_*.qm", "*.po"}, QDir::Files | QDir::NoDotAndDotDot); + for(auto & entry: entries) + { + auto completeSuffix = entry.completeSuffix(); + QString langCode; + FileType fileType = FileType::NONE; + if(completeSuffix == "qm") + { + langCode = entry.baseName().remove(0,4); + fileType = FileType::QM; + } + else if(completeSuffix == "po") + { + langCode = entry.baseName(); + fileType = FileType::PO; + } + else + { + continue; + } + + auto langIter = languages.find(langCode); + if(langIter != languages.end()) + { + auto & language = *langIter; + if(int(fileType) > int(language.localFileType)) + { + language.localFileType = fileType; + } + } + else + { + if(fileType == FileType::PO) + { + Language localFound(langCode); + localFound.localFileType = FileType::PO; + languages.insert(langCode, localFound); + } + } + } + + // changed and removed languages + for(auto iter = d->m_languages.begin(); iter != d->m_languages.end();) + { + auto &language = *iter; + auto row = iter - d->m_languages.begin(); + + auto updatedLanguageIter = languages.find(language.key); + if(updatedLanguageIter != languages.end()) + { + if(language.isIdenticalTo(*updatedLanguageIter)) + { + languages.remove(language.key); + } + else + { + language.apply(*updatedLanguageIter); + emit dataChanged(index(row), index(row)); + languages.remove(language.key); + } + iter++; + } + else + { + beginRemoveRows(QModelIndex(), row, row); + iter = d->m_languages.erase(iter); + endRemoveRows(); + } + } + // added languages + if(languages.isEmpty()) + { + return; + } + beginInsertRows(QModelIndex(), d->m_languages.size(), d->m_languages.size() + languages.size() - 1); + for(auto & language: languages) + { + d->m_languages.append(language); + } + endInsertRows(); +} + +namespace { +enum class Column +{ + Language, + Completeness +}; +} + + QVariant TranslationsModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - - if (row < 0 || row >= d->m_languages.size()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - return d->m_languages[row].locale.nativeLanguageName(); - case Qt::UserRole: - return d->m_languages[row].key; - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + auto column = static_cast(index.column()); + + if (row < 0 || row >= d->m_languages.size()) + return QVariant(); + + auto & lang = d->m_languages[row]; + switch (role) + { + case Qt::DisplayRole: + { + switch(column) + { + case Column::Language: + { + return d->m_languages[row].locale.nativeLanguageName(); + } + case Column::Completeness: + { + QString text; + text.sprintf("%3.1f %%", lang.percentTranslated()); + return text; + } + } + } + case Qt::ToolTipRole: + { + return tr("%1:\n%2 translated\n%3 fuzzy\n%4 total").arg(lang.key, QString::number(lang.translated), QString::number(lang.fuzzy), QString::number(lang.total)); + } + case Qt::UserRole: + return d->m_languages[row].key; + default: + return QVariant(); + } } -int TranslationsModel::rowCount(const QModelIndex& parent) const +QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const { - return d->m_languages.size(); + auto column = static_cast(section); + if(role == Qt::DisplayRole) + { + switch(column) + { + case Column::Language: + { + return tr("Language"); + } + case Column::Completeness: + { + return tr("Completeness"); + } + } + } + else if(role == Qt::ToolTipRole) + { + switch(column) + { + case Column::Language: + { + return tr("The native language name."); + } + case Column::Completeness: + { + return tr("Completeness is the percentage of fully translated strings, not counting automatically guessed ones."); + } + } + } + return QAbstractListModel::headerData(section, orientation, role); } -Language * TranslationsModel::findLanguage(const QString& key) +int TranslationsModel::rowCount(const QModelIndex& parent) const { - auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang) - { - return lang.key == key; - }); - if(found == d->m_languages.end()) - { - return nullptr; - } - else - { - return found; - } + return d->m_languages.size(); } -bool TranslationsModel::selectLanguage(QString key) +int TranslationsModel::columnCount(const QModelIndex& parent) const { - QString &langCode = key; - auto langPtr = findLanguage(key); - if(!langPtr) - { - qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; - langCode = defaultLangCode; - } - else - { - langCode = langPtr->key; - } - - // uninstall existing translators if there are any - if (d->m_app_translator) - { - QCoreApplication::removeTranslator(d->m_app_translator.get()); - d->m_app_translator.reset(); - } - if (d->m_qt_translator) - { - QCoreApplication::removeTranslator(d->m_qt_translator.get()); - d->m_qt_translator.reset(); - } - - /* - * FIXME: potential source of crashes: - * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. - * This function is not reentrant. - */ - QLocale locale(langCode); - QLocale::setDefault(locale); - - // if it's the default UI language, finish - if(langCode == defaultLangCode) - { - d->m_selectedLanguage = langCode; - return true; - } - - // otherwise install new translations - bool successful = false; - // FIXME: this is likely never present. FIX IT. - d->m_qt_translator.reset(new QTranslator()); - if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) - { - qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) - { - qCritical() << "Loading Qt Language File failed."; - d->m_qt_translator.reset(); - } - else - { - successful = true; - } - } - else - { - d->m_qt_translator.reset(); - } - - d->m_app_translator.reset(new QTranslator()); - if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) - { - qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_app_translator.get())) - { - qCritical() << "Loading Application Language File failed."; - d->m_app_translator.reset(); - } - else - { - successful = true; - } - } - else - { - d->m_app_translator.reset(); - } - d->m_selectedLanguage = langCode; - return successful; + return 2; } -QModelIndex TranslationsModel::selectedIndex() +Language * TranslationsModel::findLanguage(const QString& key) { - auto found = findLanguage(d->m_selectedLanguage); - if(found) - { - // QVector iterator freely converts to pointer to contained type - return index(found - d->m_languages.begin(), 0, QModelIndex()); - } - return QModelIndex(); + auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang) + { + return lang.key == key; + }); + if(found == d->m_languages.end()) + { + return nullptr; + } + else + { + return found; + } } -QString TranslationsModel::selectedLanguage() +bool TranslationsModel::selectLanguage(QString key) { - return d->m_selectedLanguage; + QString &langCode = key; + auto langPtr = findLanguage(key); + if(!langPtr) + { + qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; + langCode = defaultLangCode; + } + else + { + langCode = langPtr->key; + } + + // uninstall existing translators if there are any + if (d->m_app_translator) + { + QCoreApplication::removeTranslator(d->m_app_translator.get()); + d->m_app_translator.reset(); + } + if (d->m_qt_translator) + { + QCoreApplication::removeTranslator(d->m_qt_translator.get()); + d->m_qt_translator.reset(); + } + + /* + * FIXME: potential source of crashes: + * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. + * This function is not reentrant. + */ + QLocale locale(langCode); + QLocale::setDefault(locale); + + // if it's the default UI language, finish + if(langCode == defaultLangCode) + { + d->m_selectedLanguage = langCode; + return true; + } + + // otherwise install new translations + bool successful = false; + // FIXME: this is likely never present. FIX IT. + d->m_qt_translator.reset(new QTranslator()); + if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + { + qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; + if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) + { + qCritical() << "Loading Qt Language File failed."; + d->m_qt_translator.reset(); + } + else + { + successful = true; + } + } + else + { + d->m_qt_translator.reset(); + } + + if(langPtr->localFileType == FileType::PO) + { + qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; + auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po")); + if(!poTranslator->isEmpty()) + { + if (!QCoreApplication::installTranslator(poTranslator)) + { + delete poTranslator; + qCritical() << "Installing Application Language File failed."; + } + else + { + d->m_app_translator.reset(poTranslator); + successful = true; + } + } + else + { + qCritical() << "Loading Application Language File failed."; + d->m_app_translator.reset(); + } + } + else if(langPtr->localFileType == FileType::QM) + { + d->m_app_translator.reset(new QTranslator()); + if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) + { + qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; + if (!QCoreApplication::installTranslator(d->m_app_translator.get())) + { + qCritical() << "Installing Application Language File failed."; + d->m_app_translator.reset(); + } + else + { + successful = true; + } + } + else + { + d->m_app_translator.reset(); + } + } + else + { + d->m_app_translator.reset(); + } + d->m_selectedLanguage = langCode; + return successful; } -void TranslationsModel::downloadIndex() +QModelIndex TranslationsModel::selectedIndex() { - if(d->m_index_job || d->m_dl_job) - { - return; - } - qDebug() << "Downloading Translations Index..."; - d->m_index_job.reset(new NetJob("Translations Index")); - MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index"); - d->m_index_task = Net::Download::makeCached(QUrl("http://files.multimc.org/translations/index"), entry); - d->m_index_job->addNetAction(d->m_index_task); - connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); - connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexRecieved); - d->m_index_job->start(); + auto found = findLanguage(d->m_selectedLanguage); + if(found) + { + // QVector iterator freely converts to pointer to contained type + return index(found - d->m_languages.begin(), 0, QModelIndex()); + } + return QModelIndex(); } -void TranslationsModel::indexRecieved() +QString TranslationsModel::selectedLanguage() { - qDebug() << "Got translations index!"; - d->m_index_job.reset(); - loadLocalIndex(); - if(d->m_selectedLanguage != defaultLangCode) - { - downloadTranslation(d->m_selectedLanguage); - } + return d->m_selectedLanguage; } -void TranslationsModel::loadLocalIndex() +void TranslationsModel::downloadIndex() { - QByteArray data; - try - { - data = FS::read(d->m_dir.absoluteFilePath("index")); - } - catch (Exception &e) - { - qCritical() << "Translations Download Failed: index file not readable"; - return; - } - QVector languages; - QList lines = data.split('\n'); - // add the default english. - languages.append({defaultLangCode, QLocale(defaultLangCode), true}); - for (const auto line : lines) - { - if(!line.isEmpty()) - { - auto str = QString::fromLatin1(line); - str.remove(".qm"); - languages.append({str, QLocale(str), false}); - } - } - beginResetModel(); - d->m_languages.swap(languages); - endResetModel(); + if(d->m_index_job || d->m_dl_job) + { + return; + } + qDebug() << "Downloading Translations Index..."; + d->m_index_job.reset(new NetJob("Translations Index")); + MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index_v2.json"); + entry->setStale(true); + d->m_index_task = Net::Download::makeCached(QUrl("https://files.multimc.org/translations/index_v2.json"), entry); + d->m_index_job->addNetAction(d->m_index_task); + connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); + connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); + d->m_index_job->start(); } void TranslationsModel::updateLanguage(QString key) { - if(key == defaultLangCode) - { - qWarning() << "Cannot update builtin language" << key; - return; - } - auto found = findLanguage(key); - if(!found) - { - qWarning() << "Cannot update invalid language" << key; - return; - } - if(!found->updated) - { - downloadTranslation(key); - } + if(key == defaultLangCode) + { + qWarning() << "Cannot update builtin language" << key; + return; + } + auto found = findLanguage(key); + if(!found) + { + qWarning() << "Cannot update invalid language" << key; + return; + } + if(!found->updated) + { + downloadTranslation(key); + } } void TranslationsModel::downloadTranslation(QString key) { - if(d->m_dl_job) - { - d->m_nextDownload = key; - return; - } - d->m_downloadingTranslation = key; - MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); - entry->setStale(true); - d->m_dl_job.reset(new NetJob("Translation for " + key)); - d->m_dl_job->addNetAction(Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + key + ".qm"), entry)); - connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); - connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); - d->m_dl_job->start(); + if(d->m_dl_job) + { + d->m_nextDownload = key; + return; + } + auto lang = findLanguage(key); + if(!lang) + { + qWarning() << "Will not download an unknown translation" << key; + return; + } + + d->m_downloadingTranslation = key; + MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); + entry->setStale(true); + + auto dl = Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + lang->file_name), entry); + auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1()); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); + dl->m_total_progress = lang->file_size; + + d->m_dl_job.reset(new NetJob("Translation for " + key)); + d->m_dl_job->addNetAction(dl); + + connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); + connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); + + d->m_dl_job->start(); } void TranslationsModel::downloadNext() { - if(!d->m_nextDownload.isEmpty()) - { - downloadTranslation(d->m_nextDownload); - d->m_nextDownload.clear(); - } + if(!d->m_nextDownload.isEmpty()) + { + downloadTranslation(d->m_nextDownload); + d->m_nextDownload.clear(); + } } void TranslationsModel::dlFailed(QString reason) { - qCritical() << "Translations Download Failed:" << reason; - d->m_dl_job.reset(); - downloadNext(); + qCritical() << "Translations Download Failed:" << reason; + d->m_dl_job.reset(); + downloadNext(); } void TranslationsModel::dlGood() { - qDebug() << "Got translation:" << d->m_downloadingTranslation; - - if(d->m_downloadingTranslation == d->m_selectedLanguage) - { - selectLanguage(d->m_selectedLanguage); - } - d->m_dl_job.reset(); - downloadNext(); + qDebug() << "Got translation:" << d->m_downloadingTranslation; + + if(d->m_downloadingTranslation == d->m_selectedLanguage) + { + selectLanguage(d->m_selectedLanguage); + } + d->m_dl_job.reset(); + downloadNext(); } void TranslationsModel::indexFailed(QString reason) { - qCritical() << "Translations Index Download Failed:" << reason; - d->m_index_job.reset(); + qCritical() << "Translations Index Download Failed:" << reason; + d->m_index_job.reset(); } diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h index bd481134..a0327fcd 100644 --- a/api/logic/translations/TranslationsModel.h +++ b/api/logic/translations/TranslationsModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,39 +23,43 @@ struct Language; class MULTIMC_LOGIC_EXPORT TranslationsModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - explicit TranslationsModel(QString path, QObject *parent = 0); - virtual ~TranslationsModel(); + explicit TranslationsModel(QString path, QObject *parent = 0); + virtual ~TranslationsModel(); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & parent) const override; - bool selectLanguage(QString key); - void updateLanguage(QString key); - QModelIndex selectedIndex(); - QString selectedLanguage(); + bool selectLanguage(QString key); + void updateLanguage(QString key); + QModelIndex selectedIndex(); + QString selectedLanguage(); - void downloadIndex(); + void downloadIndex(); private: - Language *findLanguage(const QString & key); - void loadLocalIndex(); - void downloadTranslation(QString key); - void downloadNext(); + Language *findLanguage(const QString & key); + void reloadLocalFiles(); + void downloadTranslation(QString key); + void downloadNext(); - // hide copy constructor - TranslationsModel(const TranslationsModel &) = delete; - // hide assign op - TranslationsModel &operator=(const TranslationsModel &) = delete; + // hide copy constructor + TranslationsModel(const TranslationsModel &) = delete; + // hide assign op + TranslationsModel &operator=(const TranslationsModel &) = delete; private slots: - void indexRecieved(); - void indexFailed(QString reason); - void dlFailed(QString reason); - void dlGood(); + void indexReceived(); + void indexFailed(QString reason); + void dlFailed(QString reason); + void dlGood(); + void translationDirChanged(const QString &path); + private: /* data */ - struct Private; - std::unique_ptr d; + struct Private; + std::unique_ptr d; }; diff --git a/api/logic/updater/DownloadTask.cpp b/api/logic/updater/DownloadTask.cpp index e0adf593..dbd8c65a 100644 --- a/api/logic/updater/DownloadTask.cpp +++ b/api/logic/updater/DownloadTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,140 +27,147 @@ namespace GoUpdate { DownloadTask::DownloadTask(Status status, QString target, QObject *parent) - : Task(parent), m_updateFilesDir(target) + : Task(parent), m_updateFilesDir(target) { - m_status = status; + m_status = status; - m_updateFilesDir.setAutoRemove(false); + m_updateFilesDir.setAutoRemove(false); } void DownloadTask::executeTask() { - loadVersionInfo(); + loadVersionInfo(); } void DownloadTask::loadVersionInfo() { - setStatus(tr("Loading version information...")); + setStatus(tr("Loading version information...")); - NetJob *netJob = new NetJob("Version Info"); + NetJob *netJob = new NetJob("Version Info"); - // Find the index URL. - QUrl newIndexUrl = QUrl(m_status.newRepoUrl).resolved(QString::number(m_status.newVersionId) + ".json"); - qDebug() << m_status.newRepoUrl << " turns into " << newIndexUrl; + // Find the index URL. + QUrl newIndexUrl = QUrl(m_status.newRepoUrl).resolved(QString::number(m_status.newVersionId) + ".json"); + qDebug() << m_status.newRepoUrl << " turns into " << newIndexUrl; - netJob->addNetAction(m_newVersionFileListDownload = Net::Download::makeByteArray(newIndexUrl, &newVersionFileListData)); + netJob->addNetAction(m_newVersionFileListDownload = Net::Download::makeByteArray(newIndexUrl, &newVersionFileListData)); - // If we have a current version URL, get that one too. - if (!m_status.currentRepoUrl.isEmpty()) - { - QUrl cIndexUrl = QUrl(m_status.currentRepoUrl).resolved(QString::number(m_status.currentVersionId) + ".json"); - netJob->addNetAction(m_currentVersionFileListDownload = Net::Download::makeByteArray(cIndexUrl, ¤tVersionFileListData)); - qDebug() << m_status.currentRepoUrl << " turns into " << cIndexUrl; - } + // If we have a current version URL, get that one too. + if (!m_status.currentRepoUrl.isEmpty()) + { + QUrl cIndexUrl = QUrl(m_status.currentRepoUrl).resolved(QString::number(m_status.currentVersionId) + ".json"); + netJob->addNetAction(m_currentVersionFileListDownload = Net::Download::makeByteArray(cIndexUrl, ¤tVersionFileListData)); + qDebug() << m_status.currentRepoUrl << " turns into " << cIndexUrl; + } - // connect signals and start the job - connect(netJob, &NetJob::succeeded, this, &DownloadTask::processDownloadedVersionInfo); - connect(netJob, &NetJob::failed, this, &DownloadTask::vinfoDownloadFailed); - m_vinfoNetJob.reset(netJob); - netJob->start(); + // connect signals and start the job + connect(netJob, &NetJob::succeeded, this, &DownloadTask::processDownloadedVersionInfo); + connect(netJob, &NetJob::failed, this, &DownloadTask::vinfoDownloadFailed); + m_vinfoNetJob.reset(netJob); + netJob->start(); } void DownloadTask::vinfoDownloadFailed() { - // Something failed. We really need the second download (current version info), so parse - // downloads anyways as long as the first one succeeded. - if (m_newVersionFileListDownload->wasSuccessful()) - { - processDownloadedVersionInfo(); - return; - } - - // TODO: Give a more detailed error message. - qCritical() << "Failed to download version info files."; - emitFailed(tr("Failed to download version info files.")); + // Something failed. We really need the second download (current version info), so parse + // downloads anyways as long as the first one succeeded. + if (m_newVersionFileListDownload->wasSuccessful()) + { + processDownloadedVersionInfo(); + return; + } + + // TODO: Give a more detailed error message. + qCritical() << "Failed to download version info files."; + emitFailed(tr("Failed to download version info files.")); } void DownloadTask::processDownloadedVersionInfo() { - VersionFileList m_currentVersionFileList; - VersionFileList m_newVersionFileList; - - setStatus(tr("Reading file list for new version...")); - qDebug() << "Reading file list for new version..."; - QString error; - if (!parseVersionInfo(newVersionFileListData, m_newVersionFileList, error)) - { - qCritical() << error; - emitFailed(error); - return; - } - - // if we have the current version info, use it. - if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful()) - { - setStatus(tr("Reading file list for current version...")); - qDebug() << "Reading file list for current version..."; - // if this fails, it's not a complete loss. - QString error; - if(!parseVersionInfo( currentVersionFileListData, m_currentVersionFileList, error)) - { - qDebug() << error << "This is not a fatal error."; - } - } - - // We don't need this any more. - m_currentVersionFileListDownload.reset(); - m_newVersionFileListDownload.reset(); - m_vinfoNetJob.reset(); - - setStatus(tr("Processing file lists - figuring out how to install the update...")); - - // make a new netjob for the actual update files - NetJobPtr netJob (new NetJob("Update Files")); - - // fill netJob and operationList - if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, m_operations)) - { - emitFailed(tr("Failed to process update lists...")); - return; - } - - // Now start the download. - QObject::connect(netJob.get(), &NetJob::succeeded, this, &DownloadTask::fileDownloadFinished); - QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged); - QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed); - - setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size()))); - qDebug() << "Begin downloading update files to" << m_updateFilesDir.path(); - m_filesNetJob = netJob; - m_filesNetJob->start(); + VersionFileList m_currentVersionFileList; + VersionFileList m_newVersionFileList; + + setStatus(tr("Reading file list for new version...")); + qDebug() << "Reading file list for new version..."; + QString error; + if (!parseVersionInfo(newVersionFileListData, m_newVersionFileList, error)) + { + qCritical() << error; + emitFailed(error); + return; + } + + // if we have the current version info, use it. + if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful()) + { + setStatus(tr("Reading file list for current version...")); + qDebug() << "Reading file list for current version..."; + // if this fails, it's not a complete loss. + QString error; + if(!parseVersionInfo( currentVersionFileListData, m_currentVersionFileList, error)) + { + qDebug() << error << "This is not a fatal error."; + } + } + + // We don't need this any more. + m_currentVersionFileListDownload.reset(); + m_newVersionFileListDownload.reset(); + m_vinfoNetJob.reset(); + + setStatus(tr("Processing file lists - figuring out how to install the update...")); + + // make a new netjob for the actual update files + NetJobPtr netJob (new NetJob("Update Files")); + + // fill netJob and operationList + if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, m_operations)) + { + emitFailed(tr("Failed to process update lists...")); + return; + } + + // Now start the download. + QObject::connect(netJob.get(), &NetJob::succeeded, this, &DownloadTask::fileDownloadFinished); + QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged); + QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed); + + if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/MultiMC5/issues/1701 + { + setStatus(tr("Downloading one update file.")); + } + else + { + setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size()))); + } + qDebug() << "Begin downloading update files to" << m_updateFilesDir.path(); + m_filesNetJob = netJob; + m_filesNetJob->start(); } void DownloadTask::fileDownloadFinished() { - emitSucceeded(); + emitSucceeded(); } void DownloadTask::fileDownloadFailed(QString reason) { - qCritical() << "Failed to download update files:" << reason; - emitFailed(tr("Failed to download update files: %1").arg(reason)); + qCritical() << "Failed to download update files:" << reason; + emitFailed(tr("Failed to download update files: %1").arg(reason)); } void DownloadTask::fileDownloadProgressChanged(qint64 current, qint64 total) { - setProgress(current, total); + setProgress(current, total); } QString DownloadTask::updateFilesDir() { - return m_updateFilesDir.path(); + return m_updateFilesDir.path(); } OperationList DownloadTask::operations() { - return m_operations; + return m_operations; } } \ No newline at end of file diff --git a/api/logic/updater/DownloadTask.h b/api/logic/updater/DownloadTask.h index 186826e2..5c973912 100644 --- a/api/logic/updater/DownloadTask.h +++ b/api/logic/updater/DownloadTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,69 +29,70 @@ namespace GoUpdate */ class MULTIMC_LOGIC_EXPORT DownloadTask : public Task { - Q_OBJECT + Q_OBJECT public: - /** - * Create a download task - * - * target is a template - XXXXXX at the end will be replaced with a random generated string, ensuring uniqueness - */ - explicit DownloadTask(Status status, QString target, QObject* parent = 0); + /** + * Create a download task + * + * target is a template - XXXXXX at the end will be replaced with a random generated string, ensuring uniqueness + */ + explicit DownloadTask(Status status, QString target, QObject* parent = 0); + virtual ~DownloadTask() {}; - /// Get the directory that will contain the update files. - QString updateFilesDir(); + /// Get the directory that will contain the update files. + QString updateFilesDir(); - /// Get the list of operations that should be done - OperationList operations(); + /// Get the list of operations that should be done + OperationList operations(); - /// set updater download behavior - void setUseLocalUpdater(bool useLocal); + /// set updater download behavior + void setUseLocalUpdater(bool useLocal); protected: - //! Entry point for tasks. - virtual void executeTask() override; - - /*! - * Downloads the version info files from the repository. - * The files for both the current build, and the build that we're updating to need to be downloaded. - * If the current version's info file can't be found, MultiMC will not delete files that - * were removed between versions. It will still replace files that have changed, however. - * Note that although the repository URL for the current version is not given to the update task, - * the task will attempt to look it up in the UpdateChecker's channel list. - * If an error occurs here, the function will call emitFailed and return false. - */ - void loadVersionInfo(); - - NetJobPtr m_vinfoNetJob; - QByteArray currentVersionFileListData; - QByteArray newVersionFileListData; - Net::Download::Ptr m_currentVersionFileListDownload; - Net::Download::Ptr m_newVersionFileListDownload; - - NetJobPtr m_filesNetJob; - - Status m_status; - - OperationList m_operations; - - /*! - * Temporary directory to store update files in. - * This will be set to not auto delete. Task will fail if this fails to be created. - */ - QTemporaryDir m_updateFilesDir; + //! Entry point for tasks. + virtual void executeTask() override; + + /*! + * Downloads the version info files from the repository. + * The files for both the current build, and the build that we're updating to need to be downloaded. + * If the current version's info file can't be found, MultiMC will not delete files that + * were removed between versions. It will still replace files that have changed, however. + * Note that although the repository URL for the current version is not given to the update task, + * the task will attempt to look it up in the UpdateChecker's channel list. + * If an error occurs here, the function will call emitFailed and return false. + */ + void loadVersionInfo(); + + NetJobPtr m_vinfoNetJob; + QByteArray currentVersionFileListData; + QByteArray newVersionFileListData; + Net::Download::Ptr m_currentVersionFileListDownload; + Net::Download::Ptr m_newVersionFileListDownload; + + NetJobPtr m_filesNetJob; + + Status m_status; + + OperationList m_operations; + + /*! + * Temporary directory to store update files in. + * This will be set to not auto delete. Task will fail if this fails to be created. + */ + QTemporaryDir m_updateFilesDir; protected slots: - /*! - * This function is called when version information is finished downloading - * and at least the new file list download succeeded - */ - void processDownloadedVersionInfo(); - void vinfoDownloadFailed(); - - void fileDownloadFinished(); - void fileDownloadFailed(QString reason); - void fileDownloadProgressChanged(qint64 current, qint64 total); + /*! + * This function is called when version information is finished downloading + * and at least the new file list download succeeded + */ + void processDownloadedVersionInfo(); + void vinfoDownloadFailed(); + + void fileDownloadFinished(); + void fileDownloadFailed(QString reason); + void fileDownloadProgressChanged(qint64 current, qint64 total); }; } \ No newline at end of file diff --git a/api/logic/updater/DownloadTask_test.cpp b/api/logic/updater/DownloadTask_test.cpp index e75c3ffa..531b2527 100644 --- a/api/logic/updater/DownloadTask_test.cpp +++ b/api/logic/updater/DownloadTask_test.cpp @@ -12,11 +12,11 @@ using namespace GoUpdate; FileSourceList encodeBaseFile(const char *suffix) { - auto base = QDir::currentPath(); - QUrl localFile = QUrl::fromLocalFile(base + suffix); - QString localUrlString = localFile.toString(QUrl::FullyEncoded); - auto item = FileSource("http", localUrlString); - return FileSourceList({item}); + auto base = QDir::currentPath(); + QUrl localFile = QUrl::fromLocalFile(base + suffix); + QString localUrlString = localFile.toString(QUrl::FullyEncoded); + auto item = FileSource("http", localUrlString); + return FileSourceList({item}); } Q_DECLARE_METATYPE(VersionFileList) @@ -24,191 +24,191 @@ Q_DECLARE_METATYPE(Operation) QDebug operator<<(QDebug dbg, const FileSource &f) { - dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url - << " comp=" << f.compressionType << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url + << " comp=" << f.compressionType << ")"; + return dbg.maybeSpace(); } QDebug operator<<(QDebug dbg, const VersionFileEntry &v) { - dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode - << " md5=" << v.md5 << " sources=" << v.sources << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode + << " md5=" << v.md5 << " sources=" << v.sources << ")"; + return dbg.maybeSpace(); } QDebug operator<<(QDebug dbg, const Operation::Type &t) { - switch (t) - { - case Operation::OP_REPLACE: - dbg << "OP_COPY"; - break; - case Operation::OP_DELETE: - dbg << "OP_DELETE"; - break; - } - return dbg.maybeSpace(); + switch (t) + { + case Operation::OP_REPLACE: + dbg << "OP_COPY"; + break; + case Operation::OP_DELETE: + dbg << "OP_DELETE"; + break; + } + return dbg.maybeSpace(); } QDebug operator<<(QDebug dbg, const Operation &u) { - dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source - << " dest=" << u.destination << " mode=" << u.destinationMode << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source + << " dest=" << u.destination << " mode=" << u.destinationMode << ")"; + return dbg.maybeSpace(); } class DownloadTaskTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - } - void cleanupTestCase() - { - } - - void test_parseVersionInfo_data() - { - QTest::addColumn("data"); - QTest::addColumn("list"); - QTest::addColumn("error"); - QTest::addColumn("ret"); - - QTest::newRow("one") - << MULTIMC_GET_TEST_FILE("data/1.json") - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneA"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{"fileThree", - 750, - encodeBaseFile("/data/fileThree"), - "f12df554b21e320be6471d7154130e70"}) - << QString() << true; - QTest::newRow("two") - << MULTIMC_GET_TEST_FILE("data/2.json") - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneB"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << QString() << true; - } - void test_parseVersionInfo() - { - QFETCH(QByteArray, data); - QFETCH(VersionFileList, list); - QFETCH(QString, error); - QFETCH(bool, ret); - - VersionFileList outList; - QString outError; - bool outRet = parseVersionInfo(data, outList, outError); - QCOMPARE(outRet, ret); - QCOMPARE(outList, list); - QCOMPARE(outError, error); - } - - void test_processFileLists_data() - { - QTest::addColumn("tempFolder"); - QTest::addColumn("currentVersion"); - QTest::addColumn("newVersion"); - QTest::addColumn("expectedOperations"); - - QTemporaryDir tempFolderObj; - QString tempFolder = tempFolderObj.path(); - // update fileOne, keep fileTwo, remove fileThree - QTest::newRow("test 1") - << tempFolder << (VersionFileList() - << VersionFileEntry{ - "data/fileOne", 493, - FileSourceList() - << FileSource( - "http", "http://host/path/fileOne-1"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{ - "data/fileTwo", 644, - FileSourceList() - << FileSource( - "http", "http://host/path/fileTwo-1"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{ - "data/fileThree", 420, - FileSourceList() - << FileSource( - "http", "http://host/path/fileThree-1"), - "f12df554b21e320be6471d7154130e70"}) - << (VersionFileList() - << VersionFileEntry{ - "data/fileOne", 493, - FileSourceList() - << FileSource("http", - "http://host/path/fileOne-2"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{ - "data/fileTwo", 644, - FileSourceList() - << FileSource("http", - "http://host/path/fileTwo-2"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << (OperationList() - << Operation::DeleteOp("data/fileThree") - << Operation::CopyOp( - FS::PathCombine(tempFolder, - QString("data/fileOne").replace("/", "_")), - "data/fileOne", 493)); - } - void test_processFileLists() - { - QFETCH(QString, tempFolder); - QFETCH(VersionFileList, currentVersion); - QFETCH(VersionFileList, newVersion); - QFETCH(OperationList, expectedOperations); - - OperationList operations; - - processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy"), operations); - qDebug() << (operations == expectedOperations); - qDebug() << operations; - qDebug() << expectedOperations; - QCOMPARE(operations, expectedOperations); - } - - void test_OSXPathFixup() - { - QString path, pathOrig; - bool result; - // Proper OSX path - pathOrig = path = "MultiMC.app/Foo/Bar/Baz"; - qDebug() << "Proper OSX path: " << path; - result = fixPathForOSX(path); - QCOMPARE(path, QString("Foo/Bar/Baz")); - QCOMPARE(result, true); - - // Bad OSX path - pathOrig = path = "translations/klingon.lol"; - qDebug() << "Bad OSX path: " << path; - result = fixPathForOSX(path); - QCOMPARE(path, pathOrig); - QCOMPARE(result, false); - } + void initTestCase() + { + } + void cleanupTestCase() + { + } + + void test_parseVersionInfo_data() + { + QTest::addColumn("data"); + QTest::addColumn("list"); + QTest::addColumn("error"); + QTest::addColumn("ret"); + + QTest::newRow("one") + << MULTIMC_GET_TEST_FILE("data/1.json") + << (VersionFileList() + << VersionFileEntry{"fileOne", + 493, + encodeBaseFile("/data/fileOneA"), + "9eb84090956c484e32cb6c08455a667b"} + << VersionFileEntry{"fileTwo", + 644, + encodeBaseFile("/data/fileTwo"), + "38f94f54fa3eb72b0ea836538c10b043"} + << VersionFileEntry{"fileThree", + 750, + encodeBaseFile("/data/fileThree"), + "f12df554b21e320be6471d7154130e70"}) + << QString() << true; + QTest::newRow("two") + << MULTIMC_GET_TEST_FILE("data/2.json") + << (VersionFileList() + << VersionFileEntry{"fileOne", + 493, + encodeBaseFile("/data/fileOneB"), + "42915a71277c9016668cce7b82c6b577"} + << VersionFileEntry{"fileTwo", + 644, + encodeBaseFile("/data/fileTwo"), + "38f94f54fa3eb72b0ea836538c10b043"}) + << QString() << true; + } + void test_parseVersionInfo() + { + QFETCH(QByteArray, data); + QFETCH(VersionFileList, list); + QFETCH(QString, error); + QFETCH(bool, ret); + + VersionFileList outList; + QString outError; + bool outRet = parseVersionInfo(data, outList, outError); + QCOMPARE(outRet, ret); + QCOMPARE(outList, list); + QCOMPARE(outError, error); + } + + void test_processFileLists_data() + { + QTest::addColumn("tempFolder"); + QTest::addColumn("currentVersion"); + QTest::addColumn("newVersion"); + QTest::addColumn("expectedOperations"); + + QTemporaryDir tempFolderObj; + QString tempFolder = tempFolderObj.path(); + // update fileOne, keep fileTwo, remove fileThree + QTest::newRow("test 1") + << tempFolder << (VersionFileList() + << VersionFileEntry{ + "data/fileOne", 493, + FileSourceList() + << FileSource( + "http", "http://host/path/fileOne-1"), + "9eb84090956c484e32cb6c08455a667b"} + << VersionFileEntry{ + "data/fileTwo", 644, + FileSourceList() + << FileSource( + "http", "http://host/path/fileTwo-1"), + "38f94f54fa3eb72b0ea836538c10b043"} + << VersionFileEntry{ + "data/fileThree", 420, + FileSourceList() + << FileSource( + "http", "http://host/path/fileThree-1"), + "f12df554b21e320be6471d7154130e70"}) + << (VersionFileList() + << VersionFileEntry{ + "data/fileOne", 493, + FileSourceList() + << FileSource("http", + "http://host/path/fileOne-2"), + "42915a71277c9016668cce7b82c6b577"} + << VersionFileEntry{ + "data/fileTwo", 644, + FileSourceList() + << FileSource("http", + "http://host/path/fileTwo-2"), + "38f94f54fa3eb72b0ea836538c10b043"}) + << (OperationList() + << Operation::DeleteOp("data/fileThree") + << Operation::CopyOp( + FS::PathCombine(tempFolder, + QString("data/fileOne").replace("/", "_")), + "data/fileOne", 493)); + } + void test_processFileLists() + { + QFETCH(QString, tempFolder); + QFETCH(VersionFileList, currentVersion); + QFETCH(VersionFileList, newVersion); + QFETCH(OperationList, expectedOperations); + + OperationList operations; + + processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy"), operations); + qDebug() << (operations == expectedOperations); + qDebug() << operations; + qDebug() << expectedOperations; + QCOMPARE(operations, expectedOperations); + } + + void test_OSXPathFixup() + { + QString path, pathOrig; + bool result; + // Proper OSX path + pathOrig = path = "MultiMC.app/Foo/Bar/Baz"; + qDebug() << "Proper OSX path: " << path; + result = fixPathForOSX(path); + QCOMPARE(path, QString("Foo/Bar/Baz")); + QCOMPARE(result, true); + + // Bad OSX path + pathOrig = path = "translations/klingon.lol"; + qDebug() << "Bad OSX path: " << path; + result = fixPathForOSX(path); + QCOMPARE(path, pathOrig); + QCOMPARE(result, false); + } }; extern "C" { - QTEST_GUILESS_MAIN(DownloadTaskTest) + QTEST_GUILESS_MAIN(DownloadTaskTest) } #include "DownloadTask_test.moc" diff --git a/api/logic/updater/GoUpdate.cpp b/api/logic/updater/GoUpdate.cpp index 716048a0..ef040db6 100644 --- a/api/logic/updater/GoUpdate.cpp +++ b/api/logic/updater/GoUpdate.cpp @@ -12,208 +12,208 @@ namespace GoUpdate bool parseVersionInfo(const QByteArray &data, VersionFileList &list, QString &error) { - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - error = QString("Failed to parse version info JSON: %1 at %2") - .arg(jsonError.errorString()) - .arg(jsonError.offset); - qCritical() << error; - return false; - } - - QJsonObject json = jsonDoc.object(); - - qDebug() << data; - qDebug() << "Loading version info from JSON."; - QJsonArray filesArray = json.value("Files").toArray(); - for (QJsonValue fileValue : filesArray) - { - QJsonObject fileObj = fileValue.toObject(); - - QString file_path = fileObj.value("Path").toString(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + error = QString("Failed to parse version info JSON: %1 at %2") + .arg(jsonError.errorString()) + .arg(jsonError.offset); + qCritical() << error; + return false; + } + + QJsonObject json = jsonDoc.object(); + + qDebug() << data; + qDebug() << "Loading version info from JSON."; + QJsonArray filesArray = json.value("Files").toArray(); + for (QJsonValue fileValue : filesArray) + { + QJsonObject fileObj = fileValue.toObject(); + + QString file_path = fileObj.value("Path").toString(); #ifdef Q_OS_MAC - // On OSX, the paths for the updater need to be fixed. - // basically, anything that isn't in the .app folder is ignored. - // everything else is changed so the code that processes the files actually finds - // them and puts the replacements in the right spots. - fixPathForOSX(file_path); + // On OSX, the paths for the updater need to be fixed. + // basically, anything that isn't in the .app folder is ignored. + // everything else is changed so the code that processes the files actually finds + // them and puts the replacements in the right spots. + fixPathForOSX(file_path); #endif - VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(), - FileSourceList(), fileObj.value("MD5").toString(), }; - qDebug() << "File" << file.path << "with perms" << file.mode; - - QJsonArray sourceArray = fileObj.value("Sources").toArray(); - for (QJsonValue val : sourceArray) - { - QJsonObject sourceObj = val.toObject(); - - QString type = sourceObj.value("SourceType").toString(); - if (type == "http") - { - file.sources.append(FileSource("http", sourceObj.value("Url").toString())); - } - else - { - qWarning() << "Unknown source type" << type << "ignored."; - } - } - - qDebug() << "Loaded info for" << file.path; - - list.append(file); - } - - return true; + VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(), + FileSourceList(), fileObj.value("MD5").toString(), }; + qDebug() << "File" << file.path << "with perms" << file.mode; + + QJsonArray sourceArray = fileObj.value("Sources").toArray(); + for (QJsonValue val : sourceArray) + { + QJsonObject sourceObj = val.toObject(); + + QString type = sourceObj.value("SourceType").toString(); + if (type == "http") + { + file.sources.append(FileSource("http", sourceObj.value("Url").toString())); + } + else + { + qWarning() << "Unknown source type" << type << "ignored."; + } + } + + qDebug() << "Loaded info for" << file.path; + + list.append(file); + } + + return true; } bool processFileLists ( - const VersionFileList ¤tVersion, - const VersionFileList &newVersion, - const QString &rootPath, - const QString &tempPath, - NetJobPtr job, - OperationList &ops + const VersionFileList ¤tVersion, + const VersionFileList &newVersion, + const QString &rootPath, + const QString &tempPath, + NetJobPtr job, + OperationList &ops ) { - // First, if we've loaded the current version's file list, we need to iterate through it and - // delete anything in the current one version's list that isn't in the new version's list. - for (VersionFileEntry entry : currentVersion) - { - QFileInfo toDelete(FS::PathCombine(rootPath, entry.path)); - if (!toDelete.exists()) - { - qCritical() << "Expected file " << toDelete.absoluteFilePath() - << " doesn't exist!"; - } - bool keep = false; - - // - for (VersionFileEntry newEntry : newVersion) - { - if (newEntry.path == entry.path) - { - qDebug() << "Not deleting" << entry.path - << "because it is still present in the new version."; - keep = true; - break; - } - } - - // If the loop reaches the end and we didn't find a match, delete the file. - if (!keep) - { - if (toDelete.exists()) - ops.append(Operation::DeleteOp(entry.path)); - } - } - - // Next, check each file in MultiMC's folder and see if we need to update them. - for (VersionFileEntry entry : newVersion) - { - // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a - // way to do this in the background. - QString fileMD5; - QString realEntryPath = FS::PathCombine(rootPath, entry.path); - QFile entryFile(realEntryPath); - QFileInfo entryInfo(realEntryPath); - - bool needs_upgrade = false; - if (!entryFile.exists()) - { - needs_upgrade = true; - } - else - { - bool pass = true; - if (!entryInfo.isReadable()) - { - qCritical() << "File " << realEntryPath << " is not readable."; - pass = false; - } - if (!entryInfo.isWritable()) - { - qCritical() << "File " << realEntryPath << " is not writable."; - pass = false; - } - if (!entryFile.open(QFile::ReadOnly)) - { - qCritical() << "File " << realEntryPath << " cannot be opened for reading."; - pass = false; - } - if (!pass) - { - ops.clear(); - return false; - } - } - - if(!needs_upgrade) - { - QCryptographicHash hash(QCryptographicHash::Md5); - auto foo = entryFile.readAll(); - - hash.addData(foo); - fileMD5 = hash.result().toHex(); - if ((fileMD5 != entry.md5)) - { - qDebug() << "MD5Sum does not match!"; - qDebug() << "Expected:'" << entry.md5 << "'"; - qDebug() << "Got: '" << fileMD5 << "'"; - needs_upgrade = true; - } - } - - // skip file. it doesn't need an upgrade. - if (!needs_upgrade) - { - qDebug() << "File" << realEntryPath << " does not need updating."; - continue; - } - - // yep. this file actually needs an upgrade. PROCEED. - qDebug() << "Found file" << realEntryPath << " that needs updating."; - - // Go through the sources list and find one to use. - // TODO: Make a NetAction that takes a source list and tries each of them until one - // works. For now, we'll just use the first http one. - for (FileSource source : entry.sources) - { - if (source.type != "http") - continue; - - qDebug() << "Will download" << entry.path << "from" << source.url; - - // Download it to updatedir/- where filepath is the file's - // path with slashes replaced by underscores. - QString dlPath = FS::PathCombine(tempPath, QString(entry.path).replace("/", "_")); - - // We need to download the file to the updatefiles folder and add a task - // to copy it to its install path. - auto download = Net::Download::makeFile(source.url, dlPath); - auto rawMd5 = QByteArray::fromHex(entry.md5.toLatin1()); - download->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); - job->addNetAction(download); - ops.append(Operation::CopyOp(dlPath, entry.path, entry.mode)); - } - } - return true; + // First, if we've loaded the current version's file list, we need to iterate through it and + // delete anything in the current one version's list that isn't in the new version's list. + for (VersionFileEntry entry : currentVersion) + { + QFileInfo toDelete(FS::PathCombine(rootPath, entry.path)); + if (!toDelete.exists()) + { + qCritical() << "Expected file " << toDelete.absoluteFilePath() + << " doesn't exist!"; + } + bool keep = false; + + // + for (VersionFileEntry newEntry : newVersion) + { + if (newEntry.path == entry.path) + { + qDebug() << "Not deleting" << entry.path + << "because it is still present in the new version."; + keep = true; + break; + } + } + + // If the loop reaches the end and we didn't find a match, delete the file. + if (!keep) + { + if (toDelete.exists()) + ops.append(Operation::DeleteOp(entry.path)); + } + } + + // Next, check each file in MultiMC's folder and see if we need to update them. + for (VersionFileEntry entry : newVersion) + { + // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a + // way to do this in the background. + QString fileMD5; + QString realEntryPath = FS::PathCombine(rootPath, entry.path); + QFile entryFile(realEntryPath); + QFileInfo entryInfo(realEntryPath); + + bool needs_upgrade = false; + if (!entryFile.exists()) + { + needs_upgrade = true; + } + else + { + bool pass = true; + if (!entryInfo.isReadable()) + { + qCritical() << "File " << realEntryPath << " is not readable."; + pass = false; + } + if (!entryInfo.isWritable()) + { + qCritical() << "File " << realEntryPath << " is not writable."; + pass = false; + } + if (!entryFile.open(QFile::ReadOnly)) + { + qCritical() << "File " << realEntryPath << " cannot be opened for reading."; + pass = false; + } + if (!pass) + { + ops.clear(); + return false; + } + } + + if(!needs_upgrade) + { + QCryptographicHash hash(QCryptographicHash::Md5); + auto foo = entryFile.readAll(); + + hash.addData(foo); + fileMD5 = hash.result().toHex(); + if ((fileMD5 != entry.md5)) + { + qDebug() << "MD5Sum does not match!"; + qDebug() << "Expected:'" << entry.md5 << "'"; + qDebug() << "Got: '" << fileMD5 << "'"; + needs_upgrade = true; + } + } + + // skip file. it doesn't need an upgrade. + if (!needs_upgrade) + { + qDebug() << "File" << realEntryPath << " does not need updating."; + continue; + } + + // yep. this file actually needs an upgrade. PROCEED. + qDebug() << "Found file" << realEntryPath << " that needs updating."; + + // Go through the sources list and find one to use. + // TODO: Make a NetAction that takes a source list and tries each of them until one + // works. For now, we'll just use the first http one. + for (FileSource source : entry.sources) + { + if (source.type != "http") + continue; + + qDebug() << "Will download" << entry.path << "from" << source.url; + + // Download it to updatedir/- where filepath is the file's + // path with slashes replaced by underscores. + QString dlPath = FS::PathCombine(tempPath, QString(entry.path).replace("/", "_")); + + // We need to download the file to the updatefiles folder and add a task + // to copy it to its install path. + auto download = Net::Download::makeFile(source.url, dlPath); + auto rawMd5 = QByteArray::fromHex(entry.md5.toLatin1()); + download->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); + job->addNetAction(download); + ops.append(Operation::CopyOp(dlPath, entry.path, entry.mode)); + } + } + return true; } bool fixPathForOSX(QString &path) { - if (path.startsWith("MultiMC.app/")) - { - // remove the prefix and add a new, more appropriate one. - path.remove(0, 12); - return true; - } - else - { - qCritical() << "Update path not within .app: " << path; - return false; - } + if (path.startsWith("MultiMC.app/")) + { + // remove the prefix and add a new, more appropriate one. + path.remove(0, 12); + return true; + } + else + { + qCritical() << "Update path not within .app: " << path; + return false; + } } } \ No newline at end of file diff --git a/api/logic/updater/GoUpdate.h b/api/logic/updater/GoUpdate.h index 95f26b8c..54559a3c 100644 --- a/api/logic/updater/GoUpdate.h +++ b/api/logic/updater/GoUpdate.h @@ -12,16 +12,16 @@ namespace GoUpdate */ struct MULTIMC_LOGIC_EXPORT Status { - bool updateAvailable = false; + bool updateAvailable = false; - int newVersionId = -1; - QString newRepoUrl; + int newVersionId = -1; + QString newRepoUrl; - int currentVersionId = -1; - QString currentRepoUrl; + int currentVersionId = -1; + QString currentRepoUrl; - // path to the root of the application - QString rootPath; + // path to the root of the application + QString rootPath; }; /** @@ -29,21 +29,21 @@ struct MULTIMC_LOGIC_EXPORT Status */ struct MULTIMC_LOGIC_EXPORT FileSource { - FileSource(QString type, QString url, QString compression="") - { - this->type = type; - this->url = url; - this->compressionType = compression; - } - - bool operator==(const FileSource &f2) const - { - return type == f2.type && url == f2.url && compressionType == f2.compressionType; - } - - QString type; - QString url; - QString compressionType; + FileSource(QString type, QString url, QString compression="") + { + this->type = type; + this->url = url; + this->compressionType = compression; + } + + bool operator==(const FileSource &f2) const + { + return type == f2.type && url == f2.url && compressionType == f2.compressionType; + } + + QString type; + QString url; + QString compressionType; }; typedef QList FileSourceList; @@ -52,14 +52,14 @@ typedef QList FileSourceList; */ struct MULTIMC_LOGIC_EXPORT VersionFileEntry { - QString path; - int mode; - FileSourceList sources; - QString md5; - bool operator==(const VersionFileEntry &v2) const - { - return path == v2.path && mode == v2.mode && sources == v2.sources && md5 == v2.md5; - } + QString path; + int mode; + FileSourceList sources; + QString md5; + bool operator==(const VersionFileEntry &v2) const + { + return path == v2.path && mode == v2.mode && sources == v2.sources && md5 == v2.md5; + } }; typedef QList VersionFileList; @@ -68,39 +68,39 @@ typedef QList VersionFileList; */ struct MULTIMC_LOGIC_EXPORT Operation { - static Operation CopyOp(QString from, QString to, int fmode=0644) - { - return Operation{OP_REPLACE, from, to, fmode}; - } - static Operation DeleteOp(QString file) - { - return Operation{OP_DELETE, QString(), file, 0644}; - } - - // FIXME: for some types, some of the other fields are irrelevant! - bool operator==(const Operation &u2) const - { - return type == u2.type && - source == u2.source && - destination == u2.destination && - destinationMode == u2.destinationMode; - } - - //! Specifies the type of operation that this is. - enum Type - { - OP_REPLACE, - OP_DELETE, - } type; - - //! The source file, if any - QString source; - - //! The destination file. - QString destination; - - //! The mode to change the destination file to. - int destinationMode; + static Operation CopyOp(QString from, QString to, int fmode=0644) + { + return Operation{OP_REPLACE, from, to, fmode}; + } + static Operation DeleteOp(QString file) + { + return Operation{OP_DELETE, QString(), file, 0644}; + } + + // FIXME: for some types, some of the other fields are irrelevant! + bool operator==(const Operation &u2) const + { + return type == u2.type && + source == u2.source && + destination == u2.destination && + destinationMode == u2.destinationMode; + } + + //! Specifies the type of operation that this is. + enum Type + { + OP_REPLACE, + OP_DELETE, + } type; + + //! The source file, if any + QString source; + + //! The destination file. + QString destination; + + //! The mode to change the destination file to. + int destinationMode; }; typedef QList OperationList; @@ -115,12 +115,12 @@ bool MULTIMC_LOGIC_EXPORT parseVersionInfo(const QByteArray &data, VersionFileLi */ bool MULTIMC_LOGIC_EXPORT processFileLists ( - const VersionFileList ¤tVersion, - const VersionFileList &newVersion, - const QString &rootPath, - const QString &tempPath, - NetJobPtr job, - OperationList &ops + const VersionFileList ¤tVersion, + const VersionFileList &newVersion, + const QString &rootPath, + const QString &tempPath, + NetJobPtr job, + OperationList &ops ); /*! @@ -133,4 +133,4 @@ bool MULTIMC_LOGIC_EXPORT processFileLists bool MULTIMC_LOGIC_EXPORT fixPathForOSX(QString &path); } -Q_DECLARE_METATYPE(GoUpdate::Status); \ No newline at end of file +Q_DECLARE_METATYPE(GoUpdate::Status) diff --git a/api/logic/updater/UpdateChecker.cpp b/api/logic/updater/UpdateChecker.cpp index d8be2c1a..b3c2641d 100644 --- a/api/logic/updater/UpdateChecker.cpp +++ b/api/logic/updater/UpdateChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,236 +25,236 @@ UpdateChecker::UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild) { - m_channelListUrl = channelListUrl; - m_currentChannel = currentChannel; - m_currentBuild = currentBuild; + m_channelListUrl = channelListUrl; + m_currentChannel = currentChannel; + m_currentBuild = currentBuild; } QList UpdateChecker::getChannelList() const { - return m_channels; + return m_channels; } bool UpdateChecker::hasChannels() const { - return !m_channels.isEmpty(); + return !m_channels.isEmpty(); } void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) { - qDebug() << "Checking for updates."; - - // If the channel list hasn't loaded yet, load it and defer checking for updates until - // later. - if (!m_chanListLoaded) - { - qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring " - "update check."; - m_checkUpdateWaiting = true; - m_deferredUpdateChannel = updateChannel; - updateChanList(notifyNoUpdate); - return; - } - - if (m_updateChecking) - { - qDebug() << "Ignoring update check request. Already checking for updates."; - return; - } - - m_updateChecking = true; - - // Find the desired channel within the channel list and get its repo URL. If if cannot be - // found, error. - m_newRepoUrl = ""; - for (ChannelListEntry entry : m_channels) - { - if (entry.id == updateChannel) - m_newRepoUrl = entry.url; - if (entry.id == m_currentChannel) - m_currentRepoUrl = entry.url; - } - - qDebug() << "m_repoUrl = " << m_newRepoUrl; - - // If we didn't find our channel, error. - if (m_newRepoUrl.isEmpty()) - { - qCritical() << "m_repoUrl is empty!"; - emit updateCheckFailed(); - return; - } - - QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json")); - - auto job = new NetJob("GoUpdate Repository Index"); - job->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData)); - connect(job, &NetJob::succeeded, [this, notifyNoUpdate](){ updateCheckFinished(notifyNoUpdate); }); - connect(job, &NetJob::failed, this, &UpdateChecker::updateCheckFailed); - indexJob.reset(job); - job->start(); + qDebug() << "Checking for updates."; + + // If the channel list hasn't loaded yet, load it and defer checking for updates until + // later. + if (!m_chanListLoaded) + { + qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring " + "update check."; + m_checkUpdateWaiting = true; + m_deferredUpdateChannel = updateChannel; + updateChanList(notifyNoUpdate); + return; + } + + if (m_updateChecking) + { + qDebug() << "Ignoring update check request. Already checking for updates."; + return; + } + + m_updateChecking = true; + + // Find the desired channel within the channel list and get its repo URL. If if cannot be + // found, error. + m_newRepoUrl = ""; + for (ChannelListEntry entry : m_channels) + { + if (entry.id == updateChannel) + m_newRepoUrl = entry.url; + if (entry.id == m_currentChannel) + m_currentRepoUrl = entry.url; + } + + qDebug() << "m_repoUrl = " << m_newRepoUrl; + + // If we didn't find our channel, error. + if (m_newRepoUrl.isEmpty()) + { + qCritical() << "m_repoUrl is empty!"; + emit updateCheckFailed(); + return; + } + + QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json")); + + auto job = new NetJob("GoUpdate Repository Index"); + job->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData)); + connect(job, &NetJob::succeeded, [this, notifyNoUpdate](){ updateCheckFinished(notifyNoUpdate); }); + connect(job, &NetJob::failed, this, &UpdateChecker::updateCheckFailed); + indexJob.reset(job); + job->start(); } void UpdateChecker::updateCheckFinished(bool notifyNoUpdate) { - qDebug() << "Finished downloading repo index. Checking for new versions."; - - QJsonParseError jsonError; - indexJob.reset(); - - QJsonDocument jsonDoc = QJsonDocument::fromJson(indexData, &jsonError); - indexData.clear(); - if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject()) - { - qCritical() << "Failed to parse GoUpdate repository index. JSON error" - << jsonError.errorString() << "at offset" << jsonError.offset; - m_updateChecking = false; - return; - } - - QJsonObject object = jsonDoc.object(); - - bool success = false; - int apiVersion = object.value("ApiVersion").toVariant().toInt(&success); - if (apiVersion != API_VERSION || !success) - { - qCritical() << "Failed to check for updates. API version mismatch. We're using" - << API_VERSION << "server has" << apiVersion; - m_updateChecking = false; - return; - } - - qDebug() << "Processing repository version list."; - QJsonObject newestVersion; - QJsonArray versions = object.value("Versions").toArray(); - for (QJsonValue versionVal : versions) - { - QJsonObject version = versionVal.toObject(); - if (newestVersion.value("Id").toVariant().toInt() < - version.value("Id").toVariant().toInt()) - { - newestVersion = version; - } - } - - // We've got the version with the greatest ID number. Now compare it to our current build - // number and update if they're different. - int newBuildNumber = newestVersion.value("Id").toVariant().toInt(); - if (newBuildNumber != m_currentBuild) - { - qDebug() << "Found newer version with ID" << newBuildNumber; - // Update! - GoUpdate::Status updateStatus; - updateStatus.updateAvailable = true; - updateStatus.currentVersionId = m_currentBuild; - updateStatus.currentRepoUrl = m_currentRepoUrl; - updateStatus.newVersionId = newBuildNumber; - updateStatus.newRepoUrl = m_newRepoUrl; - emit updateAvailable(updateStatus); - } - else if (notifyNoUpdate) - { - emit noUpdateFound(); - } - m_updateChecking = false; + qDebug() << "Finished downloading repo index. Checking for new versions."; + + QJsonParseError jsonError; + indexJob.reset(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(indexData, &jsonError); + indexData.clear(); + if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject()) + { + qCritical() << "Failed to parse GoUpdate repository index. JSON error" + << jsonError.errorString() << "at offset" << jsonError.offset; + m_updateChecking = false; + return; + } + + QJsonObject object = jsonDoc.object(); + + bool success = false; + int apiVersion = object.value("ApiVersion").toVariant().toInt(&success); + if (apiVersion != API_VERSION || !success) + { + qCritical() << "Failed to check for updates. API version mismatch. We're using" + << API_VERSION << "server has" << apiVersion; + m_updateChecking = false; + return; + } + + qDebug() << "Processing repository version list."; + QJsonObject newestVersion; + QJsonArray versions = object.value("Versions").toArray(); + for (QJsonValue versionVal : versions) + { + QJsonObject version = versionVal.toObject(); + if (newestVersion.value("Id").toVariant().toInt() < + version.value("Id").toVariant().toInt()) + { + newestVersion = version; + } + } + + // We've got the version with the greatest ID number. Now compare it to our current build + // number and update if they're different. + int newBuildNumber = newestVersion.value("Id").toVariant().toInt(); + if (newBuildNumber != m_currentBuild) + { + qDebug() << "Found newer version with ID" << newBuildNumber; + // Update! + GoUpdate::Status updateStatus; + updateStatus.updateAvailable = true; + updateStatus.currentVersionId = m_currentBuild; + updateStatus.currentRepoUrl = m_currentRepoUrl; + updateStatus.newVersionId = newBuildNumber; + updateStatus.newRepoUrl = m_newRepoUrl; + emit updateAvailable(updateStatus); + } + else if (notifyNoUpdate) + { + emit noUpdateFound(); + } + m_updateChecking = false; } void UpdateChecker::updateCheckFailed() { - qCritical() << "Update check failed for reasons unknown."; + qCritical() << "Update check failed for reasons unknown."; } void UpdateChecker::updateChanList(bool notifyNoUpdate) { - qDebug() << "Loading the channel list."; - - if (m_chanListLoading) - { - qDebug() << "Ignoring channel list update request. Already grabbing channel list."; - return; - } - - if (m_channelListUrl.isEmpty()) - { - qCritical() << "Failed to update channel list. No channel list URL set." - << "If you'd like to use MultiMC's update system, please pass the channel " - "list URL to CMake at compile time."; - return; - } - - m_chanListLoading = true; - NetJob *job = new NetJob("Update System Channel List"); - job->addNetAction(Net::Download::makeByteArray(QUrl(m_channelListUrl), &chanlistData)); - connect(job, &NetJob::succeeded, [this, notifyNoUpdate]() { chanListDownloadFinished(notifyNoUpdate); }); - QObject::connect(job, &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed); - chanListJob.reset(job); - job->start(); + qDebug() << "Loading the channel list."; + + if (m_chanListLoading) + { + qDebug() << "Ignoring channel list update request. Already grabbing channel list."; + return; + } + + if (m_channelListUrl.isEmpty()) + { + qCritical() << "Failed to update channel list. No channel list URL set." + << "If you'd like to use MultiMC's update system, please pass the channel " + "list URL to CMake at compile time."; + return; + } + + m_chanListLoading = true; + NetJob *job = new NetJob("Update System Channel List"); + job->addNetAction(Net::Download::makeByteArray(QUrl(m_channelListUrl), &chanlistData)); + connect(job, &NetJob::succeeded, [this, notifyNoUpdate]() { chanListDownloadFinished(notifyNoUpdate); }); + QObject::connect(job, &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed); + chanListJob.reset(job); + job->start(); } void UpdateChecker::chanListDownloadFinished(bool notifyNoUpdate) { - chanListJob.reset(); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(chanlistData, &jsonError); - chanlistData.clear(); - if (jsonError.error != QJsonParseError::NoError) - { - // TODO: Report errors to the user. - qCritical() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset; - m_chanListLoading = false; - return; - } - - QJsonObject object = jsonDoc.object(); - - bool success = false; - int formatVersion = object.value("format_version").toVariant().toInt(&success); - if (formatVersion != CHANLIST_FORMAT || !success) - { - qCritical() - << "Failed to check for updates. Channel list format version mismatch. We're using" - << CHANLIST_FORMAT << "server has" << formatVersion; - m_chanListLoading = false; - return; - } - - // Load channels into a temporary array. - QList loadedChannels; - QJsonArray channelArray = object.value("channels").toArray(); - for (QJsonValue chanVal : channelArray) - { - QJsonObject channelObj = chanVal.toObject(); - ChannelListEntry entry{channelObj.value("id").toVariant().toString(), - channelObj.value("name").toVariant().toString(), - channelObj.value("description").toVariant().toString(), - channelObj.value("url").toVariant().toString()}; - if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty()) - { - qCritical() << "Channel list entry with empty ID, name, or URL. Skipping."; - continue; - } - loadedChannels.append(entry); - } - - // Swap the channel list we just loaded into the object's channel list. - m_channels.swap(loadedChannels); - - m_chanListLoading = false; - m_chanListLoaded = true; - qDebug() << "Successfully loaded UpdateChecker channel list."; - - // If we're waiting to check for updates, do that now. - if (m_checkUpdateWaiting) - checkForUpdate(m_deferredUpdateChannel, notifyNoUpdate); - - emit channelListLoaded(); + chanListJob.reset(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(chanlistData, &jsonError); + chanlistData.clear(); + if (jsonError.error != QJsonParseError::NoError) + { + // TODO: Report errors to the user. + qCritical() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset; + m_chanListLoading = false; + return; + } + + QJsonObject object = jsonDoc.object(); + + bool success = false; + int formatVersion = object.value("format_version").toVariant().toInt(&success); + if (formatVersion != CHANLIST_FORMAT || !success) + { + qCritical() + << "Failed to check for updates. Channel list format version mismatch. We're using" + << CHANLIST_FORMAT << "server has" << formatVersion; + m_chanListLoading = false; + return; + } + + // Load channels into a temporary array. + QList loadedChannels; + QJsonArray channelArray = object.value("channels").toArray(); + for (QJsonValue chanVal : channelArray) + { + QJsonObject channelObj = chanVal.toObject(); + ChannelListEntry entry{channelObj.value("id").toVariant().toString(), + channelObj.value("name").toVariant().toString(), + channelObj.value("description").toVariant().toString(), + channelObj.value("url").toVariant().toString()}; + if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty()) + { + qCritical() << "Channel list entry with empty ID, name, or URL. Skipping."; + continue; + } + loadedChannels.append(entry); + } + + // Swap the channel list we just loaded into the object's channel list. + m_channels.swap(loadedChannels); + + m_chanListLoading = false; + m_chanListLoaded = true; + qDebug() << "Successfully loaded UpdateChecker channel list."; + + // If we're waiting to check for updates, do that now. + if (m_checkUpdateWaiting) + checkForUpdate(m_deferredUpdateChannel, notifyNoUpdate); + + emit channelListLoaded(); } void UpdateChecker::chanListDownloadFailed(QString reason) { - m_chanListLoading = false; - qCritical() << QString("Failed to download channel list: %1").arg(reason); - emit channelListLoaded(); + m_chanListLoading = false; + qCritical() << QString("Failed to download channel list: %1").arg(reason); + emit channelListLoaded(); } diff --git a/api/logic/updater/UpdateChecker.h b/api/logic/updater/UpdateChecker.h index 4996da26..4ad39c0b 100644 --- a/api/logic/updater/UpdateChecker.h +++ b/api/logic/updater/UpdateChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,100 +22,100 @@ class MULTIMC_LOGIC_EXPORT UpdateChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild); - void checkForUpdate(QString updateChannel, bool notifyNoUpdate); - - /*! - * Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake). - * If this isn't called before checkForUpdate(), it will automatically be called. - */ - void updateChanList(bool notifyNoUpdate); - - /*! - * An entry in the channel list. - */ - struct ChannelListEntry - { - QString id; - QString name; - QString description; - QString url; - }; - - /*! - * Returns a the current channel list. - * If the channel list hasn't been loaded, this list will be empty. - */ - QList getChannelList() const; - - /*! - * Returns false if the channel list is empty. - */ - bool hasChannels() const; + UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild); + void checkForUpdate(QString updateChannel, bool notifyNoUpdate); + + /*! + * Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake). + * If this isn't called before checkForUpdate(), it will automatically be called. + */ + void updateChanList(bool notifyNoUpdate); + + /*! + * An entry in the channel list. + */ + struct ChannelListEntry + { + QString id; + QString name; + QString description; + QString url; + }; + + /*! + * Returns a the current channel list. + * If the channel list hasn't been loaded, this list will be empty. + */ + QList getChannelList() const; + + /*! + * Returns false if the channel list is empty. + */ + bool hasChannels() const; signals: - //! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version. - void updateAvailable(GoUpdate::Status status); + //! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version. + void updateAvailable(GoUpdate::Status status); - //! Signal emitted when the channel list finishes loading or fails to load. - void channelListLoaded(); + //! Signal emitted when the channel list finishes loading or fails to load. + void channelListLoaded(); - void noUpdateFound(); + void noUpdateFound(); private slots: - void updateCheckFinished(bool notifyNoUpdate); - void updateCheckFailed(); + void updateCheckFinished(bool notifyNoUpdate); + void updateCheckFailed(); - void chanListDownloadFinished(bool notifyNoUpdate); - void chanListDownloadFailed(QString reason); + void chanListDownloadFinished(bool notifyNoUpdate); + void chanListDownloadFailed(QString reason); private: - friend class UpdateCheckerTest; - - NetJobPtr indexJob; - QByteArray indexData; - NetJobPtr chanListJob; - QByteArray chanlistData; - - QString m_channelListUrl; - - QList m_channels; - - /*! - * True while the system is checking for updates. - * If checkForUpdate is called while this is true, it will be ignored. - */ - bool m_updateChecking = false; - - /*! - * True if the channel list has loaded. - * If this is false, trying to check for updates will call updateChanList first. - */ - bool m_chanListLoaded = false; - - /*! - * Set to true while the channel list is currently loading. - */ - bool m_chanListLoading = false; - - /*! - * Set to true when checkForUpdate is called while the channel list isn't loaded. - * When the channel list finishes loading, if this is true, the update checker will check for updates. - */ - bool m_checkUpdateWaiting = false; - - /*! - * if m_checkUpdateWaiting, this is the last used update channel - */ - QString m_deferredUpdateChannel; - - int m_currentBuild = -1; - QString m_currentChannel; - QString m_currentRepoUrl; - - QString m_newRepoUrl; + friend class UpdateCheckerTest; + + NetJobPtr indexJob; + QByteArray indexData; + NetJobPtr chanListJob; + QByteArray chanlistData; + + QString m_channelListUrl; + + QList m_channels; + + /*! + * True while the system is checking for updates. + * If checkForUpdate is called while this is true, it will be ignored. + */ + bool m_updateChecking = false; + + /*! + * True if the channel list has loaded. + * If this is false, trying to check for updates will call updateChanList first. + */ + bool m_chanListLoaded = false; + + /*! + * Set to true while the channel list is currently loading. + */ + bool m_chanListLoading = false; + + /*! + * Set to true when checkForUpdate is called while the channel list isn't loaded. + * When the channel list finishes loading, if this is true, the update checker will check for updates. + */ + bool m_checkUpdateWaiting = false; + + /*! + * if m_checkUpdateWaiting, this is the last used update channel + */ + QString m_deferredUpdateChannel; + + int m_currentBuild = -1; + QString m_currentChannel; + QString m_currentRepoUrl; + + QString m_newRepoUrl; }; diff --git a/api/logic/updater/UpdateChecker_test.cpp b/api/logic/updater/UpdateChecker_test.cpp index 16b21614..5702d9c6 100644 --- a/api/logic/updater/UpdateChecker_test.cpp +++ b/api/logic/updater/UpdateChecker_test.cpp @@ -8,137 +8,138 @@ Q_DECLARE_METATYPE(UpdateChecker::ChannelListEntry) bool operator==(const UpdateChecker::ChannelListEntry &e1, const UpdateChecker::ChannelListEntry &e2) { - qDebug() << e1.url << "vs" << e2.url; - return e1.id == e2.id && - e1.name == e2.name && - e1.description == e2.description && - e1.url == e2.url; + qDebug() << e1.url << "vs" << e2.url; + return e1.id == e2.id && + e1.name == e2.name && + e1.description == e2.description && + e1.url == e2.url; } QDebug operator<<(QDebug dbg, const UpdateChecker::ChannelListEntry &c) { - dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")"; + return dbg.maybeSpace(); +} + +QString findTestDataUrl(const char *file) +{ + return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString(); } class UpdateCheckerTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - static QString findTestDataUrl(const char *file) - { - return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString(); - } - void tst_ChannelListParsing_data() - { - QTest::addColumn("channel"); - QTest::addColumn("channelUrl"); - QTest::addColumn("hasChannels"); - QTest::addColumn("valid"); - QTest::addColumn >("result"); - - QTest::newRow("garbage") - << QString() - << findTestDataUrl("data/garbageChannels.json") - << false - << false - << QList(); - QTest::newRow("errors") - << QString() - << findTestDataUrl("data/errorChannels.json") - << false - << true - << QList(); - QTest::newRow("no channels") - << QString() - << findTestDataUrl("data/noChannels.json") - << false - << true - << QList(); - QTest::newRow("one channel") - << QString("develop") - << findTestDataUrl("data/oneChannel.json") - << true - << true - << (QList() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); - QTest::newRow("several channels") - << QString("develop") - << findTestDataUrl("data/channels.json") - << true - << true - << (QList() - << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")} - << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")} - << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"}); - } - void tst_ChannelListParsing() - { - - QFETCH(QString, channel); - QFETCH(QString, channelUrl); - QFETCH(bool, hasChannels); - QFETCH(bool, valid); - QFETCH(QList, result); - - UpdateChecker checker(channelUrl, channel, 0); - - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - - if (valid) - { - QVERIFY(channelListLoadedSpy.wait()); - QCOMPARE(channelListLoadedSpy.size(), 1); - } - else - { - channelListLoadedSpy.wait(); - QCOMPARE(channelListLoadedSpy.size(), 0); - } - - QCOMPARE(checker.hasChannels(), hasChannels); - QCOMPARE(checker.getChannelList(), result); - } - - void tst_UpdateChecking() - { - QString channel = "develop"; - QString channelUrl = findTestDataUrl("data/channels.json"); - int currentBuild = 2; - - UpdateChecker checker(channelUrl, channel, currentBuild); - - QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status))); - QVERIFY(updateAvailableSpy.isValid()); - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - QVERIFY(channelListLoadedSpy.wait()); - - qDebug() << "CWD:" << QDir::current().absolutePath(); - checker.m_channels[0].url = findTestDataUrl("data/"); - checker.checkForUpdate(channel, false); - - QVERIFY(updateAvailableSpy.wait()); - - auto status = updateAvailableSpy.first().first().value(); - QCOMPARE(checker.m_channels[0].url, status.newRepoUrl); - QCOMPARE(3, status.newVersionId); - QCOMPARE(currentBuild, status.currentVersionId); - } + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void tst_ChannelListParsing_data() + { + QTest::addColumn("channel"); + QTest::addColumn("channelUrl"); + QTest::addColumn("hasChannels"); + QTest::addColumn("valid"); + QTest::addColumn >("result"); + + QTest::newRow("garbage") + << QString() + << findTestDataUrl("data/garbageChannels.json") + << false + << false + << QList(); + QTest::newRow("errors") + << QString() + << findTestDataUrl("data/errorChannels.json") + << false + << true + << QList(); + QTest::newRow("no channels") + << QString() + << findTestDataUrl("data/noChannels.json") + << false + << true + << QList(); + QTest::newRow("one channel") + << QString("develop") + << findTestDataUrl("data/oneChannel.json") + << true + << true + << (QList() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); + QTest::newRow("several channels") + << QString("develop") + << findTestDataUrl("data/channels.json") + << true + << true + << (QList() + << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")} + << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")} + << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"}); + } + void tst_ChannelListParsing() + { + + QFETCH(QString, channel); + QFETCH(QString, channelUrl); + QFETCH(bool, hasChannels); + QFETCH(bool, valid); + QFETCH(QList, result); + + UpdateChecker checker(channelUrl, channel, 0); + + QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); + QVERIFY(channelListLoadedSpy.isValid()); + + checker.updateChanList(false); + + if (valid) + { + QVERIFY(channelListLoadedSpy.wait()); + QCOMPARE(channelListLoadedSpy.size(), 1); + } + else + { + channelListLoadedSpy.wait(); + QCOMPARE(channelListLoadedSpy.size(), 0); + } + + QCOMPARE(checker.hasChannels(), hasChannels); + QCOMPARE(checker.getChannelList(), result); + } + + void tst_UpdateChecking() + { + QString channel = "develop"; + QString channelUrl = findTestDataUrl("data/channels.json"); + int currentBuild = 2; + + UpdateChecker checker(channelUrl, channel, currentBuild); + + QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status))); + QVERIFY(updateAvailableSpy.isValid()); + QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); + QVERIFY(channelListLoadedSpy.isValid()); + + checker.updateChanList(false); + QVERIFY(channelListLoadedSpy.wait()); + + qDebug() << "CWD:" << QDir::current().absolutePath(); + checker.m_channels[0].url = findTestDataUrl("data/"); + checker.checkForUpdate(channel, false); + + QVERIFY(updateAvailableSpy.wait()); + + auto status = updateAvailableSpy.first().first().value(); + QCOMPARE(checker.m_channels[0].url, status.newRepoUrl); + QCOMPARE(3, status.newVersionId); + QCOMPARE(currentBuild, status.currentVersionId); + } }; QTEST_GUILESS_MAIN(UpdateCheckerTest) diff --git a/api/logic/updater/testdata/1.json b/api/logic/updater/testdata/1.json index 3dd189e5..7af7e52d 100644 --- a/api/logic/updater/testdata/1.json +++ b/api/logic/updater/testdata/1.json @@ -1,43 +1,43 @@ { - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneA" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "9eb84090956c484e32cb6c08455a667b" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - }, - { - "Path": "fileThree", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileThree" - } - ], - "Executable": false, - "Perms": "750", - "MD5": "f12df554b21e320be6471d7154130e70" - } - ] + "ApiVersion": 0, + "Id": 1, + "Name": "1.0.1", + "Files": [ + { + "Path": "fileOne", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileOneA" + } + ], + "Executable": true, + "Perms": 493, + "MD5": "9eb84090956c484e32cb6c08455a667b" + }, + { + "Path": "fileTwo", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileTwo" + } + ], + "Executable": false, + "Perms": 644, + "MD5": "38f94f54fa3eb72b0ea836538c10b043" + }, + { + "Path": "fileThree", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileThree" + } + ], + "Executable": false, + "Perms": "750", + "MD5": "f12df554b21e320be6471d7154130e70" + } + ] } diff --git a/api/logic/updater/testdata/2.json b/api/logic/updater/testdata/2.json index a7ba7029..96d430d5 100644 --- a/api/logic/updater/testdata/2.json +++ b/api/logic/updater/testdata/2.json @@ -1,31 +1,31 @@ { - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneB" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "42915a71277c9016668cce7b82c6b577" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - } - ] + "ApiVersion": 0, + "Id": 1, + "Name": "1.0.1", + "Files": [ + { + "Path": "fileOne", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileOneB" + } + ], + "Executable": true, + "Perms": 493, + "MD5": "42915a71277c9016668cce7b82c6b577" + }, + { + "Path": "fileTwo", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileTwo" + } + ], + "Executable": false, + "Perms": 644, + "MD5": "38f94f54fa3eb72b0ea836538c10b043" + } + ] } diff --git a/api/logic/updater/testdata/channels.json b/api/logic/updater/testdata/channels.json index b46c64c8..5c6e42cb 100644 --- a/api/logic/updater/testdata/channels.json +++ b/api/logic/updater/testdata/channels.json @@ -1,23 +1,23 @@ { - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "@TEST_DATA_URL@" - }, - { - "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "@TEST_DATA_URL@" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] + "format_version": 0, + "channels": [ + { + "id": "develop", + "name": "Develop", + "description": "The channel called \"develop\"", + "url": "@TEST_DATA_URL@" + }, + { + "id": "stable", + "name": "Stable", + "description": "It's stable at least", + "url": "@TEST_DATA_URL@" + }, + { + "id": "42", + "name": "The Channel", + "description": "This is the channel that is going to answer all of your questions", + "url": "https://dent.me/tea" + } + ] } diff --git a/api/logic/updater/testdata/errorChannels.json b/api/logic/updater/testdata/errorChannels.json index 333cd445..a2cb2165 100644 --- a/api/logic/updater/testdata/errorChannels.json +++ b/api/logic/updater/testdata/errorChannels.json @@ -1,23 +1,23 @@ { - "format_version": 0, - "channels": [ - { - "id": "", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - }, - { - "id": "stable", - "name": "", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "" - } - ] + "format_version": 0, + "channels": [ + { + "id": "", + "name": "Develop", + "description": "The channel called \"develop\"", + "url": "http://example.org/stuff" + }, + { + "id": "stable", + "name": "", + "description": "It's stable at least", + "url": "ftp://username@host/path/to/stuff" + }, + { + "id": "42", + "name": "The Channel", + "description": "This is the channel that is going to answer all of your questions", + "url": "" + } + ] } diff --git a/api/logic/updater/testdata/garbageChannels.json b/api/logic/updater/testdata/garbageChannels.json index 1450fb9c..34437451 100644 --- a/api/logic/updater/testdata/garbageChannels.json +++ b/api/logic/updater/testdata/garbageChannels.json @@ -1,22 +1,22 @@ { - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", -aa "url": "http://example.org/stuff" - }, -a "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42"f - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] + "format_version": 0, + "channels": [ + { + "id": "develop", + "name": "Develop", + "description": "The channel called \"develop\"", +aa "url": "http://example.org/stuff" + }, +a "id": "stable", + "name": "Stable", + "description": "It's stable at least", + "url": "ftp://username@host/path/to/stuff" + }, + { + "id": "42"f + "name": "The Channel", + "description": "This is the channel that is going to answer all of your questions", + "url": "https://dent.me/tea" + } + ] } diff --git a/api/logic/updater/testdata/index.json b/api/logic/updater/testdata/index.json index 20ceb9f4..867bdcfb 100644 --- a/api/logic/updater/testdata/index.json +++ b/api/logic/updater/testdata/index.json @@ -1,9 +1,9 @@ { - "ApiVersion": 0, - "Versions": [ - { "Id": 0, "Name": "1.0.0" }, - { "Id": 1, "Name": "1.0.1" }, - { "Id": 2, "Name": "1.0.2" }, - { "Id": 3, "Name": "1.0.3" } - ] + "ApiVersion": 0, + "Versions": [ + { "Id": 0, "Name": "1.0.0" }, + { "Id": 1, "Name": "1.0.1" }, + { "Id": 2, "Name": "1.0.2" }, + { "Id": 3, "Name": "1.0.3" } + ] } diff --git a/api/logic/updater/testdata/noChannels.json b/api/logic/updater/testdata/noChannels.json index bbb2cb70..76988982 100644 --- a/api/logic/updater/testdata/noChannels.json +++ b/api/logic/updater/testdata/noChannels.json @@ -1,5 +1,5 @@ { - "format_version": 0, - "channels": [ - ] + "format_version": 0, + "channels": [ + ] } diff --git a/api/logic/updater/testdata/oneChannel.json b/api/logic/updater/testdata/oneChannel.json index 84727ac7..cc8ed255 100644 --- a/api/logic/updater/testdata/oneChannel.json +++ b/api/logic/updater/testdata/oneChannel.json @@ -1,11 +1,11 @@ { - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - } - ] + "format_version": 0, + "channels": [ + { + "id": "develop", + "name": "Develop", + "description": "The channel called \"develop\"", + "url": "http://example.org/stuff" + } + ] } diff --git a/application/BuildConfig.cpp.in b/application/BuildConfig.cpp.in index 0a28f074..4253afd0 100644 --- a/application/BuildConfig.cpp.in +++ b/application/BuildConfig.cpp.in @@ -5,50 +5,49 @@ Config BuildConfig; Config::Config() { - // Version information - VERSION_MAJOR = @MultiMC_VERSION_MAJOR@; - VERSION_MINOR = @MultiMC_VERSION_MINOR@; - VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@; - VERSION_BUILD = @MultiMC_VERSION_BUILD@; - - BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@"; - CHANLIST_URL = "@MultiMC_CHANLIST_URL@"; -/* ANALYTICS_ID = "@MultiMC_ANALYTICS_ID@"; */ - NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@"; - FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@"; - - GIT_COMMIT = "@MultiMC_GIT_COMMIT@"; - GIT_REFSPEC = "@MultiMC_GIT_REFSPEC@"; - if(GIT_REFSPEC.startsWith("refs/heads/") && !CHANLIST_URL.isEmpty() && VERSION_BUILD >= 0) - { - VERSION_CHANNEL = GIT_REFSPEC; - VERSION_CHANNEL.remove("refs/heads/"); - UPDATER_ENABLED = true; - } - else - { - VERSION_CHANNEL = QObject::tr("custom"); - } - - VERSION_STR = "@MultiMC_VERSION_STRING@"; - NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@"; - PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@"; + // Version information + VERSION_MAJOR = @MultiMC_VERSION_MAJOR@; + VERSION_MINOR = @MultiMC_VERSION_MINOR@; + VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@; + VERSION_BUILD = @MultiMC_VERSION_BUILD@; + + BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@"; + CHANLIST_URL = "@MultiMC_CHANLIST_URL@"; + NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@"; + FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@"; + + GIT_COMMIT = "@MultiMC_GIT_COMMIT@"; + GIT_REFSPEC = "@MultiMC_GIT_REFSPEC@"; + if(GIT_REFSPEC.startsWith("refs/heads/") && !CHANLIST_URL.isEmpty() && VERSION_BUILD >= 0) + { + VERSION_CHANNEL = GIT_REFSPEC; + VERSION_CHANNEL.remove("refs/heads/"); + UPDATER_ENABLED = true; + } + else + { + VERSION_CHANNEL = QObject::tr("custom"); + } + + VERSION_STR = "@MultiMC_VERSION_STRING@"; + NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@"; + PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@"; } QString Config::printableVersionString() const { - QString vstr = QString("%1.%2.%3").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR), QString::number(VERSION_HOTFIX)); - - // If the build is not a main release, append the channel - if(VERSION_CHANNEL != "stable") - { - vstr += "-" + VERSION_CHANNEL; - } - - // if a build number is set, also add it to the end - if(VERSION_BUILD >= 0) - { - vstr += "-" + QString::number(VERSION_BUILD); - } - return vstr; + QString vstr = QString("%1.%2.%3").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR), QString::number(VERSION_HOTFIX)); + + // If the build is not a main release, append the channel + if(VERSION_CHANNEL != "stable") + { + vstr += "-" + VERSION_CHANNEL; + } + + // if a build number is set, also add it to the end + if(VERSION_BUILD >= 0) + { + vstr += "-" + QString::number(VERSION_BUILD); + } + return vstr; } diff --git a/application/BuildConfig.h b/application/BuildConfig.h index e561cb52..b7965e10 100644 --- a/application/BuildConfig.h +++ b/application/BuildConfig.h @@ -7,64 +7,61 @@ class Config { public: - Config(); - /// The major version number. - int VERSION_MAJOR; - /// The minor version number. - int VERSION_MINOR; - /// The hotfix number. - int VERSION_HOTFIX; - /// The build number. - int VERSION_BUILD; + Config(); + /// The major version number. + int VERSION_MAJOR; + /// The minor version number. + int VERSION_MINOR; + /// The hotfix number. + int VERSION_HOTFIX; + /// The build number. + int VERSION_BUILD; - /** - * The version channel - * This is used by the updater to determine what channel the current version came from. - */ - QString VERSION_CHANNEL; + /** + * The version channel + * This is used by the updater to determine what channel the current version came from. + */ + QString VERSION_CHANNEL; - bool UPDATER_ENABLED = false; + bool UPDATER_ENABLED = false; - /// A short string identifying this build's platform. For example, "lin64" or "win32". - QString BUILD_PLATFORM; + /// A short string identifying this build's platform. For example, "lin64" or "win32". + QString BUILD_PLATFORM; - /// URL for the updater's channel - QString CHANLIST_URL; + /// URL for the updater's channel + QString CHANLIST_URL; -/* /// Google analytics ID - QString ANALYTICS_ID; -*/ - /// URL for notifications - QString NOTIFICATION_URL; + /// URL for notifications + QString NOTIFICATION_URL; - /// Used for matching notifications - QString FULL_VERSION_STR; + /// Used for matching notifications + QString FULL_VERSION_STR; - /// The git commit hash of this build - QString GIT_COMMIT; + /// The git commit hash of this build + QString GIT_COMMIT; - /// The git refspec of this build - QString GIT_REFSPEC; + /// The git refspec of this build + QString GIT_REFSPEC; - /// This is printed on start to standard output - QString VERSION_STR; + /// This is printed on start to standard output + QString VERSION_STR; - /** - * This is used to fetch the news RSS feed. - * It defaults in CMakeLists.txt to "http://multimc.org/rss.xml" - */ - QString NEWS_RSS_URL; + /** + * This is used to fetch the news RSS feed. + * It defaults in CMakeLists.txt to "https://multimc.org/rss.xml" + */ + QString NEWS_RSS_URL; - /** - * API key you can get from paste.ee when you register an account - */ - QString PASTE_EE_KEY; + /** + * API key you can get from paste.ee when you register an account + */ + QString PASTE_EE_KEY; - /** - * \brief Converts the Version to a string. - * \return The version number in string format (major.minor.revision.build). - */ - QString printableVersionString() const; + /** + * \brief Converts the Version to a string. + * \return The version number in string format (major.minor.revision.build). + */ + QString printableVersionString() const; }; extern Config BuildConfig; diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index a63167dc..8e73f2d0 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -7,286 +7,296 @@ configure_file("${PROJECT_SOURCE_DIR}/BuildConfig.cpp.in" "${PROJECT_BINARY_DIR} ######## Sources and headers ######## SET(MULTIMC_SOURCES - # Application base - main.cpp - MultiMC.h - MultiMC.cpp - BuildConfig.h - ${PROJECT_BINARY_DIR}/BuildConfig.cpp - UpdateController.cpp - UpdateController.h + # Application base + main.cpp + MultiMC.h + MultiMC.cpp + BuildConfig.h + ${PROJECT_BINARY_DIR}/BuildConfig.cpp + UpdateController.cpp + UpdateController.h - # GUI - general utilities - GuiUtil.h - GuiUtil.cpp - ColumnResizer.h - ColumnResizer.cpp - InstanceProxyModel.h - InstanceProxyModel.cpp - VersionProxyModel.h - VersionProxyModel.cpp - ColorCache.h - ColorCache.cpp - HoeDown.h + # GUI - general utilities + GuiUtil.h + GuiUtil.cpp + ColumnResizer.h + ColumnResizer.cpp + InstanceProxyModel.h + InstanceProxyModel.cpp + VersionProxyModel.h + VersionProxyModel.cpp + ColorCache.h + ColorCache.cpp + HoeDown.h - # Super secret! - KonamiCode.h - KonamiCode.cpp + # Super secret! + KonamiCode.h + KonamiCode.cpp - # GUI - windows - MainWindow.h - MainWindow.cpp - InstanceWindow.h - InstanceWindow.cpp + # GUI - windows + MainWindow.h + MainWindow.cpp + InstanceWindow.h + InstanceWindow.cpp - # GUI - setup wizard - setupwizard/SetupWizard.h - setupwizard/SetupWizard.cpp - setupwizard/AnalyticsWizardPage.cpp - setupwizard/AnalyticsWizardPage.h - setupwizard/BaseWizardPage.h - setupwizard/JavaWizardPage.cpp - setupwizard/JavaWizardPage.h - setupwizard/LanguageWizardPage.cpp - setupwizard/LanguageWizardPage.h + # GUI - setup wizard + setupwizard/SetupWizard.h + setupwizard/SetupWizard.cpp + setupwizard/AnalyticsWizardPage.cpp + setupwizard/AnalyticsWizardPage.h + setupwizard/BaseWizardPage.h + setupwizard/JavaWizardPage.cpp + setupwizard/JavaWizardPage.h + setupwizard/LanguageWizardPage.cpp + setupwizard/LanguageWizardPage.h - # GUI - themes - themes/FusionTheme.cpp - themes/FusionTheme.h - themes/BrightTheme.cpp - themes/BrightTheme.h - themes/CustomTheme.cpp - themes/CustomTheme.h - themes/DarkTheme.cpp - themes/DarkTheme.h - themes/ITheme.cpp - themes/ITheme.h - themes/SystemTheme.cpp - themes/SystemTheme.h + # GUI - themes + themes/FusionTheme.cpp + themes/FusionTheme.h + themes/BrightTheme.cpp + themes/BrightTheme.h + themes/CustomTheme.cpp + themes/CustomTheme.h + themes/DarkTheme.cpp + themes/DarkTheme.h + themes/ITheme.cpp + themes/ITheme.h + themes/SystemTheme.cpp + themes/SystemTheme.h - # GUI - settings-specific wrappers for paged dialog - SettingsUI.h + # Processes + LaunchController.h + LaunchController.cpp - # Processes - LaunchController.h - LaunchController.cpp + # page provider for instances + InstancePageProvider.h - # page provider for instances - InstancePageProvider.h + # Common java checking UI + JavaCommon.h + JavaCommon.cpp - # Common java checking UI - JavaCommon.h - JavaCommon.cpp + # GUI - paged dialog base + pages/BasePage.h + pages/BasePageContainer.h + pages/BasePageProvider.h - # GUI - paged dialog base - pages/BasePage.h - pages/BasePageContainer.h - pages/BasePageProvider.h + # GUI - instance pages + pages/instance/GameOptionsPage.cpp + pages/instance/GameOptionsPage.h + pages/instance/VersionPage.cpp + pages/instance/VersionPage.h + pages/instance/TexturePackPage.h + pages/instance/ResourcePackPage.h + pages/instance/ModFolderPage.cpp + pages/instance/ModFolderPage.h + pages/instance/NotesPage.cpp + pages/instance/NotesPage.h + pages/instance/LogPage.cpp + pages/instance/LogPage.h + pages/instance/InstanceSettingsPage.cpp + pages/instance/InstanceSettingsPage.h + pages/instance/ScreenshotsPage.cpp + pages/instance/ScreenshotsPage.h + pages/instance/OtherLogsPage.cpp + pages/instance/OtherLogsPage.h + pages/instance/ServersPage.cpp + pages/instance/ServersPage.h + pages/instance/LegacyUpgradePage.cpp + pages/instance/LegacyUpgradePage.h + pages/instance/WorldListPage.cpp + pages/instance/WorldListPage.h - # GUI - instance pages - pages/instance/VersionPage.cpp - pages/instance/VersionPage.h - pages/instance/TexturePackPage.h - pages/instance/ResourcePackPage.h - pages/instance/ModFolderPage.cpp - pages/instance/ModFolderPage.h - pages/instance/NotesPage.cpp - pages/instance/NotesPage.h - pages/instance/LogPage.cpp - pages/instance/LogPage.h - pages/instance/InstanceSettingsPage.cpp - pages/instance/InstanceSettingsPage.h - pages/instance/ScreenshotsPage.cpp - pages/instance/ScreenshotsPage.h - pages/instance/OtherLogsPage.cpp - pages/instance/OtherLogsPage.h - pages/instance/LegacyUpgradePage.cpp - pages/instance/LegacyUpgradePage.h - pages/instance/WorldListPage.cpp - pages/instance/WorldListPage.h + # GUI - global settings pages + pages/global/AccountListPage.cpp + pages/global/AccountListPage.h + pages/global/CustomCommandsPage.cpp + pages/global/CustomCommandsPage.h + pages/global/ExternalToolsPage.cpp + pages/global/ExternalToolsPage.h + pages/global/JavaPage.cpp + pages/global/JavaPage.h + pages/global/LanguagePage.cpp + pages/global/LanguagePage.h + pages/global/MinecraftPage.cpp + pages/global/MinecraftPage.h + pages/global/MultiMCPage.cpp + pages/global/MultiMCPage.h + pages/global/ProxyPage.cpp + pages/global/ProxyPage.h + pages/global/PasteEEPage.cpp + pages/global/PasteEEPage.h + pages/global/PackagesPage.cpp + pages/global/PackagesPage.h - # GUI - global settings pages - pages/global/AccountListPage.cpp - pages/global/AccountListPage.h - pages/global/CustomCommandsPage.cpp - pages/global/CustomCommandsPage.h - pages/global/ExternalToolsPage.cpp - pages/global/ExternalToolsPage.h - pages/global/JavaPage.cpp - pages/global/JavaPage.h - pages/global/MinecraftPage.cpp - pages/global/MinecraftPage.h - pages/global/MultiMCPage.cpp - pages/global/MultiMCPage.h - pages/global/ProxyPage.cpp - pages/global/ProxyPage.h - pages/global/PasteEEPage.cpp - pages/global/PasteEEPage.h - pages/global/PackagesPage.cpp - pages/global/PackagesPage.h + # GUI - platform pages + pages/modplatform/VanillaPage.cpp + pages/modplatform/VanillaPage.h + pages/modplatform/FTBPage.cpp + pages/modplatform/FTBPage.h + pages/modplatform/FtbListModel.h + pages/modplatform/FtbListModel.cpp + pages/modplatform/TwitchPage.cpp + pages/modplatform/TwitchPage.h + pages/modplatform/TechnicPage.cpp + pages/modplatform/TechnicPage.h + pages/modplatform/ImportPage.cpp + pages/modplatform/ImportPage.h - # GUI - platform pages - pages/modplatform/VanillaPage.cpp - pages/modplatform/VanillaPage.h - pages/modplatform/FTBPage.cpp - pages/modplatform/FTBPage.h - pages/modplatform/FtbListModel.h - pages/modplatform/FtbListModel.cpp - pages/modplatform/TwitchPage.cpp - pages/modplatform/TwitchPage.h - pages/modplatform/TechnicPage.cpp - pages/modplatform/TechnicPage.h - pages/modplatform/ImportPage.cpp - pages/modplatform/ImportPage.h + # GUI - dialogs + dialogs/AboutDialog.cpp + dialogs/AboutDialog.h + dialogs/ProfileSelectDialog.cpp + dialogs/ProfileSelectDialog.h + dialogs/CopyInstanceDialog.cpp + dialogs/CopyInstanceDialog.h + dialogs/CustomMessageBox.cpp + dialogs/CustomMessageBox.h + dialogs/EditAccountDialog.cpp + dialogs/EditAccountDialog.h + dialogs/ExportInstanceDialog.cpp + dialogs/ExportInstanceDialog.h + dialogs/IconPickerDialog.cpp + dialogs/IconPickerDialog.h + dialogs/LoginDialog.cpp + dialogs/LoginDialog.h + dialogs/NewComponentDialog.cpp + dialogs/NewComponentDialog.h + dialogs/NewInstanceDialog.cpp + dialogs/NewInstanceDialog.h + dialogs/NotificationDialog.cpp + dialogs/NotificationDialog.h + pagedialog/PageDialog.cpp + pagedialog/PageDialog.h + dialogs/ProgressDialog.cpp + dialogs/ProgressDialog.h + dialogs/UpdateDialog.cpp + dialogs/UpdateDialog.h + dialogs/VersionSelectDialog.cpp + dialogs/VersionSelectDialog.h + dialogs/SkinUploadDialog.cpp + dialogs/SkinUploadDialog.h - # GUI - dialogs - dialogs/AboutDialog.cpp - dialogs/AboutDialog.h - dialogs/ProfileSelectDialog.cpp - dialogs/ProfileSelectDialog.h - dialogs/CopyInstanceDialog.cpp - dialogs/CopyInstanceDialog.h - dialogs/CustomMessageBox.cpp - dialogs/CustomMessageBox.h - dialogs/EditAccountDialog.cpp - dialogs/EditAccountDialog.h - dialogs/ExportInstanceDialog.cpp - dialogs/ExportInstanceDialog.h - dialogs/IconPickerDialog.cpp - dialogs/IconPickerDialog.h - dialogs/LoginDialog.cpp - dialogs/LoginDialog.h - dialogs/ModEditDialogCommon.cpp - dialogs/ModEditDialogCommon.h - dialogs/NewComponentDialog.cpp - dialogs/NewComponentDialog.h - dialogs/NewInstanceDialog.cpp - dialogs/NewInstanceDialog.h - dialogs/NotificationDialog.cpp - dialogs/NotificationDialog.h - pagedialog/PageDialog.cpp - pagedialog/PageDialog.h - dialogs/ProgressDialog.cpp - dialogs/ProgressDialog.h - dialogs/UpdateDialog.cpp - dialogs/UpdateDialog.h - dialogs/VersionSelectDialog.cpp - dialogs/VersionSelectDialog.h - dialogs/SkinUploadDialog.cpp - dialogs/SkinUploadDialog.h + # GUI - widgets + widgets/Common.cpp + widgets/Common.h + widgets/CustomCommands.cpp + widgets/CustomCommands.h + widgets/FocusLineEdit.cpp + widgets/FocusLineEdit.h + widgets/IconLabel.cpp + widgets/IconLabel.h + widgets/JavaSettingsWidget.cpp + widgets/JavaSettingsWidget.h + widgets/LabeledToolButton.cpp + widgets/LabeledToolButton.h + widgets/LanguageSelectionWidget.cpp + widgets/LanguageSelectionWidget.h + widgets/LineSeparator.cpp + widgets/LineSeparator.h + widgets/LogView.cpp + widgets/LogView.h + widgets/MCModInfoFrame.cpp + widgets/MCModInfoFrame.h + widgets/ModListView.cpp + widgets/ModListView.h + widgets/PageContainer.cpp + widgets/PageContainer.h + widgets/PageContainer_p.h + widgets/ServerStatus.cpp + widgets/ServerStatus.h + widgets/VersionListView.cpp + widgets/VersionListView.h + widgets/VersionSelectWidget.cpp + widgets/VersionSelectWidget.h + widgets/ProgressWidget.h + widgets/ProgressWidget.cpp + widgets/WideBar.h + widgets/WideBar.cpp - # GUI - widgets - widgets/Common.cpp - widgets/Common.h - widgets/CustomCommands.cpp - widgets/CustomCommands.h - widgets/FocusLineEdit.cpp - widgets/FocusLineEdit.h - widgets/IconLabel.cpp - widgets/IconLabel.h - widgets/JavaSettingsWidget.cpp - widgets/JavaSettingsWidget.h - widgets/LabeledToolButton.cpp - widgets/LabeledToolButton.h - widgets/LineSeparator.cpp - widgets/LineSeparator.h - widgets/LogView.cpp - widgets/LogView.h - widgets/MCModInfoFrame.cpp - widgets/MCModInfoFrame.h - widgets/ModListView.cpp - widgets/ModListView.h - widgets/PageContainer.cpp - widgets/PageContainer.h - widgets/PageContainer_p.h - widgets/ServerStatus.cpp - widgets/ServerStatus.h - widgets/VersionListView.cpp - widgets/VersionListView.h - widgets/VersionSelectWidget.cpp - widgets/VersionSelectWidget.h - widgets/ProgressWidget.h - widgets/ProgressWidget.cpp - - # GUI - instance group view - groupview/GroupedProxyModel.cpp - groupview/GroupedProxyModel.h - groupview/GroupView.cpp - groupview/GroupView.h - groupview/InstanceDelegate.cpp - groupview/InstanceDelegate.h - groupview/VisualGroup.cpp - groupview/VisualGroup.h - ) + # GUI - instance group view + groupview/GroupedProxyModel.cpp + groupview/GroupedProxyModel.h + groupview/AccessibleGroupView.cpp + groupview/AccessibleGroupView.h + groupview/AccessibleGroupView_p.h + groupview/GroupView.cpp + groupview/GroupView.h + groupview/InstanceDelegate.cpp + groupview/InstanceDelegate.h + groupview/VisualGroup.cpp + groupview/VisualGroup.h + ) ######## UIs ######## SET(MULTIMC_UIS - # Instance pages - pages/instance/VersionPage.ui - pages/instance/ModFolderPage.ui - pages/instance/LogPage.ui - pages/instance/InstanceSettingsPage.ui - pages/instance/NotesPage.ui - pages/instance/ScreenshotsPage.ui - pages/instance/OtherLogsPage.ui - pages/instance/LegacyUpgradePage.ui - pages/instance/WorldListPage.ui + # Instance pages + pages/instance/GameOptionsPage.ui + pages/instance/VersionPage.ui + pages/instance/ModFolderPage.ui + pages/instance/LogPage.ui + pages/instance/InstanceSettingsPage.ui + pages/instance/NotesPage.ui + pages/instance/ScreenshotsPage.ui + pages/instance/OtherLogsPage.ui + pages/instance/LegacyUpgradePage.ui + pages/instance/ServersPage.ui + pages/instance/WorldListPage.ui - # Global settings pages - pages/global/AccountListPage.ui - pages/global/ExternalToolsPage.ui - pages/global/JavaPage.ui - pages/global/MinecraftPage.ui - pages/global/MultiMCPage.ui - pages/global/ProxyPage.ui - pages/global/PasteEEPage.ui - pages/global/PackagesPage.ui + # Global settings pages + pages/global/AccountListPage.ui + pages/global/ExternalToolsPage.ui + pages/global/JavaPage.ui + pages/global/MinecraftPage.ui + pages/global/MultiMCPage.ui + pages/global/ProxyPage.ui + pages/global/PasteEEPage.ui + pages/global/PackagesPage.ui - # Platform pages - pages/modplatform/VanillaPage.ui - pages/modplatform/FTBPage.ui - pages/modplatform/TwitchPage.ui - pages/modplatform/TechnicPage.ui - pages/modplatform/ImportPage.ui + # Platform pages + pages/modplatform/VanillaPage.ui + pages/modplatform/FTBPage.ui + pages/modplatform/TwitchPage.ui + pages/modplatform/TechnicPage.ui + pages/modplatform/ImportPage.ui - # Dialogs - dialogs/CopyInstanceDialog.ui - dialogs/NewComponentDialog.ui - dialogs/NewInstanceDialog.ui - dialogs/AboutDialog.ui - dialogs/ProgressDialog.ui - dialogs/IconPickerDialog.ui - dialogs/ProfileSelectDialog.ui - dialogs/EditAccountDialog.ui - dialogs/ExportInstanceDialog.ui - dialogs/LoginDialog.ui - dialogs/UpdateDialog.ui - dialogs/NotificationDialog.ui - dialogs/SkinUploadDialog.ui + # Dialogs + dialogs/CopyInstanceDialog.ui + dialogs/NewComponentDialog.ui + dialogs/NewInstanceDialog.ui + dialogs/AboutDialog.ui + dialogs/ProgressDialog.ui + dialogs/IconPickerDialog.ui + dialogs/ProfileSelectDialog.ui + dialogs/EditAccountDialog.ui + dialogs/ExportInstanceDialog.ui + dialogs/LoginDialog.ui + dialogs/UpdateDialog.ui + dialogs/NotificationDialog.ui + dialogs/SkinUploadDialog.ui - # Widgets/other - widgets/CustomCommands.ui - widgets/MCModInfoFrame.ui + # Widgets/other + widgets/CustomCommands.ui + widgets/MCModInfoFrame.ui ) set(MULTIMC_QRCS - resources/assets/assets.qrc - resources/backgrounds/backgrounds.qrc - resources/multimc/multimc.qrc - resources/pe_dark/pe_dark.qrc - resources/pe_light/pe_light.qrc - resources/pe_colored/pe_colored.qrc - resources/pe_blue/pe_blue.qrc - resources/OSX/OSX.qrc - resources/iOS/iOS.qrc - resources/flat/flat.qrc - resources/documents/documents.qrc + resources/assets/assets.qrc + resources/backgrounds/backgrounds.qrc + resources/multimc/multimc.qrc + resources/pe_dark/pe_dark.qrc + resources/pe_light/pe_light.qrc + resources/pe_colored/pe_colored.qrc + resources/pe_blue/pe_blue.qrc + resources/OSX/OSX.qrc + resources/iOS/iOS.qrc + resources/flat/flat.qrc + resources/documents/documents.qrc ) ######## Windows resource files ######## if(WIN32) - set(MULTIMC_RCS resources/multimc.rc) + set(MULTIMC_RCS resources/multimc.rc) endif() # Qt 5 stuff @@ -298,89 +308,89 @@ add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MU #target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer ganalytics) target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer) if(DEFINED MultiMC_APP_BINARY_NAME) - set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}") + set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}") endif() if(DEFINED MultiMC_BINARY_RPATH) - SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}") + SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}") endif() if(DEFINED MultiMC_APP_BINARY_DEFS) - target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS}) + target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS}) endif() install(TARGETS MultiMC - BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime - RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime + BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime + RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime ) #### The MultiMC bundle mess! #### # Bundle utilities are used to complete the portable packages - they add all the libraries that would otherwise be missing on the target system. # NOTE: it seems that this absolutely has to be here, and nowhere else. if(INSTALL_BUNDLE STREQUAL "full") - # Add qt.conf - this makes Qt stop looking for things outside the bundle - install( - CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")" - COMPONENT Runtime - ) - # Bundle plugins - if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - # Image formats - install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "tga|tiff|mng|webp" EXCLUDE - ) - # Icon engines - install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - ) - # Platform plugins - install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE - ) - else() - # Image formats - install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "tga|tiff|mng|webp" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - # Icon engines - install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - # Platform plugins - install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" - @ONLY - ) - install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime) + # Add qt.conf - this makes Qt stop looking for things outside the bundle + install( + CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")" + COMPONENT Runtime + ) + # Bundle plugins + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + # Image formats + install( + DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "tga|tiff|mng|webp" EXCLUDE + ) + # Icon engines + install( + DIRECTORY "${QT_PLUGINS_DIR}/iconengines" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "fontawesome" EXCLUDE + ) + # Platform plugins + install( + DIRECTORY "${QT_PLUGINS_DIR}/platforms" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "minimal|linuxfb|offscreen" EXCLUDE + ) + else() + # Image formats + install( + DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "tga|tiff|mng|webp" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + # Icon engines + install( + DIRECTORY "${QT_PLUGINS_DIR}/iconengines" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "fontawesome" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + # Platform plugins + install( + DIRECTORY "${QT_PLUGINS_DIR}/platforms" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "minimal|linuxfb|offscreen" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + endif() + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" + @ONLY + ) + install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime) endif() diff --git a/application/ColorCache.cpp b/application/ColorCache.cpp index e216b597..ef268dd2 100644 --- a/application/ColorCache.cpp +++ b/application/ColorCache.cpp @@ -6,13 +6,13 @@ */ QColor ColorCache::blend(QColor color) { - if (Rainbow::luma(m_front) > Rainbow::luma(m_back)) - { - // for dark color schemes, produce a fitting color first - color = Rainbow::tint(m_front, color, 0.5); - } - // adapt contrast - return Rainbow::mix(m_front, color, m_bias); + if (Rainbow::luma(m_front) > Rainbow::luma(m_back)) + { + // for dark color schemes, produce a fitting color first + color = Rainbow::tint(m_front, color, 0.5); + } + // adapt contrast + return Rainbow::mix(m_front, color, m_bias); } /** @@ -20,16 +20,16 @@ QColor ColorCache::blend(QColor color) */ QColor ColorCache::blendBackground(QColor color) { - // adapt contrast - return Rainbow::mix(m_back, color, m_bias); + // adapt contrast + return Rainbow::mix(m_back, color, m_bias); } void ColorCache::recolorAll() { - auto iter = m_colors.begin(); - while(iter != m_colors.end()) - { - iter->front = blend(iter->original); - iter->back = blendBackground(iter->original); - } + auto iter = m_colors.begin(); + while(iter != m_colors.end()) + { + iter->front = blend(iter->original); + iter->back = blendBackground(iter->original); + } } diff --git a/application/ColorCache.h b/application/ColorCache.h index 1ce1c211..6ae633b9 100644 --- a/application/ColorCache.h +++ b/application/ColorCache.h @@ -7,113 +7,113 @@ class ColorCache { public: - ColorCache(QColor front, QColor back, qreal bias) - { - m_front = front; - m_back = back; - m_bias = bias; - }; + ColorCache(QColor front, QColor back, qreal bias) + { + m_front = front; + m_back = back; + m_bias = bias; + }; - void addColor(int key, QColor color) - { - m_colors[key] = {color, blend(color), blendBackground(color)}; - } + void addColor(int key, QColor color) + { + m_colors[key] = {color, blend(color), blendBackground(color)}; + } - void setForeground(QColor front) - { - if(m_front != front) - { - m_front = front; - recolorAll(); - } - } + void setForeground(QColor front) + { + if(m_front != front) + { + m_front = front; + recolorAll(); + } + } - void setBackground(QColor back) - { - if(m_back != back) - { - m_back = back; - recolorAll(); - } - } + void setBackground(QColor back) + { + if(m_back != back) + { + m_back = back; + recolorAll(); + } + } - QColor getFront(int key) - { - auto iter = m_colors.find(key); - if(iter == m_colors.end()) - { - return QColor(); - } - return (*iter).front; - } + QColor getFront(int key) + { + auto iter = m_colors.find(key); + if(iter == m_colors.end()) + { + return QColor(); + } + return (*iter).front; + } - QColor getBack(int key) - { - auto iter = m_colors.find(key); - if(iter == m_colors.end()) - { - return QColor(); - } - return (*iter).back; - } + QColor getBack(int key) + { + auto iter = m_colors.find(key); + if(iter == m_colors.end()) + { + return QColor(); + } + return (*iter).back; + } - /** - * Blend the color with the front color, adapting to the back color - */ - QColor blend(QColor color); + /** + * Blend the color with the front color, adapting to the back color + */ + QColor blend(QColor color); - /** - * Blend the color with the back color - */ - QColor blendBackground(QColor color); + /** + * Blend the color with the back color + */ + QColor blendBackground(QColor color); protected: - void recolorAll(); + void recolorAll(); protected: - struct ColorEntry - { - QColor original; - QColor front; - QColor back; - }; + struct ColorEntry + { + QColor original; + QColor front; + QColor back; + }; protected: - qreal m_bias; - QColor m_front; - QColor m_back; - QMap m_colors; + qreal m_bias; + QColor m_front; + QColor m_back; + QMap m_colors; }; class LogColorCache : public ColorCache { public: - LogColorCache(QColor front, QColor back) - : ColorCache(front, back, 1.0) - { - addColor((int)MessageLevel::MultiMC, QColor("purple")); - addColor((int)MessageLevel::Debug, QColor("green")); - addColor((int)MessageLevel::Warning, QColor("orange")); - addColor((int)MessageLevel::Error, QColor("red")); - addColor((int)MessageLevel::Fatal, QColor("red")); - addColor((int)MessageLevel::Message, front); - } + LogColorCache(QColor front, QColor back) + : ColorCache(front, back, 1.0) + { + addColor((int)MessageLevel::MultiMC, QColor("purple")); + addColor((int)MessageLevel::Debug, QColor("green")); + addColor((int)MessageLevel::Warning, QColor("orange")); + addColor((int)MessageLevel::Error, QColor("red")); + addColor((int)MessageLevel::Fatal, QColor("red")); + addColor((int)MessageLevel::Message, front); + } - QColor getFront(MessageLevel::Enum level) - { - if(!m_colors.contains((int) level)) - { - return ColorCache::getFront((int)MessageLevel::Message); - } - return ColorCache::getFront((int)level); - } + QColor getFront(MessageLevel::Enum level) + { + if(!m_colors.contains((int) level)) + { + return ColorCache::getFront((int)MessageLevel::Message); + } + return ColorCache::getFront((int)level); + } - QColor getBack(MessageLevel::Enum level) - { - if(level == MessageLevel::Fatal) - { - return QColor(Qt::black); - } - return QColor(Qt::transparent); - } + QColor getBack(MessageLevel::Enum level) + { + if(level == MessageLevel::Fatal) + { + return QColor(Qt::black); + } + return QColor(Qt::transparent); + } }; diff --git a/application/ColumnResizer.cpp b/application/ColumnResizer.cpp index ee99bf40..fe415067 100644 --- a/application/ColumnResizer.cpp +++ b/application/ColumnResizer.cpp @@ -14,67 +14,67 @@ class FormLayoutWidgetItem : public QWidgetItem { public: - FormLayoutWidgetItem(QWidget* widget, QFormLayout* formLayout, QFormLayout::ItemRole itemRole) - : QWidgetItem(widget) - , m_width(-1) - , m_formLayout(formLayout) - , m_itemRole(itemRole) - {} - - QSize sizeHint() const - { - QSize size = QWidgetItem::sizeHint(); - if (m_width != -1) { - size.setWidth(m_width); - } - return size; - } - - QSize minimumSize() const - { - QSize size = QWidgetItem::minimumSize(); - if (m_width != -1) { - size.setWidth(m_width); - } - return size; - } - - QSize maximumSize() const - { - QSize size = QWidgetItem::maximumSize(); - if (m_width != -1) { - size.setWidth(m_width); - } - return size; - } - - void setWidth(int width) - { - if (width != m_width) { - m_width = width; - invalidate(); - } - } - - void setGeometry(const QRect& _rect) - { - QRect rect = _rect; - int width = widget()->sizeHint().width(); - if (m_itemRole == QFormLayout::LabelRole && m_formLayout->labelAlignment() & Qt::AlignRight) { - rect.setLeft(rect.right() - width); - } - QWidgetItem::setGeometry(rect); - } - - QFormLayout* formLayout() const - { - return m_formLayout; - } + FormLayoutWidgetItem(QWidget* widget, QFormLayout* formLayout, QFormLayout::ItemRole itemRole) + : QWidgetItem(widget) + , m_width(-1) + , m_formLayout(formLayout) + , m_itemRole(itemRole) + {} + + QSize sizeHint() const + { + QSize size = QWidgetItem::sizeHint(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + QSize minimumSize() const + { + QSize size = QWidgetItem::minimumSize(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + QSize maximumSize() const + { + QSize size = QWidgetItem::maximumSize(); + if (m_width != -1) { + size.setWidth(m_width); + } + return size; + } + + void setWidth(int width) + { + if (width != m_width) { + m_width = width; + invalidate(); + } + } + + void setGeometry(const QRect& _rect) + { + QRect rect = _rect; + int width = widget()->sizeHint().width(); + if (m_itemRole == QFormLayout::LabelRole && m_formLayout->labelAlignment() & Qt::AlignRight) { + rect.setLeft(rect.right() - width); + } + QWidgetItem::setGeometry(rect); + } + + QFormLayout* formLayout() const + { + return m_formLayout; + } private: - int m_width; - QFormLayout* m_formLayout; - QFormLayout::ItemRole m_itemRole; + int m_width; + QFormLayout* m_formLayout; + QFormLayout::ItemRole m_itemRole; }; typedef QPair GridColumnInfo; @@ -82,25 +82,25 @@ typedef QPair GridColumnInfo; class ColumnResizerPrivate { public: - ColumnResizerPrivate(ColumnResizer* q_ptr) - : q(q_ptr) - , m_updateTimer(new QTimer(q)) - { - m_updateTimer->setSingleShot(true); - m_updateTimer->setInterval(0); - QObject::connect(m_updateTimer, SIGNAL(timeout()), q, SLOT(updateWidth())); - } - - void scheduleWidthUpdate() - { - m_updateTimer->start(); - } - - ColumnResizer* q; - QTimer* m_updateTimer; - QList m_widgets; - QList m_wrWidgetItemList; - QList m_gridColumnInfoList; + ColumnResizerPrivate(ColumnResizer* q_ptr) + : q(q_ptr) + , m_updateTimer(new QTimer(q)) + { + m_updateTimer->setSingleShot(true); + m_updateTimer->setInterval(0); + QObject::connect(m_updateTimer, SIGNAL(timeout()), q, SLOT(updateWidth())); + } + + void scheduleWidthUpdate() + { + m_updateTimer->start(); + } + + ColumnResizer* q; + QTimer* m_updateTimer; + QList m_widgets; + QList m_wrWidgetItemList; + QList m_gridColumnInfoList; }; ColumnResizer::ColumnResizer(QObject* parent) @@ -110,90 +110,90 @@ ColumnResizer::ColumnResizer(QObject* parent) ColumnResizer::~ColumnResizer() { - delete d; + delete d; } void ColumnResizer::addWidget(QWidget* widget) { - d->m_widgets.append(widget); - widget->installEventFilter(this); - d->scheduleWidthUpdate(); + d->m_widgets.append(widget); + widget->installEventFilter(this); + d->scheduleWidthUpdate(); } void ColumnResizer::updateWidth() { - int width = 0; - Q_FOREACH(QWidget* widget, d->m_widgets) { - width = qMax(widget->sizeHint().width(), width); - } - Q_FOREACH(FormLayoutWidgetItem* item, d->m_wrWidgetItemList) { - item->setWidth(width); - item->formLayout()->update(); - } - Q_FOREACH(GridColumnInfo info, d->m_gridColumnInfoList) { - info.first->setColumnMinimumWidth(info.second, width); - } + int width = 0; + Q_FOREACH(QWidget* widget, d->m_widgets) { + width = qMax(widget->sizeHint().width(), width); + } + Q_FOREACH(FormLayoutWidgetItem* item, d->m_wrWidgetItemList) { + item->setWidth(width); + item->formLayout()->update(); + } + Q_FOREACH(GridColumnInfo info, d->m_gridColumnInfoList) { + info.first->setColumnMinimumWidth(info.second, width); + } } bool ColumnResizer::eventFilter(QObject*, QEvent* event) { - if (event->type() == QEvent::Resize) { - d->scheduleWidthUpdate(); - } - return false; + if (event->type() == QEvent::Resize) { + d->scheduleWidthUpdate(); + } + return false; } void ColumnResizer::addWidgetsFromLayout(QLayout* layout, int column) { - Q_ASSERT(column >= 0); - QGridLayout* gridLayout = qobject_cast(layout); - QFormLayout* formLayout = qobject_cast(layout); - if (gridLayout) { - addWidgetsFromGridLayout(gridLayout, column); - } else if (formLayout) { - if (column > QFormLayout::SpanningRole) { - qCritical() << "column should not be more than" << QFormLayout::SpanningRole << "for QFormLayout"; - return; - } - QFormLayout::ItemRole role = static_cast(column); - addWidgetsFromFormLayout(formLayout, role); - } else { - qCritical() << "Don't know how to handle layout" << layout; - } + Q_ASSERT(column >= 0); + QGridLayout* gridLayout = qobject_cast(layout); + QFormLayout* formLayout = qobject_cast(layout); + if (gridLayout) { + addWidgetsFromGridLayout(gridLayout, column); + } else if (formLayout) { + if (column > QFormLayout::SpanningRole) { + qCritical() << "column should not be more than" << QFormLayout::SpanningRole << "for QFormLayout"; + return; + } + QFormLayout::ItemRole role = static_cast(column); + addWidgetsFromFormLayout(formLayout, role); + } else { + qCritical() << "Don't know how to handle layout" << layout; + } } void ColumnResizer::addWidgetsFromGridLayout(QGridLayout* layout, int column) { - for (int row = 0; row < layout->rowCount(); ++row) { - QLayoutItem* item = layout->itemAtPosition(row, column); - if (!item) { - continue; - } - QWidget* widget = item->widget(); - if (!widget) { - continue; - } - addWidget(widget); - } - d->m_gridColumnInfoList << GridColumnInfo(layout, column); + for (int row = 0; row < layout->rowCount(); ++row) { + QLayoutItem* item = layout->itemAtPosition(row, column); + if (!item) { + continue; + } + QWidget* widget = item->widget(); + if (!widget) { + continue; + } + addWidget(widget); + } + d->m_gridColumnInfoList << GridColumnInfo(layout, column); } void ColumnResizer::addWidgetsFromFormLayout(QFormLayout* layout, QFormLayout::ItemRole role) { - for (int row = 0; row < layout->rowCount(); ++row) { - QLayoutItem* item = layout->itemAt(row, role); - if (!item) { - continue; - } - QWidget* widget = item->widget(); - if (!widget) { - continue; - } - layout->removeItem(item); - delete item; - FormLayoutWidgetItem* newItem = new FormLayoutWidgetItem(widget, layout, role); - layout->setItem(row, role, newItem); - addWidget(widget); - d->m_wrWidgetItemList << newItem; - } + for (int row = 0; row < layout->rowCount(); ++row) { + QLayoutItem* item = layout->itemAt(row, role); + if (!item) { + continue; + } + QWidget* widget = item->widget(); + if (!widget) { + continue; + } + layout->removeItem(item); + delete item; + FormLayoutWidgetItem* newItem = new FormLayoutWidgetItem(widget, layout, role); + layout->setItem(row, role, newItem); + addWidget(widget); + d->m_wrWidgetItemList << newItem; + } } diff --git a/application/ColumnResizer.h b/application/ColumnResizer.h index 78966a7e..8c920f01 100644 --- a/application/ColumnResizer.h +++ b/application/ColumnResizer.h @@ -18,24 +18,24 @@ class QWidget; class ColumnResizerPrivate; class ColumnResizer : public QObject { - Q_OBJECT + Q_OBJECT public: - ColumnResizer(QObject* parent = 0); - ~ColumnResizer(); + ColumnResizer(QObject* parent = 0); + ~ColumnResizer(); - void addWidget(QWidget* widget); - void addWidgetsFromLayout(QLayout*, int column); - void addWidgetsFromGridLayout(QGridLayout*, int column); - void addWidgetsFromFormLayout(QFormLayout*, QFormLayout::ItemRole role); + void addWidget(QWidget* widget); + void addWidgetsFromLayout(QLayout*, int column); + void addWidgetsFromGridLayout(QGridLayout*, int column); + void addWidgetsFromFormLayout(QFormLayout*, QFormLayout::ItemRole role); private Q_SLOTS: - void updateWidth(); + void updateWidth(); protected: - bool eventFilter(QObject*, QEvent* event); + bool eventFilter(QObject*, QEvent* event); private: - ColumnResizerPrivate* const d; + ColumnResizerPrivate* const d; }; #endif /* COLUMNRESIZER_H */ diff --git a/application/GuiUtil.cpp b/application/GuiUtil.cpp index b05fc57c..302206f5 100644 --- a/application/GuiUtil.cpp +++ b/application/GuiUtil.cpp @@ -15,117 +15,117 @@ QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget) { - ProgressDialog dialog(parentWidget); - auto APIKeySetting = MMC->settings()->get("PasteEEAPIKey").toString(); - if(APIKeySetting == "multimc") - { - APIKeySetting = BuildConfig.PASTE_EE_KEY; - } - std::unique_ptr paste(new PasteUpload(parentWidget, text, APIKeySetting)); - - if (!paste->validateText()) - { - CustomMessageBox::selectable( - parentWidget, QObject::tr("Upload failed"), - QObject::tr("The log file is too big. You'll have to upload it manually."), - QMessageBox::Warning)->exec(); - return QString(); - } - - dialog.execWithTask(paste.get()); - if (!paste->wasSuccessful()) - { - CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), - paste->failReason(), QMessageBox::Critical)->exec(); - return QString(); - } - else - { - const QString link = paste->pasteLink(); - setClipboardText(link); - CustomMessageBox::selectable( - parentWidget, QObject::tr("Upload finished"), - QObject::tr("The link to the uploaded log has been placed in your clipboard.").arg(link), - QMessageBox::Information)->exec(); - return link; - } + ProgressDialog dialog(parentWidget); + auto APIKeySetting = MMC->settings()->get("PasteEEAPIKey").toString(); + if(APIKeySetting == "multimc") + { + APIKeySetting = BuildConfig.PASTE_EE_KEY; + } + std::unique_ptr paste(new PasteUpload(parentWidget, text, APIKeySetting)); + + if (!paste->validateText()) + { + CustomMessageBox::selectable( + parentWidget, QObject::tr("Upload failed"), + QObject::tr("The log file is too big. You'll have to upload it manually."), + QMessageBox::Warning)->exec(); + return QString(); + } + + dialog.execWithTask(paste.get()); + if (!paste->wasSuccessful()) + { + CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), + paste->failReason(), QMessageBox::Critical)->exec(); + return QString(); + } + else + { + const QString link = paste->pasteLink(); + setClipboardText(link); + CustomMessageBox::selectable( + parentWidget, QObject::tr("Upload finished"), + QObject::tr("The link to the uploaded log has been placed in your clipboard.").arg(link), + QMessageBox::Information)->exec(); + return link; + } } void GuiUtil::setClipboardText(const QString &text) { - QApplication::clipboard()->setText(text); + QApplication::clipboard()->setText(text); } static QStringList BrowseForFileInternal(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget, bool single) { - static QMap savedPaths; - - QFileDialog w(parentWidget, caption); - QSet locations; - auto f = [&](QStandardPaths::StandardLocation l) - { - QString location = QStandardPaths::writableLocation(l); - QFileInfo finfo(location); - if (!finfo.exists()) - return; - locations.insert(location); - }; - f(QStandardPaths::DesktopLocation); - f(QStandardPaths::DocumentsLocation); - f(QStandardPaths::DownloadLocation); - f(QStandardPaths::HomeLocation); - QList urls; - for (auto location : locations) - { - urls.append(QUrl::fromLocalFile(location)); - } - urls.append(QUrl::fromLocalFile(defaultPath)); - - w.setFileMode(single ? QFileDialog::ExistingFile : QFileDialog::ExistingFiles); - w.setAcceptMode(QFileDialog::AcceptOpen); - w.setNameFilter(filter); - - QString pathToOpen; - if(savedPaths.contains(context)) - { - pathToOpen = savedPaths[context]; - } - else - { - pathToOpen = defaultPath; - } - if(!pathToOpen.isEmpty()) - { - QFileInfo finfo(pathToOpen); - if(finfo.exists() && finfo.isDir()) - { - w.setDirectory(finfo.absoluteFilePath()); - } - } - - w.setSidebarUrls(urls); - - if (w.exec()) - { - savedPaths[context] = w.directory().absolutePath(); - return w.selectedFiles(); - } - savedPaths[context] = w.directory().absolutePath(); - return {}; + static QMap savedPaths; + + QFileDialog w(parentWidget, caption); + QSet locations; + auto f = [&](QStandardPaths::StandardLocation l) + { + QString location = QStandardPaths::writableLocation(l); + QFileInfo finfo(location); + if (!finfo.exists()) + return; + locations.insert(location); + }; + f(QStandardPaths::DesktopLocation); + f(QStandardPaths::DocumentsLocation); + f(QStandardPaths::DownloadLocation); + f(QStandardPaths::HomeLocation); + QList urls; + for (auto location : locations) + { + urls.append(QUrl::fromLocalFile(location)); + } + urls.append(QUrl::fromLocalFile(defaultPath)); + + w.setFileMode(single ? QFileDialog::ExistingFile : QFileDialog::ExistingFiles); + w.setAcceptMode(QFileDialog::AcceptOpen); + w.setNameFilter(filter); + + QString pathToOpen; + if(savedPaths.contains(context)) + { + pathToOpen = savedPaths[context]; + } + else + { + pathToOpen = defaultPath; + } + if(!pathToOpen.isEmpty()) + { + QFileInfo finfo(pathToOpen); + if(finfo.exists() && finfo.isDir()) + { + w.setDirectory(finfo.absoluteFilePath()); + } + } + + w.setSidebarUrls(urls); + + if (w.exec()) + { + savedPaths[context] = w.directory().absolutePath(); + return w.selectedFiles(); + } + savedPaths[context] = w.directory().absolutePath(); + return {}; } QString GuiUtil::BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget) { - auto resultList = BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, true); - if(resultList.size()) - { - return resultList[0]; - } - return QString(); + auto resultList = BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, true); + if(resultList.size()) + { + return resultList[0]; + } + return QString(); } QStringList GuiUtil::BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget) { - return BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, false); + return BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, false); } diff --git a/application/HoeDown.h b/application/HoeDown.h index 18c315c6..c94a312f 100644 --- a/application/HoeDown.h +++ b/application/HoeDown.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,52 +25,52 @@ class HoeDown { public: - class buffer - { - public: - buffer(size_t unit = 4096) - { - buf = hoedown_buffer_new(unit); - } - ~buffer() - { - hoedown_buffer_free(buf); - } - const char * cstr() - { - return hoedown_buffer_cstr(buf); - } - void put(QByteArray input) - { - hoedown_buffer_put(buf, (uint8_t *) input.data(), input.size()); - } - const uint8_t * data() const - { - return buf->data; - } - size_t size() const - { - return buf->size; - } - hoedown_buffer * buf; - } ib, ob; - HoeDown() - { - renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0); - document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8); - } - ~HoeDown() - { - hoedown_document_free(document); - hoedown_html_renderer_free(renderer); - } - QString process(QByteArray input) - { - ib.put(input); - hoedown_document_render(document, ob.buf, ib.data(), ib.size()); - return ob.cstr(); - } + class buffer + { + public: + buffer(size_t unit = 4096) + { + buf = hoedown_buffer_new(unit); + } + ~buffer() + { + hoedown_buffer_free(buf); + } + const char * cstr() + { + return hoedown_buffer_cstr(buf); + } + void put(QByteArray input) + { + hoedown_buffer_put(buf, (uint8_t *) input.data(), input.size()); + } + const uint8_t * data() const + { + return buf->data; + } + size_t size() const + { + return buf->size; + } + hoedown_buffer * buf; + } ib, ob; + HoeDown() + { + renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0); + document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8); + } + ~HoeDown() + { + hoedown_document_free(document); + hoedown_html_renderer_free(renderer); + } + QString process(QByteArray input) + { + ib.put(input); + hoedown_document_render(document, ob.buf, ib.data(), ib.size()); + return ob.cstr(); + } private: - hoedown_document * document; - hoedown_renderer * renderer; + hoedown_document * document; + hoedown_renderer * renderer; }; diff --git a/application/InstancePageProvider.h b/application/InstancePageProvider.h index 9dda7859..dde36aef 100644 --- a/application/InstancePageProvider.h +++ b/application/InstancePageProvider.h @@ -15,57 +15,62 @@ #include "pages/instance/OtherLogsPage.h" #include "pages/instance/LegacyUpgradePage.h" #include "pages/instance/WorldListPage.h" +#include "pages/instance/ServersPage.h" +#include "pages/instance/GameOptionsPage.h" +#include "Env.h" class InstancePageProvider : public QObject, public BasePageProvider { - Q_OBJECT + Q_OBJECT public: - explicit InstancePageProvider(InstancePtr parent) - { - inst = parent; - } + explicit InstancePageProvider(InstancePtr parent) + { + inst = parent; + } - virtual ~InstancePageProvider() {}; - virtual QList getPages() override - { - QList values; - values.append(new LogPage(inst)); - std::shared_ptr onesix = std::dynamic_pointer_cast(inst); - if(onesix) - { - values.append(new VersionPage(onesix.get())); - auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods"); - modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); - values.append(modsPage); - values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods")); - values.append(new ResourcePackPage(onesix.get())); - values.append(new TexturePackPage(onesix.get())); - values.append(new NotesPage(onesix.get())); - values.append(new WorldListPage(onesix.get(), onesix->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds")); - values.append(new ScreenshotsPage(FS::PathCombine(onesix->minecraftRoot(), "screenshots"))); - values.append(new InstanceSettingsPage(onesix.get())); - } - std::shared_ptr legacy = std::dynamic_pointer_cast(inst); - if(legacy) - { - values.append(new LegacyUpgradePage(legacy)); - values.append(new NotesPage(legacy.get())); - values.append(new WorldListPage(legacy.get(), legacy->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds")); - values.append(new ScreenshotsPage(FS::PathCombine(legacy->minecraftRoot(), "screenshots"))); - } - auto logMatcher = inst->getLogFileMatcher(); - if(logMatcher) - { - values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); - } - return values; - } + virtual ~InstancePageProvider() {}; + virtual QList getPages() override + { + QList values; + values.append(new LogPage(inst)); + std::shared_ptr onesix = std::dynamic_pointer_cast(inst); + if(onesix) + { + values.append(new VersionPage(onesix.get())); + auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods"); + modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); + values.append(modsPage); + values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods")); + values.append(new ResourcePackPage(onesix.get())); + values.append(new TexturePackPage(onesix.get())); + values.append(new NotesPage(onesix.get())); + values.append(new WorldListPage(onesix.get(), onesix->worldList())); + values.append(new ServersPage(onesix.get())); + // values.append(new GameOptionsPage(onesix.get())); + values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); + values.append(new InstanceSettingsPage(onesix.get())); + } + std::shared_ptr legacy = std::dynamic_pointer_cast(inst); + if(legacy) + { + values.append(new LegacyUpgradePage(legacy)); + values.append(new NotesPage(legacy.get())); + values.append(new WorldListPage(legacy.get(), legacy->worldList())); + values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots"))); + } + auto logMatcher = inst->getLogFileMatcher(); + if(logMatcher) + { + values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); + } + return values; + } - virtual QString dialogTitle() override - { - return tr("Edit Instance (%1)").arg(inst->name()); - } + virtual QString dialogTitle() override + { + return tr("Edit Instance (%1)").arg(inst->name()); + } protected: - InstancePtr inst; + InstancePtr inst; }; diff --git a/application/InstanceProxyModel.cpp b/application/InstanceProxyModel.cpp index d0e9e10d..5317f60c 100644 --- a/application/InstanceProxyModel.cpp +++ b/application/InstanceProxyModel.cpp @@ -9,26 +9,26 @@ InstanceProxyModel::InstanceProxyModel(QObject *parent) : GroupedProxyModel(pare QVariant InstanceProxyModel::data(const QModelIndex & index, int role) const { - QVariant data = QSortFilterProxyModel::data(index, role); - if(role == Qt::DecorationRole) - { - return QVariant(MMC->icons()->getIcon(data.toString())); - } - return data; + QVariant data = QSortFilterProxyModel::data(index, role); + if(role == Qt::DecorationRole) + { + return QVariant(MMC->icons()->getIcon(data.toString())); + } + return data; } bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, - const QModelIndex &right) const + const QModelIndex &right) const { - BaseInstance *pdataLeft = static_cast(left.internalPointer()); - BaseInstance *pdataRight = static_cast(right.internalPointer()); - QString sortMode = MMC->settings()->get("InstSortMode").toString(); - if (sortMode == "LastLaunch") - { - return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); - } - else - { - return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; - } + BaseInstance *pdataLeft = static_cast(left.internalPointer()); + BaseInstance *pdataRight = static_cast(right.internalPointer()); + QString sortMode = MMC->settings()->get("InstSortMode").toString(); + if (sortMode == "LastLaunch") + { + return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); + } + else + { + return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; + } } diff --git a/application/InstanceProxyModel.h b/application/InstanceProxyModel.h index c063f526..fab6f834 100644 --- a/application/InstanceProxyModel.h +++ b/application/InstanceProxyModel.h @@ -8,9 +8,9 @@ class InstanceProxyModel : public GroupedProxyModel { public: - explicit InstanceProxyModel(QObject *parent = 0); - QVariant data(const QModelIndex & index, int role) const override; + explicit InstanceProxyModel(QObject *parent = 0); + QVariant data(const QModelIndex & index, int role) const override; protected: - virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const override; + virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const override; }; diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp index 5895ca3a..02c1bb23 100644 --- a/application/InstanceWindow.cpp +++ b/application/InstanceWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,184 +31,188 @@ #include "icons/IconList.h" InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) - : QMainWindow(parent), m_instance(instance) + : QMainWindow(parent), m_instance(instance) { - setAttribute(Qt::WA_DeleteOnClose); - - auto icon = MMC->icons()->getIcon(m_instance->iconKey()); - QString windowTitle = tr("Console window for ") + m_instance->name(); - - // Set window properties - { - setWindowIcon(icon); - setWindowTitle(windowTitle); - } - - // Add page container - { - auto provider = std::make_shared(m_instance); - m_container = new PageContainer(provider.get(), "console", this); - m_container->setParentContainer(this); - setCentralWidget(m_container); - } - - // Add custom buttons to the page container layout. - { - auto horizontalLayout = new QHBoxLayout(); - horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - horizontalLayout->setContentsMargins(6, -1, 6, -1); - - auto btnHelp = new QPushButton(); - btnHelp->setText(tr("Help")); - horizontalLayout->addWidget(btnHelp); - connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); - - auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout->addSpacerItem(spacer); - - m_killButton = new QPushButton(); - horizontalLayout->addWidget(m_killButton); - connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); - - m_launchOfflineButton = new QPushButton(); - horizontalLayout->addWidget(m_launchOfflineButton); - m_launchOfflineButton->setText(tr("Launch Offline")); - updateLaunchButtons(); - connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); - - m_closeButton = new QPushButton(); - m_closeButton->setText(tr("Close")); - horizontalLayout->addWidget(m_closeButton); - connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); - - m_container->addButtons(horizontalLayout); - } - - // restore window state - { - auto base64State = MMC->settings()->get("ConsoleWindowState").toByteArray(); - restoreState(QByteArray::fromBase64(base64State)); - auto base64Geometry = MMC->settings()->get("ConsoleWindowGeometry").toByteArray(); - restoreGeometry(QByteArray::fromBase64(base64Geometry)); - } - - // set up instance and launch process recognition - { - auto launchTask = m_instance->getLaunchTask(); - on_InstanceLaunchTask_changed(launchTask); - connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::on_InstanceLaunchTask_changed); - connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::on_RunningState_changed); - } - - // set up instance destruction detection - { - connect(m_instance.get(), &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged); - } - show(); + setAttribute(Qt::WA_DeleteOnClose); + + auto icon = MMC->icons()->getIcon(m_instance->iconKey()); + QString windowTitle = tr("Console window for ") + m_instance->name(); + + // Set window properties + { + setWindowIcon(icon); + setWindowTitle(windowTitle); + } + + // Add page container + { + auto provider = std::make_shared(m_instance); + m_container = new PageContainer(provider.get(), "console", this); + m_container->setParentContainer(this); + setCentralWidget(m_container); + setContentsMargins(0, 0, 0, 0); + } + + // Add custom buttons to the page container layout. + { + auto horizontalLayout = new QHBoxLayout(); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setContentsMargins(6, -1, 6, -1); + + auto btnHelp = new QPushButton(); + btnHelp->setText(tr("Help")); + horizontalLayout->addWidget(btnHelp); + connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); + + auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout->addSpacerItem(spacer); + + m_killButton = new QPushButton(); + horizontalLayout->addWidget(m_killButton); + connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); + + m_launchOfflineButton = new QPushButton(); + horizontalLayout->addWidget(m_launchOfflineButton); + m_launchOfflineButton->setText(tr("Launch Offline")); + updateLaunchButtons(); + connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); + + m_closeButton = new QPushButton(); + m_closeButton->setText(tr("Close")); + horizontalLayout->addWidget(m_closeButton); + connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); + + m_container->addButtons(horizontalLayout); + } + + // restore window state + { + auto base64State = MMC->settings()->get("ConsoleWindowState").toByteArray(); + restoreState(QByteArray::fromBase64(base64State)); + auto base64Geometry = MMC->settings()->get("ConsoleWindowGeometry").toByteArray(); + restoreGeometry(QByteArray::fromBase64(base64Geometry)); + } + + // set up instance and launch process recognition + { + auto launchTask = m_instance->getLaunchTask(); + on_InstanceLaunchTask_changed(launchTask); + connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::on_InstanceLaunchTask_changed); + connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::on_RunningState_changed); + } + + // set up instance destruction detection + { + connect(m_instance.get(), &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged); + } + show(); } void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus) { - if(newStatus == BaseInstance::Status::Gone) - { - m_doNotSave = true; - close(); - } + if(newStatus == BaseInstance::Status::Gone) + { + m_doNotSave = true; + close(); + } } void InstanceWindow::updateLaunchButtons() { - if(m_instance->isRunning()) - { - m_launchOfflineButton->setEnabled(false); - m_killButton->setText(tr("Kill")); - m_killButton->setToolTip(tr("Kill the running instance")); - } - else if(!m_instance->canLaunch()) - { - m_launchOfflineButton->setEnabled(false); - m_killButton->setText(tr("Launch")); - m_killButton->setToolTip(tr("Launch the instance")); - m_killButton->setEnabled(false); - } - else - { - m_launchOfflineButton->setEnabled(true); - m_killButton->setText(tr("Launch")); - m_killButton->setToolTip(tr("Launch the instance")); - } + if(m_instance->isRunning()) + { + m_launchOfflineButton->setEnabled(false); + m_killButton->setText(tr("Kill")); + m_killButton->setToolTip(tr("Kill the running instance")); + } + else if(!m_instance->canLaunch()) + { + m_launchOfflineButton->setEnabled(false); + m_killButton->setText(tr("Launch")); + m_killButton->setToolTip(tr("Launch the instance")); + m_killButton->setEnabled(false); + } + else + { + m_launchOfflineButton->setEnabled(true); + m_killButton->setText(tr("Launch")); + m_killButton->setToolTip(tr("Launch the instance")); + } } void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() { - MMC->launch(m_instance, false, nullptr); + MMC->launch(m_instance, false, nullptr); } -void InstanceWindow::on_InstanceLaunchTask_changed(std::shared_ptr proc) +void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr proc) { - m_proc = proc; + m_proc = proc; } -void InstanceWindow::on_RunningState_changed(bool) +void InstanceWindow::on_RunningState_changed(bool running) { - updateLaunchButtons(); - m_container->refreshContainer(); + updateLaunchButtons(); + m_container->refreshContainer(); + if(running) { + selectPage("log"); + } } void InstanceWindow::on_closeButton_clicked() { - close(); + close(); } void InstanceWindow::closeEvent(QCloseEvent *event) { - bool proceed = true; - if(!m_doNotSave) - { - proceed &= m_container->prepareToClose(); - } - - if(!proceed) - { - return; - } - - MMC->settings()->set("ConsoleWindowState", saveState().toBase64()); - MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64()); - emit isClosing(); - event->accept(); + bool proceed = true; + if(!m_doNotSave) + { + proceed &= m_container->prepareToClose(); + } + + if(!proceed) + { + return; + } + + MMC->settings()->set("ConsoleWindowState", saveState().toBase64()); + MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64()); + emit isClosing(); + event->accept(); } bool InstanceWindow::saveAll() { - return m_container->prepareToClose(); + return m_container->saveAll(); } void InstanceWindow::on_btnKillMinecraft_clicked() { - if(m_instance->isRunning()) - { - MMC->kill(m_instance); - } - else - { - MMC->launch(m_instance, true, nullptr); - } + if(m_instance->isRunning()) + { + MMC->kill(m_instance); + } + else + { + MMC->launch(m_instance, true, nullptr); + } } QString InstanceWindow::instanceId() { - return m_instance->id(); + return m_instance->id(); } bool InstanceWindow::selectPage(QString pageId) { - return m_container->selectPage(pageId); + return m_container->selectPage(pageId); } void InstanceWindow::refreshContainer() { - m_container->refreshContainer(); + m_container->refreshContainer(); } InstanceWindow::~InstanceWindow() @@ -217,10 +221,10 @@ InstanceWindow::~InstanceWindow() bool InstanceWindow::requestClose() { - if(m_container->prepareToClose()) - { - close(); - return true; - } - return false; + if(m_container->prepareToClose()) + { + close(); + return true; + } + return false; } diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h index 2b08644e..e5bd4464 100644 --- a/application/InstanceWindow.h +++ b/application/InstanceWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,48 +26,48 @@ class QPushButton; class PageContainer; class InstanceWindow : public QMainWindow, public BasePageContainer { - Q_OBJECT + Q_OBJECT public: - explicit InstanceWindow(InstancePtr proc, QWidget *parent = 0); - virtual ~InstanceWindow(); + explicit InstanceWindow(InstancePtr proc, QWidget *parent = 0); + virtual ~InstanceWindow(); - bool selectPage(QString pageId) override; - void refreshContainer() override; + bool selectPage(QString pageId) override; + void refreshContainer() override; - QString instanceId(); + QString instanceId(); - // save all settings and changes (prepare for launch) - bool saveAll(); + // save all settings and changes (prepare for launch) + bool saveAll(); - // request closing the window (from a page) - bool requestClose() override; + // request closing the window (from a page) + bool requestClose() override; signals: - void isClosing(); + void isClosing(); private slots: - void on_closeButton_clicked(); - void on_btnKillMinecraft_clicked(); - void on_btnLaunchMinecraftOffline_clicked(); + void on_closeButton_clicked(); + void on_btnKillMinecraft_clicked(); + void on_btnLaunchMinecraftOffline_clicked(); - void on_InstanceLaunchTask_changed(std::shared_ptr proc); - void on_RunningState_changed(bool running); - void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); + void on_InstanceLaunchTask_changed(shared_qobject_ptr proc); + void on_RunningState_changed(bool running); + void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); protected: - void closeEvent(QCloseEvent *) override; + void closeEvent(QCloseEvent *) override; private: - void updateLaunchButtons(); + void updateLaunchButtons(); private: - std::shared_ptr m_proc; - InstancePtr m_instance; - bool m_doNotSave = false; - PageContainer *m_container = nullptr; - QPushButton *m_closeButton = nullptr; - QPushButton *m_killButton = nullptr; - QPushButton *m_launchOfflineButton = nullptr; + shared_qobject_ptr m_proc; + InstancePtr m_instance; + bool m_doNotSave = false; + PageContainer *m_container = nullptr; + QPushButton *m_closeButton = nullptr; + QPushButton *m_killButton = nullptr; + QPushButton *m_launchOfflineButton = nullptr; }; diff --git a/application/JavaCommon.cpp b/application/JavaCommon.cpp index 0008fc04..563dfb35 100644 --- a/application/JavaCommon.cpp +++ b/application/JavaCommon.cpp @@ -4,100 +4,100 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent) { - if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]")) - || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) - { - auto warnStr = QObject::tr( - "You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" or \"-Xms\").\n" - "There are dedicated boxes for these in the settings (Java tab, in the Memory group at the top).\n" - "This message will be displayed until you remove them from the JVM arguments."); - CustomMessageBox::selectable( - parent, QObject::tr("JVM arguments warning"), - warnStr, - QMessageBox::Warning)->exec(); - return false; - } - return true; + if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]")) + || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) + { + auto warnStr = QObject::tr( + "You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" or \"-Xms\").\n" + "There are dedicated boxes for these in the settings (Java tab, in the Memory group at the top).\n" + "This message will be displayed until you remove them from the JVM arguments."); + CustomMessageBox::selectable( + parent, QObject::tr("JVM arguments warning"), + warnStr, + QMessageBox::Warning)->exec(); + return false; + } + return true; } void JavaCommon::javaWasOk(QWidget *parent, JavaCheckResult result) { - QString text; - text += QObject::tr("Java test succeeded!
Platform reported: %1
Java version " - "reported: %2
").arg(result.realPlatform, result.javaVersion.toString()); - if (result.errorLog.size()) - { - auto htmlError = result.errorLog; - htmlError.replace('\n', "
"); - text += QObject::tr("
Warnings:
%1").arg(htmlError); - } - CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); + QString text; + text += QObject::tr("Java test succeeded!
Platform reported: %1
Java version " + "reported: %2
").arg(result.realPlatform, result.javaVersion.toString()); + if (result.errorLog.size()) + { + auto htmlError = result.errorLog; + htmlError.replace('\n', "
"); + text += QObject::tr("
Warnings:
%1").arg(htmlError); + } + CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); } void JavaCommon::javaArgsWereBad(QWidget *parent, JavaCheckResult result) { - auto htmlError = result.errorLog; - QString text; - htmlError.replace('\n', "
"); - text += QObject::tr("The specified java binary didn't work with the arguments you provided:
"); - text += QString("%1").arg(htmlError); - CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); + auto htmlError = result.errorLog; + QString text; + htmlError.replace('\n', "
"); + text += QObject::tr("The specified java binary didn't work with the arguments you provided:
"); + text += QString("%1").arg(htmlError); + CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result) { - QString text; - text += QObject::tr( - "The specified java binary didn't work.
You should use the auto-detect feature, " - "or set the path to the java executable.
"); - CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); + QString text; + text += QObject::tr( + "The specified java binary didn't work.
You should use the auto-detect feature, " + "or set the path to the java executable.
"); + CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } void JavaCommon::TestCheck::run() { - if (!JavaCommon::checkJVMArgs(m_args, m_parent)) - { - emit finished(); - return; - } - checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinished(JavaCheckResult))); - checker->m_path = m_path; - checker->performCheck(); + if (!JavaCommon::checkJVMArgs(m_args, m_parent)) + { + emit finished(); + return; + } + checker.reset(new JavaChecker()); + connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, + SLOT(checkFinished(JavaCheckResult))); + checker->m_path = m_path; + checker->performCheck(); } void JavaCommon::TestCheck::checkFinished(JavaCheckResult result) { - if (result.validity != JavaCheckResult::Validity::Valid) - { - javaBinaryWasBad(m_parent, result); - emit finished(); - return; - } - checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinishedWithArgs(JavaCheckResult))); - checker->m_path = m_path; - checker->m_args = m_args; - checker->m_minMem = m_minMem; - checker->m_maxMem = m_maxMem; - if (result.javaVersion.requiresPermGen()) - { - checker->m_permGen = m_permGen; - } - checker->performCheck(); + if (result.validity != JavaCheckResult::Validity::Valid) + { + javaBinaryWasBad(m_parent, result); + emit finished(); + return; + } + checker.reset(new JavaChecker()); + connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, + SLOT(checkFinishedWithArgs(JavaCheckResult))); + checker->m_path = m_path; + checker->m_args = m_args; + checker->m_minMem = m_minMem; + checker->m_maxMem = m_maxMem; + if (result.javaVersion.requiresPermGen()) + { + checker->m_permGen = m_permGen; + } + checker->performCheck(); } void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result) { - if (result.validity == JavaCheckResult::Validity::Valid) - { - javaWasOk(m_parent, result); - emit finished(); - return; - } - javaArgsWereBad(m_parent, result); - emit finished(); + if (result.validity == JavaCheckResult::Validity::Valid) + { + javaWasOk(m_parent, result); + emit finished(); + return; + } + javaArgsWereBad(m_parent, result); + emit finished(); } diff --git a/application/JavaCommon.h b/application/JavaCommon.h index 4e4cd633..ca98145c 100644 --- a/application/JavaCommon.h +++ b/application/JavaCommon.h @@ -8,41 +8,41 @@ class QWidget; */ namespace JavaCommon { - bool checkJVMArgs(QString args, QWidget *parent); - - // Show a dialog saying that the Java binary was not usable - void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); - // Show a dialog saying that the Java binary was not usable because of bad options - void javaArgsWereBad(QWidget *parent, JavaCheckResult result); - // Show a dialog saying that the Java binary was usable - void javaWasOk(QWidget *parent, JavaCheckResult result); - - class TestCheck : public QObject - { - Q_OBJECT - public: - TestCheck(QWidget *parent, QString path, QString args, int minMem, int maxMem, int permGen) - :m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen) - { - } - virtual ~TestCheck() {}; - - void run(); - - signals: - void finished(); - - private slots: - void checkFinished(JavaCheckResult result); - void checkFinishedWithArgs(JavaCheckResult result); - - private: - std::shared_ptr checker; - QWidget *m_parent = nullptr; - QString m_path; - QString m_args; - int m_minMem = 0; - int m_maxMem = 0; - int m_permGen = 64; - }; + bool checkJVMArgs(QString args, QWidget *parent); + + // Show a dialog saying that the Java binary was not usable + void javaBinaryWasBad(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was not usable because of bad options + void javaArgsWereBad(QWidget *parent, JavaCheckResult result); + // Show a dialog saying that the Java binary was usable + void javaWasOk(QWidget *parent, JavaCheckResult result); + + class TestCheck : public QObject + { + Q_OBJECT + public: + TestCheck(QWidget *parent, QString path, QString args, int minMem, int maxMem, int permGen) + :m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen) + { + } + virtual ~TestCheck() {}; + + void run(); + + signals: + void finished(); + + private slots: + void checkFinished(JavaCheckResult result); + void checkFinishedWithArgs(JavaCheckResult result); + + private: + std::shared_ptr checker; + QWidget *m_parent = nullptr; + QString m_path; + QString m_args; + int m_minMem = 0; + int m_maxMem = 0; + int m_permGen = 64; + }; } diff --git a/application/KonamiCode.cpp b/application/KonamiCode.cpp index 07c285bc..4c5af837 100644 --- a/application/KonamiCode.cpp +++ b/application/KonamiCode.cpp @@ -6,11 +6,13 @@ namespace { const std::array konamiCode = { - Qt::Key_Up, Qt::Key_Up, - Qt::Key_Down, Qt::Key_Down, - Qt::Key_Left, Qt::Key_Right, - Qt::Key_Left, Qt::Key_Right, - Qt::Key_B, Qt::Key_A + { + Qt::Key_Up, Qt::Key_Up, + Qt::Key_Down, Qt::Key_Down, + Qt::Key_Left, Qt::Key_Right, + Qt::Key_Left, Qt::Key_Right, + Qt::Key_B, Qt::Key_A + } }; } @@ -21,22 +23,22 @@ KonamiCode::KonamiCode(QObject* parent) : QObject(parent) void KonamiCode::input(QEvent* event) { - if( event->type() == QEvent::KeyPress ) - { - QKeyEvent *keyEvent = static_cast( event ); - auto key = Qt::Key(keyEvent->key()); - if(key == konamiCode[m_progress]) - { - m_progress ++; - } - else - { - m_progress = 0; - } - if(m_progress == konamiCode.size()) - { - m_progress = 0; - emit triggered(); - } - } + if( event->type() == QEvent::KeyPress ) + { + QKeyEvent *keyEvent = static_cast( event ); + auto key = Qt::Key(keyEvent->key()); + if(key == konamiCode[m_progress]) + { + m_progress ++; + } + else + { + m_progress = 0; + } + if(m_progress == konamiCode.size()) + { + m_progress = 0; + emit triggered(); + } + } } diff --git a/application/KonamiCode.h b/application/KonamiCode.h index ad17f8be..3d320ae7 100644 --- a/application/KonamiCode.h +++ b/application/KonamiCode.h @@ -4,14 +4,14 @@ class KonamiCode : public QObject { - Q_OBJECT + Q_OBJECT public: - KonamiCode(QObject *parent = 0); - void input(QEvent *event); + KonamiCode(QObject *parent = 0); + void input(QEvent *event); signals: - void triggered(); + void triggered(); private: - int m_progress = 0; + int m_progress = 0; }; diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp index 2e711933..e39048f1 100644 --- a/application/LaunchController.cpp +++ b/application/LaunchController.cpp @@ -9,7 +9,6 @@ #include "InstanceWindow.h" #include "BuildConfig.h" #include "JavaCommon.h" -#include "SettingsUI.h" #include #include #include @@ -23,291 +22,291 @@ LaunchController::LaunchController(QObject *parent) : Task(parent) void LaunchController::executeTask() { - if (!m_instance) - { - emitFailed(tr("No instance specified")); - return; - } + if (!m_instance) + { + emitFailed(tr("No instance specified")); + return; + } - login(); + login(); } // FIXME: minecraft specific void LaunchController::login() { - JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget); + JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget); - // Find an account to use. - std::shared_ptr accounts = MMC->accounts(); - MojangAccountPtr account = accounts->activeAccount(); - if (accounts->count() <= 0) - { - // Tell the user they need to log in at least one account in order to play. - auto reply = CustomMessageBox::selectable( - m_parentWidget, tr("No Accounts"), - tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " - "account logged in to MultiMC." - "Would you like to open the account manager to add an account now?"), - QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec(); + // Find an account to use. + std::shared_ptr accounts = MMC->accounts(); + MojangAccountPtr account = accounts->activeAccount(); + if (accounts->count() <= 0) + { + // Tell the user they need to log in at least one account in order to play. + auto reply = CustomMessageBox::selectable( + m_parentWidget, tr("No Accounts"), + tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " + "account logged in to MultiMC." + "Would you like to open the account manager to add an account now?"), + QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec(); - if (reply == QMessageBox::Yes) - { - // Open the account manager. - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), m_parentWidget, "accounts"); - } - } - else if (account.get() == nullptr) - { - // If no default account is set, ask the user which one to use. - ProfileSelectDialog selectDialog(tr("Which profile would you like to use?"), - ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget); + if (reply == QMessageBox::Yes) + { + // Open the account manager. + MMC->ShowGlobalSettings(m_parentWidget, "accounts"); + } + } + else if (account.get() == nullptr) + { + // If no default account is set, ask the user which one to use. + ProfileSelectDialog selectDialog(tr("Which profile would you like to use?"), + ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget); - selectDialog.exec(); + selectDialog.exec(); - // Launch the instance with the selected account. - account = selectDialog.selectedAccount(); + // Launch the instance with the selected account. + account = selectDialog.selectedAccount(); - // If the user said to use the account as default, do that. - if (selectDialog.useAsGlobalDefault() && account.get() != nullptr) - accounts->setActiveAccount(account->username()); - } + // If the user said to use the account as default, do that. + if (selectDialog.useAsGlobalDefault() && account.get() != nullptr) + accounts->setActiveAccount(account->username()); + } - // if no account is selected, we bail - if (!account.get()) - { - emitFailed(tr("No account selected for launch")); - return; - } + // if no account is selected, we bail + if (!account.get()) + { + emitFailed(tr("No account selected for launch")); + return; + } - // we try empty password first :) - QString password; - // we loop until the user succeeds in logging in or gives up - bool tryagain = true; - // the failure. the default failure. - const QString needLoginAgain = tr("Your account is currently not logged in. Please enter " - "your password to log in again."); - QString failReason = needLoginAgain; + // we try empty password first :) + QString password; + // we loop until the user succeeds in logging in or gives up + bool tryagain = true; + // the failure. the default failure. + const QString needLoginAgain = tr("Your account is currently not logged in. Please enter " + "your password to log in again."); + QString failReason = needLoginAgain; - while (tryagain) - { - m_session = std::make_shared(); - m_session->wants_online = m_online; - auto task = account->login(m_session, password); - if (task) - { - // We'll need to validate the access token to make sure the account - // is still logged in. - ProgressDialog progDialog(m_parentWidget); - if (m_online) - { - progDialog.setSkipButton(true, tr("Play Offline")); - } - progDialog.execWithTask(task.get()); - if (!task->wasSuccessful()) - { - auto failReasonNew = task->failReason(); - if(failReasonNew == "Invalid token.") - { - account->invalidateClientToken(); - failReason = needLoginAgain; - } - else failReason = failReasonNew; - } - } - switch (m_session->status) - { - case AuthSession::Undetermined: - { - qCritical() << "Received undetermined session status during login. Bye."; - tryagain = false; - emitFailed(tr("Received undetermined session status during login.")); - break; - } - case AuthSession::RequiresPassword: - { - EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField); - auto username = m_session->username; - auto chopN = [](QString toChop, int N) -> QString - { - if(toChop.size() > N) - { - auto left = toChop.left(N); - left += QString("\u25CF").repeated(toChop.size() - N); - return left; - } - return toChop; - }; + while (tryagain) + { + m_session = std::make_shared(); + m_session->wants_online = m_online; + auto task = account->login(m_session, password); + if (task) + { + // We'll need to validate the access token to make sure the account + // is still logged in. + ProgressDialog progDialog(m_parentWidget); + if (m_online) + { + progDialog.setSkipButton(true, tr("Play Offline")); + } + progDialog.execWithTask(task.get()); + if (!task->wasSuccessful()) + { + auto failReasonNew = task->failReason(); + if(failReasonNew == "Invalid token.") + { + account->invalidateClientToken(); + failReason = needLoginAgain; + } + else failReason = failReasonNew; + } + } + switch (m_session->status) + { + case AuthSession::Undetermined: + { + qCritical() << "Received undetermined session status during login. Bye."; + tryagain = false; + emitFailed(tr("Received undetermined session status during login.")); + break; + } + case AuthSession::RequiresPassword: + { + EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField); + auto username = m_session->username; + auto chopN = [](QString toChop, int N) -> QString + { + if(toChop.size() > N) + { + auto left = toChop.left(N); + left += QString("\u25CF").repeated(toChop.size() - N); + return left; + } + return toChop; + }; - if(username.contains('@')) - { - auto parts = username.split('@'); - auto mailbox = chopN(parts[0],3); - QString domain = chopN(parts[1], 3); - username = mailbox + '@' + domain; - } - passDialog.setUsername(username); - if (passDialog.exec() == QDialog::Accepted) - { - password = passDialog.password(); - } - else - { - tryagain = false; - } - break; - } - case AuthSession::PlayableOffline: - { - // we ask the user for a player name - bool ok = false; - QString usedname = m_session->player_name; - QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), - tr("Choose your offline mode player name."), - QLineEdit::Normal, m_session->player_name, &ok); - if (!ok) - { - tryagain = false; - break; - } - if (name.length()) - { - usedname = name; - } - m_session->MakeOffline(usedname); - // offline flavored game from here :3 - } - case AuthSession::PlayableOnline: - { - launchInstance(); - tryagain = false; - return; - } - } - } - emitFailed(tr("Failed to launch.")); + if(username.contains('@')) + { + auto parts = username.split('@'); + auto mailbox = chopN(parts[0],3); + QString domain = chopN(parts[1], 3); + username = mailbox + '@' + domain; + } + passDialog.setUsername(username); + if (passDialog.exec() == QDialog::Accepted) + { + password = passDialog.password(); + } + else + { + tryagain = false; + } + break; + } + case AuthSession::PlayableOffline: + { + // we ask the user for a player name + bool ok = false; + QString usedname = m_session->player_name; + QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), + tr("Choose your offline mode player name."), + QLineEdit::Normal, m_session->player_name, &ok); + if (!ok) + { + tryagain = false; + break; + } + if (name.length()) + { + usedname = name; + } + m_session->MakeOffline(usedname); + // offline flavored game from here :3 + } + case AuthSession::PlayableOnline: + { + launchInstance(); + tryagain = false; + return; + } + } + } + emitFailed(tr("Failed to launch.")); } void LaunchController::launchInstance() { - Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL"); - Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL"); + Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL"); + Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL"); - if(!m_instance->reloadSettings()) - { - QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile.")); - emitFailed(tr("Couldn't load the instance profile.")); - return; - } + if(!m_instance->reloadSettings()) + { + QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile.")); + emitFailed(tr("Couldn't load the instance profile.")); + return; + } - m_launcher = m_instance->createLaunchTask(m_session); - if (!m_launcher) - { - emitFailed(tr("Couldn't instantiate a launcher.")); - return; - } + m_launcher = m_instance->createLaunchTask(m_session); + if (!m_launcher) + { + emitFailed(tr("Couldn't instantiate a launcher.")); + return; + } - auto console = qobject_cast(m_parentWidget); - auto showConsole = m_instance->settings()->get("ShowConsole").toBool(); - if(!console && showConsole) - { - MMC->showInstanceWindow(m_instance); - } - connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); - connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded); - connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed); - connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested); + auto console = qobject_cast(m_parentWidget); + auto showConsole = m_instance->settings()->get("ShowConsole").toBool(); + if(!console && showConsole) + { + MMC->showInstanceWindow(m_instance); + } + connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); + connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded); + connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed); + connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested); - m_launcher->prependStep(std::make_shared(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); - m_launcher->start(); + m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); + m_launcher->start(); } void LaunchController::readyForLaunch() { - if (!m_profiler) - { - m_launcher->proceed(); - return; - } + if (!m_profiler) + { + m_launcher->proceed(); + return; + } - QString error; - if (!m_profiler->check(&error)) - { - m_launcher->abort(); - QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); - emitFailed("Profiler startup failed"); - return; - } - BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); + QString error; + if (!m_profiler->check(&error)) + { + m_launcher->abort(); + QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); + emitFailed("Profiler startup failed"); + return; + } + BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); - connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message) - { - QMessageBox msg; - msg.setText(tr("The game launch is delayed until you press the " - "button. This is the right time to setup the profiler, as the " - "profiler server is running now.\n\n%1").arg(message)); - msg.setWindowTitle(tr("Waiting")); - msg.setIcon(QMessageBox::Information); - msg.addButton(tr("Launch"), QMessageBox::AcceptRole); - msg.setModal(true); - msg.exec(); - m_launcher->proceed(); - }); - connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message) - { - QMessageBox msg; - msg.setText(tr("Couldn't start the profiler: %1").arg(message)); - msg.setWindowTitle(tr("Error")); - msg.setIcon(QMessageBox::Critical); - msg.addButton(QMessageBox::Ok); - msg.setModal(true); - msg.exec(); - m_launcher->abort(); - emitFailed("Profiler startup failed"); - }); - profilerInstance->beginProfiling(m_launcher); + connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message) + { + QMessageBox msg; + msg.setText(tr("The game launch is delayed until you press the " + "button. This is the right time to setup the profiler, as the " + "profiler server is running now.\n\n%1").arg(message)); + msg.setWindowTitle(tr("Waiting")); + msg.setIcon(QMessageBox::Information); + msg.addButton(tr("Launch"), QMessageBox::AcceptRole); + msg.setModal(true); + msg.exec(); + m_launcher->proceed(); + }); + connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message) + { + QMessageBox msg; + msg.setText(tr("Couldn't start the profiler: %1").arg(message)); + msg.setWindowTitle(tr("Error")); + msg.setIcon(QMessageBox::Critical); + msg.addButton(QMessageBox::Ok); + msg.setModal(true); + msg.exec(); + m_launcher->abort(); + emitFailed("Profiler startup failed"); + }); + profilerInstance->beginProfiling(m_launcher); } void LaunchController::onSucceeded() { - emitSucceeded(); + emitSucceeded(); } void LaunchController::onFailed(QString reason) { - if(m_instance->settings()->get("ShowConsoleOnError").toBool()) - { - MMC->showInstanceWindow(m_instance, "console"); - } - emitFailed(reason); + if(m_instance->settings()->get("ShowConsoleOnError").toBool()) + { + MMC->showInstanceWindow(m_instance, "console"); + } + emitFailed(reason); } void LaunchController::onProgressRequested(Task* task) { - ProgressDialog progDialog(m_parentWidget); - progDialog.setSkipButton(true, tr("Abort")); - m_launcher->proceed(); - progDialog.execWithTask(task); + ProgressDialog progDialog(m_parentWidget); + progDialog.setSkipButton(true, tr("Abort")); + m_launcher->proceed(); + progDialog.execWithTask(task); } bool LaunchController::abort() { - if(!m_launcher) - { - return true; - } - if(!m_launcher->canAbort()) - { - return false; - } - auto response = CustomMessageBox::selectable( - m_parentWidget, tr("Kill Minecraft?"), - tr("This can cause the instance to get corrupted and should only be used if Minecraft " - "is frozen for some reason"), - QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); - if (response == QMessageBox::Yes) - { - return m_launcher->abort(); - } - return false; + if(!m_launcher) + { + return true; + } + if(!m_launcher->canAbort()) + { + return false; + } + auto response = CustomMessageBox::selectable( + m_parentWidget, tr("Kill Minecraft?"), + tr("This can cause the instance to get corrupted and should only be used if Minecraft " + "is frozen for some reason"), + QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); + if (response == QMessageBox::Yes) + { + return m_launcher->abort(); + } + return false; } diff --git a/application/LaunchController.h b/application/LaunchController.h index 84c65743..1d879028 100644 --- a/application/LaunchController.h +++ b/application/LaunchController.h @@ -6,56 +6,56 @@ class InstanceWindow; class LaunchController: public Task { - Q_OBJECT + Q_OBJECT public: - void executeTask() override; + void executeTask() override; - LaunchController(QObject * parent = nullptr); - virtual ~LaunchController(){}; + LaunchController(QObject * parent = nullptr); + virtual ~LaunchController(){}; - void setInstance(InstancePtr instance) - { - m_instance = instance; - } - InstancePtr instance() - { - return m_instance; - } - void setOnline(bool online) - { - m_online = online; - } - void setProfiler(BaseProfilerFactory *profiler) - { - m_profiler = profiler; - } - void setParentWidget(QWidget * widget) - { - m_parentWidget = widget; - } - QString id() - { - return m_instance->id(); - } - bool abort() override; + void setInstance(InstancePtr instance) + { + m_instance = instance; + } + InstancePtr instance() + { + return m_instance; + } + void setOnline(bool online) + { + m_online = online; + } + void setProfiler(BaseProfilerFactory *profiler) + { + m_profiler = profiler; + } + void setParentWidget(QWidget * widget) + { + m_parentWidget = widget; + } + QString id() + { + return m_instance->id(); + } + bool abort() override; private: - void login(); - void launchInstance(); + void login(); + void launchInstance(); private slots: - void readyForLaunch(); + void readyForLaunch(); - void onSucceeded(); - void onFailed(QString reason); - void onProgressRequested(Task *task); + void onSucceeded(); + void onFailed(QString reason); + void onProgressRequested(Task *task); private: - BaseProfilerFactory *m_profiler = nullptr; - bool m_online = true; - InstancePtr m_instance; - QWidget * m_parentWidget = nullptr; - InstanceWindow *m_console = nullptr; - AuthSessionPtr m_session; - std::shared_ptr m_launcher; + BaseProfilerFactory *m_profiler = nullptr; + bool m_online = true; + InstancePtr m_instance; + QWidget * m_parentWidget = nullptr; + InstanceWindow *m_console = nullptr; + AuthSessionPtr m_session; + shared_qobject_ptr m_launcher; }; diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index 60742412..1db09c6c 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Andrew Okin * Peterix @@ -70,7 +70,6 @@ #include "InstanceProxyModel.h" #include "JavaCommon.h" #include "LaunchController.h" -#include "SettingsUI.h" #include "groupview/GroupView.h" #include "groupview/InstanceDelegate.h" #include "widgets/LabeledToolButton.h" @@ -86,7 +85,6 @@ #include "dialogs/EditAccountDialog.h" #include "dialogs/NotificationDialog.h" #include "dialogs/ExportInstanceDialog.h" -#include #include #include "UpdateController.h" #include "KonamiCode.h" @@ -97,42 +95,42 @@ template class Translated { public: - Translated(){} - Translated(QWidget *parent) - { - m_contained = new T(parent); - } - void setTooltipId(const char * tooltip) - { - m_tooltip = tooltip; - } - void setTextId(const char * text) - { - m_text = text; - } - operator T*() - { - return m_contained; - } - T * operator->() - { - return m_contained; - } - void retranslate() - { - if(m_text) - { - m_contained->setText(QApplication::translate("MainWindow", m_text)); - } - if(m_tooltip) - { - m_contained->setToolTip(QApplication::translate("MainWindow", m_tooltip)); - } - } + Translated(){} + Translated(QWidget *parent) + { + m_contained = new T(parent); + } + void setTooltipId(const char * tooltip) + { + m_tooltip = tooltip; + } + void setTextId(const char * text) + { + m_text = text; + } + operator T*() + { + return m_contained; + } + T * operator->() + { + return m_contained; + } + void retranslate() + { + if(m_text) + { + m_contained->setText(QApplication::translate("MainWindow", m_text)); + } + if(m_tooltip) + { + m_contained->setToolTip(QApplication::translate("MainWindow", m_tooltip)); + } + } private: - T * m_contained = nullptr; - const char * m_text = nullptr; - const char * m_tooltip = nullptr; + T * m_contained = nullptr; + const char * m_text = nullptr; + const char * m_tooltip = nullptr; }; using TranslatedAction = Translated; using TranslatedToolButton = Translated; @@ -140,889 +138,944 @@ using TranslatedToolButton = Translated; class TranslatedToolbar { public: - TranslatedToolbar(){} - TranslatedToolbar(QWidget *parent) - { - m_contained = new QToolBar(parent); - } - void setWindowTitleId(const char * title) - { - m_title = title; - } - operator QToolBar*() - { - return m_contained; - } - QToolBar * operator->() - { - return m_contained; - } - void retranslate() - { - if(m_title) - { - m_contained->setWindowTitle(QApplication::translate("MainWindow", m_title)); - } - } + TranslatedToolbar(){} + TranslatedToolbar(QWidget *parent) + { + m_contained = new QToolBar(parent); + } + void setWindowTitleId(const char * title) + { + m_title = title; + } + operator QToolBar*() + { + return m_contained; + } + QToolBar * operator->() + { + return m_contained; + } + void retranslate() + { + if(m_title) + { + m_contained->setWindowTitle(QApplication::translate("MainWindow", m_title)); + } + } private: - QToolBar * m_contained = nullptr; - const char * m_title = nullptr; + QToolBar * m_contained = nullptr; + const char * m_title = nullptr; }; class MainWindow::Ui { public: - TranslatedAction actionAddInstance; - //TranslatedAction actionRefresh; - TranslatedAction actionCheckUpdate; - TranslatedAction actionSettings; - TranslatedAction actionPatreon; - TranslatedAction actionMoreNews; - TranslatedAction actionManageAccounts; - TranslatedAction actionLaunchInstance; - TranslatedAction actionRenameInstance; - TranslatedAction actionChangeInstGroup; - TranslatedAction actionChangeInstIcon; - TranslatedAction actionEditInstNotes; - TranslatedAction actionEditInstance; - TranslatedAction actionWorlds; - TranslatedAction actionViewSelectedInstFolder; - TranslatedAction actionDeleteInstance; - TranslatedAction actionConfig_Folder; - TranslatedAction actionCAT; - TranslatedAction actionCopyInstance; - TranslatedAction actionLaunchInstanceOffline; - TranslatedAction actionScreenshots; - TranslatedAction actionExportInstance; - QVector all_actions; - - LabeledToolButton *renameButton = nullptr; - LabeledToolButton *changeIconButton = nullptr; - - QMenu * foldersMenu = nullptr; - TranslatedToolButton foldersMenuButton; - TranslatedAction actionViewInstanceFolder; - TranslatedAction actionViewCentralModsFolder; - - QMenu * helpMenu = nullptr; - TranslatedToolButton helpMenuButton; - TranslatedAction actionReportBug; - TranslatedAction actionDISCORD; - TranslatedAction actionREDDIT; - TranslatedAction actionAbout; - - QVector all_toolbuttons; - - QWidget *centralWidget = nullptr; - QHBoxLayout *horizontalLayout = nullptr; - QStatusBar *statusBar = nullptr; - - TranslatedToolbar mainToolBar; - TranslatedToolbar instanceToolBar; - TranslatedToolbar newsToolBar; - QVector all_toolbars; - bool m_kill = false; - - void updateLaunchAction() - { - if(m_kill) - { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Kill")); - actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); - } - else - { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch")); - actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); - } - actionLaunchInstance.retranslate(); - } - void setLaunchAction(bool kill) - { - m_kill = kill; - updateLaunchAction(); - } - - void createMainToolbar(QMainWindow *MainWindow) - { - mainToolBar = TranslatedToolbar(MainWindow); - mainToolBar->setObjectName(QStringLiteral("mainToolBar")); - mainToolBar->setMovable(false); - mainToolBar->setAllowedAreas(Qt::TopToolBarArea); - mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - mainToolBar->setFloatable(false); - mainToolBar.setWindowTitleId(QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar")); - - actionAddInstance = TranslatedAction(MainWindow); - actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); - actionAddInstance->setIcon(MMC->getThemedIcon("new")); - actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instance")); - actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance.")); - all_actions.append(&actionAddInstance); - mainToolBar->addAction(actionAddInstance); - - mainToolBar->addSeparator(); - - foldersMenu = new QMenu(MainWindow); - foldersMenu->setToolTipsVisible(true); - - actionViewInstanceFolder = TranslatedAction(MainWindow); - actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder")); - actionViewInstanceFolder->setIcon(MMC->getThemedIcon("viewfolder")); - actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder")); - actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance folder in a file browser.")); - all_actions.append(&actionViewInstanceFolder); - foldersMenu->addAction(actionViewInstanceFolder); - - actionViewCentralModsFolder = TranslatedAction(MainWindow); - actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder")); - actionViewCentralModsFolder->setIcon(MMC->getThemedIcon("centralmods")); - actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder")); - actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the central mods folder in a file browser.")); - all_actions.append(&actionViewCentralModsFolder); - foldersMenu->addAction(actionViewCentralModsFolder); - - foldersMenuButton = TranslatedToolButton(MainWindow); - foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Folders")); - foldersMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open one of the folders shared between instances.")); - foldersMenuButton->setMenu(foldersMenu); - foldersMenuButton->setPopupMode(QToolButton::InstantPopup); - foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder")); - all_toolbuttons.append(&foldersMenuButton); - QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow); - foldersButtonAction->setDefaultWidget(foldersMenuButton); - mainToolBar->addAction(foldersButtonAction); - - actionSettings = TranslatedAction(MainWindow); - actionSettings->setObjectName(QStringLiteral("actionSettings")); - actionSettings->setIcon(MMC->getThemedIcon("settings")); - actionSettings->setMenuRole(QAction::PreferencesRole); - actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings")); - actionSettings.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change settings.")); - all_actions.append(&actionSettings); - mainToolBar->addAction(actionSettings); - - helpMenu = new QMenu(MainWindow); - helpMenu->setToolTipsVisible(true); - - actionReportBug = TranslatedAction(MainWindow); - actionReportBug->setObjectName(QStringLiteral("actionReportBug")); - actionReportBug->setIcon(MMC->getThemedIcon("bug")); - actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug")); - actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC.")); - all_actions.append(&actionReportBug); - helpMenu->addAction(actionReportBug); - - actionDISCORD = TranslatedAction(MainWindow); - actionDISCORD->setObjectName(QStringLiteral("actionDISCORD")); - actionDISCORD->setIcon(MMC->getThemedIcon("discord")); - actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord")); - actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat.")); - all_actions.append(&actionDISCORD); - helpMenu->addAction(actionDISCORD); - - actionREDDIT = TranslatedAction(MainWindow); - actionREDDIT->setObjectName(QStringLiteral("actionREDDIT")); - actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien")); - actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit")); - actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit.")); - all_actions.append(&actionREDDIT); - helpMenu->addAction(actionREDDIT); - - actionAbout = TranslatedAction(MainWindow); - actionAbout->setObjectName(QStringLiteral("actionAbout")); - actionAbout->setIcon(MMC->getThemedIcon("about")); - actionAbout->setMenuRole(QAction::AboutRole); - actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "About MultiMC")); - actionAbout.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View information about MultiMC.")); - all_actions.append(&actionAbout); - helpMenu->addAction(actionAbout); - - helpMenuButton = TranslatedToolButton(MainWindow); - helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help")); - helpMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Get help with MultiMC or Minecraft.")); - helpMenuButton->setMenu(helpMenu); - helpMenuButton->setPopupMode(QToolButton::InstantPopup); - helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - helpMenuButton->setIcon(MMC->getThemedIcon("help")); - all_toolbuttons.append(&helpMenuButton); - QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow); - helpButtonAction->setDefaultWidget(helpMenuButton); - mainToolBar->addAction(helpButtonAction); - - if(BuildConfig.UPDATER_ENABLED) - { - actionCheckUpdate = TranslatedAction(MainWindow); - actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate")); - actionCheckUpdate->setIcon(MMC->getThemedIcon("checkupdate")); - actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Update")); - actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Check for new updates for MultiMC.")); - all_actions.append(&actionCheckUpdate); - mainToolBar->addAction(actionCheckUpdate); - } - - mainToolBar->addSeparator(); - - actionPatreon = TranslatedAction(MainWindow); - actionPatreon->setObjectName(QStringLiteral("actionPatreon")); - actionPatreon->setIcon(MMC->getThemedIcon("patreon")); - actionPatreon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Support MultiMC")); - actionPatreon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC Patreon page.")); - all_actions.append(&actionPatreon); - mainToolBar->addAction(actionPatreon); - - actionCAT = TranslatedAction(MainWindow); - actionCAT->setObjectName(QStringLiteral("actionCAT")); - actionCAT->setCheckable(true); - actionCAT->setIcon(MMC->getThemedIcon("cat")); - actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Meow")); - actionCAT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "It's a fluffy kitty :3")); - actionCAT->setPriority(QAction::LowPriority); - all_actions.append(&actionCAT); - mainToolBar->addAction(actionCAT); - - // profile menu and its actions - actionManageAccounts = TranslatedAction(MainWindow); - actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts")); - actionManageAccounts.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Accounts")); - // FIXME: no tooltip! - actionManageAccounts->setCheckable(false); - actionManageAccounts->setIcon(MMC->getThemedIcon("accounts")); - all_actions.append(&actionManageAccounts); - - all_toolbars.append(&mainToolBar); - MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); - } - - void createStatusBar(QMainWindow *MainWindow) - { - statusBar = new QStatusBar(MainWindow); - statusBar->setObjectName(QStringLiteral("statusBar")); - MainWindow->setStatusBar(statusBar); - } - - void createNewsToolbar(QMainWindow *MainWindow) - { - newsToolBar = TranslatedToolbar(MainWindow); - newsToolBar->setObjectName(QStringLiteral("newsToolBar")); - newsToolBar->setMovable(false); - newsToolBar->setAllowedAreas(Qt::BottomToolBarArea); - newsToolBar->setIconSize(QSize(16, 16)); - newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - newsToolBar->setFloatable(false); - newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar")); - - actionMoreNews = TranslatedAction(MainWindow); - actionMoreNews->setObjectName(QStringLiteral("actionMoreNews")); - actionMoreNews->setIcon(MMC->getThemedIcon("news")); - actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news...")); - actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC development blog to read more news about MultiMC.")); - all_actions.append(&actionMoreNews); - newsToolBar->addAction(actionMoreNews); - - all_toolbars.append(&newsToolBar); - MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); - } - - void createInstanceToolbar(QMainWindow *MainWindow) - { - instanceToolBar = TranslatedToolbar(MainWindow); - instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); - // disabled until we have an instance selected - instanceToolBar->setEnabled(false); - instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); - instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); - instanceToolBar->setFloatable(false); - instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); - - // NOTE: not added to toolbar, but used for instance context menu (right click) - actionChangeInstIcon = TranslatedAction(MainWindow); - actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon")); - actionChangeInstIcon->setIcon(QIcon(":/icons/instances/infinity")); - actionChangeInstIcon->setIconVisibleInMenu(true); - actionChangeInstIcon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Icon")); - actionChangeInstIcon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's icon.")); - all_actions.append(&actionChangeInstIcon); - - changeIconButton = new LabeledToolButton(MainWindow); - changeIconButton->setObjectName(QStringLiteral("changeIconButton")); - changeIconButton->setIcon(MMC->getThemedIcon("news")); - changeIconButton->setToolTip(actionChangeInstIcon->toolTip()); - changeIconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - instanceToolBar->addWidget(changeIconButton); - - // NOTE: not added to toolbar, but used for instance context menu (right click) - actionRenameInstance = TranslatedAction(MainWindow); - actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance")); - actionRenameInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Rename")); - actionRenameInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Rename the selected instance.")); - all_actions.append(&actionRenameInstance); - - // the rename label is inside the rename tool button - renameButton = new LabeledToolButton(MainWindow); - renameButton->setObjectName(QStringLiteral("renameButton")); - renameButton->setToolTip(actionRenameInstance->toolTip()); - renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - instanceToolBar->addWidget(renameButton); - - instanceToolBar->addSeparator(); - - actionLaunchInstance = TranslatedAction(MainWindow); - actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); - all_actions.append(&actionLaunchInstance); - instanceToolBar->addAction(actionLaunchInstance); - - actionLaunchInstanceOffline = TranslatedAction(MainWindow); - actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline")); - actionLaunchInstanceOffline.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch Offline")); - actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); - all_actions.append(&actionLaunchInstanceOffline); - instanceToolBar->addAction(actionLaunchInstanceOffline); - - instanceToolBar->addSeparator(); - - actionEditInstance = TranslatedAction(MainWindow); - actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); - actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Instance")); - actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions.")); - all_actions.append(&actionEditInstance); - instanceToolBar->addAction(actionEditInstance); - - actionEditInstNotes = TranslatedAction(MainWindow); - actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes")); - actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Notes")); - actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Edit the notes for the selected instance.")); - all_actions.append(&actionEditInstNotes); - instanceToolBar->addAction(actionEditInstNotes); - - actionWorlds = TranslatedAction(MainWindow); - actionWorlds->setObjectName(QStringLiteral("actionWorlds")); - actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Worlds")); - actionWorlds.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the worlds of this instance.")); - all_actions.append(&actionWorlds); - instanceToolBar->addAction(actionWorlds); - - actionScreenshots = TranslatedAction(MainWindow); - actionScreenshots->setObjectName(QStringLiteral("actionScreenshots")); - actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Screenshots")); - actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View and upload screenshots for this instance.")); - all_actions.append(&actionScreenshots); - instanceToolBar->addAction(actionScreenshots); - - actionChangeInstGroup = TranslatedAction(MainWindow); - actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup")); - actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Group")); - actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's group.")); - all_actions.append(&actionChangeInstGroup); - instanceToolBar->addAction(actionChangeInstGroup); - - instanceToolBar->addSeparator(); - - actionViewSelectedInstFolder = TranslatedAction(MainWindow); - actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); - actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Instance Folder")); - actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser.")); - all_actions.append(&actionViewSelectedInstFolder); - instanceToolBar->addAction(actionViewSelectedInstFolder); - - actionConfig_Folder = TranslatedAction(MainWindow); - actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder")); - actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Config Folder")); - actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance's config folder.")); - all_actions.append(&actionConfig_Folder); - instanceToolBar->addAction(actionConfig_Folder); - - instanceToolBar->addSeparator(); - - actionExportInstance = TranslatedAction(MainWindow); - actionExportInstance->setObjectName(QStringLiteral("actionExportInstance")); - actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Export Instance")); - // FIXME: missing tooltip - all_actions.append(&actionExportInstance); - instanceToolBar->addAction(actionExportInstance); - - actionDeleteInstance = TranslatedAction(MainWindow); - actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); - actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Delete")); - actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); - all_actions.append(&actionDeleteInstance); - instanceToolBar->addAction(actionDeleteInstance); - - actionCopyInstance = TranslatedAction(MainWindow); - actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); - actionCopyInstance->setIcon(MMC->getThemedIcon("copy")); - actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Copy Instance")); - actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance.")); - all_actions.append(&actionCopyInstance); - instanceToolBar->addAction(actionCopyInstance); - - all_toolbars.append(&instanceToolBar); - MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); - } - - void setupUi(QMainWindow *MainWindow) - { - if (MainWindow->objectName().isEmpty()) - { - MainWindow->setObjectName(QStringLiteral("MainWindow")); - } - MainWindow->resize(694, 563); - MainWindow->setWindowIcon(MMC->getThemedIcon("logo")); - MainWindow->setWindowTitle("MultiMC 5"); - - createMainToolbar(MainWindow); - - centralWidget = new QWidget(MainWindow); - centralWidget->setObjectName(QStringLiteral("centralWidget")); - horizontalLayout = new QHBoxLayout(centralWidget); - horizontalLayout->setSpacing(0); - horizontalLayout->setContentsMargins(11, 11, 11, 11); - horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint); - horizontalLayout->setContentsMargins(0, 0, 0, 0); - MainWindow->setCentralWidget(centralWidget); - - createStatusBar(MainWindow); - createNewsToolbar(MainWindow); - createInstanceToolbar(MainWindow); - - retranslateUi(MainWindow); - - QMetaObject::connectSlotsByName(MainWindow); - } // setupUi - - void retranslateUi(QMainWindow *MainWindow) - { - QString winTitle = tr("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString()); - if (!BuildConfig.BUILD_PLATFORM.isEmpty()) - { - winTitle += tr(" on %1", "on platform, as in operating system").arg(BuildConfig.BUILD_PLATFORM); - } - MainWindow->setWindowTitle(winTitle); - // all the actions - for(auto * item: all_actions) - { - item->retranslate(); - } - for(auto * item: all_toolbars) - { - item->retranslate(); - } - for(auto * item: all_toolbuttons) - { - item->retranslate(); - } - // submenu buttons - foldersMenuButton->setText(tr("Folders")); - helpMenuButton->setText(tr("Help")); - } // retranslateUi + TranslatedAction actionAddInstance; + //TranslatedAction actionRefresh; + TranslatedAction actionCheckUpdate; + TranslatedAction actionSettings; + TranslatedAction actionPatreon; + TranslatedAction actionMoreNews; + TranslatedAction actionManageAccounts; + TranslatedAction actionLaunchInstance; + TranslatedAction actionRenameInstance; + TranslatedAction actionChangeInstGroup; + TranslatedAction actionChangeInstIcon; + TranslatedAction actionEditInstNotes; + TranslatedAction actionEditInstance; + TranslatedAction actionWorlds; + TranslatedAction actionViewSelectedInstFolder; + TranslatedAction actionViewSelectedMCFolder; + TranslatedAction actionDeleteInstance; + TranslatedAction actionConfig_Folder; + TranslatedAction actionCAT; + TranslatedAction actionCopyInstance; + TranslatedAction actionLaunchInstanceOffline; + TranslatedAction actionScreenshots; + TranslatedAction actionExportInstance; + QVector all_actions; + + LabeledToolButton *renameButton = nullptr; + LabeledToolButton *changeIconButton = nullptr; + + QMenu * foldersMenu = nullptr; + TranslatedToolButton foldersMenuButton; + TranslatedAction actionViewInstanceFolder; + TranslatedAction actionViewCentralModsFolder; + + QMenu * helpMenu = nullptr; + TranslatedToolButton helpMenuButton; + TranslatedAction actionReportBug; + TranslatedAction actionDISCORD; + TranslatedAction actionREDDIT; + TranslatedAction actionAbout; + + QVector all_toolbuttons; + + QWidget *centralWidget = nullptr; + QHBoxLayout *horizontalLayout = nullptr; + QStatusBar *statusBar = nullptr; + + TranslatedToolbar mainToolBar; + TranslatedToolbar instanceToolBar; + TranslatedToolbar newsToolBar; + QVector all_toolbars; + bool m_kill = false; + + void updateLaunchAction() + { + if(m_kill) + { + actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Kill")); + actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); + } + else + { + actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch")); + actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); + } + actionLaunchInstance.retranslate(); + } + void setLaunchAction(bool kill) + { + m_kill = kill; + updateLaunchAction(); + } + + void createMainToolbar(QMainWindow *MainWindow) + { + mainToolBar = TranslatedToolbar(MainWindow); + mainToolBar->setObjectName(QStringLiteral("mainToolBar")); + mainToolBar->setMovable(false); + mainToolBar->setAllowedAreas(Qt::TopToolBarArea); + mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + mainToolBar->setFloatable(false); + mainToolBar.setWindowTitleId(QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar")); + + actionAddInstance = TranslatedAction(MainWindow); + actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); + actionAddInstance->setIcon(MMC->getThemedIcon("new")); + actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instance")); + actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance.")); + all_actions.append(&actionAddInstance); + mainToolBar->addAction(actionAddInstance); + + mainToolBar->addSeparator(); + + foldersMenu = new QMenu(MainWindow); + foldersMenu->setToolTipsVisible(true); + + actionViewInstanceFolder = TranslatedAction(MainWindow); + actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder")); + actionViewInstanceFolder->setIcon(MMC->getThemedIcon("viewfolder")); + actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder")); + actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance folder in a file browser.")); + all_actions.append(&actionViewInstanceFolder); + foldersMenu->addAction(actionViewInstanceFolder); + + actionViewCentralModsFolder = TranslatedAction(MainWindow); + actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder")); + actionViewCentralModsFolder->setIcon(MMC->getThemedIcon("centralmods")); + actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder")); + actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the central mods folder in a file browser.")); + all_actions.append(&actionViewCentralModsFolder); + foldersMenu->addAction(actionViewCentralModsFolder); + + foldersMenuButton = TranslatedToolButton(MainWindow); + foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Folders")); + foldersMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open one of the folders shared between instances.")); + foldersMenuButton->setMenu(foldersMenu); + foldersMenuButton->setPopupMode(QToolButton::InstantPopup); + foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder")); + foldersMenuButton->setFocusPolicy(Qt::NoFocus); + all_toolbuttons.append(&foldersMenuButton); + QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow); + foldersButtonAction->setDefaultWidget(foldersMenuButton); + mainToolBar->addAction(foldersButtonAction); + + actionSettings = TranslatedAction(MainWindow); + actionSettings->setObjectName(QStringLiteral("actionSettings")); + actionSettings->setIcon(MMC->getThemedIcon("settings")); + actionSettings->setMenuRole(QAction::PreferencesRole); + actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings")); + actionSettings.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change settings.")); + all_actions.append(&actionSettings); + mainToolBar->addAction(actionSettings); + + helpMenu = new QMenu(MainWindow); + helpMenu->setToolTipsVisible(true); + + actionReportBug = TranslatedAction(MainWindow); + actionReportBug->setObjectName(QStringLiteral("actionReportBug")); + actionReportBug->setIcon(MMC->getThemedIcon("bug")); + actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug")); + actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC.")); + all_actions.append(&actionReportBug); + helpMenu->addAction(actionReportBug); + + actionDISCORD = TranslatedAction(MainWindow); + actionDISCORD->setObjectName(QStringLiteral("actionDISCORD")); + actionDISCORD->setIcon(MMC->getThemedIcon("discord")); + actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord")); + actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat.")); + all_actions.append(&actionDISCORD); + helpMenu->addAction(actionDISCORD); + + actionREDDIT = TranslatedAction(MainWindow); + actionREDDIT->setObjectName(QStringLiteral("actionREDDIT")); + actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien")); + actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit")); + actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit.")); + all_actions.append(&actionREDDIT); + helpMenu->addAction(actionREDDIT); + + actionAbout = TranslatedAction(MainWindow); + actionAbout->setObjectName(QStringLiteral("actionAbout")); + actionAbout->setIcon(MMC->getThemedIcon("about")); + actionAbout->setMenuRole(QAction::AboutRole); + actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "About MultiMC")); + actionAbout.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View information about MultiMC.")); + all_actions.append(&actionAbout); + helpMenu->addAction(actionAbout); + + helpMenuButton = TranslatedToolButton(MainWindow); + helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help")); + helpMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Get help with MultiMC or Minecraft.")); + helpMenuButton->setMenu(helpMenu); + helpMenuButton->setPopupMode(QToolButton::InstantPopup); + helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + helpMenuButton->setIcon(MMC->getThemedIcon("help")); + helpMenuButton->setFocusPolicy(Qt::NoFocus); + all_toolbuttons.append(&helpMenuButton); + QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow); + helpButtonAction->setDefaultWidget(helpMenuButton); + mainToolBar->addAction(helpButtonAction); + + if(BuildConfig.UPDATER_ENABLED) + { + actionCheckUpdate = TranslatedAction(MainWindow); + actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate")); + actionCheckUpdate->setIcon(MMC->getThemedIcon("checkupdate")); + actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Update")); + actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Check for new updates for MultiMC.")); + all_actions.append(&actionCheckUpdate); + mainToolBar->addAction(actionCheckUpdate); + } + + mainToolBar->addSeparator(); + + actionPatreon = TranslatedAction(MainWindow); + actionPatreon->setObjectName(QStringLiteral("actionPatreon")); + actionPatreon->setIcon(MMC->getThemedIcon("patreon")); + actionPatreon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Support MultiMC")); + actionPatreon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC Patreon page.")); + all_actions.append(&actionPatreon); + mainToolBar->addAction(actionPatreon); + + actionCAT = TranslatedAction(MainWindow); + actionCAT->setObjectName(QStringLiteral("actionCAT")); + actionCAT->setCheckable(true); + actionCAT->setIcon(MMC->getThemedIcon("cat")); + actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Meow")); + actionCAT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "It's a fluffy kitty :3")); + actionCAT->setPriority(QAction::LowPriority); + all_actions.append(&actionCAT); + mainToolBar->addAction(actionCAT); + + // profile menu and its actions + actionManageAccounts = TranslatedAction(MainWindow); + actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts")); + actionManageAccounts.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Accounts")); + // FIXME: no tooltip! + actionManageAccounts->setCheckable(false); + actionManageAccounts->setIcon(MMC->getThemedIcon("accounts")); + all_actions.append(&actionManageAccounts); + + all_toolbars.append(&mainToolBar); + MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); + } + + void createStatusBar(QMainWindow *MainWindow) + { + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QStringLiteral("statusBar")); + MainWindow->setStatusBar(statusBar); + } + + void createNewsToolbar(QMainWindow *MainWindow) + { + newsToolBar = TranslatedToolbar(MainWindow); + newsToolBar->setObjectName(QStringLiteral("newsToolBar")); + newsToolBar->setMovable(false); + newsToolBar->setAllowedAreas(Qt::BottomToolBarArea); + newsToolBar->setIconSize(QSize(16, 16)); + newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + newsToolBar->setFloatable(false); + newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar")); + + actionMoreNews = TranslatedAction(MainWindow); + actionMoreNews->setObjectName(QStringLiteral("actionMoreNews")); + actionMoreNews->setIcon(MMC->getThemedIcon("news")); + actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news...")); + actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC development blog to read more news about MultiMC.")); + all_actions.append(&actionMoreNews); + newsToolBar->addAction(actionMoreNews); + + all_toolbars.append(&newsToolBar); + MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); + } + + void createInstanceToolbar(QMainWindow *MainWindow) + { + instanceToolBar = TranslatedToolbar(MainWindow); + instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); + // disabled until we have an instance selected + instanceToolBar->setEnabled(false); + instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); + instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); + instanceToolBar->setFloatable(false); + instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); + + // NOTE: not added to toolbar, but used for instance context menu (right click) + actionChangeInstIcon = TranslatedAction(MainWindow); + actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon")); + actionChangeInstIcon->setIcon(QIcon(":/icons/instances/infinity")); + actionChangeInstIcon->setIconVisibleInMenu(true); + actionChangeInstIcon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Icon")); + actionChangeInstIcon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's icon.")); + all_actions.append(&actionChangeInstIcon); + + changeIconButton = new LabeledToolButton(MainWindow); + changeIconButton->setObjectName(QStringLiteral("changeIconButton")); + changeIconButton->setIcon(MMC->getThemedIcon("news")); + changeIconButton->setToolTip(actionChangeInstIcon->toolTip()); + changeIconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + instanceToolBar->addWidget(changeIconButton); + + // NOTE: not added to toolbar, but used for instance context menu (right click) + actionRenameInstance = TranslatedAction(MainWindow); + actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance")); + actionRenameInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Rename")); + actionRenameInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Rename the selected instance.")); + all_actions.append(&actionRenameInstance); + + // the rename label is inside the rename tool button + renameButton = new LabeledToolButton(MainWindow); + renameButton->setObjectName(QStringLiteral("renameButton")); + renameButton->setToolTip(actionRenameInstance->toolTip()); + renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + instanceToolBar->addWidget(renameButton); + + instanceToolBar->addSeparator(); + + actionLaunchInstance = TranslatedAction(MainWindow); + actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); + all_actions.append(&actionLaunchInstance); + instanceToolBar->addAction(actionLaunchInstance); + + actionLaunchInstanceOffline = TranslatedAction(MainWindow); + actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline")); + actionLaunchInstanceOffline.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch Offline")); + actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); + all_actions.append(&actionLaunchInstanceOffline); + instanceToolBar->addAction(actionLaunchInstanceOffline); + + instanceToolBar->addSeparator(); + + actionEditInstance = TranslatedAction(MainWindow); + actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); + actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Instance")); + actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions.")); + all_actions.append(&actionEditInstance); + instanceToolBar->addAction(actionEditInstance); + + actionEditInstNotes = TranslatedAction(MainWindow); + actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes")); + actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Notes")); + actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Edit the notes for the selected instance.")); + all_actions.append(&actionEditInstNotes); + instanceToolBar->addAction(actionEditInstNotes); + + actionWorlds = TranslatedAction(MainWindow); + actionWorlds->setObjectName(QStringLiteral("actionWorlds")); + actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Worlds")); + actionWorlds.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the worlds of this instance.")); + all_actions.append(&actionWorlds); + instanceToolBar->addAction(actionWorlds); + + actionScreenshots = TranslatedAction(MainWindow); + actionScreenshots->setObjectName(QStringLiteral("actionScreenshots")); + actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Screenshots")); + actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View and upload screenshots for this instance.")); + all_actions.append(&actionScreenshots); + instanceToolBar->addAction(actionScreenshots); + + actionChangeInstGroup = TranslatedAction(MainWindow); + actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup")); + actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Group")); + actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's group.")); + all_actions.append(&actionChangeInstGroup); + instanceToolBar->addAction(actionChangeInstGroup); + + instanceToolBar->addSeparator(); + + actionViewSelectedMCFolder = TranslatedAction(MainWindow); + actionViewSelectedMCFolder->setObjectName(QStringLiteral("actionViewSelectedMCFolder")); + actionViewSelectedMCFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Minecraft Folder")); + actionViewSelectedMCFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's minecraft folder in a file browser.")); + all_actions.append(&actionViewSelectedMCFolder); + instanceToolBar->addAction(actionViewSelectedMCFolder); + + actionConfig_Folder = TranslatedAction(MainWindow); + actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder")); + actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Config Folder")); + actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance's config folder.")); + all_actions.append(&actionConfig_Folder); + instanceToolBar->addAction(actionConfig_Folder); + + actionViewSelectedInstFolder = TranslatedAction(MainWindow); + actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); + actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Instance Folder")); + actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser.")); + all_actions.append(&actionViewSelectedInstFolder); + instanceToolBar->addAction(actionViewSelectedInstFolder); + + instanceToolBar->addSeparator(); + + actionExportInstance = TranslatedAction(MainWindow); + actionExportInstance->setObjectName(QStringLiteral("actionExportInstance")); + actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Export Instance")); + // FIXME: missing tooltip + all_actions.append(&actionExportInstance); + instanceToolBar->addAction(actionExportInstance); + + actionDeleteInstance = TranslatedAction(MainWindow); + actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); + actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Delete")); + actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); + all_actions.append(&actionDeleteInstance); + instanceToolBar->addAction(actionDeleteInstance); + + actionCopyInstance = TranslatedAction(MainWindow); + actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); + actionCopyInstance->setIcon(MMC->getThemedIcon("copy")); + actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Copy Instance")); + actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance.")); + all_actions.append(&actionCopyInstance); + instanceToolBar->addAction(actionCopyInstance); + + all_toolbars.append(&instanceToolBar); + MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); + } + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + { + MainWindow->setObjectName(QStringLiteral("MainWindow")); + } + MainWindow->resize(694, 563); + MainWindow->setWindowIcon(MMC->getThemedIcon("logo")); + MainWindow->setWindowTitle("MultiMC 5"); + + createMainToolbar(MainWindow); + + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QStringLiteral("centralWidget")); + horizontalLayout = new QHBoxLayout(centralWidget); + horizontalLayout->setSpacing(0); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + horizontalLayout->setContentsMargins(0, 0, 0, 0); + MainWindow->setCentralWidget(centralWidget); + + createStatusBar(MainWindow); + createNewsToolbar(MainWindow); + createInstanceToolbar(MainWindow); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + QString winTitle = tr("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString()); + if (!BuildConfig.BUILD_PLATFORM.isEmpty()) + { + winTitle += tr(" on %1", "on platform, as in operating system").arg(BuildConfig.BUILD_PLATFORM); + } + MainWindow->setWindowTitle(winTitle); + // all the actions + for(auto * item: all_actions) + { + item->retranslate(); + } + for(auto * item: all_toolbars) + { + item->retranslate(); + } + for(auto * item: all_toolbuttons) + { + item->retranslate(); + } + // submenu buttons + foldersMenuButton->setText(tr("Folders")); + helpMenuButton->setText(tr("Help")); + } // retranslateUi }; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow::Ui) { - ui->setupUi(this); - - // OSX magic. - setUnifiedTitleAndToolBarOnMac(true); - - // Global shortcuts - { - // FIXME: This is kinda weird. and bad. We need some kind of managed shutdown. - auto q = new QShortcut(QKeySequence::Quit, this); - connect(q, SIGNAL(activated()), qApp, SLOT(quit())); - } - - // Konami Code - { - secretEventFilter = new KonamiCode(this); - connect(secretEventFilter, &KonamiCode::triggered, this, &MainWindow::konamiTriggered); - } - - // Add the news label to the news toolbar. - { - m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL)); - newsLabel = new QToolButton(); - newsLabel->setIcon(MMC->getThemedIcon("news")); - newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); - QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); - QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); - updateNewsLabel(); - } - - // Create the instance list widget - { - view = new GroupView(ui->centralWidget); - - view->setSelectionMode(QAbstractItemView::SingleSelection); - // FIXME: leaks ListViewDelegate - view->setItemDelegate(new ListViewDelegate(this)); - view->setFrameShape(QFrame::NoFrame); - // do not show ugly blue border on the mac - view->setAttribute(Qt::WA_MacShowFocusRect, false); - - view->installEventFilter(this); - view->setContextMenuPolicy(Qt::CustomContextMenu); - connect(view, &QWidget::customContextMenuRequested, this, &MainWindow::showInstanceContextMenu); - connect(view, &GroupView::droppedURLs, this, &MainWindow::droppedURLs); - - proxymodel = new InstanceProxyModel(this); - proxymodel->setSourceModel(MMC->instances().get()); - proxymodel->sort(0); - connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); - - view->setModel(proxymodel); - ui->horizontalLayout->addWidget(view); - } - // The cat background - { - bool cat_enable = MMC->settings()->get("TheCat").toBool(); - ui->actionCAT->setChecked(cat_enable); - connect(ui->actionCAT, SIGNAL(toggled(bool)), SLOT(onCatToggled(bool))); - setCatBackground(cat_enable); - } - // start instance when double-clicked - connect(view, &GroupView::doubleClicked, this, &MainWindow::instanceActivated); - - // track the selection -- update the instance toolbar - connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged); - - // track icon changes and update the toolbar! - connect(MMC->icons().get(), &IconList::iconUpdated, this, &MainWindow::iconUpdated); - - // model reset -> selection is invalid. All the instance pointers are wrong. - connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad); - - m_statusLeft = new QLabel(tr("No instance selected"), this); - m_statusRight = new ServerStatus(this); - statusBar()->addPermanentWidget(m_statusLeft, 1); - statusBar()->addPermanentWidget(m_statusRight, 0); - - // Add "manage accounts" button, right align - QWidget *spacer = new QWidget(); - spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - ui->mainToolBar->addWidget(spacer); - - accountMenu = new QMenu(this); - - repopulateAccountsMenu(); - - accountMenuButton = new QToolButton(this); - accountMenuButton->setText(tr("Profiles")); - accountMenuButton->setMenu(accountMenu); - accountMenuButton->setPopupMode(QToolButton::InstantPopup); - accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); - - QWidgetAction *accountMenuButtonAction = new QWidgetAction(this); - accountMenuButtonAction->setDefaultWidget(accountMenuButton); - - ui->mainToolBar->addAction(accountMenuButtonAction); - - // Update the menu when the active account changes. - // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. - // Template hell sucks... - connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this] - { - activeAccountChanged(); - }); - connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this] - { - repopulateAccountsMenu(); - }); - - // Show initial account - activeAccountChanged(); - - auto accounts = MMC->accounts(); - - QList skin_dls; - for (int i = 0; i < accounts->count(); i++) - { - auto account = accounts->at(i); - if (!account) - { - qWarning() << "Null account at index" << i; - continue; - } - for (auto profile : account->profiles()) - { - auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); - auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta); - skin_dls.append(action); - meta->setStale(true); - } - } - if (!skin_dls.isEmpty()) - { - auto job = new NetJob("Startup player skins download"); - connect(job, &NetJob::succeeded, this, &MainWindow::skinJobFinished); - connect(job, &NetJob::failed, this, &MainWindow::skinJobFinished); - for (auto action : skin_dls) - { - job->addNetAction(action); - } - skin_download_job.reset(job); - job->start(); - } - - // load the news - { - m_newsChecker->reloadNews(); - updateNewsLabel(); - } - - - if(BuildConfig.UPDATER_ENABLED) - { - bool updatesAllowed = MMC->updatesAreAllowed(); - updatesAllowedChanged(updatesAllowed); - - connect(ui->actionCheckUpdate, &QAction::triggered, this, &MainWindow::checkForUpdates); - - // set up the updater object. - auto updater = MMC->updateChecker(); - connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable); - connect(updater.get(), &UpdateChecker::noUpdateFound, this, &MainWindow::updateNotAvailable); - // if automatic update checks are allowed, start one. - if (MMC->settings()->get("AutoUpdate").toBool() && updatesAllowed) - { - updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), false); - } - } - - { - auto checker = new NotificationChecker(); - checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)); - checker->setApplicationChannel(BuildConfig.VERSION_CHANNEL); - checker->setApplicationPlatform(BuildConfig.BUILD_PLATFORM); - checker->setApplicationFullVersion(BuildConfig.FULL_VERSION_STR); - m_notificationChecker.reset(checker); - connect(m_notificationChecker.get(), &NotificationChecker::notificationCheckFinished, this, &MainWindow::notificationsChanged); - checker->checkForNotifications(); - } - - setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); - - // removing this looks stupid - view->setFocus(); + ui->setupUi(this); + + // OSX magic. + setUnifiedTitleAndToolBarOnMac(true); + + // Global shortcuts + { + // FIXME: This is kinda weird. and bad. We need some kind of managed shutdown. + auto q = new QShortcut(QKeySequence::Quit, this); + connect(q, SIGNAL(activated()), qApp, SLOT(quit())); + } + + // Konami Code + { + secretEventFilter = new KonamiCode(this); + connect(secretEventFilter, &KonamiCode::triggered, this, &MainWindow::konamiTriggered); + } + + // Add the news label to the news toolbar. + { + m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL)); + newsLabel = new QToolButton(); + newsLabel->setIcon(MMC->getThemedIcon("news")); + newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + newsLabel->setFocusPolicy(Qt::NoFocus); + ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); + QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); + QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); + updateNewsLabel(); + } + + // Create the instance list widget + { + view = new GroupView(ui->centralWidget); + + view->setSelectionMode(QAbstractItemView::SingleSelection); + // FIXME: leaks ListViewDelegate + view->setItemDelegate(new ListViewDelegate(this)); + view->setFrameShape(QFrame::NoFrame); + // do not show ugly blue border on the mac + view->setAttribute(Qt::WA_MacShowFocusRect, false); + + view->installEventFilter(this); + view->setContextMenuPolicy(Qt::CustomContextMenu); + connect(view, &QWidget::customContextMenuRequested, this, &MainWindow::showInstanceContextMenu); + connect(view, &GroupView::droppedURLs, this, &MainWindow::droppedURLs, Qt::QueuedConnection); + + proxymodel = new InstanceProxyModel(this); + proxymodel->setSourceModel(MMC->instances().get()); + proxymodel->sort(0); + connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); + + view->setModel(proxymodel); + ui->horizontalLayout->addWidget(view); + } + // The cat background + { + bool cat_enable = MMC->settings()->get("TheCat").toBool(); + ui->actionCAT->setChecked(cat_enable); + // NOTE: calling the operator like that is an ugly hack to appease ancient gcc... + connect(ui->actionCAT.operator->(), SIGNAL(toggled(bool)), SLOT(onCatToggled(bool))); + setCatBackground(cat_enable); + } + // start instance when double-clicked + connect(view, &GroupView::activated, this, &MainWindow::instanceActivated); + + // track the selection -- update the instance toolbar + connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged); + + // track icon changes and update the toolbar! + connect(MMC->icons().get(), &IconList::iconUpdated, this, &MainWindow::iconUpdated); + + // model reset -> selection is invalid. All the instance pointers are wrong. + connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad); + + // handle newly added instances + connect(MMC->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest); + + // When the global settings page closes, we want to know about it and update our state + connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed); + + m_statusLeft = new QLabel(tr("No instance selected"), this); + m_statusRight = new ServerStatus(this); + statusBar()->addPermanentWidget(m_statusLeft, 1); + statusBar()->addPermanentWidget(m_statusRight, 0); + + // Add "manage accounts" button, right align + QWidget *spacer = new QWidget(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + ui->mainToolBar->addWidget(spacer); + + accountMenu = new QMenu(this); + + repopulateAccountsMenu(); + + accountMenuButton = new QToolButton(this); + accountMenuButton->setText(tr("Profiles")); + accountMenuButton->setMenu(accountMenu); + accountMenuButton->setPopupMode(QToolButton::InstantPopup); + accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); + + QWidgetAction *accountMenuButtonAction = new QWidgetAction(this); + accountMenuButtonAction->setDefaultWidget(accountMenuButton); + + ui->mainToolBar->addAction(accountMenuButtonAction); + + // Update the menu when the active account changes. + // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. + // Template hell sucks... + connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this] + { + activeAccountChanged(); + }); + connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this] + { + repopulateAccountsMenu(); + }); + + // Show initial account + activeAccountChanged(); + + auto accounts = MMC->accounts(); + + QList skin_dls; + for (int i = 0; i < accounts->count(); i++) + { + auto account = accounts->at(i); + if (!account) + { + qWarning() << "Null account at index" << i; + continue; + } + for (auto profile : account->profiles()) + { + auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); + auto action = Net::Download::makeCached(QUrl(URLConstants::SKINS_BASE + profile.id + ".png"), meta); + skin_dls.append(action); + meta->setStale(true); + } + } + if (!skin_dls.isEmpty()) + { + auto job = new NetJob("Startup player skins download"); + connect(job, &NetJob::succeeded, this, &MainWindow::skinJobFinished); + connect(job, &NetJob::failed, this, &MainWindow::skinJobFinished); + for (auto action : skin_dls) + { + job->addNetAction(action); + } + skin_download_job.reset(job); + job->start(); + } + + // load the news + { + m_newsChecker->reloadNews(); + updateNewsLabel(); + } + + + if(BuildConfig.UPDATER_ENABLED) + { + bool updatesAllowed = MMC->updatesAreAllowed(); + updatesAllowedChanged(updatesAllowed); + + // NOTE: calling the operator like that is an ugly hack to appease ancient gcc... + connect(ui->actionCheckUpdate.operator->(), &QAction::triggered, this, &MainWindow::checkForUpdates); + + // set up the updater object. + auto updater = MMC->updateChecker(); + connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable); + connect(updater.get(), &UpdateChecker::noUpdateFound, this, &MainWindow::updateNotAvailable); + // if automatic update checks are allowed, start one. + if (MMC->settings()->get("AutoUpdate").toBool() && updatesAllowed) + { + updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), false); + } + } + + { + auto checker = new NotificationChecker(); + checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)); + checker->setApplicationChannel(BuildConfig.VERSION_CHANNEL); + checker->setApplicationPlatform(BuildConfig.BUILD_PLATFORM); + checker->setApplicationFullVersion(BuildConfig.FULL_VERSION_STR); + m_notificationChecker.reset(checker); + connect(m_notificationChecker.get(), &NotificationChecker::notificationCheckFinished, this, &MainWindow::notificationsChanged); + checker->checkForNotifications(); + } + + setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); + + // removing this looks stupid + view->setFocus(); } MainWindow::~MainWindow() { } +QMenu * MainWindow::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() ); + return filteredMenu; +} + void MainWindow::konamiTriggered() { - qDebug() << "Super Secret Mode ACTIVATED!"; + // ENV.enableFeature("NewModsPage"); + qDebug() << "Super Secret Mode ACTIVATED!"; } void MainWindow::skinJobFinished() { - activeAccountChanged(); - skin_download_job.reset(); + activeAccountChanged(); + skin_download_job.reset(); } void MainWindow::showInstanceContextMenu(const QPoint &pos) { - QList actions; - - QAction *actionSep = new QAction("", this); - actionSep->setSeparator(true); - - bool onInstance = view->indexAt(pos).isValid(); - if (onInstance) - { - actions = ui->instanceToolBar->actions(); - - // replace the change icon widget with an actual action - actions.replace(0, ui->actionChangeInstIcon); - - // replace the rename widget with an actual action - actions.replace(1, ui->actionRenameInstance); - - // add header - actions.prepend(actionSep); - QAction *actionVoid = new QAction(m_selectedInstance->name(), this); - actionVoid->setEnabled(false); - actions.prepend(actionVoid); - } - else - { - auto group = view->groupNameAt(pos); - - QAction *actionVoid = new QAction("MultiMC", this); - actionVoid->setEnabled(false); - - QAction *actionCreateInstance = new QAction(tr("Create instance"), this); - actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); - if(!group.isNull()) - { - QVariantMap data; - data["group"] = group; - actionCreateInstance->setData(data); - } - - connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered())); - - actions.prepend(actionSep); - actions.prepend(actionVoid); - actions.append(actionCreateInstance); - if(!group.isNull()) - { - QAction *actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this); - QVariantMap data; - data["group"] = group; - actionDeleteGroup->setData(data); - connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup())); - actions.append(actionDeleteGroup); - } - } - QMenu myMenu; - myMenu.addActions(actions); - /* - if (onInstance) - myMenu.setEnabled(m_selectedInstance->canLaunch()); - */ - myMenu.exec(view->mapToGlobal(pos)); + QList actions; + + QAction *actionSep = new QAction("", this); + actionSep->setSeparator(true); + + bool onInstance = view->indexAt(pos).isValid(); + if (onInstance) + { + actions = ui->instanceToolBar->actions(); + + // replace the change icon widget with an actual action + actions.replace(0, ui->actionChangeInstIcon); + + // replace the rename widget with an actual action + actions.replace(1, ui->actionRenameInstance); + + // add header + actions.prepend(actionSep); + QAction *actionVoid = new QAction(m_selectedInstance->name(), this); + actionVoid->setEnabled(false); + actions.prepend(actionVoid); + } + else + { + auto group = view->groupNameAt(pos); + + QAction *actionVoid = new QAction("MultiMC", this); + actionVoid->setEnabled(false); + + QAction *actionCreateInstance = new QAction(tr("Create instance"), this); + actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); + if(!group.isNull()) + { + QVariantMap data; + data["group"] = group; + actionCreateInstance->setData(data); + } + + connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered())); + + actions.prepend(actionSep); + actions.prepend(actionVoid); + actions.append(actionCreateInstance); + if(!group.isNull()) + { + QAction *actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this); + QVariantMap data; + data["group"] = group; + actionDeleteGroup->setData(data); + connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup())); + actions.append(actionDeleteGroup); + } + } + QMenu myMenu; + myMenu.addActions(actions); + /* + if (onInstance) + myMenu.setEnabled(m_selectedInstance->canLaunch()); + */ + myMenu.exec(view->mapToGlobal(pos)); } void MainWindow::updateToolsMenu() { - QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); - if(!m_selectedInstance || m_selectedInstance->isRunning()) - { - ui->actionLaunchInstance->setMenu(nullptr); - launchButton->setPopupMode(QToolButton::InstantPopup); - return; - } - - QMenu *launchMenu = ui->actionLaunchInstance->menu(); - launchButton->setPopupMode(QToolButton::MenuButtonPopup); - if (launchMenu) - { - launchMenu->clear(); - } - else - { - launchMenu = new QMenu(this); - } - - QAction *normalLaunch = launchMenu->addAction(tr("Launch")); - connect(normalLaunch, &QAction::triggered, [this]() - { - MMC->launch(m_selectedInstance); - }); - launchMenu->addSeparator()->setText(tr("Profilers")); - for (auto profiler : MMC->profilers().values()) - { - QAction *profilerAction = launchMenu->addAction(profiler->name()); - QString error; - if (!profiler->check(&error)) - { - profilerAction->setDisabled(true); - profilerAction->setToolTip(tr("Profiler not setup correctly. Go into settings, \"External Tools\".")); - } - else - { - connect(profilerAction, &QAction::triggered, [this, profiler]() - { - MMC->launch(m_selectedInstance, true, profiler.get()); - }); - } - } - ui->actionLaunchInstance->setMenu(launchMenu); + QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); + QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); + + if(!m_selectedInstance || m_selectedInstance->isRunning()) + { + ui->actionLaunchInstance->setMenu(nullptr); + ui->actionLaunchInstanceOffline->setMenu(nullptr); + launchButton->setPopupMode(QToolButton::InstantPopup); + launchOfflineButton->setPopupMode(QToolButton::InstantPopup); + return; + } + + QMenu *launchMenu = ui->actionLaunchInstance->menu(); + QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); + launchButton->setPopupMode(QToolButton::MenuButtonPopup); + launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); + if (launchMenu) + { + launchMenu->clear(); + } + else + { + launchMenu = new QMenu(this); + } + if (launchOfflineMenu) { + launchOfflineMenu->clear(); + } + else + { + launchOfflineMenu = new QMenu(this); + } + + QAction *normalLaunch = launchMenu->addAction(tr("Launch")); + QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); + connect(normalLaunch, &QAction::triggered, [this]() + { + MMC->launch(m_selectedInstance, true); + }); + connect(normalLaunchOffline, &QAction::triggered, [this]() + { + MMC->launch(m_selectedInstance, false); + }); + QString profilersTitle = tr("Profilers"); + launchMenu->addSeparator()->setText(profilersTitle); + launchOfflineMenu->addSeparator()->setText(profilersTitle); + for (auto profiler : MMC->profilers().values()) + { + QAction *profilerAction = launchMenu->addAction(profiler->name()); + QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); + QString error; + if (!profiler->check(&error)) + { + profilerAction->setDisabled(true); + profilerOfflineAction->setDisabled(true); + QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); + profilerAction->setToolTip(profilerToolTip); + profilerOfflineAction->setToolTip(profilerToolTip); + } + else + { + connect(profilerAction, &QAction::triggered, [this, profiler]() + { + MMC->launch(m_selectedInstance, true, profiler.get()); + }); + connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() + { + MMC->launch(m_selectedInstance, false, profiler.get()); + }); + } + } + ui->actionLaunchInstance->setMenu(launchMenu); + ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); } QString profileInUseFilter(const QString & profile, bool used) { - if(used) - { - return profile + QObject::tr(" (in use)"); - } - else - { - return profile; - } + if(used) + { + return profile + QObject::tr(" (in use)"); + } + else + { + return profile; + } } void MainWindow::repopulateAccountsMenu() { - accountMenu->clear(); - - std::shared_ptr accounts = MMC->accounts(); - MojangAccountPtr active_account = accounts->activeAccount(); - - QString active_username = ""; - if (active_account != nullptr) - { - active_username = active_account->username(); - const AccountProfile *profile = active_account->currentProfile(); - // this can be called before accountMenuButton exists - if (profile != nullptr && accountMenuButton) - { - auto profileLabel = profileInUseFilter(profile->name, active_account->isInUse()); - accountMenuButton->setText(profileLabel); - } - } - - if (accounts->count() <= 0) - { - QAction *action = new QAction(tr("No accounts added!"), this); - action->setEnabled(false); - accountMenu->addAction(action); - } - else - { - // TODO: Nicer way to iterate? - for (int i = 0; i < accounts->count(); i++) - { - MojangAccountPtr account = accounts->at(i); - for (auto profile : account->profiles()) - { - auto profileLabel = profileInUseFilter(profile.name, account->isInUse()); - QAction *action = new QAction(profileLabel, this); - action->setData(account->username()); - action->setCheckable(true); - if (active_username == account->username()) - { - action->setChecked(true); - } - - action->setIcon(SkinUtils::getFaceFromCache(profile.id)); - accountMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); - } - } - } - - accountMenu->addSeparator(); - - QAction *action = new QAction(tr("No Default Account"), this); - action->setCheckable(true); - action->setIcon(MMC->getThemedIcon("noaccount")); - action->setData(""); - if (active_username.isEmpty()) - { - action->setChecked(true); - } - - accountMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); - - accountMenu->addSeparator(); - accountMenu->addAction(ui->actionManageAccounts); + accountMenu->clear(); + + std::shared_ptr accounts = MMC->accounts(); + MojangAccountPtr active_account = accounts->activeAccount(); + + QString active_username = ""; + if (active_account != nullptr) + { + active_username = active_account->username(); + const AccountProfile *profile = active_account->currentProfile(); + // this can be called before accountMenuButton exists + if (profile != nullptr && accountMenuButton) + { + auto profileLabel = profileInUseFilter(profile->name, active_account->isInUse()); + accountMenuButton->setText(profileLabel); + } + } + + if (accounts->count() <= 0) + { + QAction *action = new QAction(tr("No accounts added!"), this); + action->setEnabled(false); + accountMenu->addAction(action); + } + else + { + // TODO: Nicer way to iterate? + for (int i = 0; i < accounts->count(); i++) + { + MojangAccountPtr account = accounts->at(i); + for (auto profile : account->profiles()) + { + auto profileLabel = profileInUseFilter(profile.name, account->isInUse()); + QAction *action = new QAction(profileLabel, this); + action->setData(account->username()); + action->setCheckable(true); + if (active_username == account->username()) + { + action->setChecked(true); + } + + action->setIcon(SkinUtils::getFaceFromCache(profile.id)); + accountMenu->addAction(action); + connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); + } + } + } + + accountMenu->addSeparator(); + + QAction *action = new QAction(tr("No Default Account"), this); + action->setCheckable(true); + action->setIcon(MMC->getThemedIcon("noaccount")); + action->setData(""); + if (active_username.isEmpty()) + { + action->setChecked(true); + } + + accountMenu->addAction(action); + connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); + + accountMenu->addSeparator(); + accountMenu->addAction(ui->actionManageAccounts); } void MainWindow::updatesAllowedChanged(bool allowed) { - if(!BuildConfig.UPDATER_ENABLED) - { - return; - } - ui->actionCheckUpdate->setEnabled(allowed); + if(!BuildConfig.UPDATER_ENABLED) + { + return; + } + ui->actionCheckUpdate->setEnabled(allowed); } /* @@ -1030,799 +1083,825 @@ void MainWindow::updatesAllowedChanged(bool allowed) */ void MainWindow::changeActiveAccount() { - QAction *sAction = (QAction *)sender(); - // Profile's associated Mojang username - // Will need to change when profiles are properly implemented - if (sAction->data().type() != QVariant::Type::String) - return; + QAction *sAction = (QAction *)sender(); + // Profile's associated Mojang username + // Will need to change when profiles are properly implemented + if (sAction->data().type() != QVariant::Type::String) + return; - QVariant data = sAction->data(); - QString id = ""; - if (!data.isNull()) - { - id = data.toString(); - } + QVariant data = sAction->data(); + QString id = ""; + if (!data.isNull()) + { + id = data.toString(); + } - MMC->accounts()->setActiveAccount(id); + MMC->accounts()->setActiveAccount(id); - activeAccountChanged(); + activeAccountChanged(); } void MainWindow::activeAccountChanged() { - repopulateAccountsMenu(); + repopulateAccountsMenu(); - MojangAccountPtr account = MMC->accounts()->activeAccount(); + MojangAccountPtr account = MMC->accounts()->activeAccount(); - if (account != nullptr && account->username() != "") - { - const AccountProfile *profile = account->currentProfile(); - if (profile != nullptr) - { - auto profileLabel = profileInUseFilter(profile->name, account->isInUse()); - accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->id)); - accountMenuButton->setText(profileLabel); - return; - } - } + if (account != nullptr && account->username() != "") + { + const AccountProfile *profile = account->currentProfile(); + if (profile != nullptr) + { + auto profileLabel = profileInUseFilter(profile->name, account->isInUse()); + accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->id)); + accountMenuButton->setText(profileLabel); + return; + } + } - // Set the icon to the "no account" icon. - accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); - accountMenuButton->setText(tr("Profiles")); + // Set the icon to the "no account" icon. + accountMenuButton->setIcon(MMC->getThemedIcon("noaccount")); + accountMenuButton->setText(tr("Profiles")); } bool MainWindow::eventFilter(QObject *obj, QEvent *ev) { - if (obj == view) - { - if (ev->type() == QEvent::KeyPress) - { - secretEventFilter->input(ev); - QKeyEvent *keyEvent = static_cast(ev); - switch (keyEvent->key()) - { - case Qt::Key_Enter: - case Qt::Key_Return: - activateInstance(m_selectedInstance); - return true; - case Qt::Key_Delete: - on_actionDeleteInstance_triggered(); - return true; - case Qt::Key_F5: - refreshInstances(); - return true; - case Qt::Key_F2: - on_actionRenameInstance_triggered(); - return true; - default: - break; - } - } - } - return QMainWindow::eventFilter(obj, ev); + if (obj == view) + { + if (ev->type() == QEvent::KeyPress) + { + secretEventFilter->input(ev); + QKeyEvent *keyEvent = static_cast(ev); + switch (keyEvent->key()) + { + /* + case Qt::Key_Enter: + case Qt::Key_Return: + activateInstance(m_selectedInstance); + return true; + */ + case Qt::Key_Delete: + on_actionDeleteInstance_triggered(); + return true; + case Qt::Key_F5: + refreshInstances(); + return true; + case Qt::Key_F2: + on_actionRenameInstance_triggered(); + return true; + default: + break; + } + } + } + return QMainWindow::eventFilter(obj, ev); } void MainWindow::updateNewsLabel() { - if (m_newsChecker->isLoadingNews()) - { - newsLabel->setText(tr("Loading news...")); - newsLabel->setEnabled(false); - } - else - { - QList entries = m_newsChecker->getNewsEntries(); - if (entries.length() > 0) - { - newsLabel->setText(entries[0]->title); - newsLabel->setEnabled(true); - } - else - { - newsLabel->setText(tr("No news available.")); - newsLabel->setEnabled(false); - } - } + if (m_newsChecker->isLoadingNews()) + { + newsLabel->setText(tr("Loading news...")); + newsLabel->setEnabled(false); + } + else + { + QList entries = m_newsChecker->getNewsEntries(); + if (entries.length() > 0) + { + newsLabel->setText(entries[0]->title); + newsLabel->setEnabled(true); + } + else + { + newsLabel->setText(tr("No news available.")); + newsLabel->setEnabled(false); + } + } } void MainWindow::updateAvailable(GoUpdate::Status status) { - if(!MMC->updatesAreAllowed()) - { - updateNotAvailable(); - return; - } - UpdateDialog dlg(true, this); - UpdateAction action = (UpdateAction)dlg.exec(); - switch (action) - { - case UPDATE_LATER: - qDebug() << "Update will be installed later."; - break; - case UPDATE_NOW: - downloadUpdates(status); - break; - } + if(!MMC->updatesAreAllowed()) + { + updateNotAvailable(); + return; + } + UpdateDialog dlg(true, this); + UpdateAction action = (UpdateAction)dlg.exec(); + switch (action) + { + case UPDATE_LATER: + qDebug() << "Update will be installed later."; + break; + case UPDATE_NOW: + downloadUpdates(status); + break; + } } void MainWindow::updateNotAvailable() { - UpdateDialog dlg(false, this); - dlg.exec(); + UpdateDialog dlg(false, this); + dlg.exec(); } QList stringToIntList(const QString &string) { - QStringList split = string.split(',', QString::SkipEmptyParts); - QList out; - for (int i = 0; i < split.size(); ++i) - { - out.append(split.at(i).toInt()); - } - return out; + QStringList split = string.split(',', QString::SkipEmptyParts); + QList out; + for (int i = 0; i < split.size(); ++i) + { + out.append(split.at(i).toInt()); + } + return out; } QString intListToString(const QList &list) { - QStringList slist; - for (int i = 0; i < list.size(); ++i) - { - slist.append(QString::number(list.at(i))); - } - return slist.join(','); + QStringList slist; + for (int i = 0; i < list.size(); ++i) + { + slist.append(QString::number(list.at(i))); + } + return slist.join(','); } void MainWindow::notificationsChanged() { - QList entries = m_notificationChecker->notificationEntries(); - QList shownNotifications = stringToIntList(MMC->settings()->get("ShownNotifications").toString()); - for (auto it = entries.begin(); it != entries.end(); ++it) - { - NotificationChecker::NotificationEntry entry = *it; - if (!shownNotifications.contains(entry.id)) - { - NotificationDialog dialog(entry, this); - if (dialog.exec() == NotificationDialog::DontShowAgain) - { - shownNotifications.append(entry.id); - } - } - } - MMC->settings()->set("ShownNotifications", intListToString(shownNotifications)); + QList entries = m_notificationChecker->notificationEntries(); + QList shownNotifications = stringToIntList(MMC->settings()->get("ShownNotifications").toString()); + for (auto it = entries.begin(); it != entries.end(); ++it) + { + NotificationChecker::NotificationEntry entry = *it; + if (!shownNotifications.contains(entry.id)) + { + NotificationDialog dialog(entry, this); + if (dialog.exec() == NotificationDialog::DontShowAgain) + { + shownNotifications.append(entry.id); + } + } + } + MMC->settings()->set("ShownNotifications", intListToString(shownNotifications)); } void MainWindow::downloadUpdates(GoUpdate::Status status) { - if(!MMC->updatesAreAllowed()) - { - return; - } - qDebug() << "Downloading updates."; - ProgressDialog updateDlg(this); - status.rootPath = MMC->root(); - - auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX"); - if (!FS::ensureFilePathExists(dlPath)) - { - CustomMessageBox::selectable(this, tr("Error"), tr("Couldn't create folder for update downloads:\n%1").arg(dlPath), QMessageBox::Warning)->show(); - } - GoUpdate::DownloadTask updateTask(status, dlPath, &updateDlg); - // If the task succeeds, install the updates. - if (updateDlg.execWithTask(&updateTask)) - { - /** - * NOTE: This disables launching instances until the update either succeeds (and this process exits) - * or the update fails (and the control leaves this scope). - */ - MMC->updateIsRunning(true); - UpdateController update(this, MMC->root(), updateTask.updateFilesDir(), updateTask.operations()); - update.installUpdates(); - MMC->updateIsRunning(false); - } - else - { - CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show(); - } + if(!MMC->updatesAreAllowed()) + { + return; + } + qDebug() << "Downloading updates."; + ProgressDialog updateDlg(this); + status.rootPath = MMC->root(); + + auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX"); + if (!FS::ensureFilePathExists(dlPath)) + { + CustomMessageBox::selectable(this, tr("Error"), tr("Couldn't create folder for update downloads:\n%1").arg(dlPath), QMessageBox::Warning)->show(); + } + GoUpdate::DownloadTask updateTask(status, dlPath, &updateDlg); + // If the task succeeds, install the updates. + if (updateDlg.execWithTask(&updateTask)) + { + /** + * NOTE: This disables launching instances until the update either succeeds (and this process exits) + * or the update fails (and the control leaves this scope). + */ + MMC->updateIsRunning(true); + UpdateController update(this, MMC->root(), updateTask.updateFilesDir(), updateTask.operations()); + update.installUpdates(); + MMC->updateIsRunning(false); + } + else + { + CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show(); + } } void MainWindow::onCatToggled(bool state) { - setCatBackground(state); - MMC->settings()->set("TheCat", state); + setCatBackground(state); + MMC->settings()->set("TheCat", state); +} + +namespace { +template +T non_stupid_abs(T in) +{ + if (in < 0) + return -in; + return in; +} } void MainWindow::setCatBackground(bool enabled) { - if (enabled) - { - view->setStyleSheet("GroupView" - "{" - "background-image: url(:/backgrounds/kitteh);" - "background-attachment: fixed;" - "background-clip: padding;" - "background-position: top right;" - "background-repeat: none;" - "background-color:palette(base);" - "}"); - } - else - { - view->setStyleSheet(QString()); - } + if (enabled) + { + QDateTime now = QDateTime::currentDateTime(); + QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); + ; + QString cat = (non_stupid_abs(now.daysTo(xmas)) <= 4) ? "catmas" : "kitteh"; + view->setStyleSheet(QString(R"( +GroupView +{ + background-image: url(:/backgrounds/%1); + background-attachment: fixed; + background-clip: padding; + background-position: top right; + background-repeat: none; + background-color:palette(base); +})").arg(cat)); + } + else + { + view->setStyleSheet(QString()); + } } void MainWindow::runModalTask(Task *task) { - connect(task, &Task::failed, [this](QString reason) - { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - }); - connect(task, &Task::succeeded, [this, task]() - { - QStringList warnings = task->warnings(); - if(warnings.count()) - { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } - }); - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(task); + connect(task, &Task::failed, [this](QString reason) + { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + }); + connect(task, &Task::succeeded, [this, task]() + { + QStringList warnings = task->warnings(); + if(warnings.count()) + { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(task); } void MainWindow::instanceFromInstanceTask(InstanceTask *rawTask) { - std::unique_ptr task(MMC->folderProvider()->wrapInstanceTask(rawTask)); - runModalTask(task.get()); - - // FIXME: handle instance selection after creation - // finalizeInstance(newInstance); + unique_qobject_ptr task(MMC->instances()->wrapInstanceTask(rawTask)); + runModalTask(task.get()); } void MainWindow::on_actionCopyInstance_triggered() { - if (!m_selectedInstance) - return; + if (!m_selectedInstance) + return; - CopyInstanceDialog copyInstDlg(m_selectedInstance, this); - if (!copyInstDlg.exec()) - return; + CopyInstanceDialog copyInstDlg(m_selectedInstance, this); + if (!copyInstDlg.exec()) + return; - auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves()); - copyTask->setName(copyInstDlg.instName()); - copyTask->setGroup(copyInstDlg.instGroup()); - copyTask->setIcon(copyInstDlg.iconKey()); - std::unique_ptr task(MMC->folderProvider()->wrapInstanceTask(copyTask)); - runModalTask(task.get()); - - // FIXME: handle instance selection after creation - // finalizeInstance(newInstance); + auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves()); + copyTask->setName(copyInstDlg.instName()); + copyTask->setGroup(copyInstDlg.instGroup()); + copyTask->setIcon(copyInstDlg.iconKey()); + unique_qobject_ptr task(MMC->instances()->wrapInstanceTask(copyTask)); + runModalTask(task.get()); } void MainWindow::finalizeInstance(InstancePtr inst) { - view->updateGeometries(); - setSelectedInstanceById(inst->id()); - if (MMC->accounts()->anyAccountIsValid()) - { - ProgressDialog loadDialog(this); - auto update = inst->createUpdateTask(Net::Mode::Online); - connect(update.get(), &Task::failed, [this](QString reason) - { - QString error = QString("Instance load failed: %1").arg(reason); - CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); - }); - if(update) - { - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(update.get()); - } - } - else - { - CustomMessageBox::selectable(this, tr("Error"), tr("MultiMC cannot download Minecraft or update instances unless you have at least " - "one account added.\nPlease add your Mojang or Minecraft account."), - QMessageBox::Warning) - ->show(); - } + view->updateGeometries(); + setSelectedInstanceById(inst->id()); + if (MMC->accounts()->anyAccountIsValid()) + { + ProgressDialog loadDialog(this); + auto update = inst->createUpdateTask(Net::Mode::Online); + connect(update.get(), &Task::failed, [this](QString reason) + { + QString error = QString("Instance load failed: %1").arg(reason); + CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); + }); + if(update) + { + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(update.get()); + } + } + else + { + CustomMessageBox::selectable(this, tr("Error"), tr("MultiMC cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning) + ->show(); + } } void MainWindow::addInstance(QString url) { - QString groupName; - do - { - QObject* obj = sender(); - if(!obj) - break; - QAction *action = qobject_cast(obj); - if(!action) - break; - auto map = action->data().toMap(); - if(!map.contains("group")) - break; - groupName = map["group"].toString(); - } while(0); - - if(groupName.isEmpty()) - { - groupName = MMC->settings()->get("LastUsedGroupForNewInstance").toString(); - } - - NewInstanceDialog newInstDlg(groupName, url, this); - if (!newInstDlg.exec()) - return; - - MMC->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); - - InstanceTask * creationTask = newInstDlg.extractTask(); - if(creationTask) - { - instanceFromInstanceTask(creationTask); - } + QString groupName; + do + { + QObject* obj = sender(); + if(!obj) + break; + QAction *action = qobject_cast(obj); + if(!action) + break; + auto map = action->data().toMap(); + if(!map.contains("group")) + break; + groupName = map["group"].toString(); + } while(0); + + if(groupName.isEmpty()) + { + groupName = MMC->settings()->get("LastUsedGroupForNewInstance").toString(); + } + + NewInstanceDialog newInstDlg(groupName, url, this); + if (!newInstDlg.exec()) + return; + + MMC->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); + + InstanceTask * creationTask = newInstDlg.extractTask(); + if(creationTask) + { + instanceFromInstanceTask(creationTask); + } } void MainWindow::on_actionAddInstance_triggered() { - addInstance(); + addInstance(); } void MainWindow::droppedURLs(QList urls) { - for(auto & url:urls) - { - if(url.isLocalFile()) - { - addInstance(url.toLocalFile()); - } - else - { - addInstance(url.toString()); - } - // Only process one dropped file... - break; - } + for(auto & url:urls) + { + if(url.isLocalFile()) + { + addInstance(url.toLocalFile()); + } + else + { + addInstance(url.toString()); + } + // Only process one dropped file... + break; + } } void MainWindow::on_actionREDDIT_triggered() { - DesktopServices::openUrl(QUrl("https://www.reddit.com/r/MultiMC/")); + DesktopServices::openUrl(QUrl("https://www.reddit.com/r/MultiMC/")); } void MainWindow::on_actionDISCORD_triggered() { - DesktopServices::openUrl(QUrl("https://discord.gg/0k2zsXGNHs0fE4Wm")); + DesktopServices::openUrl(QUrl("https://discord.gg/0k2zsXGNHs0fE4Wm")); } void MainWindow::on_actionChangeInstIcon_triggered() { - if (!m_selectedInstance) - return; + if (!m_selectedInstance) + return; - IconPickerDialog dlg(this); - dlg.execWithSelection(m_selectedInstance->iconKey()); - if (dlg.result() == QDialog::Accepted) - { - m_selectedInstance->setIconKey(dlg.selectedIconKey); - auto icon = MMC->icons()->getIcon(dlg.selectedIconKey); - ui->actionChangeInstIcon->setIcon(icon); - ui->changeIconButton->setIcon(icon); - } + IconPickerDialog dlg(this); + dlg.execWithSelection(m_selectedInstance->iconKey()); + if (dlg.result() == QDialog::Accepted) + { + m_selectedInstance->setIconKey(dlg.selectedIconKey); + auto icon = MMC->icons()->getIcon(dlg.selectedIconKey); + ui->actionChangeInstIcon->setIcon(icon); + ui->changeIconButton->setIcon(icon); + } } void MainWindow::iconUpdated(QString icon) { - if (icon == m_currentInstIcon) - { - auto icon = MMC->icons()->getIcon(m_currentInstIcon); - ui->actionChangeInstIcon->setIcon(icon); - ui->changeIconButton->setIcon(icon); - } + if (icon == m_currentInstIcon) + { + auto icon = MMC->icons()->getIcon(m_currentInstIcon); + ui->actionChangeInstIcon->setIcon(icon); + ui->changeIconButton->setIcon(icon); + } } void MainWindow::updateInstanceToolIcon(QString new_icon) { - m_currentInstIcon = new_icon; - auto icon = MMC->icons()->getIcon(m_currentInstIcon); - ui->actionChangeInstIcon->setIcon(icon); - ui->changeIconButton->setIcon(icon); + m_currentInstIcon = new_icon; + auto icon = MMC->icons()->getIcon(m_currentInstIcon); + ui->actionChangeInstIcon->setIcon(icon); + ui->changeIconButton->setIcon(icon); } void MainWindow::setSelectedInstanceById(const QString &id) { - if (id.isNull()) - return; - const QModelIndex index = MMC->instances()->getInstanceIndexById(id); - if (index.isValid()) - { - QModelIndex selectionIndex = proxymodel->mapFromSource(index); - view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); - } + if (id.isNull()) + return; + const QModelIndex index = MMC->instances()->getInstanceIndexById(id); + if (index.isValid()) + { + QModelIndex selectionIndex = proxymodel->mapFromSource(index); + view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); + } } void MainWindow::on_actionChangeInstGroup_triggered() { - if (!m_selectedInstance) - return; + if (!m_selectedInstance) + return; - bool ok = false; - QString name(m_selectedInstance->group()); - auto groups = MMC->instances()->getGroups(); - groups.insert(0, ""); - groups.sort(Qt::CaseInsensitive); - int foo = groups.indexOf(name); + bool ok = false; + InstanceId instId = m_selectedInstance->id(); + QString name(MMC->instances()->getInstanceGroup(instId)); + auto groups = MMC->instances()->getGroups(); + groups.insert(0, ""); + groups.sort(Qt::CaseInsensitive); + int foo = groups.indexOf(name); - name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok); - name = name.simplified(); - if (ok) - m_selectedInstance->setGroupPost(name); + name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok); + name = name.simplified(); + if (ok) + { + MMC->instances()->setInstanceGroup(instId, name); + } } void MainWindow::deleteGroup() { - QObject* obj = sender(); - if(!obj) - return; - QAction *action = qobject_cast(obj); - if(!action) - return; - auto map = action->data().toMap(); - if(!map.contains("group")) - return; - QString groupName = map["group"].toString(); - if(!groupName.isEmpty()) - { - MMC->instances()->deleteGroup(groupName); - } + QObject* obj = sender(); + if(!obj) + return; + QAction *action = qobject_cast(obj); + if(!action) + return; + auto map = action->data().toMap(); + if(!map.contains("group")) + return; + QString groupName = map["group"].toString(); + if(!groupName.isEmpty()) + { + auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1") + .arg(groupName), QMessageBox::Yes | QMessageBox::No); + if(reply == QMessageBox::Yes) + { + MMC->instances()->deleteGroup(groupName); + } + } } void MainWindow::on_actionViewInstanceFolder_triggered() { - QString str = MMC->settings()->get("InstanceDir").toString(); - DesktopServices::openDirectory(str); + QString str = MMC->settings()->get("InstanceDir").toString(); + DesktopServices::openDirectory(str); } void MainWindow::refreshInstances() { - MMC->instances()->loadList(true); + MMC->instances()->loadList(); } void MainWindow::on_actionViewCentralModsFolder_triggered() { - DesktopServices::openDirectory(MMC->settings()->get("CentralModsDir").toString(), true); + DesktopServices::openDirectory(MMC->settings()->get("CentralModsDir").toString(), true); } void MainWindow::on_actionConfig_Folder_triggered() { - if (m_selectedInstance) - { - QString str = m_selectedInstance->instanceConfigFolder(); - DesktopServices::openDirectory(QDir(str).absolutePath()); - } + if (m_selectedInstance) + { + QString str = m_selectedInstance->instanceConfigFolder(); + DesktopServices::openDirectory(QDir(str).absolutePath()); + } } void MainWindow::checkForUpdates() { - if(BuildConfig.UPDATER_ENABLED) - { - auto updater = MMC->updateChecker(); - updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), true); - } - else - { - qWarning() << "Updater not set up. Cannot check for updates."; - } + if(BuildConfig.UPDATER_ENABLED) + { + auto updater = MMC->updateChecker(); + updater->checkForUpdate(MMC->settings()->get("UpdateChannel").toString(), true); + } + else + { + qWarning() << "Updater not set up. Cannot check for updates."; + } } void MainWindow::on_actionSettings_triggered() { - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "global-settings"); - // FIXME: quick HACK to make this work. improve, optimize. - MMC->instances()->loadList(true); - proxymodel->invalidate(); - proxymodel->sort(0); - updateToolsMenu(); - update(); + MMC->ShowGlobalSettings(this, "global-settings"); +} + +void MainWindow::globalSettingsClosed() +{ + // FIXME: quick HACK to make this work. improve, optimize. + MMC->instances()->loadList(); + proxymodel->invalidate(); + proxymodel->sort(0); + updateToolsMenu(); + update(); } void MainWindow::on_actionInstanceSettings_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "settings"); + MMC->showInstanceWindow(m_selectedInstance, "settings"); } void MainWindow::on_actionEditInstNotes_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "notes"); + MMC->showInstanceWindow(m_selectedInstance, "notes"); } void MainWindow::on_actionWorlds_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "worlds"); + MMC->showInstanceWindow(m_selectedInstance, "worlds"); } void MainWindow::on_actionEditInstance_triggered() { - MMC->showInstanceWindow(m_selectedInstance); + MMC->showInstanceWindow(m_selectedInstance); } void MainWindow::on_actionScreenshots_triggered() { - MMC->showInstanceWindow(m_selectedInstance, "screenshots"); + MMC->showInstanceWindow(m_selectedInstance, "screenshots"); } void MainWindow::on_actionManageAccounts_triggered() { - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "accounts"); + MMC->ShowGlobalSettings(this, "accounts"); } void MainWindow::on_actionReportBug_triggered() { - DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/issues")); + DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/issues")); } void MainWindow::on_actionPatreon_triggered() { - DesktopServices::openUrl(QUrl("http://www.patreon.com/multimc")); + DesktopServices::openUrl(QUrl("https://www.patreon.com/multimc")); } void MainWindow::on_actionMoreNews_triggered() { - DesktopServices::openUrl(QUrl("http://multimc.org/posts.html")); + DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); } void MainWindow::newsButtonClicked() { - QList entries = m_newsChecker->getNewsEntries(); - if (entries.count() > 0) - { - DesktopServices::openUrl(QUrl(entries[0]->link)); - } - else - { - DesktopServices::openUrl(QUrl("http://multimc.org/posts.html")); - } + QList entries = m_newsChecker->getNewsEntries(); + if (entries.count() > 0) + { + DesktopServices::openUrl(QUrl(entries[0]->link)); + } + else + { + DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); + } } void MainWindow::on_actionAbout_triggered() { - AboutDialog dialog(this); - dialog.exec(); -} - -void MainWindow::on_mainToolBar_visibilityChanged(bool) -{ - // Don't allow hiding the main toolbar. - // This is the only way I could find to prevent it... :/ - ui->mainToolBar->setVisible(true); + AboutDialog dialog(this); + dialog.exec(); } void MainWindow::on_actionDeleteInstance_triggered() { - if (!m_selectedInstance) - { - return; - } - auto response = CustomMessageBox::selectable( - this, - tr("CAREFUL!"), - tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()), - QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No - )->exec(); - if (response == QMessageBox::Yes) - { - m_selectedInstance->nuke(); - } + if (!m_selectedInstance) + { + return; + } + auto id = m_selectedInstance->id(); + auto response = CustomMessageBox::selectable( + this, + tr("CAREFUL!"), + tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()), + QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No + )->exec(); + if (response == QMessageBox::Yes) + { + MMC->instances()->deleteInstance(id); + } } void MainWindow::on_actionExportInstance_triggered() { - if (m_selectedInstance) - { - ExportInstanceDialog dlg(m_selectedInstance, this); - dlg.exec(); - } + if (m_selectedInstance) + { + ExportInstanceDialog dlg(m_selectedInstance, this); + dlg.exec(); + } } void MainWindow::on_actionRenameInstance_triggered() { - if (m_selectedInstance) - { - bool ok = false; - QString name(m_selectedInstance->name()); - name = QInputDialog::getText(this, tr("Instance name"), tr("Enter a new instance name."), QLineEdit::Normal, name, &ok); - - name = name.trimmed(); - if (name.length() > 0) - { - if (ok && name.length()) - { - m_selectedInstance->setName(name); - ui->renameButton->setText(name); - } - } - } + if (m_selectedInstance) + { + view->edit(view->currentIndex()); + } } void MainWindow::on_actionViewSelectedInstFolder_triggered() { - if (m_selectedInstance) - { - QString str = m_selectedInstance->instanceRoot(); - DesktopServices::openDirectory(QDir(str).absolutePath()); - } + if (m_selectedInstance) + { + QString str = m_selectedInstance->instanceRoot(); + DesktopServices::openDirectory(QDir(str).absolutePath()); + } } +void MainWindow::on_actionViewSelectedMCFolder_triggered() +{ + if (m_selectedInstance) + { + QString str = m_selectedInstance->gameRoot(); + if (!FS::ensureFilePathExists(str)) + { + // TODO: report error + return; + } + DesktopServices::openDirectory(QDir(str).absolutePath()); + } +} + + void MainWindow::closeEvent(QCloseEvent *event) { - // Save the window state and geometry. - MMC->settings()->set("MainWindowState", saveState().toBase64()); - MMC->settings()->set("MainWindowGeometry", saveGeometry().toBase64()); - event->accept(); - emit isClosing(); + // Save the window state and geometry. + MMC->settings()->set("MainWindowState", saveState().toBase64()); + MMC->settings()->set("MainWindowGeometry", saveGeometry().toBase64()); + event->accept(); + emit isClosing(); } void MainWindow::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) - { - ui->retranslateUi(this); - } - QMainWindow::changeEvent(event); + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + QMainWindow::changeEvent(event); } void MainWindow::instanceActivated(QModelIndex index) { - if (!index.isValid()) - return; - QString id = index.data(InstanceList::InstanceIDRole).toString(); - InstancePtr inst = MMC->instances()->getInstanceById(id); - if (!inst) - return; + if (!index.isValid()) + return; + QString id = index.data(InstanceList::InstanceIDRole).toString(); + InstancePtr inst = MMC->instances()->getInstanceById(id); + if (!inst) + return; - activateInstance(inst); + activateInstance(inst); } void MainWindow::on_actionLaunchInstance_triggered() { - if (!m_selectedInstance) - { - return; - } - if(m_selectedInstance->isRunning()) - { - MMC->kill(m_selectedInstance); - } - else - { - MMC->launch(m_selectedInstance); - } + if (!m_selectedInstance) + { + return; + } + if(m_selectedInstance->isRunning()) + { + MMC->kill(m_selectedInstance); + } + else + { + MMC->launch(m_selectedInstance); + } } void MainWindow::activateInstance(InstancePtr instance) { - MMC->launch(instance); + MMC->launch(instance); } void MainWindow::on_actionLaunchInstanceOffline_triggered() { - if (m_selectedInstance) - { - MMC->launch(m_selectedInstance, false); - } + if (m_selectedInstance) + { + MMC->launch(m_selectedInstance, false); + } } void MainWindow::taskEnd() { - QObject *sender = QObject::sender(); - if (sender == m_versionLoadTask) - m_versionLoadTask = NULL; + QObject *sender = QObject::sender(); + if (sender == m_versionLoadTask) + m_versionLoadTask = NULL; - sender->deleteLater(); + sender->deleteLater(); } void MainWindow::startTask(Task *task) { - connect(task, SIGNAL(succeeded()), SLOT(taskEnd())); - connect(task, SIGNAL(failed(QString)), SLOT(taskEnd())); - task->start(); + connect(task, SIGNAL(succeeded()), SLOT(taskEnd())); + connect(task, SIGNAL(failed(QString)), SLOT(taskEnd())); + task->start(); } void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &previous) { - if (!current.isValid()) - { - MMC->settings()->set("SelectedInstance", QString()); - selectionBad(); - return; - } - QString id = current.data(InstanceList::InstanceIDRole).toString(); - m_selectedInstance = MMC->instances()->getInstanceById(id); - if (m_selectedInstance) - { - ui->instanceToolBar->setEnabled(true); - if(m_selectedInstance->isRunning()) - { - ui->actionLaunchInstance->setEnabled(true); - ui->setLaunchAction(true); - } - else - { - ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); - ui->setLaunchAction(false); - } - ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); - ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); - ui->renameButton->setText(m_selectedInstance->name()); - m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); - updateInstanceToolIcon(m_selectedInstance->iconKey()); - - updateToolsMenu(); - - MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); - } - else - { - ui->instanceToolBar->setEnabled(false); - MMC->settings()->set("SelectedInstance", QString()); - selectionBad(); - return; - } + if (!current.isValid()) + { + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); + return; + } + QString id = current.data(InstanceList::InstanceIDRole).toString(); + m_selectedInstance = MMC->instances()->getInstanceById(id); + if (m_selectedInstance) + { + ui->instanceToolBar->setEnabled(true); + if(m_selectedInstance->isRunning()) + { + ui->actionLaunchInstance->setEnabled(true); + ui->setLaunchAction(true); + } + else + { + ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); + ui->setLaunchAction(false); + } + ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); + ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); + ui->renameButton->setText(m_selectedInstance->name()); + m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); + updateInstanceToolIcon(m_selectedInstance->iconKey()); + + updateToolsMenu(); + + MMC->settings()->set("SelectedInstance", m_selectedInstance->id()); + } + else + { + ui->instanceToolBar->setEnabled(false); + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); + return; + } +} + +void MainWindow::instanceSelectRequest(QString id) +{ + setSelectedInstanceById(id); } void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { - auto current = view->selectionModel()->currentIndex(); - QItemSelection test(topLeft, bottomRight); - if (test.contains(current)) - { - instanceChanged(current, current); - } + auto current = view->selectionModel()->currentIndex(); + QItemSelection test(topLeft, bottomRight); + if (test.contains(current)) + { + instanceChanged(current, current); + } } void MainWindow::selectionBad() { - // start by reseting everything... - m_selectedInstance = nullptr; + // start by reseting everything... + m_selectedInstance = nullptr; - statusBar()->clearMessage(); - ui->instanceToolBar->setEnabled(false); - ui->renameButton->setText(tr("Rename Instance")); - updateInstanceToolIcon("infinity"); + statusBar()->clearMessage(); + ui->instanceToolBar->setEnabled(false); + ui->renameButton->setText(tr("Rename Instance")); + updateInstanceToolIcon("infinity"); - // ...and then see if we can enable the previously selected instance - setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); + // ...and then see if we can enable the previously selected instance + setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); } void MainWindow::checkInstancePathForProblems() { - QString instanceFolder = MMC->settings()->get("InstanceDir").toString(); - if (FS::checkProblemticPathJava(QDir(instanceFolder))) - { - QMessageBox warning(this); - warning.setText(tr("Your instance folder contains \'!\' and this is known to cause Java problems!")); - warning.setInformativeText(tr("You have now two options:
" - " - change the instance folder in the settings
" - " - move this installation of MultiMC5 to a different folder")); - warning.setDefaultButton(QMessageBox::Ok); - warning.exec(); - } - auto tempFolderText = tr("This is a problem:
" - " - MultiMC will likely be deleted without warning by the operating system
" - " - close MultiMC now and extract it to a real location, not a temporary folder"); - QString pathfoldername = QDir(instanceFolder).absolutePath(); - if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) - { - QMessageBox warning(this); - warning.setText(tr("Your instance folder contains \'Rar$\' - that means you haven't extracted the MultiMC zip!")); - warning.setInformativeText(tempFolderText); - warning.setDefaultButton(QMessageBox::Ok); - warning.exec(); - } - else if (pathfoldername.contains(QDir::tempPath())) - { - QMessageBox warning(this); - warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath())); - warning.setInformativeText(tempFolderText); - warning.setDefaultButton(QMessageBox::Ok); - warning.exec(); - } + QString instanceFolder = MMC->settings()->get("InstanceDir").toString(); + if (FS::checkProblemticPathJava(QDir(instanceFolder))) + { + QMessageBox warning(this); + warning.setText(tr("Your instance folder contains \'!\' and this is known to cause Java problems!")); + warning.setInformativeText(tr("You have now two options:
" + " - change the instance folder in the settings
" + " - move this installation of MultiMC5 to a different folder")); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + } + auto tempFolderText = tr("This is a problem:
" + " - MultiMC will likely be deleted without warning by the operating system
" + " - close MultiMC now and extract it to a real location, not a temporary folder"); + QString pathfoldername = QDir(instanceFolder).absolutePath(); + if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) + { + QMessageBox warning(this); + warning.setText(tr("Your instance folder contains \'Rar$\' - that means you haven't extracted the MultiMC zip!")); + warning.setInformativeText(tempFolderText); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + } + else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/")) + { + QMessageBox warning(this); + warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath())); + warning.setInformativeText(tempFolderText); + warning.setDefaultButton(QMessageBox::Ok); + warning.exec(); + } } diff --git a/application/MainWindow.h b/application/MainWindow.h index 8f756412..a415b5e8 100644 --- a/application/MainWindow.h +++ b/application/MainWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,174 +42,181 @@ class InstanceTask; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT - class Ui; + class Ui; public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); - bool eventFilter(QObject *obj, QEvent *ev) override; - void closeEvent(QCloseEvent *event) override; - void changeEvent(QEvent * event) override; + bool eventFilter(QObject *obj, QEvent *ev) override; + void closeEvent(QCloseEvent *event) override; + void changeEvent(QEvent * event) override; - void checkInstancePathForProblems(); + void checkInstancePathForProblems(); - void updatesAllowedChanged(bool allowed); + void updatesAllowedChanged(bool allowed); signals: - void isClosing(); + void isClosing(); + +protected: + QMenu * createPopupMenu() override; private slots: - void onCatToggled(bool); + void onCatToggled(bool); + + void on_actionAbout_triggered(); + + void on_actionAddInstance_triggered(); - void on_actionAbout_triggered(); + void on_actionREDDIT_triggered(); - void on_actionAddInstance_triggered(); + void on_actionDISCORD_triggered(); - void on_actionREDDIT_triggered(); + void on_actionCopyInstance_triggered(); - void on_actionDISCORD_triggered(); + void on_actionChangeInstGroup_triggered(); - void on_actionCopyInstance_triggered(); + void on_actionChangeInstIcon_triggered(); + void on_changeIconButton_clicked(bool) + { + on_actionChangeInstIcon_triggered(); + } - void on_actionChangeInstGroup_triggered(); + void on_actionViewInstanceFolder_triggered(); - void on_actionChangeInstIcon_triggered(); - void on_changeIconButton_clicked(bool) - { - on_actionChangeInstIcon_triggered(); - } + void on_actionConfig_Folder_triggered(); - void on_actionViewInstanceFolder_triggered(); + void on_actionViewSelectedInstFolder_triggered(); - void on_actionConfig_Folder_triggered(); + void on_actionViewSelectedMCFolder_triggered(); - void on_actionViewSelectedInstFolder_triggered(); + void refreshInstances(); - void refreshInstances(); + void on_actionViewCentralModsFolder_triggered(); - void on_actionViewCentralModsFolder_triggered(); + void checkForUpdates(); - void checkForUpdates(); + void on_actionSettings_triggered(); - void on_actionSettings_triggered(); + void on_actionInstanceSettings_triggered(); - void on_actionInstanceSettings_triggered(); + void on_actionManageAccounts_triggered(); - void on_actionManageAccounts_triggered(); + void on_actionReportBug_triggered(); - void on_actionReportBug_triggered(); + void on_actionPatreon_triggered(); - void on_actionPatreon_triggered(); + void on_actionMoreNews_triggered(); - void on_actionMoreNews_triggered(); + void newsButtonClicked(); - void newsButtonClicked(); + void on_actionLaunchInstance_triggered(); - void on_mainToolBar_visibilityChanged(bool); + void on_actionLaunchInstanceOffline_triggered(); - void on_actionLaunchInstance_triggered(); + void on_actionDeleteInstance_triggered(); - void on_actionLaunchInstanceOffline_triggered(); + void deleteGroup(); - void on_actionDeleteInstance_triggered(); + void on_actionExportInstance_triggered(); - void deleteGroup(); + void on_actionRenameInstance_triggered(); + void on_renameButton_clicked(bool) + { + on_actionRenameInstance_triggered(); + } - void on_actionExportInstance_triggered(); + void on_actionEditInstance_triggered(); - void on_actionRenameInstance_triggered(); - void on_renameButton_clicked(bool) - { - on_actionRenameInstance_triggered(); - } + void on_actionEditInstNotes_triggered(); - void on_actionEditInstance_triggered(); + void on_actionWorlds_triggered(); - void on_actionEditInstNotes_triggered(); + void on_actionScreenshots_triggered(); - void on_actionWorlds_triggered(); + void taskEnd(); - void on_actionScreenshots_triggered(); + /** + * called when an icon is changed in the icon model. + */ + void iconUpdated(QString); - void taskEnd(); + void showInstanceContextMenu(const QPoint &); - /** - * called when an icon is changed in the icon model. - */ - void iconUpdated(QString); + void updateToolsMenu(); - void showInstanceContextMenu(const QPoint &); + void skinJobFinished(); - void updateToolsMenu(); + void instanceActivated(QModelIndex); - void skinJobFinished(); + void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); - void instanceActivated(QModelIndex); + void instanceSelectRequest(QString id); - void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); + void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void selectionBad(); - void selectionBad(); + void startTask(Task *task); - void startTask(Task *task); + void updateAvailable(GoUpdate::Status status); - void updateAvailable(GoUpdate::Status status); + void updateNotAvailable(); - void updateNotAvailable(); + void notificationsChanged(); - void notificationsChanged(); + void activeAccountChanged(); - void activeAccountChanged(); + void changeActiveAccount(); - void changeActiveAccount(); + void repopulateAccountsMenu(); - void repopulateAccountsMenu(); + void updateNewsLabel(); - void updateNewsLabel(); + /*! + * Runs the DownloadTask and installs updates. + */ + void downloadUpdates(GoUpdate::Status status); - /*! - * Runs the DownloadTask and installs updates. - */ - void downloadUpdates(GoUpdate::Status status); + void droppedURLs(QList urls); - void droppedURLs(QList urls); + void konamiTriggered(); - void konamiTriggered(); + void globalSettingsClosed(); private: - void addInstance(QString url = QString()); - void activateInstance(InstancePtr instance); - void setCatBackground(bool enabled); - void updateInstanceToolIcon(QString new_icon); - void setSelectedInstanceById(const QString &id); + void addInstance(QString url = QString()); + void activateInstance(InstancePtr instance); + void setCatBackground(bool enabled); + void updateInstanceToolIcon(QString new_icon); + void setSelectedInstanceById(const QString &id); - void runModalTask(Task *task); - void instanceFromInstanceTask(InstanceTask *task); - void finalizeInstance(InstancePtr inst); + void runModalTask(Task *task); + void instanceFromInstanceTask(InstanceTask *task); + void finalizeInstance(InstancePtr inst); private: - std::unique_ptr ui; - - // these are managed by Qt's memory management model! - GroupView *view = nullptr; - InstanceProxyModel *proxymodel = nullptr; - QToolButton *newsLabel = nullptr; - QLabel *m_statusLeft = nullptr; - ServerStatus *m_statusRight = nullptr; - QMenu *accountMenu = nullptr; - QToolButton *accountMenuButton = nullptr; - KonamiCode * secretEventFilter = nullptr; - - unique_qobject_ptr skin_download_job; - unique_qobject_ptr m_newsChecker; - unique_qobject_ptr m_notificationChecker; - - InstancePtr m_selectedInstance; - QString m_currentInstIcon; - - // managed by the application object - Task *m_versionLoadTask = nullptr; + std::unique_ptr ui; + + // these are managed by Qt's memory management model! + GroupView *view = nullptr; + InstanceProxyModel *proxymodel = nullptr; + QToolButton *newsLabel = nullptr; + QLabel *m_statusLeft = nullptr; + ServerStatus *m_statusRight = nullptr; + QMenu *accountMenu = nullptr; + QToolButton *accountMenuButton = nullptr; + KonamiCode * secretEventFilter = nullptr; + + unique_qobject_ptr skin_download_job; + unique_qobject_ptr m_newsChecker; + unique_qobject_ptr m_notificationChecker; + + InstancePtr m_selectedInstance; + QString m_currentInstIcon; + + // managed by the application object + Task *m_versionLoadTask = nullptr; }; diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index 9e35717a..5626b672 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -2,10 +2,15 @@ #include "BuildConfig.h" #include "MainWindow.h" #include "InstanceWindow.h" + +#include "groupview/AccessibleGroupView.h" +#include + #include "pages/BasePageProvider.h" #include "pages/global/MultiMCPage.h" #include "pages/global/MinecraftPage.h" #include "pages/global/JavaPage.h" +#include "pages/global/LanguagePage.h" #include "pages/global/ProxyPage.h" #include "pages/global/ExternalToolsPage.h" #include "pages/global/AccountListPage.h" @@ -36,7 +41,6 @@ #include "dialogs/CustomMessageBox.h" #include "InstanceList.h" -#include "FolderInstanceProvider.h" #include #include "icons/IconList.h" @@ -65,6 +69,8 @@ #include +#include "pagedialog/PageDialog.h" + #if defined Q_OS_WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -82,1148 +88,1041 @@ static const QLatin1String liveCheckFile("live.check"); using namespace Commandline; #define MACOS_HINT "If you are on macOS Sierra, you might have to move MultiMC.app to your /Applications or ~/Applications folder. "\ - "This usually fixes the problem and you can move the application elsewhere afterwards.\n"\ - "\n" + "This usually fixes the problem and you can move the application elsewhere afterwards.\n"\ + "\n" static void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - const char *levels = "DWCFIS"; - const QString format("%1 %2 %3\n"); - - qint64 msecstotal = MMC->timeSinceStart(); - qint64 seconds = msecstotal / 1000; - qint64 msecs = msecstotal % 1000; - QString foo; - char buf[1025] = {0}; - ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs); - - QString out = format.arg(buf).arg(levels[type]).arg(msg); - - MMC->logFile->write(out.toUtf8()); - MMC->logFile->flush(); - QTextStream(stderr) << out.toLocal8Bit(); - fflush(stderr); + const char *levels = "DWCFIS"; + const QString format("%1 %2 %3\n"); + + qint64 msecstotal = MMC->timeSinceStart(); + qint64 seconds = msecstotal / 1000; + qint64 msecs = msecstotal % 1000; + QString foo; + char buf[1025] = {0}; + ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs); + + QString out = format.arg(buf).arg(levels[type]).arg(msg); + + MMC->logFile->write(out.toUtf8()); + MMC->logFile->flush(); + QTextStream(stderr) << out.toLocal8Bit(); + fflush(stderr); } MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { #if defined Q_OS_WIN32 - // attach the parent console - if(AttachConsole(ATTACH_PARENT_PROCESS)) - { - // if attach succeeds, reopen and sync all the i/o - if(freopen("CON", "w", stdout)) - { - std::cout.sync_with_stdio(); - } - if(freopen("CON", "w", stderr)) - { - std::cerr.sync_with_stdio(); - } - if(freopen("CON", "r", stdin)) - { - std::cin.sync_with_stdio(); - } - auto out = GetStdHandle (STD_OUTPUT_HANDLE); - DWORD written; - const char * endline = "\n"; - WriteConsole(out, endline, strlen(endline), &written, NULL); - consoleAttached = true; - } + // attach the parent console + if(AttachConsole(ATTACH_PARENT_PROCESS)) + { + // if attach succeeds, reopen and sync all the i/o + if(freopen("CON", "w", stdout)) + { + std::cout.sync_with_stdio(); + } + if(freopen("CON", "w", stderr)) + { + std::cerr.sync_with_stdio(); + } + if(freopen("CON", "r", stdin)) + { + std::cin.sync_with_stdio(); + } + auto out = GetStdHandle (STD_OUTPUT_HANDLE); + DWORD written; + const char * endline = "\n"; + WriteConsole(out, endline, strlen(endline), &written, NULL); + consoleAttached = true; + } #endif - setOrganizationName("MultiMC"); - setOrganizationDomain("multimc.org"); - setApplicationName("MultiMC5"); - setApplicationDisplayName("MultiMC 5"); - setApplicationVersion(BuildConfig.printableVersionString()); - - startTime = QDateTime::currentDateTime(); - - // Don't quit on hiding the last window - this->setQuitOnLastWindowClosed(false); - - // Commandline parsing - QHash args; - { - Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); - - // --help - parser.addSwitch("help"); - parser.addShortOpt("help", 'h'); - parser.addDocumentation("help", "display this help and exit."); - // --version - parser.addSwitch("version"); - parser.addShortOpt("version", 'V'); - parser.addDocumentation("version", "display program version and exit."); - // --dir - parser.addOption("dir"); - parser.addShortOpt("dir", 'd'); - parser.addDocumentation("dir", "use the supplied folder as MultiMC root instead of " - "the binary location (use '.' for current)"); - // --launch - parser.addOption("launch"); - parser.addShortOpt("launch", 'l'); - parser.addDocumentation("launch", "launch the specified instance (by instance ID)"); - // --alive - parser.addSwitch("alive"); - parser.addDocumentation("alive", "write a small '" + liveCheckFile + "' file after MultiMC starts"); - - // parse the arguments - try - { - args = parser.parse(arguments()); - } - catch (ParsingError e) - { - std::cerr << "CommandLineError: " << e.what() << std::endl; - std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." - << std::endl; - m_status = MultiMC::Failed; - return; - } - - // display help and exit - if (args["help"].toBool()) - { - std::cout << qPrintable(parser.compileHelp(arguments()[0])); - m_status = MultiMC::Succeeded; - return; - } - - // display version and exit - if (args["version"].toBool()) - { - std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl; - std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl; - m_status = MultiMC::Succeeded; - return; - } - } - m_instanceIdToLaunch = args["launch"].toString(); - m_liveCheck = args["alive"].toBool(); - - QString origcwdPath = QDir::currentPath(); - QString binPath = applicationDirPath(); - QString adjustedBy; - QString dataPath; - // change folder - QString dirParam = args["dir"].toString(); - if (!dirParam.isEmpty()) - { - // the dir param. it makes multimc data path point to whatever the user specified - // on command line - adjustedBy += "Command line " + dirParam; - dataPath = dirParam; - } - else - { + setOrganizationName("MultiMC"); + setOrganizationDomain("multimc.org"); + setApplicationName("MultiMC5"); + setApplicationDisplayName("MultiMC 5"); + setApplicationVersion(BuildConfig.printableVersionString()); + + startTime = QDateTime::currentDateTime(); + + // Don't quit on hiding the last window + this->setQuitOnLastWindowClosed(false); + + // Commandline parsing + QHash args; + { + Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); + + // --help + parser.addSwitch("help"); + parser.addShortOpt("help", 'h'); + parser.addDocumentation("help", "display this help and exit."); + // --version + parser.addSwitch("version"); + parser.addShortOpt("version", 'V'); + parser.addDocumentation("version", "display program version and exit."); + // --dir + parser.addOption("dir"); + parser.addShortOpt("dir", 'd'); + parser.addDocumentation("dir", "use the supplied folder as MultiMC root instead of " + "the binary location (use '.' for current)"); + // --launch + parser.addOption("launch"); + parser.addShortOpt("launch", 'l'); + parser.addDocumentation("launch", "launch the specified instance (by instance ID)"); + // --alive + parser.addSwitch("alive"); + parser.addDocumentation("alive", "write a small '" + liveCheckFile + "' file after MultiMC starts"); + + // parse the arguments + try + { + args = parser.parse(arguments()); + } + catch (const ParsingError &e) + { + std::cerr << "CommandLineError: " << e.what() << std::endl; + std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." + << std::endl; + m_status = MultiMC::Failed; + return; + } + + // display help and exit + if (args["help"].toBool()) + { + std::cout << qPrintable(parser.compileHelp(arguments()[0])); + m_status = MultiMC::Succeeded; + return; + } + + // display version and exit + if (args["version"].toBool()) + { + std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl; + std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl; + m_status = MultiMC::Succeeded; + return; + } + } + m_instanceIdToLaunch = args["launch"].toString(); + m_liveCheck = args["alive"].toBool(); + + QString origcwdPath = QDir::currentPath(); + QString binPath = applicationDirPath(); + QString adjustedBy; + QString dataPath; + // change folder + QString dirParam = args["dir"].toString(); + if (!dirParam.isEmpty()) + { + // the dir param. it makes multimc data path point to whatever the user specified + // on command line + adjustedBy += "Command line " + dirParam; + dataPath = dirParam; + } + else + { #ifdef MULTIMC_LINUX_DATADIR - QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); - if (xdgDataHome.isEmpty()) - xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); - dataPath = xdgDataHome + "/multimc"; - adjustedBy += "XDG standard " + dataPath; + QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); + if (xdgDataHome.isEmpty()) + xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); + dataPath = xdgDataHome + "/multimc"; + adjustedBy += "XDG standard " + dataPath; #else - dataPath = applicationDirPath(); - adjustedBy += "Fallback to binary path " + dataPath; + dataPath = applicationDirPath(); + adjustedBy += "Fallback to binary path " + dataPath; #endif - } - - if (!FS::ensureFolderPathExists(dataPath)) - { - showFatalErrorMessage( - "MultiMC data folder could not be created.", - "MultiMC data folder could not be created.\n" - "\n" + } + + if (!FS::ensureFolderPathExists(dataPath)) + { + showFatalErrorMessage( + "MultiMC data folder could not be created.", + "MultiMC data folder could not be created.\n" + "\n" #if defined(Q_OS_MAC) - MACOS_HINT + MACOS_HINT #endif - "Make sure you have the right permissions to the MultiMC data folder and any folder needed to access it.\n" - "\n" - "MultiMC cannot continue until you fix this problem." - ); - return; - } - if (!QDir::setCurrent(dataPath)) - { - showFatalErrorMessage( - "MultiMC data folder could not be opened.", - "MultiMC data folder could not be opened.\n" - "\n" + "Make sure you have the right permissions to the MultiMC data folder and any folder needed to access it.\n" + "\n" + "MultiMC cannot continue until you fix this problem." + ); + return; + } + if (!QDir::setCurrent(dataPath)) + { + showFatalErrorMessage( + "MultiMC data folder could not be opened.", + "MultiMC data folder could not be opened.\n" + "\n" #if defined(Q_OS_MAC) - MACOS_HINT + MACOS_HINT #endif - "Make sure you have the right permissions to the MultiMC data folder.\n" - "\n" - "MultiMC cannot continue until you fix this problem." - ); - return; - } - - /* - * Establish the mechanism for communication with an already running MultiMC that uses the same data path. - * If there is one, tell it what the user actually wanted to do and exit. - * We want to initialize this before logging to avoid messing with the log of a potential already running copy. - */ - auto appID = ApplicationId::fromPathAndVersion(QDir::currentPath(), BuildConfig.printableVersionString()); - { - // FIXME: you can run the same binaries with multiple data dirs and they won't clash. This could cause issues for updates. - m_peerInstance = new LocalPeer(this, appID); - connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived); - if(m_peerInstance->isClient()) - { - if(m_instanceIdToLaunch.isEmpty()) - { - m_peerInstance->sendMessage("activate", 2000); - } - else - { - m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000); - } - m_status = MultiMC::Succeeded; - return; - } - } - - // init the logger - { - static const QString logBase = "MultiMC-%0.log"; - auto moveFile = [](const QString &oldName, const QString &newName) - { - QFile::remove(newName); - QFile::copy(oldName, newName); - QFile::remove(oldName); - }; - - moveFile(logBase.arg(3), logBase.arg(4)); - moveFile(logBase.arg(2), logBase.arg(3)); - moveFile(logBase.arg(1), logBase.arg(2)); - moveFile(logBase.arg(0), logBase.arg(1)); - - logFile = std::unique_ptr(new QFile(logBase.arg(0))); - if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) - { - showFatalErrorMessage( - "MultiMC data folder is not writable!", - "MultiMC couldn't create a log file - the MultiMC data folder is not writable.\n" - "\n" - #if defined(Q_OS_MAC) - MACOS_HINT - #endif - "Make sure you have write permissions to the MultiMC data folder.\n" - "\n" - "MultiMC cannot continue until you fix this problem." - ); - return; - } - qInstallMessageHandler(appDebugOutput); - qDebug() << "<> Log initialized."; - } - - // Set up paths - { - // Root path is used for updates. + "Make sure you have the right permissions to the MultiMC data folder.\n" + "\n" + "MultiMC cannot continue until you fix this problem." + ); + return; + } + + /* + * Establish the mechanism for communication with an already running MultiMC that uses the same data path. + * If there is one, tell it what the user actually wanted to do and exit. + * We want to initialize this before logging to avoid messing with the log of a potential already running copy. + */ + auto appID = ApplicationId::fromPathAndVersion(QDir::currentPath(), BuildConfig.printableVersionString()); + { + // FIXME: you can run the same binaries with multiple data dirs and they won't clash. This could cause issues for updates. + m_peerInstance = new LocalPeer(this, appID); + connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived); + if(m_peerInstance->isClient()) + { + if(m_instanceIdToLaunch.isEmpty()) + { + m_peerInstance->sendMessage("activate", 2000); + } + else + { + m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000); + } + m_status = MultiMC::Succeeded; + return; + } + } + + // init the logger + { + static const QString logBase = "MultiMC-%0.log"; + auto moveFile = [](const QString &oldName, const QString &newName) + { + QFile::remove(newName); + QFile::copy(oldName, newName); + QFile::remove(oldName); + }; + + moveFile(logBase.arg(3), logBase.arg(4)); + moveFile(logBase.arg(2), logBase.arg(3)); + moveFile(logBase.arg(1), logBase.arg(2)); + moveFile(logBase.arg(0), logBase.arg(1)); + + logFile = std::unique_ptr(new QFile(logBase.arg(0))); + if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + showFatalErrorMessage( + "MultiMC data folder is not writable!", + "MultiMC couldn't create a log file - the MultiMC data folder is not writable.\n" + "\n" + #if defined(Q_OS_MAC) + MACOS_HINT + #endif + "Make sure you have write permissions to the MultiMC data folder.\n" + "\n" + "MultiMC cannot continue until you fix this problem." + ); + return; + } + qInstallMessageHandler(appDebugOutput); + qDebug() << "<> Log initialized."; + } + + // Set up paths + { + // Root path is used for updates. #ifdef Q_OS_LINUX - QDir foo(FS::PathCombine(binPath, "..")); - m_rootPath = foo.absolutePath(); + QDir foo(FS::PathCombine(binPath, "..")); + m_rootPath = foo.absolutePath(); #elif defined(Q_OS_WIN32) - m_rootPath = binPath; + m_rootPath = binPath; #elif defined(Q_OS_MAC) - QDir foo(FS::PathCombine(binPath, "../..")); - m_rootPath = foo.absolutePath(); - // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) - FS::updateTimestamp(m_rootPath); + QDir foo(FS::PathCombine(binPath, "../..")); + m_rootPath = foo.absolutePath(); + // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) + FS::updateTimestamp(m_rootPath); #endif #ifdef MULTIMC_JARS_LOCATION - ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) ); + ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) ); #endif - qDebug() << "MultiMC 5, (c) 2013-2018 MultiMC Contributors"; - qDebug() << "Version : " << BuildConfig.printableVersionString(); - qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; - qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; - if (adjustedBy.size()) - { - qDebug() << "Work dir before adjustment : " << origcwdPath; - qDebug() << "Work dir after adjustment : " << QDir::currentPath(); - qDebug() << "Adjusted by : " << adjustedBy; - } - else - { - qDebug() << "Work dir : " << QDir::currentPath(); - } - qDebug() << "Binary path : " << binPath; - qDebug() << "Application root path : " << m_rootPath; - if(!m_instanceIdToLaunch.isEmpty()) - { - qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; - } - qDebug() << "<> Paths set."; - } - - do // once - { - if(m_liveCheck) - { - QFile check(liveCheckFile); - if(!check.open(QIODevice::WriteOnly | QIODevice::Truncate)) - { - qWarning() << "Could not open" << liveCheckFile << "for writing!"; - break; - } - auto payload = appID.toString().toUtf8(); - if(check.write(payload) != payload.size()) - { - qWarning() << "Could not write into" << liveCheckFile; - check.remove(); - break; - } - check.close(); - } - } while(false); - - // Initialize application settings - { - m_settings.reset(new INISettingsObject("multimc.cfg", this)); - // Updates - m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL); - m_settings->registerSetting("AutoUpdate", true); - - // Theming - m_settings->registerSetting("IconTheme", QString("multimc")); - m_settings->registerSetting("ApplicationTheme", QString("system")); - - // Notifications - m_settings->registerSetting("ShownNotifications", QString()); - - // Remembered state - m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); - - QString defaultMonospace; - int defaultSize = 11; + qDebug() << "MultiMC 5, (c) 2013-2019 MultiMC Contributors"; + qDebug() << "Version : " << BuildConfig.printableVersionString(); + qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; + qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; + if (adjustedBy.size()) + { + qDebug() << "Work dir before adjustment : " << origcwdPath; + qDebug() << "Work dir after adjustment : " << QDir::currentPath(); + qDebug() << "Adjusted by : " << adjustedBy; + } + else + { + qDebug() << "Work dir : " << QDir::currentPath(); + } + qDebug() << "Binary path : " << binPath; + qDebug() << "Application root path : " << m_rootPath; + if(!m_instanceIdToLaunch.isEmpty()) + { + qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; + } + qDebug() << "<> Paths set."; + } + + do // once + { + if(m_liveCheck) + { + QFile check(liveCheckFile); + if(!check.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + qWarning() << "Could not open" << liveCheckFile << "for writing!"; + break; + } + auto payload = appID.toString().toUtf8(); + if(check.write(payload) != payload.size()) + { + qWarning() << "Could not write into" << liveCheckFile; + check.remove(); + break; + } + check.close(); + } + } while(false); + + // Initialize application settings + { + m_settings.reset(new INISettingsObject("multimc.cfg", this)); + // Updates + m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL); + m_settings->registerSetting("AutoUpdate", true); + + // Theming + m_settings->registerSetting("IconTheme", QString("multimc")); + m_settings->registerSetting("ApplicationTheme", QString("system")); + + // Notifications + m_settings->registerSetting("ShownNotifications", QString()); + + // Remembered state + m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); + + QString defaultMonospace; + int defaultSize = 11; #ifdef Q_OS_WIN32 - defaultMonospace = "Courier"; - defaultSize = 10; + defaultMonospace = "Courier"; + defaultSize = 10; #elif defined(Q_OS_MAC) - defaultMonospace = "Menlo"; + defaultMonospace = "Menlo"; #else - defaultMonospace = "Monospace"; + defaultMonospace = "Monospace"; #endif - // resolve the font so the default actually matches - QFont consoleFont; - consoleFont.setFamily(defaultMonospace); - consoleFont.setStyleHint(QFont::Monospace); - consoleFont.setFixedPitch(true); - QFontInfo consoleFontInfo(consoleFont); - QString resolvedDefaultMonospace = consoleFontInfo.family(); - QFont resolvedFont(resolvedDefaultMonospace); - qDebug() << "Detected default console font:" << resolvedDefaultMonospace - << ", substitutions:" << resolvedFont.substitutions().join(','); - - m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace); - m_settings->registerSetting("ConsoleFontSize", defaultSize); - m_settings->registerSetting("ConsoleMaxLines", 100000); - m_settings->registerSetting("ConsoleOverflowStop", true); - - // Folders - m_settings->registerSetting("InstanceDir", "instances"); - m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); - m_settings->registerSetting("IconsDir", "icons"); - - // Editors - m_settings->registerSetting("JsonEditor", QString()); - - // Language - m_settings->registerSetting("Language", QString()); - - // Console - m_settings->registerSetting("ShowConsole", false); - m_settings->registerSetting("AutoCloseConsole", false); - m_settings->registerSetting("ShowConsoleOnError", true); - m_settings->registerSetting("LogPrePostOutput", true); - - // Window Size - m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false); - m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854); - m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480); - - // Proxy Settings - m_settings->registerSetting("ProxyType", "None"); - m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1"); - m_settings->registerSetting("ProxyPort", 8080); - m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, ""); - m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, ""); - - // Memory - m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); - m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024); - m_settings->registerSetting("PermGen", 128); - - // Java Settings - m_settings->registerSetting("JavaPath", ""); - m_settings->registerSetting("JavaTimestamp", 0); - m_settings->registerSetting("JavaArchitecture", ""); - m_settings->registerSetting("JavaVersion", ""); - m_settings->registerSetting("LastHostname", ""); - m_settings->registerSetting("JvmArgs", ""); - - // Minecraft launch method - m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); - - // Wrapper command for launch - m_settings->registerSetting("WrapperCommand", ""); - - // Custom Commands - m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, ""); - m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, ""); - - // The cat - m_settings->registerSetting("TheCat", false); - - m_settings->registerSetting("InstSortMode", "Name"); - m_settings->registerSetting("SelectedInstance", QString()); - - // Window state and geometry - m_settings->registerSetting("MainWindowState", ""); - m_settings->registerSetting("MainWindowGeometry", ""); - - m_settings->registerSetting("ConsoleWindowState", ""); - m_settings->registerSetting("ConsoleWindowGeometry", ""); - - m_settings->registerSetting("SettingsGeometry", ""); - - m_settings->registerSetting("PagedGeometry", ""); - - m_settings->registerSetting("NewInstanceGeometry", ""); - - m_settings->registerSetting("UpdateDialogGeometry", ""); - - // paste.ee API key - m_settings->registerSetting("PasteEEAPIKey", "multimc"); - -/* if(!BuildConfig.ANALYTICS_ID.isEmpty()) - { - // Analytics - m_settings->registerSetting("Analytics", true); - m_settings->registerSetting("AnalyticsSeen", 0); - m_settings->registerSetting("AnalyticsClientID", QString()); - } -*/ - // Init page provider - { - m_globalSettingsProvider = std::make_shared(tr("Settings")); - m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - // m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - } - qDebug() << "<> Settings loaded."; - } - - // load translations - { - m_translations.reset(new TranslationsModel("translations")); - auto bcp47Name = m_settings->get("Language").toString(); - m_translations->selectLanguage(bcp47Name); - qDebug() << "Your language is" << bcp47Name; - qDebug() << "<> Translations loaded."; - } - - // initialize the updater - if(BuildConfig.UPDATER_ENABLED) - { - m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); - qDebug() << "<> Updater started."; - } - - // Instance icons - { - auto setting = MMC->settings()->getSetting("IconsDir"); - QStringList instFolders = - { - ":/icons/multimc/32x32/instances/", - ":/icons/multimc/50x50/instances/", - ":/icons/multimc/128x128/instances/" - }; - m_icons.reset(new IconList(instFolders, setting->get().toString())); - connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value) - { - m_icons->directoryChanged(value.toString()); - }); - ENV.registerIconList(m_icons); - qDebug() << "<> Instance icons intialized."; - } - - // Icon themes - { - // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies! - // set icon theme search path! - auto searchPaths = QIcon::themeSearchPaths(); - searchPaths.append("iconthemes"); - QIcon::setThemeSearchPaths(searchPaths); - qDebug() << "<> Icon themes initialized."; - } - - // Initialize widget themes - { - auto insertTheme = [this](ITheme * theme) - { - m_themes.insert(std::make_pair(theme->id(), std::unique_ptr(theme))); - }; - auto darkTheme = new DarkTheme(); - insertTheme(new SystemTheme()); - insertTheme(darkTheme); - insertTheme(new BrightTheme()); - insertTheme(new CustomTheme(darkTheme, "custom")); - qDebug() << "<> Widget themes initialized."; - } - - // initialize and load all instances - { - auto InstDirSetting = m_settings->getSetting("InstanceDir"); - // instance path: check for problems with '!' in instance path and warn the user in the log - // and rememer that we have to show him a dialog when the gui starts (if it does so) - QString instDir = InstDirSetting->get().toString(); - qDebug() << "Instance path : " << instDir; - if (FS::checkProblemticPathJava(QDir(instDir))) - { - qWarning() << "Your instance path contains \'!\' and this is known to cause java problems"; - } - m_instances.reset(new InstanceList(this)); - m_instanceFolder = new FolderInstanceProvider(m_settings, instDir); - connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged); - m_instances->addInstanceProvider(m_instanceFolder); - qDebug() << "Loading Instances..."; - m_instances->loadList(true); - qDebug() << "<> Instances loaded."; - } - - // and accounts - { - m_accounts.reset(new MojangAccountList(this)); - qDebug() << "Loading accounts..."; - m_accounts->setListFilePath("accounts.json", true); - m_accounts->loadList(); - qDebug() << "<> Accounts loaded."; - } - - // init the http meta cache - { - ENV.initHttpMetaCache(); - qDebug() << "<> Cache initialized."; - } - - // init proxy settings - { - QString proxyTypeStr = settings()->get("ProxyType").toString(); - QString addr = settings()->get("ProxyAddr").toString(); - int port = settings()->get("ProxyPort").value(); - QString user = settings()->get("ProxyUser").toString(); - QString pass = settings()->get("ProxyPass").toString(); - ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass); - qDebug() << "<> Proxy settings done."; - } - - // now we have network, download translation updates - m_translations->downloadIndex(); - - //FIXME: what to do with these? - m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); - m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory())); - for (auto profiler : m_profilers.values()) - { - profiler->registerSettings(m_settings); - } - - // Create the MCEdit thing... why is this here? - { - m_mcedit.reset(new MCEditTool(m_settings)); - } - - connect(this, &MultiMC::aboutToQuit, [this](){ - if(m_instances) - { - // save any remaining instance state - m_instances->saveNow(); - } - if(logFile) - { - logFile->flush(); - logFile->close(); - } - }); - - { - setIconTheme(settings()->get("IconTheme").toString()); - qDebug() << "<> Icon theme set."; - setApplicationTheme(settings()->get("ApplicationTheme").toString(), true); - qDebug() << "<> Application theme set."; - } - - // Initialize analytics -/* [this]() - { - const int analyticsVersion = 2; - if(BuildConfig.ANALYTICS_ID.isEmpty()) - { - return; - } - - auto analyticsSetting = m_settings->getSetting("Analytics"); - connect(analyticsSetting.get(), &Setting::SettingChanged, this, &MultiMC::analyticsSettingChanged); - QString clientID = m_settings->get("AnalyticsClientID").toString(); - if(clientID.isEmpty()) - { - clientID = QUuid::createUuid().toString(); - clientID.remove(QLatin1Char('{')); - clientID.remove(QLatin1Char('}')); - m_settings->set("AnalyticsClientID", clientID); - } - m_analytics = new GAnalytics(BuildConfig.ANALYTICS_ID, clientID, analyticsVersion, this); - m_analytics->setLogLevel(GAnalytics::Debug); - m_analytics->setAnonymizeIPs(true); - m_analytics->setNetworkAccessManager(&ENV.qnam()); - - if(m_settings->get("AnalyticsSeen").toInt() < m_analytics->version()) - { - qDebug() << "Analytics info not seen by user yet (or old version)."; - return; - } - if(!m_settings->get("Analytics").toBool()) - { - qDebug() << "Analytics disabled by user."; - return; - } - - m_analytics->enable(); - qDebug() << "<> Initialized analytics with tid" << BuildConfig.ANALYTICS_ID; - }(); */ - - if(createSetupWizard()) - { - return; - } - performMainStartupAction(); + // resolve the font so the default actually matches + QFont consoleFont; + consoleFont.setFamily(defaultMonospace); + consoleFont.setStyleHint(QFont::Monospace); + consoleFont.setFixedPitch(true); + QFontInfo consoleFontInfo(consoleFont); + QString resolvedDefaultMonospace = consoleFontInfo.family(); + QFont resolvedFont(resolvedDefaultMonospace); + qDebug() << "Detected default console font:" << resolvedDefaultMonospace + << ", substitutions:" << resolvedFont.substitutions().join(','); + + m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace); + m_settings->registerSetting("ConsoleFontSize", defaultSize); + m_settings->registerSetting("ConsoleMaxLines", 100000); + m_settings->registerSetting("ConsoleOverflowStop", true); + + // Folders + m_settings->registerSetting("InstanceDir", "instances"); + m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); + m_settings->registerSetting("IconsDir", "icons"); + + // Editors + m_settings->registerSetting("JsonEditor", QString()); + + // Language + m_settings->registerSetting("Language", QString()); + + // Console + m_settings->registerSetting("ShowConsole", false); + m_settings->registerSetting("AutoCloseConsole", false); + m_settings->registerSetting("ShowConsoleOnError", true); + m_settings->registerSetting("LogPrePostOutput", true); + + // Window Size + m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false); + m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854); + m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480); + + // Proxy Settings + m_settings->registerSetting("ProxyType", "None"); + m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1"); + m_settings->registerSetting("ProxyPort", 8080); + m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, ""); + m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, ""); + + // Memory + m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); + m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024); + m_settings->registerSetting("PermGen", 128); + + // Java Settings + m_settings->registerSetting("JavaPath", ""); + m_settings->registerSetting("JavaTimestamp", 0); + m_settings->registerSetting("JavaArchitecture", ""); + m_settings->registerSetting("JavaVersion", ""); + m_settings->registerSetting("LastHostname", ""); + m_settings->registerSetting("JvmArgs", ""); + + // Minecraft launch method + m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); + + // Wrapper command for launch + m_settings->registerSetting("WrapperCommand", ""); + + // Custom Commands + m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, ""); + m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, ""); + + // The cat + m_settings->registerSetting("TheCat", false); + + m_settings->registerSetting("InstSortMode", "Name"); + m_settings->registerSetting("SelectedInstance", QString()); + + // Window state and geometry + m_settings->registerSetting("MainWindowState", ""); + m_settings->registerSetting("MainWindowGeometry", ""); + + m_settings->registerSetting("ConsoleWindowState", ""); + m_settings->registerSetting("ConsoleWindowGeometry", ""); + + m_settings->registerSetting("SettingsGeometry", ""); + + m_settings->registerSetting("PagedGeometry", ""); + + m_settings->registerSetting("NewInstanceGeometry", ""); + + m_settings->registerSetting("UpdateDialogGeometry", ""); + + // paste.ee API key + m_settings->registerSetting("PasteEEAPIKey", "multimc"); + + // Init page provider + { + m_globalSettingsProvider = std::make_shared(tr("Settings")); + m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + // m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); + } + qDebug() << "<> Settings loaded."; + } + + QAccessible::installFactory(groupViewAccessibleFactory); + + // load translations + { + m_translations.reset(new TranslationsModel("translations")); + auto bcp47Name = m_settings->get("Language").toString(); + m_translations->selectLanguage(bcp47Name); + qDebug() << "Your language is" << bcp47Name; + qDebug() << "<> Translations loaded."; + } + + // initialize the updater + if(BuildConfig.UPDATER_ENABLED) + { + m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); + qDebug() << "<> Updater started."; + } + + // Instance icons + { + auto setting = MMC->settings()->getSetting("IconsDir"); + QStringList instFolders = + { + ":/icons/multimc/32x32/instances/", + ":/icons/multimc/50x50/instances/", + ":/icons/multimc/128x128/instances/", + ":/icons/multimc/scalable/instances/" + }; + m_icons.reset(new IconList(instFolders, setting->get().toString())); + connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value) + { + m_icons->directoryChanged(value.toString()); + }); + ENV.registerIconList(m_icons); + qDebug() << "<> Instance icons intialized."; + } + + // Icon themes + { + // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies! + // set icon theme search path! + auto searchPaths = QIcon::themeSearchPaths(); + searchPaths.append("iconthemes"); + QIcon::setThemeSearchPaths(searchPaths); + qDebug() << "<> Icon themes initialized."; + } + + // Initialize widget themes + { + auto insertTheme = [this](ITheme * theme) + { + m_themes.insert(std::make_pair(theme->id(), std::unique_ptr(theme))); + }; + auto darkTheme = new DarkTheme(); + insertTheme(new SystemTheme()); + insertTheme(darkTheme); + insertTheme(new BrightTheme()); + insertTheme(new CustomTheme(darkTheme, "custom")); + qDebug() << "<> Widget themes initialized."; + } + + // initialize and load all instances + { + auto InstDirSetting = m_settings->getSetting("InstanceDir"); + // instance path: check for problems with '!' in instance path and warn the user in the log + // and rememer that we have to show him a dialog when the gui starts (if it does so) + QString instDir = InstDirSetting->get().toString(); + qDebug() << "Instance path : " << instDir; + if (FS::checkProblemticPathJava(QDir(instDir))) + { + qWarning() << "Your instance path contains \'!\' and this is known to cause java problems"; + } + m_instances.reset(new InstanceList(m_settings, instDir, this)); + connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged); + qDebug() << "Loading Instances..."; + m_instances->loadList(); + qDebug() << "<> Instances loaded."; + } + + // and accounts + { + m_accounts.reset(new MojangAccountList(this)); + qDebug() << "Loading accounts..."; + m_accounts->setListFilePath("accounts.json", true); + m_accounts->loadList(); + qDebug() << "<> Accounts loaded."; + } + + // init the http meta cache + { + ENV.initHttpMetaCache(); + qDebug() << "<> Cache initialized."; + } + + // init proxy settings + { + QString proxyTypeStr = settings()->get("ProxyType").toString(); + QString addr = settings()->get("ProxyAddr").toString(); + int port = settings()->get("ProxyPort").value(); + QString user = settings()->get("ProxyUser").toString(); + QString pass = settings()->get("ProxyPass").toString(); + ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass); + qDebug() << "<> Proxy settings done."; + } + + // now we have network, download translation updates + m_translations->downloadIndex(); + + //FIXME: what to do with these? + m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); + m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory())); + for (auto profiler : m_profilers.values()) + { + profiler->registerSettings(m_settings); + } + + // Create the MCEdit thing... why is this here? + { + m_mcedit.reset(new MCEditTool(m_settings)); + } + + connect(this, &MultiMC::aboutToQuit, [this](){ + if(m_instances) + { + // save any remaining instance state + m_instances->saveNow(); + } + if(logFile) + { + logFile->flush(); + logFile->close(); + } + }); + + { + setIconTheme(settings()->get("IconTheme").toString()); + qDebug() << "<> Icon theme set."; + setApplicationTheme(settings()->get("ApplicationTheme").toString(), true); + qDebug() << "<> Application theme set."; + } + + if(createSetupWizard()) + { + return; + } + performMainStartupAction(); } bool MultiMC::createSetupWizard() { - bool javaRequired = [&]() - { - QString currentHostName = QHostInfo::localHostName(); - QString oldHostName = settings()->get("LastHostname").toString(); - if (currentHostName != oldHostName) - { - settings()->set("LastHostname", currentHostName); - return true; - } - QString currentJavaPath = settings()->get("JavaPath").toString(); - QString actualPath = FS::ResolveExecutable(currentJavaPath); - if (actualPath.isNull()) - { - return true; - } - return false; - }/*(); - bool analyticsRequired = [&]() - { - if(BuildConfig.ANALYTICS_ID.isEmpty()) - { - return false; - } - if (!settings()->get("Analytics").toBool()) - { - return false; - } - if (settings()->get("AnalyticsSeen").toInt() < analytics()->version()) - { - return true; - } - return false; - }*/(); - bool languageRequired = [&]() - { - if (settings()->get("Language").toString().isEmpty()) - return true; - return false; - }(); -/* bool wizardRequired = javaRequired || analyticsRequired || languageRequired; -*/ bool wizardRequired = javaRequired || languageRequired; - - if(wizardRequired) - { - m_setupWizard = new SetupWizard(nullptr); - if (languageRequired) - { - m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard)); - } - if (javaRequired) - { - m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); - } -/* if(analyticsRequired) - { - m_setupWizard->addPage(new AnalyticsWizardPage(m_setupWizard)); - } -*/ connect(m_setupWizard, &QDialog::finished, this, &MultiMC::setupWizardFinished); - m_setupWizard->show(); - return true; - } - return false; + bool javaRequired = [&]() + { + QString currentHostName = QHostInfo::localHostName(); + QString oldHostName = settings()->get("LastHostname").toString(); + if (currentHostName != oldHostName) + { + settings()->set("LastHostname", currentHostName); + return true; + } + QString currentJavaPath = settings()->get("JavaPath").toString(); + QString actualPath = FS::ResolveExecutable(currentJavaPath); + if (actualPath.isNull()) + { + return true; + } + return false; + }(); + bool languageRequired = [&]() + { + if (settings()->get("Language").toString().isEmpty()) + return true; + return false; + }(); + bool wizardRequired = javaRequired || languageRequired; + + if(wizardRequired) + { + m_setupWizard = new SetupWizard(nullptr); + if (languageRequired) + { + m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard)); + } + if (javaRequired) + { + m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); + } + connect(m_setupWizard, &QDialog::finished, this, &MultiMC::setupWizardFinished); + m_setupWizard->show(); + return true; + } + return false; } void MultiMC::setupWizardFinished(int status) { - qDebug() << "Wizard result =" << status; - performMainStartupAction(); + qDebug() << "Wizard result =" << status; + performMainStartupAction(); } void MultiMC::performMainStartupAction() { - m_status = MultiMC::Initialized; - if(!m_instanceIdToLaunch.isEmpty()) - { - auto inst = instances()->getInstanceById(m_instanceIdToLaunch); - if(inst) - { - qDebug() << "<> Instance launching:" << m_instanceIdToLaunch; - launch(inst, true, nullptr); - return; - } - } - if(!m_mainWindow) - { - // normal main window - showMainWindow(false); - qDebug() << "<> Main window shown."; - } + m_status = MultiMC::Initialized; + if(!m_instanceIdToLaunch.isEmpty()) + { + auto inst = instances()->getInstanceById(m_instanceIdToLaunch); + if(inst) + { + qDebug() << "<> Instance launching:" << m_instanceIdToLaunch; + launch(inst, true, nullptr); + return; + } + } + if(!m_mainWindow) + { + // normal main window + showMainWindow(false); + qDebug() << "<> Main window shown."; + } } void MultiMC::showFatalErrorMessage(const QString& title, const QString& content) { - m_status = MultiMC::Failed; - auto dialog = CustomMessageBox::selectable(nullptr, title, content, QMessageBox::Critical); - dialog->exec(); + m_status = MultiMC::Failed; + auto dialog = CustomMessageBox::selectable(nullptr, title, content, QMessageBox::Critical); + dialog->exec(); } MultiMC::~MultiMC() { - // kill the other globals. - Env::dispose(); + // kill the other globals. + Env::dispose(); - // Shut down logger by setting the logger function to nothing - qInstallMessageHandler(nullptr); + // Shut down logger by setting the logger function to nothing + qInstallMessageHandler(nullptr); #if defined Q_OS_WIN32 - // Detach from Windows console - if(consoleAttached) - { - fclose(stdout); - fclose(stdin); - fclose(stderr); - FreeConsole(); - } + // Detach from Windows console + if(consoleAttached) + { + fclose(stdout); + fclose(stdin); + fclose(stderr); + FreeConsole(); + } #endif } void MultiMC::messageReceived(const QString& message) { - if(status() != Initialized) - { - qDebug() << "Received message" << message << "while still initializing. It will be ignored."; - return; - } - if(message == "activate") - { - showMainWindow(); - } - else - { - auto inst = instances()->getInstanceById(message); - if(inst) - { - launch(inst, true, nullptr); - } - } + if(status() != Initialized) + { + qDebug() << "Received message" << message << "while still initializing. It will be ignored."; + return; + } + if(message == "activate") + { + showMainWindow(); + } + else + { + auto inst = instances()->getInstanceById(message); + if(inst) + { + launch(inst, true, nullptr); + } + } } /* void MultiMC::analyticsSettingChanged(const Setting&, QVariant value) { - if(!m_analytics) - return; - bool enabled = value.toBool(); - if(enabled) - { - qDebug() << "Analytics enabled by user."; - } - else - { - qDebug() << "Analytics disabled by user."; - } - m_analytics->enable(enabled); + if(!m_analytics) + return; + bool enabled = value.toBool(); + if(enabled) + { + qDebug() << "Analytics enabled by user."; + } + else + { + qDebug() << "Analytics disabled by user."; + } + m_analytics->enable(enabled); } */ std::shared_ptr MultiMC::translations() { - return m_translations; + return m_translations; } std::shared_ptr MultiMC::javalist() { - if (!m_javalist) - { - m_javalist.reset(new JavaInstallList()); - } - return m_javalist; + if (!m_javalist) + { + m_javalist.reset(new JavaInstallList()); + } + return m_javalist; } std::vector MultiMC::getValidApplicationThemes() { - std::vector ret; - auto iter = m_themes.cbegin(); - while (iter != m_themes.cend()) - { - ret.push_back((*iter).second.get()); - iter++; - } - return ret; + std::vector ret; + auto iter = m_themes.cbegin(); + while (iter != m_themes.cend()) + { + ret.push_back((*iter).second.get()); + iter++; + } + return ret; } void MultiMC::setApplicationTheme(const QString& name, bool initial) { - auto systemPalette = qApp->palette(); - auto themeIter = m_themes.find(name); - if(themeIter != m_themes.end()) - { - auto & theme = (*themeIter).second; - theme->apply(initial); - } - else - { - qWarning() << "Tried to set invalid theme:" << name; - } + auto systemPalette = qApp->palette(); + auto themeIter = m_themes.find(name); + if(themeIter != m_themes.end()) + { + auto & theme = (*themeIter).second; + theme->apply(initial); + } + else + { + qWarning() << "Tried to set invalid theme:" << name; + } } void MultiMC::setIconTheme(const QString& name) { - XdgIcon::setThemeName(name); + XdgIcon::setThemeName(name); } QIcon MultiMC::getThemedIcon(const QString& name) { - return XdgIcon::fromTheme(name); + return XdgIcon::fromTheme(name); } bool MultiMC::openJsonEditor(const QString &filename) { - const QString file = QDir::current().absoluteFilePath(filename); - if (m_settings->get("JsonEditor").toString().isEmpty()) - { - return DesktopServices::openUrl(QUrl::fromLocalFile(file)); - } - else - { - //return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file); - return DesktopServices::run(m_settings->get("JsonEditor").toString(), {file}); - } + const QString file = QDir::current().absoluteFilePath(filename); + if (m_settings->get("JsonEditor").toString().isEmpty()) + { + return DesktopServices::openUrl(QUrl::fromLocalFile(file)); + } + else + { + //return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file); + return DesktopServices::run(m_settings->get("JsonEditor").toString(), {file}); + } } bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler) { - if(m_updateRunning) - { - qDebug() << "Cannot launch instances while an update is running."; - } - else if(instance->canLaunch()) - { - auto & extras = m_instanceExtras[instance->id()]; - auto & window = extras.window; - if(window) - { - if(!window->saveAll()) - { - return false; - } - } - auto & controller = extras.controller; - controller.reset(new LaunchController()); - controller->setInstance(instance); - controller->setOnline(online); - controller->setProfiler(profiler); - if(window) - { - controller->setParentWidget(window); - } - else if(m_mainWindow) - { - controller->setParentWidget(m_mainWindow); - } - connect(controller.get(), &LaunchController::succeeded, this, &MultiMC::controllerSucceeded); - connect(controller.get(), &LaunchController::failed, this, &MultiMC::controllerFailed); - addRunningInstance(); - controller->start(); - return true; - } - else if (instance->isRunning()) - { - showInstanceWindow(instance, "console"); - return true; - } - else if (instance->canEdit()) - { - showInstanceWindow(instance); - return true; - } - return false; + if(m_updateRunning) + { + qDebug() << "Cannot launch instances while an update is running."; + } + else if(instance->canLaunch()) + { + auto & extras = m_instanceExtras[instance->id()]; + auto & window = extras.window; + if(window) + { + if(!window->saveAll()) + { + return false; + } + } + auto & controller = extras.controller; + controller.reset(new LaunchController()); + controller->setInstance(instance); + controller->setOnline(online); + controller->setProfiler(profiler); + if(window) + { + controller->setParentWidget(window); + } + else if(m_mainWindow) + { + controller->setParentWidget(m_mainWindow); + } + connect(controller.get(), &LaunchController::succeeded, this, &MultiMC::controllerSucceeded); + connect(controller.get(), &LaunchController::failed, this, &MultiMC::controllerFailed); + addRunningInstance(); + controller->start(); + return true; + } + else if (instance->isRunning()) + { + showInstanceWindow(instance, "console"); + return true; + } + else if (instance->canEdit()) + { + showInstanceWindow(instance); + return true; + } + return false; } bool MultiMC::kill(InstancePtr instance) { - if (!instance->isRunning()) - { - qWarning() << "Attempted to kill instance" << instance->id() << "which isn't running."; - return false; - } - auto & extras = m_instanceExtras[instance->id()]; - // NOTE: copy of the shared pointer keeps it alive - auto controller = extras.controller; - if(controller) - { - return controller->abort(); - } - return true; + if (!instance->isRunning()) + { + qWarning() << "Attempted to kill instance" << instance->id() << "which isn't running."; + return false; + } + auto & extras = m_instanceExtras[instance->id()]; + // NOTE: copy of the shared pointer keeps it alive + auto controller = extras.controller; + if(controller) + { + return controller->abort(); + } + return true; } void MultiMC::addRunningInstance() { - m_runningInstances ++; - if(m_runningInstances == 1) - { - emit updateAllowedChanged(false); - } + m_runningInstances ++; + if(m_runningInstances == 1) + { + emit updateAllowedChanged(false); + } } void MultiMC::subRunningInstance() { - if(m_runningInstances == 0) - { - qCritical() << "Something went really wrong and we now have less than 0 running instances... WTF"; - return; - } - m_runningInstances --; - if(m_runningInstances == 0) - { - emit updateAllowedChanged(true); - } + if(m_runningInstances == 0) + { + qCritical() << "Something went really wrong and we now have less than 0 running instances... WTF"; + return; + } + m_runningInstances --; + if(m_runningInstances == 0) + { + emit updateAllowedChanged(true); + } } bool MultiMC::shouldExitNow() const { - return m_runningInstances == 0 && m_openWindows == 0; + return m_runningInstances == 0 && m_openWindows == 0; } bool MultiMC::updatesAreAllowed() { - return m_runningInstances == 0; + return m_runningInstances == 0; } void MultiMC::updateIsRunning(bool running) { - m_updateRunning = running; + m_updateRunning = running; } void MultiMC::controllerSucceeded() { - auto controller = qobject_cast(QObject::sender()); - if(!controller) - return; - auto id = controller->id(); - auto & extras = m_instanceExtras[id]; - - // on success, do... - if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) - { - if(extras.window) - { - extras.window->close(); - } - } - extras.controller.reset(); - subRunningInstance(); - - // quit when there are no more windows. - if(shouldExitNow()) - { - m_status = Status::Succeeded; - exit(0); - } + auto controller = qobject_cast(QObject::sender()); + if(!controller) + return; + auto id = controller->id(); + auto & extras = m_instanceExtras[id]; + + // on success, do... + if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) + { + if(extras.window) + { + extras.window->close(); + } + } + extras.controller.reset(); + subRunningInstance(); + + // quit when there are no more windows. + if(shouldExitNow()) + { + m_status = Status::Succeeded; + exit(0); + } } void MultiMC::controllerFailed(const QString& error) { - Q_UNUSED(error); - auto controller = qobject_cast(QObject::sender()); - if(!controller) - return; - auto id = controller->id(); - auto & extras = m_instanceExtras[id]; - - // on failure, do... nothing - extras.controller.reset(); - subRunningInstance(); - - // quit when there are no more windows. - if(shouldExitNow()) - { - m_status = Status::Failed; - exit(1); - } + Q_UNUSED(error); + auto controller = qobject_cast(QObject::sender()); + if(!controller) + return; + auto id = controller->id(); + auto & extras = m_instanceExtras[id]; + + // on failure, do... nothing + extras.controller.reset(); + subRunningInstance(); + + // quit when there are no more windows. + if(shouldExitNow()) + { + m_status = Status::Failed; + exit(1); + } +} + +void MultiMC::ShowGlobalSettings(class QWidget* parent, QString open_page) +{ + if(!m_globalSettingsProvider) { + return; + } + emit globalSettingsAboutToOpen(); + { + SettingsObject::Lock lock(MMC->settings()); + PageDialog dlg(m_globalSettingsProvider.get(), open_page, parent); + dlg.exec(); + } + emit globalSettingsClosed(); } MainWindow* MultiMC::showMainWindow(bool minimized) { - if(m_mainWindow) - { - m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); - m_mainWindow->raise(); - m_mainWindow->activateWindow(); - } - else - { - m_mainWindow = new MainWindow(); - m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); - m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); - if(minimized) - { - m_mainWindow->showMinimized(); - } - else - { - m_mainWindow->show(); - } - - m_mainWindow->checkInstancePathForProblems(); - connect(this, &MultiMC::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); - connect(m_mainWindow, &MainWindow::isClosing, this, &MultiMC::on_windowClose); - m_openWindows++; - } -/* // FIXME: move this somewhere else... - if(m_analytics) - { - auto windowSize = m_mainWindow->size(); - auto sizeString = QString("%1x%2").arg(windowSize.width()).arg(windowSize.height()); - qDebug() << "Viewport size" << sizeString; - m_analytics->setViewportSize(sizeString); -*/ /* - * cm1 = java min heap [MB] - * cm2 = java max heap [MB] - * cm3 = system RAM [MB] - * - * cd1 = java version - * cd2 = java architecture - * cd3 = system architecture - * cd4 = CPU architecture - */ -/* QVariantMap customValues; - int min = m_settings->get("MinMemAlloc").toInt(); - int max = m_settings->get("MaxMemAlloc").toInt(); - if(min < max) - { - customValues["cm1"] = min; - customValues["cm2"] = max; - } - else - { - customValues["cm1"] = max; - customValues["cm2"] = min; - } - - constexpr uint64_t Mega = 1024ull * 1024ull; - int ramSize = int(Sys::getSystemRam() / Mega); - qDebug() << "RAM size is" << ramSize << "MB"; - customValues["cm3"] = ramSize; - - customValues["cd1"] = m_settings->get("JavaVersion"); - customValues["cd2"] = m_settings->get("JavaArchitecture"); - customValues["cd3"] = Sys::isSystem64bit() ? "64":"32"; - customValues["cd4"] = Sys::isCPU64bit() ? "64":"32"; - auto kernelInfo = Sys::getKernelInfo(); - customValues["cd5"] = kernelInfo.kernelName; - customValues["cd6"] = kernelInfo.kernelVersion; - auto distInfo = Sys::getDistributionInfo(); - if(!distInfo.distributionName.isEmpty()) - { - customValues["cd7"] = distInfo.distributionName; - } - if(!distInfo.distributionVersion.isEmpty()) - { - customValues["cd8"] = distInfo.distributionVersion; - } - m_analytics->sendScreenView("Main Window", customValues); - } -*/ - return m_mainWindow; + if(m_mainWindow) + { + m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); + m_mainWindow->raise(); + m_mainWindow->activateWindow(); + } + else + { + m_mainWindow = new MainWindow(); + m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); + m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); + if(minimized) + { + m_mainWindow->showMinimized(); + } + else + { + m_mainWindow->show(); + } + + m_mainWindow->checkInstancePathForProblems(); + connect(this, &MultiMC::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); + connect(m_mainWindow, &MainWindow::isClosing, this, &MultiMC::on_windowClose); + m_openWindows++; + } + return m_mainWindow; } InstanceWindow *MultiMC::showInstanceWindow(InstancePtr instance, QString page) { - if(!instance) - return nullptr; - auto id = instance->id(); - auto & extras = m_instanceExtras[id]; - auto & window = extras.window; - - if(window) - { - window->raise(); - window->activateWindow(); - } - else - { - window = new InstanceWindow(instance); - m_openWindows ++; - connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose); - } - if(!page.isEmpty()) - { - window->selectPage(page); - } - if(extras.controller) - { - extras.controller->setParentWidget(window); - } - return window; + if(!instance) + return nullptr; + auto id = instance->id(); + auto & extras = m_instanceExtras[id]; + auto & window = extras.window; + + if(window) + { + window->raise(); + window->activateWindow(); + } + else + { + window = new InstanceWindow(instance); + m_openWindows ++; + connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose); + } + if(!page.isEmpty()) + { + window->selectPage(page); + } + if(extras.controller) + { + extras.controller->setParentWidget(window); + } + return window; } void MultiMC::on_windowClose() { - m_openWindows--; - auto instWindow = qobject_cast(QObject::sender()); - if(instWindow) - { - auto & extras = m_instanceExtras[instWindow->instanceId()]; - extras.window = nullptr; - if(extras.controller) - { - extras.controller->setParentWidget(m_mainWindow); - } - } - auto mainWindow = qobject_cast(QObject::sender()); - if(mainWindow) - { - m_mainWindow = nullptr; - } - // quit when there are no more windows. - if(shouldExitNow()) - { - exit(0); - } + m_openWindows--; + auto instWindow = qobject_cast(QObject::sender()); + if(instWindow) + { + auto & extras = m_instanceExtras[instWindow->instanceId()]; + extras.window = nullptr; + if(extras.controller) + { + extras.controller->setParentWidget(m_mainWindow); + } + } + auto mainWindow = qobject_cast(QObject::sender()); + if(mainWindow) + { + m_mainWindow = nullptr; + } + // quit when there are no more windows. + if(shouldExitNow()) + { + exit(0); + } } diff --git a/application/MultiMC.h b/application/MultiMC.h index 9c91d06d..056fca88 100644 --- a/application/MultiMC.h +++ b/application/MultiMC.h @@ -39,182 +39,189 @@ class MCEditTool; class MultiMC : public QApplication { - // friends for the purpose of limiting access to deprecated stuff - Q_OBJECT + // friends for the purpose of limiting access to deprecated stuff + Q_OBJECT public: - enum Status - { - StartingUp, - Failed, - Succeeded, - Initialized - }; + enum Status + { + StartingUp, + Failed, + Succeeded, + Initialized + }; public: - MultiMC(int &argc, char **argv); - virtual ~MultiMC(); + MultiMC(int &argc, char **argv); + virtual ~MultiMC(); - std::shared_ptr settings() const - { - return m_settings; - } + std::shared_ptr settings() const + { + return m_settings; + } - std::shared_ptr globalSettingsPages() const - { - return m_globalSettingsProvider; - } + qint64 timeSinceStart() const + { + return startTime.msecsTo(QDateTime::currentDateTime()); + } - qint64 timeSinceStart() const - { - return startTime.msecsTo(QDateTime::currentDateTime()); - } + QIcon getThemedIcon(const QString& name); - QIcon getThemedIcon(const QString& name); + void setIconTheme(const QString& name); - void setIconTheme(const QString& name); + std::vector getValidApplicationThemes(); - std::vector getValidApplicationThemes(); + void setApplicationTheme(const QString& name, bool initial); - void setApplicationTheme(const QString& name, bool initial); + // DownloadUpdateTask + std::shared_ptr updateChecker() + { + return m_updateChecker; + } - // DownloadUpdateTask - std::shared_ptr updateChecker() - { - return m_updateChecker; - } + std::shared_ptr translations(); - std::shared_ptr translations(); + std::shared_ptr javalist(); - std::shared_ptr javalist(); + std::shared_ptr instances() const + { + return m_instances; + } - std::shared_ptr instances() const - { - return m_instances; - } + FolderInstanceProvider * folderProvider() const + { + return m_instanceFolder; + } - FolderInstanceProvider * folderProvider() const - { - return m_instanceFolder; - } + std::shared_ptr icons() const + { + return m_icons; + } - std::shared_ptr icons() const - { - return m_icons; - } + MCEditTool *mcedit() const + { + return m_mcedit.get(); + } - MCEditTool *mcedit() const - { - return m_mcedit.get(); - } + std::shared_ptr accounts() const + { + return m_accounts; + } - std::shared_ptr accounts() const - { - return m_accounts; - } + Status status() const + { + return m_status; + } - Status status() const - { - return m_status; - } + const QMap> &profilers() const + { + return m_profilers; + } - const QMap> &profilers() const - { - return m_profilers; - } + /// this is the root of the 'installation'. Used for automatic updates + const QString &root() + { + return m_rootPath; + } - /// this is the root of the 'installation'. Used for automatic updates - const QString &root() - { - return m_rootPath; - } + /*! + * Opens a json file using either a system default editor, or, if not empty, the editor + * specified in the settings + */ + bool openJsonEditor(const QString &filename); - /*! - * Opens a json file using either a system default editor, or, if not empty, the editor - * specified in the settings - */ - bool openJsonEditor(const QString &filename); + InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString()); + MainWindow *showMainWindow(bool minimized = false); - InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString()); - MainWindow *showMainWindow(bool minimized = false); + void updateIsRunning(bool running); + bool updatesAreAllowed(); - void updateIsRunning(bool running); - bool updatesAreAllowed(); + void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); signals: - void updateAllowedChanged(bool status); + void updateAllowedChanged(bool status); + void globalSettingsAboutToOpen(); + void globalSettingsClosed(); public slots: - bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr); - bool kill(InstancePtr instance); + bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr); + bool kill(InstancePtr instance); private slots: +<<<<<<< HEAD void on_windowClose(); void messageReceived(const QString & message); void controllerSucceeded(); void controllerFailed(const QString & error); /* void analyticsSettingChanged(const Setting &setting, QVariant value); */ void setupWizardFinished(int status); +======= + void on_windowClose(); + void messageReceived(const QString & message); + void controllerSucceeded(); + void controllerFailed(const QString & error); + void analyticsSettingChanged(const Setting &setting, QVariant value); + void setupWizardFinished(int status); +>>>>>>> origin/stable private: - bool createSetupWizard(); - void performMainStartupAction(); + bool createSetupWizard(); + void performMainStartupAction(); - // sets the fatal error message and m_status to Failed. - void showFatalErrorMessage(const QString & title, const QString & content); + // sets the fatal error message and m_status to Failed. + void showFatalErrorMessage(const QString & title, const QString & content); private: - void addRunningInstance(); - void subRunningInstance(); - bool shouldExitNow() const; + void addRunningInstance(); + void subRunningInstance(); + bool shouldExitNow() const; private: - QDateTime startTime; + QDateTime startTime; - std::shared_ptr m_settings; - std::shared_ptr m_instances; - FolderInstanceProvider * m_instanceFolder = nullptr; - std::shared_ptr m_icons; - std::shared_ptr m_updateChecker; - std::shared_ptr m_accounts; - std::shared_ptr m_javalist; - std::shared_ptr m_translations; - std::shared_ptr m_globalSettingsProvider; - std::map> m_themes; - std::unique_ptr m_mcedit; + std::shared_ptr m_settings; + std::shared_ptr m_instances; + FolderInstanceProvider * m_instanceFolder = nullptr; + std::shared_ptr m_icons; + std::shared_ptr m_updateChecker; + std::shared_ptr m_accounts; + std::shared_ptr m_javalist; + std::shared_ptr m_translations; + std::shared_ptr m_globalSettingsProvider; + std::map> m_themes; + std::unique_ptr m_mcedit; - QMap> m_profilers; + QMap> m_profilers; - QString m_rootPath; - Status m_status = MultiMC::StartingUp; + QString m_rootPath; + Status m_status = MultiMC::StartingUp; #if defined Q_OS_WIN32 - // used on Windows to attach the standard IO streams - bool consoleAttached = false; + // used on Windows to attach the standard IO streams + bool consoleAttached = false; #endif - // FIXME: attach to instances instead. - struct InstanceXtras - { - InstanceWindow * window = nullptr; - shared_qobject_ptr controller; - }; - std::map m_instanceExtras; + // FIXME: attach to instances instead. + struct InstanceXtras + { + InstanceWindow * window = nullptr; + shared_qobject_ptr controller; + }; + std::map m_instanceExtras; - // main state variables - size_t m_openWindows = 0; - size_t m_runningInstances = 0; - bool m_updateRunning = false; + // main state variables + size_t m_openWindows = 0; + size_t m_runningInstances = 0; + bool m_updateRunning = false; - // main window, if any - MainWindow * m_mainWindow = nullptr; + // main window, if any + MainWindow * m_mainWindow = nullptr; - // peer MultiMC instance connector - used to implement single instance MultiMC and signalling - LocalPeer * m_peerInstance = nullptr; + // peer MultiMC instance connector - used to implement single instance MultiMC and signalling + LocalPeer * m_peerInstance = nullptr; -/* GAnalytics * m_analytics = nullptr; */ - SetupWizard * m_setupWizard = nullptr; + SetupWizard * m_setupWizard = nullptr; public: - QString m_instanceIdToLaunch; - bool m_liveCheck = false; - std::unique_ptr logFile; + QString m_instanceIdToLaunch; + bool m_liveCheck = false; + std::unique_ptr logFile; }; diff --git a/application/SettingsUI.h b/application/SettingsUI.h deleted file mode 100644 index 0d14fbee..00000000 --- a/application/SettingsUI.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "pages/BasePageProvider.h" -#include "MultiMC.h" -#include "pagedialog/PageDialog.h" -#include "InstancePageProvider.h" -#include -#include - -/* - * FIXME: this is a fragment. find a better place for it. - */ -namespace SettingsUI -{ -template -void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QString()) -{ - auto provider = std::dynamic_pointer_cast(raw_provider); - if(!provider) - return; - { - SettingsObject::Lock lock(MMC->settings()); - PageDialog dlg(provider.get(), open_page, parent); - dlg.exec(); - } -} -} diff --git a/application/UpdateController.cpp b/application/UpdateController.cpp index 8df5b3fc..0309ad93 100644 --- a/application/UpdateController.cpp +++ b/application/UpdateController.cpp @@ -27,423 +27,423 @@ #endif static QFile::Permissions unixModeToPermissions(const int mode) { - QFile::Permissions perms; + QFile::Permissions perms; - if (mode & S_IRUSR) - { - perms |= QFile::ReadUser; - } - if (mode & S_IWUSR) - { - perms |= QFile::WriteUser; - } - if (mode & S_IXUSR) - { - perms |= QFile::ExeUser; - } + if (mode & S_IRUSR) + { + perms |= QFile::ReadUser; + } + if (mode & S_IWUSR) + { + perms |= QFile::WriteUser; + } + if (mode & S_IXUSR) + { + perms |= QFile::ExeUser; + } - if (mode & S_IRGRP) - { - perms |= QFile::ReadGroup; - } - if (mode & S_IWGRP) - { - perms |= QFile::WriteGroup; - } - if (mode & S_IXGRP) - { - perms |= QFile::ExeGroup; - } + if (mode & S_IRGRP) + { + perms |= QFile::ReadGroup; + } + if (mode & S_IWGRP) + { + perms |= QFile::WriteGroup; + } + if (mode & S_IXGRP) + { + perms |= QFile::ExeGroup; + } - if (mode & S_IROTH) - { - perms |= QFile::ReadOther; - } - if (mode & S_IWOTH) - { - perms |= QFile::WriteOther; - } - if (mode & S_IXOTH) - { - perms |= QFile::ExeOther; - } - return perms; + if (mode & S_IROTH) + { + perms |= QFile::ReadOther; + } + if (mode & S_IWOTH) + { + perms |= QFile::WriteOther; + } + if (mode & S_IXOTH) + { + perms |= QFile::ExeOther; + } + return perms; } static const QLatin1String liveCheckFile("live.check"); UpdateController::UpdateController(QWidget * parent, const QString& root, const QString updateFilesDir, GoUpdate::OperationList operations) { - m_parent = parent; - m_root = root; - m_updateFilesDir = updateFilesDir; - m_operations = operations; + m_parent = parent; + m_root = root; + m_updateFilesDir = updateFilesDir; + m_operations = operations; } void UpdateController::installUpdates() { - qint64 pid = -1; - QStringList args; - bool started = false; + qint64 pid = -1; + QStringList args; + bool started = false; - qDebug() << "Installing updates."; + qDebug() << "Installing updates."; #ifdef Q_OS_WIN - QString finishCmd = QApplication::applicationFilePath(); + QString finishCmd = QApplication::applicationFilePath(); #elif defined Q_OS_LINUX - QString finishCmd = FS::PathCombine(m_root, "MultiMC"); + QString finishCmd = FS::PathCombine(m_root, "MultiMC"); #elif defined Q_OS_MAC - QString finishCmd = QApplication::applicationFilePath(); + QString finishCmd = QApplication::applicationFilePath(); #else #error Unsupported operating system. #endif - QString backupPath = FS::PathCombine(m_root, "update", "backup"); - QDir origin(m_root); + QString backupPath = FS::PathCombine(m_root, "update", "backup"); + QDir origin(m_root); - // clean up the backup folder. it should be empty before we start - if(!FS::deletePath(backupPath)) - { - qWarning() << "couldn't remove previous backup folder" << backupPath; - } - // and it should exist. - if(!FS::ensureFolderPathExists(backupPath)) - { - qWarning() << "couldn't create folder" << backupPath; - return; - } + // clean up the backup folder. it should be empty before we start + if(!FS::deletePath(backupPath)) + { + qWarning() << "couldn't remove previous backup folder" << backupPath; + } + // and it should exist. + if(!FS::ensureFolderPathExists(backupPath)) + { + qWarning() << "couldn't create folder" << backupPath; + return; + } - bool useXPHack = false; - QString exePath; - QString exeOrigin; - QString exeBackup; + bool useXPHack = false; + QString exePath; + QString exeOrigin; + QString exeBackup; - // perform the update operations - for(auto op: m_operations) - { - switch(op.type) - { - // replace = move original out to backup, if it exists, move the new file in its place - case GoUpdate::Operation::OP_REPLACE: - { + // perform the update operations + for(auto op: m_operations) + { + switch(op.type) + { + // replace = move original out to backup, if it exists, move the new file in its place + case GoUpdate::Operation::OP_REPLACE: + { #ifdef Q_OS_WIN32 - // hack for people renaming the .exe because ... reasons :) - if(op.destination == "MultiMC.exe") - { - op.destination = QFileInfo(QApplication::applicationFilePath()).fileName(); - } + // hack for people renaming the .exe because ... reasons :) + if(op.destination == "MultiMC.exe") + { + op.destination = QFileInfo(QApplication::applicationFilePath()).fileName(); + } #endif - QFileInfo destination (FS::PathCombine(m_root, op.destination)); + QFileInfo destination (FS::PathCombine(m_root, op.destination)); #ifdef Q_OS_WIN32 - if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA) - { - if(destination.fileName() == "MultiMC.exe") - { - QDir rootDir(m_root); - exeOrigin = rootDir.relativeFilePath(op.source); - exePath = rootDir.relativeFilePath(op.destination); - exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName())); - useXPHack = true; - continue; - } - } + if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA) + { + if(destination.fileName() == "MultiMC.exe") + { + QDir rootDir(m_root); + exeOrigin = rootDir.relativeFilePath(op.source); + exePath = rootDir.relativeFilePath(op.destination); + exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName())); + useXPHack = true; + continue; + } + } #endif - if(destination.exists()) - { - QString backupName = op.destination; - backupName.replace('/', '_'); - QString backupFilePath = FS::PathCombine(backupPath, backupName); - if(!QFile::rename(destination.absoluteFilePath(), backupFilePath)) - { - qWarning() << "Couldn't move:" << destination.absoluteFilePath() << "to" << backupFilePath; - m_failedOperationType = Replace; - m_failedFile = op.destination; - fail(); - return; - } - BackupEntry be; - be.original = destination.absoluteFilePath(); - be.backup = backupFilePath; - be.update = op.source; - m_replace_backups.append(be); - } - // make sure the folder we are putting this into exists - if(!FS::ensureFilePathExists(destination.absoluteFilePath())) - { - qWarning() << "REPLACE: Couldn't create folder:" << destination.absoluteFilePath(); - m_failedOperationType = Replace; - m_failedFile = op.destination; - fail(); - return; - } - // now move the new file in - if(!QFile::rename(op.source, destination.absoluteFilePath())) - { - qWarning() << "REPLACE: Couldn't move:" << op.source << "to" << destination.absoluteFilePath(); - m_failedOperationType = Replace; - m_failedFile = op.destination; - fail(); - return; - } - QFile::setPermissions(destination.absoluteFilePath(), unixModeToPermissions(op.destinationMode)); - } - break; - // delete = move original to backup - case GoUpdate::Operation::OP_DELETE: - { - QString destFilePath = FS::PathCombine(m_root, op.destination); - if(QFile::exists(destFilePath)) - { - QString backupName = op.destination; - backupName.replace('/', '_'); - QString trashFilePath = FS::PathCombine(backupPath, backupName); + if(destination.exists()) + { + QString backupName = op.destination; + backupName.replace('/', '_'); + QString backupFilePath = FS::PathCombine(backupPath, backupName); + if(!QFile::rename(destination.absoluteFilePath(), backupFilePath)) + { + qWarning() << "Couldn't move:" << destination.absoluteFilePath() << "to" << backupFilePath; + m_failedOperationType = Replace; + m_failedFile = op.destination; + fail(); + return; + } + BackupEntry be; + be.original = destination.absoluteFilePath(); + be.backup = backupFilePath; + be.update = op.source; + m_replace_backups.append(be); + } + // make sure the folder we are putting this into exists + if(!FS::ensureFilePathExists(destination.absoluteFilePath())) + { + qWarning() << "REPLACE: Couldn't create folder:" << destination.absoluteFilePath(); + m_failedOperationType = Replace; + m_failedFile = op.destination; + fail(); + return; + } + // now move the new file in + if(!QFile::rename(op.source, destination.absoluteFilePath())) + { + qWarning() << "REPLACE: Couldn't move:" << op.source << "to" << destination.absoluteFilePath(); + m_failedOperationType = Replace; + m_failedFile = op.destination; + fail(); + return; + } + QFile::setPermissions(destination.absoluteFilePath(), unixModeToPermissions(op.destinationMode)); + } + break; + // delete = move original to backup + case GoUpdate::Operation::OP_DELETE: + { + QString destFilePath = FS::PathCombine(m_root, op.destination); + if(QFile::exists(destFilePath)) + { + QString backupName = op.destination; + backupName.replace('/', '_'); + QString trashFilePath = FS::PathCombine(backupPath, backupName); - if(!QFile::rename(destFilePath, trashFilePath)) - { - qWarning() << "DELETE: Couldn't move:" << op.destination << "to" << trashFilePath; - m_failedFile = op.destination; - m_failedOperationType = Delete; - fail(); - return; - } - BackupEntry be; - be.original = destFilePath; - be.backup = trashFilePath; - m_delete_backups.append(be); - } - } - break; - } - } + if(!QFile::rename(destFilePath, trashFilePath)) + { + qWarning() << "DELETE: Couldn't move:" << op.destination << "to" << trashFilePath; + m_failedFile = op.destination; + m_failedOperationType = Delete; + fail(); + return; + } + BackupEntry be; + be.original = destFilePath; + be.backup = trashFilePath; + m_delete_backups.append(be); + } + } + break; + } + } - // try to start the new binary - args = qApp->arguments(); - args.removeFirst(); + // try to start the new binary + args = qApp->arguments(); + args.removeFirst(); - // on old Windows, do insane things... no error checking here, this is just to have something. - if(useXPHack) - { - QString script; - auto nativePath = QDir::toNativeSeparators(exePath); - auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin); - auto nativeBackupPath = QDir::toNativeSeparators(exeBackup); + // on old Windows, do insane things... no error checking here, this is just to have something. + if(useXPHack) + { + QString script; + auto nativePath = QDir::toNativeSeparators(exePath); + auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin); + auto nativeBackupPath = QDir::toNativeSeparators(exeBackup); - // so we write this vbscript thing... - QTextStream out(&script); - out << "WScript.Sleep 1000\n"; - out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n"; - out << "Set shell=CreateObject(\"WScript.Shell\")\n"; - out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n"; - out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n"; - out << "shell.Run \"" << nativePath << "\"\n"; + // so we write this vbscript thing... + QTextStream out(&script); + out << "WScript.Sleep 1000\n"; + out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n"; + out << "Set shell=CreateObject(\"WScript.Shell\")\n"; + out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n"; + out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n"; + out << "shell.Run \"" << nativePath << "\"\n"; - QString scriptPath = FS::PathCombine(m_root, "update", "update.vbs"); + QString scriptPath = FS::PathCombine(m_root, "update", "update.vbs"); - // we save it - QFile scriptFile(scriptPath); - scriptFile.open(QIODevice::WriteOnly); - scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n")); - scriptFile.close(); + // we save it + QFile scriptFile(scriptPath); + scriptFile.open(QIODevice::WriteOnly); + scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n")); + scriptFile.close(); - // we run it - started = QProcess::startDetached("wscript", {scriptPath}, m_root); + // we run it + started = QProcess::startDetached("wscript", {scriptPath}, m_root); - // and we quit. conscious thought. - qApp->quit(); - return; - } - bool doLiveCheck = true; - bool startFailed = false; + // and we quit. conscious thought. + qApp->quit(); + return; + } + bool doLiveCheck = true; + bool startFailed = false; - // remove live check file, if any - if(QFile::exists(liveCheckFile)) - { - if(!QFile::remove(liveCheckFile)) - { - qWarning() << "Couldn't remove the" << liveCheckFile << "file! We will proceed without :("; - doLiveCheck = false; - } - } + // remove live check file, if any + if(QFile::exists(liveCheckFile)) + { + if(!QFile::remove(liveCheckFile)) + { + qWarning() << "Couldn't remove the" << liveCheckFile << "file! We will proceed without :("; + doLiveCheck = false; + } + } - if(doLiveCheck) - { - if(!args.contains("--alive")) - { - args.append("--alive"); - } - } + if(doLiveCheck) + { + if(!args.contains("--alive")) + { + args.append("--alive"); + } + } - // FIXME: reparse args and construct a safe variant from scratch. This is a workaround for GH-1874: - QStringList realargs; - int skip = 0; - for(auto & arg: args) - { - if(skip) - { - skip--; - continue; - } - if(arg == "-l") - { - skip = 1; - continue; - } - realargs.append(arg); - } + // FIXME: reparse args and construct a safe variant from scratch. This is a workaround for GH-1874: + QStringList realargs; + int skip = 0; + for(auto & arg: args) + { + if(skip) + { + skip--; + continue; + } + if(arg == "-l") + { + skip = 1; + continue; + } + realargs.append(arg); + } - // start the updated application - started = QProcess::startDetached(finishCmd, realargs, QDir::currentPath(), &pid); - // much dumber check - just find out if the call - if(!started || pid == -1) - { - qWarning() << "Couldn't start new process properly!"; - startFailed = true; - } - if(!startFailed && doLiveCheck) - { - int attempts = 0; - while(attempts < 10) - { - attempts++; - QString key; - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - if(!QFile::exists(liveCheckFile)) - { - qWarning() << "Couldn't find the" << liveCheckFile << "file!"; - startFailed = true; - continue; - } - try - { - key = QString::fromUtf8(FS::read(liveCheckFile)); - auto id = ApplicationId::fromRawString(key); - LocalPeer peer(nullptr, id); - if(peer.isClient()) - { - startFailed = false; - qDebug() << "Found process started with key " << key; - break; - } - else - { - startFailed = true; - qDebug() << "Process started with key " << key << "apparently died or is not reponding..."; - break; - } - } - catch(Exception e) - { - qWarning() << "Couldn't read the" << liveCheckFile << "file!"; - startFailed = true; - continue; - } - } - } - if(startFailed) - { - m_failedOperationType = Start; - fail(); - return; - } - else - { - origin.rmdir(m_updateFilesDir); - qApp->quit(); - return; - } + // start the updated application + started = QProcess::startDetached(finishCmd, realargs, QDir::currentPath(), &pid); + // much dumber check - just find out if the call + if(!started || pid == -1) + { + qWarning() << "Couldn't start new process properly!"; + startFailed = true; + } + if(!startFailed && doLiveCheck) + { + int attempts = 0; + while(attempts < 10) + { + attempts++; + QString key; + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + if(!QFile::exists(liveCheckFile)) + { + qWarning() << "Couldn't find the" << liveCheckFile << "file!"; + startFailed = true; + continue; + } + try + { + key = QString::fromUtf8(FS::read(liveCheckFile)); + auto id = ApplicationId::fromRawString(key); + LocalPeer peer(nullptr, id); + if(peer.isClient()) + { + startFailed = false; + qDebug() << "Found process started with key " << key; + break; + } + else + { + startFailed = true; + qDebug() << "Process started with key " << key << "apparently died or is not reponding..."; + break; + } + } + catch (const Exception &e) + { + qWarning() << "Couldn't read the" << liveCheckFile << "file!"; + startFailed = true; + continue; + } + } + } + if(startFailed) + { + m_failedOperationType = Start; + fail(); + return; + } + else + { + origin.rmdir(m_updateFilesDir); + qApp->quit(); + return; + } } void UpdateController::fail() { - qWarning() << "Update failed!"; + qWarning() << "Update failed!"; - QString msg; - bool doRollback = false; - QString failTitle = QObject::tr("Update failed!"); - QString rollFailTitle = QObject::tr("Rollback failed!"); - switch (m_failedOperationType) - { - case Replace: - { - msg = QObject::tr("Couldn't replace file %1. Changes will be reverted.\n" - "See the MultiMC log file for details.").arg(m_failedFile); - doRollback = true; - QMessageBox::critical(m_parent, failTitle, msg); - break; - } - case Delete: - { - msg = QObject::tr("Couldn't remove file %1. Changes will be reverted.\n" - "See the MultiMC log file for details.").arg(m_failedFile); - doRollback = true; - QMessageBox::critical(m_parent, failTitle, msg); - break; - } - case Start: - { - msg = QObject::tr("The new version didn't start or is too old and doesn't respond to startup checks.\n" - "\n" - "Roll back to previous version?"); - auto result = QMessageBox::critical( - m_parent, - failTitle, - msg, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes - ); - doRollback = (result == QMessageBox::Yes); - break; - } - case Nothing: - default: - return; - } - if(doRollback) - { - auto rollbackOK = rollback(); - if(!rollbackOK) - { - msg = QObject::tr("The rollback failed too.\n" - "You will have to repair MultiMC manually.\n" - "Please let us know why and how this happened.").arg(m_failedFile); - QMessageBox::critical(m_parent, rollFailTitle, msg); - qApp->quit(); - } - } - else - { - qApp->quit(); - } + QString msg; + bool doRollback = false; + QString failTitle = QObject::tr("Update failed!"); + QString rollFailTitle = QObject::tr("Rollback failed!"); + switch (m_failedOperationType) + { + case Replace: + { + msg = QObject::tr("Couldn't replace file %1. Changes will be reverted.\n" + "See the MultiMC log file for details.").arg(m_failedFile); + doRollback = true; + QMessageBox::critical(m_parent, failTitle, msg); + break; + } + case Delete: + { + msg = QObject::tr("Couldn't remove file %1. Changes will be reverted.\n" + "See the MultiMC log file for details.").arg(m_failedFile); + doRollback = true; + QMessageBox::critical(m_parent, failTitle, msg); + break; + } + case Start: + { + msg = QObject::tr("The new version didn't start or is too old and doesn't respond to startup checks.\n" + "\n" + "Roll back to previous version?"); + auto result = QMessageBox::critical( + m_parent, + failTitle, + msg, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes + ); + doRollback = (result == QMessageBox::Yes); + break; + } + case Nothing: + default: + return; + } + if(doRollback) + { + auto rollbackOK = rollback(); + if(!rollbackOK) + { + msg = QObject::tr("The rollback failed too.\n" + "You will have to repair MultiMC manually.\n" + "Please let us know why and how this happened.").arg(m_failedFile); + QMessageBox::critical(m_parent, rollFailTitle, msg); + qApp->quit(); + } + } + else + { + qApp->quit(); + } } bool UpdateController::rollback() { - bool revertOK = true; - // if the above failed, roll back changes - for(auto backup:m_replace_backups) - { - qWarning() << "restoring" << backup.original << "from" << backup.backup; - if(!QFile::rename(backup.original, backup.update)) - { - revertOK = false; - qWarning() << "moving new" << backup.original << "back to" << backup.update << "failed!"; - continue; - } + bool revertOK = true; + // if the above failed, roll back changes + for(auto backup:m_replace_backups) + { + qWarning() << "restoring" << backup.original << "from" << backup.backup; + if(!QFile::rename(backup.original, backup.update)) + { + revertOK = false; + qWarning() << "moving new" << backup.original << "back to" << backup.update << "failed!"; + continue; + } - if(!QFile::rename(backup.backup, backup.original)) - { - revertOK = false; - qWarning() << "restoring" << backup.original << "failed!"; - } - } - for(auto backup:m_delete_backups) - { - qWarning() << "restoring" << backup.original << "from" << backup.backup; - if(!QFile::rename(backup.backup, backup.original)) - { - revertOK = false; - qWarning() << "restoring" << backup.original << "failed!"; - } - } - return revertOK; + if(!QFile::rename(backup.backup, backup.original)) + { + revertOK = false; + qWarning() << "restoring" << backup.original << "failed!"; + } + } + for(auto backup:m_delete_backups) + { + qWarning() << "restoring" << backup.original << "from" << backup.backup; + if(!QFile::rename(backup.backup, backup.original)) + { + revertOK = false; + qWarning() << "restoring" << backup.original << "failed!"; + } + } + return revertOK; } diff --git a/application/UpdateController.h b/application/UpdateController.h index 2b258701..715554e5 100644 --- a/application/UpdateController.h +++ b/application/UpdateController.h @@ -9,36 +9,36 @@ class QWidget; class UpdateController { public: - UpdateController(QWidget * parent, const QString &root, const QString updateFilesDir, GoUpdate::OperationList operations); - void installUpdates(); + UpdateController(QWidget * parent, const QString &root, const QString updateFilesDir, GoUpdate::OperationList operations); + void installUpdates(); private: - void fail(); - bool rollback(); + void fail(); + bool rollback(); private: - QString m_root; - QString m_updateFilesDir; - GoUpdate::OperationList m_operations; - QWidget * m_parent; + QString m_root; + QString m_updateFilesDir; + GoUpdate::OperationList m_operations; + QWidget * m_parent; - struct BackupEntry - { - // path where we got the new file from - QString update; - // path of what is being actually updated - QString original; - // path where the backup of the updated file was placed - QString backup; - }; - QList m_replace_backups; - QList m_delete_backups; - enum Failure - { - Replace, - Delete, - Start, - Nothing - } m_failedOperationType = Nothing; - QString m_failedFile; + struct BackupEntry + { + // path where we got the new file from + QString update; + // path of what is being actually updated + QString original; + // path where the backup of the updated file was placed + QString backup; + }; + QList m_replace_backups; + QList m_delete_backups; + enum Failure + { + Replace, + Delete, + Start, + Nothing + } m_failedOperationType = Nothing; + QString m_failedFile; }; diff --git a/application/VersionProxyModel.cpp b/application/VersionProxyModel.cpp index d59fceee..5587136f 100644 --- a/application/VersionProxyModel.cpp +++ b/application/VersionProxyModel.cpp @@ -7,430 +7,441 @@ class VersionFilterModel : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT public: - VersionFilterModel(VersionProxyModel *parent) : QSortFilterProxyModel(parent) - { - m_parent = parent; - setSortRole(BaseVersionList::SortRole); - sort(0, Qt::DescendingOrder); - } - - bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const - { - const auto &filters = m_parent->filters(); - for (auto it = filters.begin(); it != filters.end(); ++it) - { - auto idx = sourceModel()->index(source_row, 0, source_parent); - auto data = sourceModel()->data(idx, it.key()); - auto match = data.toString(); - if(!it.value()->accepts(match)) - { - return false; - } - } - return true; - } - - void filterChanged() - { - invalidateFilter(); - } + VersionFilterModel(VersionProxyModel *parent) : QSortFilterProxyModel(parent) + { + m_parent = parent; + setSortRole(BaseVersionList::SortRole); + sort(0, Qt::DescendingOrder); + } + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const + { + const auto &filters = m_parent->filters(); + for (auto it = filters.begin(); it != filters.end(); ++it) + { + auto idx = sourceModel()->index(source_row, 0, source_parent); + auto data = sourceModel()->data(idx, it.key()); + auto match = data.toString(); + if(!it.value()->accepts(match)) + { + return false; + } + } + return true; + } + + void filterChanged() + { + invalidateFilter(); + } private: - VersionProxyModel *m_parent; + VersionProxyModel *m_parent; }; VersionProxyModel::VersionProxyModel(QObject *parent) : QAbstractProxyModel(parent) { - filterModel = new VersionFilterModel(this); - connect(filterModel, &QAbstractItemModel::dataChanged, this, &VersionProxyModel::sourceDataChanged); - connect(filterModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &VersionProxyModel::sourceRowsAboutToBeInserted); - connect(filterModel, &QAbstractItemModel::rowsInserted, this, &VersionProxyModel::sourceRowsInserted); - connect(filterModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &VersionProxyModel::sourceRowsAboutToBeRemoved); - connect(filterModel, &QAbstractItemModel::rowsRemoved, this, &VersionProxyModel::sourceRowsRemoved); - // FIXME: implement when needed - /* - connect(replacing, &QAbstractItemModel::rowsAboutToBeMoved, this, &VersionProxyModel::sourceRowsAboutToBeMoved); - connect(replacing, &QAbstractItemModel::rowsMoved, this, &VersionProxyModel::sourceRowsMoved); - connect(replacing, &QAbstractItemModel::layoutAboutToBeChanged, this, &VersionProxyModel::sourceLayoutAboutToBeChanged); - connect(replacing, &QAbstractItemModel::layoutChanged, this, &VersionProxyModel::sourceLayoutChanged); - */ - connect(filterModel, &QAbstractItemModel::modelAboutToBeReset, this, &VersionProxyModel::sourceAboutToBeReset); - connect(filterModel, &QAbstractItemModel::modelReset, this, &VersionProxyModel::sourceReset); - - QAbstractProxyModel::setSourceModel(filterModel); + filterModel = new VersionFilterModel(this); + connect(filterModel, &QAbstractItemModel::dataChanged, this, &VersionProxyModel::sourceDataChanged); + connect(filterModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &VersionProxyModel::sourceRowsAboutToBeInserted); + connect(filterModel, &QAbstractItemModel::rowsInserted, this, &VersionProxyModel::sourceRowsInserted); + connect(filterModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &VersionProxyModel::sourceRowsAboutToBeRemoved); + connect(filterModel, &QAbstractItemModel::rowsRemoved, this, &VersionProxyModel::sourceRowsRemoved); + // FIXME: implement when needed + /* + connect(replacing, &QAbstractItemModel::rowsAboutToBeMoved, this, &VersionProxyModel::sourceRowsAboutToBeMoved); + connect(replacing, &QAbstractItemModel::rowsMoved, this, &VersionProxyModel::sourceRowsMoved); + connect(replacing, &QAbstractItemModel::layoutAboutToBeChanged, this, &VersionProxyModel::sourceLayoutAboutToBeChanged); + connect(replacing, &QAbstractItemModel::layoutChanged, this, &VersionProxyModel::sourceLayoutChanged); + */ + connect(filterModel, &QAbstractItemModel::modelAboutToBeReset, this, &VersionProxyModel::sourceAboutToBeReset); + connect(filterModel, &QAbstractItemModel::modelReset, this, &VersionProxyModel::sourceReset); + + QAbstractProxyModel::setSourceModel(filterModel); } QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { - if(section < 0 || section >= m_columns.size()) - return QVariant(); - if(orientation != Qt::Horizontal) - return QVariant(); - auto column = m_columns[section]; - if(role == Qt::DisplayRole) - { - switch(column) - { - case Name: - return tr("Version"); - case ParentVersion: - return tr("Minecraft"); //FIXME: this should come from metadata - case Branch: - return tr("Branch"); - case Type: - return tr("Type"); - case Architecture: - return tr("Architecture"); - case Path: - return tr("Path"); - case Time: - return tr("Released"); - } - } - else if(role == Qt::ToolTipRole) - { - switch(column) - { - case Name: - return tr("The name of the version."); - case ParentVersion: - return tr("Minecraft version"); //FIXME: this should come from metadata - case Branch: - return tr("The version's branch"); - case Type: - return tr("The version's type"); - case Architecture: - return tr("CPU Architecture"); - case Path: - return tr("Filesystem path to this version"); - case Time: - return tr("Release date of this version"); - } - } - return QVariant(); + if(section < 0 || section >= m_columns.size()) + return QVariant(); + if(orientation != Qt::Horizontal) + return QVariant(); + auto column = m_columns[section]; + if(role == Qt::DisplayRole) + { + switch(column) + { + case Name: + return tr("Version"); + case ParentVersion: + return tr("Minecraft"); //FIXME: this should come from metadata + case Branch: + return tr("Branch"); + case Type: + return tr("Type"); + case Architecture: + return tr("Architecture"); + case Path: + return tr("Path"); + case Time: + return tr("Released"); + } + } + else if(role == Qt::ToolTipRole) + { + switch(column) + { + case Name: + return tr("The name of the version."); + case ParentVersion: + return tr("Minecraft version"); //FIXME: this should come from metadata + case Branch: + return tr("The version's branch"); + case Type: + return tr("The version's type"); + case Architecture: + return tr("CPU Architecture"); + case Path: + return tr("Filesystem path to this version"); + case Time: + return tr("Release date of this version"); + } + } + return QVariant(); } QVariant VersionProxyModel::data(const QModelIndex &index, int role) const { - if(!index.isValid()) - { - return QVariant(); - } - auto column = m_columns[index.column()]; - auto parentIndex = mapToSource(index); - switch(role) - { - case Qt::DisplayRole: - { - switch(column) - { - case Name: - return sourceModel()->data(parentIndex, BaseVersionList::VersionRole); - case ParentVersion: - return sourceModel()->data(parentIndex, BaseVersionList::ParentVersionRole); - case Branch: - return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); - case Type: - return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); - case Architecture: - return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); - case Path: - return sourceModel()->data(parentIndex, BaseVersionList::PathRole); - case Time: - return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); - default: - return QVariant(); - } - } - case Qt::ToolTipRole: - { - switch(column) - { - case Name: - { - if(hasRecommended) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); - if(value.toBool()) - { - return tr("Recommended"); - } - else if(hasLatest) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if(value.toBool()) - { - return tr("Latest"); - } - } - else if(index.row() == 0) - { - return tr("Latest"); - } - } - } - default: - { - return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); - } - } - } - case Qt::DecorationRole: - { - switch(column) - { - case Name: - { - if(hasRecommended) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); - if(value.toBool()) - { - return MMC->getThemedIcon("star"); - } - else if(hasLatest) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if(value.toBool()) - { - return MMC->getThemedIcon("bug"); - } - } - else if(index.row() == 0) - { - return MMC->getThemedIcon("bug"); - } - auto pixmap = QPixmapCache::find("placeholder"); - if(!pixmap) - { - QPixmap px(16,16); - px.fill(Qt::transparent); - QPixmapCache::insert("placeholder", px); - return px; - } - return *pixmap; - } - } - default: - { - return QVariant(); - } - } - } - default: - { - if(roles.contains((BaseVersionList::ModelRoles)role)) - { - return sourceModel()->data(parentIndex, role); - } - return QVariant(); - } - } -}; + if(!index.isValid()) + { + return QVariant(); + } + auto column = m_columns[index.column()]; + auto parentIndex = mapToSource(index); + switch(role) + { + case Qt::DisplayRole: + { + switch(column) + { + case Name: + { + QString version = sourceModel()->data(parentIndex, BaseVersionList::VersionRole).toString(); + if(version == m_currentVersion) + { + return tr("%1 (installed)").arg(version); + } + return version; + } + case ParentVersion: + return sourceModel()->data(parentIndex, BaseVersionList::ParentVersionRole); + case Branch: + return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); + case Type: + return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); + case Architecture: + return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); + case Path: + return sourceModel()->data(parentIndex, BaseVersionList::PathRole); + case Time: + return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); + default: + return QVariant(); + } + } + case Qt::ToolTipRole: + { + switch(column) + { + case Name: + { + if(hasRecommended) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); + if(value.toBool()) + { + return tr("Recommended"); + } + else if(hasLatest) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if(value.toBool()) + { + return tr("Latest"); + } + } + else if(index.row() == 0) + { + return tr("Latest"); + } + } + } + default: + { + return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); + } + } + } + case Qt::DecorationRole: + { + switch(column) + { + case Name: + { + if(hasRecommended) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); + if(value.toBool()) + { + return MMC->getThemedIcon("star"); + } + else if(hasLatest) + { + auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if(value.toBool()) + { + return MMC->getThemedIcon("bug"); + } + } + else if(index.row() == 0) + { + return MMC->getThemedIcon("bug"); + } + auto pixmap = QPixmapCache::find("placeholder"); + if(!pixmap) + { + QPixmap px(16,16); + px.fill(Qt::transparent); + QPixmapCache::insert("placeholder", px); + return px; + } + return *pixmap; + } + } + default: + { + return QVariant(); + } + } + } + default: + { + if(roles.contains((BaseVersionList::ModelRoles)role)) + { + return sourceModel()->data(parentIndex, role); + } + return QVariant(); + } + } +} QModelIndex VersionProxyModel::parent(const QModelIndex &child) const { - return QModelIndex(); + return QModelIndex(); } QModelIndex VersionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { - if(sourceIndex.isValid()) - { - return index(sourceIndex.row(), 0); - } - return QModelIndex(); + if(sourceIndex.isValid()) + { + return index(sourceIndex.row(), 0); + } + return QModelIndex(); } QModelIndex VersionProxyModel::mapToSource(const QModelIndex &proxyIndex) const { - if(proxyIndex.isValid()) - { - return sourceModel()->index(proxyIndex.row(), 0); - } - return QModelIndex(); + if(proxyIndex.isValid()) + { + return sourceModel()->index(proxyIndex.row(), 0); + } + return QModelIndex(); } QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &parent) const { - // no trees here... shoo - if(parent.isValid()) - { - return QModelIndex(); - } - if(row < 0 || row >= sourceModel()->rowCount()) - return QModelIndex(); - if(column < 0 || column >= columnCount()) - return QModelIndex(); - return QAbstractItemModel::createIndex(row, column); + // no trees here... shoo + if(parent.isValid()) + { + return QModelIndex(); + } + if(row < 0 || row >= sourceModel()->rowCount()) + return QModelIndex(); + if(column < 0 || column >= columnCount()) + return QModelIndex(); + return QAbstractItemModel::createIndex(row, column); } int VersionProxyModel::columnCount(const QModelIndex &parent) const { - return m_columns.size(); + return m_columns.size(); } int VersionProxyModel::rowCount(const QModelIndex &parent) const { - if(sourceModel()) - { - return sourceModel()->rowCount(); - } - return 0; + if(sourceModel()) + { + return sourceModel()->rowCount(); + } + return 0; } void VersionProxyModel::sourceDataChanged(const QModelIndex &source_top_left, - const QModelIndex &source_bottom_right) + const QModelIndex &source_bottom_right) { - if(source_top_left.parent() != source_bottom_right.parent()) - return; + if(source_top_left.parent() != source_bottom_right.parent()) + return; - // whole row is getting changed - auto topLeft = createIndex(source_top_left.row(), 0); - auto bottomRight = createIndex(source_bottom_right.row(), columnCount() - 1); - emit dataChanged(topLeft, bottomRight); + // whole row is getting changed + auto topLeft = createIndex(source_top_left.row(), 0); + auto bottomRight = createIndex(source_bottom_right.row(), columnCount() - 1); + emit dataChanged(topLeft, bottomRight); } void VersionProxyModel::setSourceModel(QAbstractItemModel *replacingRaw) { - auto replacing = dynamic_cast(replacingRaw); - beginResetModel(); - - m_columns.clear(); - if(!replacing) - { - roles.clear(); - filterModel->setSourceModel(replacing); - return; - } - - roles = replacing->providesRoles(); - if(roles.contains(BaseVersionList::VersionRole)) - { - m_columns.push_back(Name); - } - /* - if(roles.contains(BaseVersionList::ParentVersionRole)) - { - m_columns.push_back(ParentVersion); - } - */ - if(roles.contains(BaseVersionList::ArchitectureRole)) - { - m_columns.push_back(Architecture); - } - if(roles.contains(BaseVersionList::PathRole)) - { - m_columns.push_back(Path); - } - if(roles.contains(Meta::VersionList::TimeRole)) - { - m_columns.push_back(Time); - } - if(roles.contains(BaseVersionList::BranchRole)) - { - m_columns.push_back(Branch); - } - if(roles.contains(BaseVersionList::TypeRole)) - { - m_columns.push_back(Type); - } - if(roles.contains(BaseVersionList::RecommendedRole)) - { - hasRecommended = true; - } - if(roles.contains(BaseVersionList::LatestRole)) - { - hasLatest = true; - } - filterModel->setSourceModel(replacing); - - endResetModel(); + auto replacing = dynamic_cast(replacingRaw); + beginResetModel(); + + m_columns.clear(); + if(!replacing) + { + roles.clear(); + filterModel->setSourceModel(replacing); + return; + } + + roles = replacing->providesRoles(); + if(roles.contains(BaseVersionList::VersionRole)) + { + m_columns.push_back(Name); + } + /* + if(roles.contains(BaseVersionList::ParentVersionRole)) + { + m_columns.push_back(ParentVersion); + } + */ + if(roles.contains(BaseVersionList::ArchitectureRole)) + { + m_columns.push_back(Architecture); + } + if(roles.contains(BaseVersionList::PathRole)) + { + m_columns.push_back(Path); + } + if(roles.contains(Meta::VersionList::TimeRole)) + { + m_columns.push_back(Time); + } + if(roles.contains(BaseVersionList::BranchRole)) + { + m_columns.push_back(Branch); + } + if(roles.contains(BaseVersionList::TypeRole)) + { + m_columns.push_back(Type); + } + if(roles.contains(BaseVersionList::RecommendedRole)) + { + hasRecommended = true; + } + if(roles.contains(BaseVersionList::LatestRole)) + { + hasLatest = true; + } + filterModel->setSourceModel(replacing); + + endResetModel(); } QModelIndex VersionProxyModel::getRecommended() const { - if(!roles.contains(BaseVersionList::RecommendedRole)) - { - return index(0, 0); - } - int recommended = 0; - for (int i = 0; i < rowCount(); i++) - { - auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::RecommendedRole); - if (value.toBool()) - { - recommended = i; - } - } - return index(recommended, 0); + if(!roles.contains(BaseVersionList::RecommendedRole)) + { + return index(0, 0); + } + int recommended = 0; + for (int i = 0; i < rowCount(); i++) + { + auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::RecommendedRole); + if (value.toBool()) + { + recommended = i; + } + } + return index(recommended, 0); } QModelIndex VersionProxyModel::getVersion(const QString& version) const { - int found = -1; - for (int i = 0; i < rowCount(); i++) - { - auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::VersionRole); - if (value.toString() == version) - { - found = i; - } - } - if(found == -1) - { - return QModelIndex(); - } - return index(found, 0); + int found = -1; + for (int i = 0; i < rowCount(); i++) + { + auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::VersionRole); + if (value.toString() == version) + { + found = i; + } + } + if(found == -1) + { + return QModelIndex(); + } + return index(found, 0); } void VersionProxyModel::clearFilters() { - m_filters.clear(); - filterModel->filterChanged(); + m_filters.clear(); + filterModel->filterChanged(); } void VersionProxyModel::setFilter(const BaseVersionList::ModelRoles column, Filter * f) { - m_filters[column].reset(f); - filterModel->filterChanged(); + m_filters[column].reset(f); + filterModel->filterChanged(); } const VersionProxyModel::FilterMap &VersionProxyModel::filters() const { - return m_filters; + return m_filters; } void VersionProxyModel::sourceAboutToBeReset() { - beginResetModel(); + beginResetModel(); } void VersionProxyModel::sourceReset() { - endResetModel(); + endResetModel(); } void VersionProxyModel::sourceRowsAboutToBeInserted(const QModelIndex& parent, int first, int last) { - beginInsertRows(parent, first, last); + beginInsertRows(parent, first, last); } void VersionProxyModel::sourceRowsInserted(const QModelIndex& parent, int first, int last) { - endInsertRows(); + endInsertRows(); } void VersionProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { - beginRemoveRows(parent, first, last); + beginRemoveRows(parent, first, last); } void VersionProxyModel::sourceRowsRemoved(const QModelIndex& parent, int first, int last) { - endRemoveRows(); + endRemoveRows(); } +void VersionProxyModel::setCurrentVersion(const QString &version) +{ + m_currentVersion = version; +} #include "VersionProxyModel.moc" diff --git a/application/VersionProxyModel.h b/application/VersionProxyModel.h index 91d1ba23..8991c31b 100644 --- a/application/VersionProxyModel.h +++ b/application/VersionProxyModel.h @@ -8,58 +8,60 @@ class VersionFilterModel; class VersionProxyModel: public QAbstractProxyModel { - Q_OBJECT + Q_OBJECT public: - enum Column - { - Name, - ParentVersion, - Branch, - Type, - Architecture, - Path, - Time - }; - typedef QHash> FilterMap; + enum Column + { + Name, + ParentVersion, + Branch, + Type, + Architecture, + Path, + Time + }; + typedef QHash> FilterMap; public: VersionProxyModel ( QObject* parent = 0 ); virtual ~VersionProxyModel() {}; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; - virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual QModelIndex parent(const QModelIndex &child) const override; - virtual void setSourceModel(QAbstractItemModel *sourceModel) override; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; + virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual QModelIndex parent(const QModelIndex &child) const override; + virtual void setSourceModel(QAbstractItemModel *sourceModel) override; - const FilterMap &filters() const; - void setFilter(const BaseVersionList::ModelRoles column, Filter * filter); - void clearFilters(); - QModelIndex getRecommended() const; - QModelIndex getVersion(const QString & version) const; + const FilterMap &filters() const; + void setFilter(const BaseVersionList::ModelRoles column, Filter * filter); + void clearFilters(); + QModelIndex getRecommended() const; + QModelIndex getVersion(const QString & version) const; + void setCurrentVersion(const QString &version); private slots: - void sourceDataChanged(const QModelIndex &source_top_left,const QModelIndex &source_bottom_right); + void sourceDataChanged(const QModelIndex &source_top_left,const QModelIndex &source_bottom_right); - void sourceAboutToBeReset(); - void sourceReset(); + void sourceAboutToBeReset(); + void sourceReset(); - void sourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last); - void sourceRowsInserted(const QModelIndex &parent, int first, int last); + void sourceRowsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void sourceRowsInserted(const QModelIndex &parent, int first, int last); - void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); - void sourceRowsRemoved(const QModelIndex &parent, int first, int last); + void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void sourceRowsRemoved(const QModelIndex &parent, int first, int last); private: - QList m_columns; - FilterMap m_filters; - BaseVersionList::RoleList roles; - VersionFilterModel * filterModel; - bool hasRecommended = false; - bool hasLatest = false; + QList m_columns; + FilterMap m_filters; + BaseVersionList::RoleList roles; + VersionFilterModel * filterModel; + bool hasRecommended = false; + bool hasLatest = false; + QString m_currentVersion; }; diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp index 8f2e72c2..ca1dfd94 100644 --- a/application/dialogs/AboutDialog.cpp +++ b/application/dialogs/AboutDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,113 +27,113 @@ // This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument... static QString getCreditsHtml(QStringList patrons) { - QString creditsHtml = QObject::tr( - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "

MultiMC Developers

" - "

Andrew Okin <forkk@forkk.net>

" - "

Petr Mrázek <peterix@gmail.com>

" - "

Sky Welch <multimc@bunnies.io>

" - "

Jan (02JanDal) <02jandal@gmail.com>

" - "

RoboSky <@RoboSky_>

" - "" - "

With thanks to

" - "

Orochimarufan <orochimarufan.x3@gmail.com>

" - "

TakSuyu <taksuyu@gmail.com>

" - "

Kilobyte <stiepen22@gmx.de>

" - "

Rootbear75 <@rootbear75>

" - "" - "

Patrons

" - "%1" - "" - "" - ""); - if (patrons.isEmpty()) - return creditsHtml.arg(QObject::tr("

Loading...

")); - else - { - QString patronsStr; - for (QString patron : patrons) - { - patronsStr.append(QString("

%1

").arg(patron)); - } - - return creditsHtml.arg(patronsStr); - } + QString creditsHtml = QObject::tr( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "

MultiMC Developers

" + "

Andrew Okin <forkk@forkk.net>

" + "

Petr Mrázek <peterix@gmail.com>

" + "

Sky Welch <multimc@bunnies.io>

" + "

Jan (02JanDal) <02jandal@gmail.com>

" + "

RoboSky <@RoboSky_>

" + "" + "

With thanks to

" + "

Orochimarufan <orochimarufan.x3@gmail.com>

" + "

TakSuyu <taksuyu@gmail.com>

" + "

Kilobyte <stiepen22@gmx.de>

" + "

Rootbear75 <@rootbear75>

" + "" + "

Patrons

" + "%1" + "" + "" + ""); + if (patrons.isEmpty()) + return creditsHtml.arg(QObject::tr("

Loading...

")); + else + { + QString patronsStr; + for (QString patron : patrons) + { + patronsStr.append(QString("

%1

").arg(patron)); + } + + return creditsHtml.arg(patronsStr); + } } static QString getLicenseHtml() { - HoeDown hoedown; - QFile dataFile(":/documents/COPYING.md"); - dataFile.open(QIODevice::ReadOnly); - QString output = hoedown.process(dataFile.readAll()); - return output; + HoeDown hoedown; + QFile dataFile(":/documents/COPYING.md"); + dataFile.open(QIODevice::ReadOnly); + QString output = hoedown.process(dataFile.readAll()); + return output; } AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { - ui->setupUi(this); + ui->setupUi(this); - QString chtml = getCreditsHtml(QStringList()); - ui->creditsText->setHtml(chtml); + QString chtml = getCreditsHtml(QStringList()); + ui->creditsText->setHtml(chtml); - QString lhtml = getLicenseHtml(); - ui->licenseText->setHtml(lhtml); + QString lhtml = getLicenseHtml(); + ui->licenseText->setHtml(lhtml); - ui->urlLabel->setOpenExternalLinks(true); + ui->urlLabel->setOpenExternalLinks(true); - ui->icon->setPixmap(MMC->getThemedIcon("logo").pixmap(64)); - ui->title->setText("MultiMC 5"); + ui->icon->setPixmap(MMC->getThemedIcon("logo").pixmap(64)); + ui->title->setText("MultiMC 5"); - ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString()); - ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM); + ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString()); + ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM); - if (BuildConfig.VERSION_BUILD >= 0) - ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD)); - else - ui->buildNumLabel->setVisible(false); + if (BuildConfig.VERSION_BUILD >= 0) + ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD)); + else + ui->buildNumLabel->setVisible(false); - if (!BuildConfig.VERSION_CHANNEL.isEmpty()) - ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL); - else - ui->channelLabel->setVisible(false); + if (!BuildConfig.VERSION_CHANNEL.isEmpty()) + ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL); + else + ui->channelLabel->setVisible(false); - connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); + connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); - connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt); + connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt); - loadPatronList(); + loadPatronList(); } AboutDialog::~AboutDialog() { - delete ui; + delete ui; } void AboutDialog::loadPatronList() { - netJob.reset(new NetJob("Patreon Patron List")); - netJob->addNetAction(Net::Download::makeByteArray(QUrl("http://files.multimc.org/patrons.txt"), &dataSink)); - connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded); - netJob->start(); + netJob.reset(new NetJob("Patreon Patron List")); + netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://files.multimc.org/patrons.txt"), &dataSink)); + connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded); + netJob->start(); } void AboutDialog::patronListLoaded() { - QString patronListStr(dataSink); - dataSink.clear(); - QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts)); - ui->creditsText->setHtml(html); + QString patronListStr(dataSink); + dataSink.clear(); + QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts)); + ui->creditsText->setHtml(html); } diff --git a/application/dialogs/AboutDialog.h b/application/dialogs/AboutDialog.h index 9768b866..21001c07 100644 --- a/application/dialogs/AboutDialog.h +++ b/application/dialogs/AboutDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,23 +25,23 @@ class AboutDialog; class AboutDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit AboutDialog(QWidget *parent = 0); - ~AboutDialog(); + explicit AboutDialog(QWidget *parent = 0); + ~AboutDialog(); public slots: - /// Starts loading a list of Patreon patrons. - void loadPatronList(); - - /// Slot for when the patron list loads successfully. - void patronListLoaded(); + /// Starts loading a list of Patreon patrons. + void loadPatronList(); + + /// Slot for when the patron list loads successfully. + void patronListLoaded(); private: - Ui::AboutDialog *ui; + Ui::AboutDialog *ui; - NetJobPtr netJob; - QByteArray dataSink; + NetJobPtr netJob; + QByteArray dataSink; }; diff --git a/application/dialogs/AboutDialog.ui b/application/dialogs/AboutDialog.ui index 5e8e3e68..a312aa0c 100644 --- a/application/dialogs/AboutDialog.ui +++ b/application/dialogs/AboutDialog.ui @@ -165,7 +165,7 @@ - © 2012-2018 MultiMC Contributors + © 2012-2019 MultiMC Contributors Qt::AlignCenter @@ -180,7 +180,7 @@ - <html><head/><body><p><a href="http://github.com/MultiMC/MultiMC5">http://github.com/MultiMC/MultiMC5</a></p></body></html> + <html><head/><body><p><a href="https://github.com/MultiMC/MultiMC5">https://github.com/MultiMC/MultiMC5</a></p></body></html> Qt::AlignCenter diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp index 72ef00fa..6100860c 100644 --- a/application/dialogs/CopyInstanceDialog.cpp +++ b/application/dialogs/CopyInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,87 +29,97 @@ #include "InstanceList.h" CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) - :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) + :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) { - ui->setupUi(this); - resize(minimumSizeHint()); - layout()->setSizeConstraint(QLayout::SetFixedSize); - - InstIconKey = original->iconKey(); - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - ui->instNameTextBox->setText(original->name()); - ui->instNameTextBox->setFocus(); - auto groups = MMC->instances()->getGroups().toSet(); - auto groupList = QStringList(groups.toList()); - groupList.sort(Qt::CaseInsensitive); - groupList.removeOne(""); - groupList.push_front(""); - ui->groupBox->addItems(groupList); - int index = groupList.indexOf(m_original->group()); - if(index == -1) - { - index = 0; - } - ui->groupBox->setCurrentIndex(index); - ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); - ui->copySavesCheckbox->setChecked(m_copySaves); + ui->setupUi(this); + resize(minimumSizeHint()); + layout()->setSizeConstraint(QLayout::SetFixedSize); + + InstIconKey = original->iconKey(); + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + ui->instNameTextBox->setText(original->name()); + ui->instNameTextBox->setFocus(); + auto groups = MMC->instances()->getGroups().toSet(); + auto groupList = QStringList(groups.toList()); + groupList.sort(Qt::CaseInsensitive); + groupList.removeOne(""); + groupList.push_front(""); + ui->groupBox->addItems(groupList); + int index = groupList.indexOf(MMC->instances()->getInstanceGroup(m_original->id())); + if(index == -1) + { + index = 0; + } + ui->groupBox->setCurrentIndex(index); + ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); + ui->copySavesCheckbox->setChecked(m_copySaves); } CopyInstanceDialog::~CopyInstanceDialog() { - delete ui; + delete ui; } void CopyInstanceDialog::updateDialogState() { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!instName().isEmpty()); + auto allowOK = !instName().isEmpty(); + auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok); + if(OkButton->isEnabled() != allowOK) + { + OkButton->setEnabled(allowOK); + } } QString CopyInstanceDialog::instName() const { - return ui->instNameTextBox->text(); + auto result = ui->instNameTextBox->text().trimmed(); + if(result.size()) + { + return result; + } + return QString(); } QString CopyInstanceDialog::iconKey() const { - return InstIconKey; + return InstIconKey; } QString CopyInstanceDialog::instGroup() const { - return ui->groupBox->currentText(); + return ui->groupBox->currentText(); } void CopyInstanceDialog::on_iconButton_clicked() { - IconPickerDialog dlg(this); - dlg.execWithSelection(InstIconKey); - - if (dlg.result() == QDialog::Accepted) - { - InstIconKey = dlg.selectedIconKey; - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - } + IconPickerDialog dlg(this); + dlg.execWithSelection(InstIconKey); + + if (dlg.result() == QDialog::Accepted) + { + InstIconKey = dlg.selectedIconKey; + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + } } void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) { - updateDialogState(); + updateDialogState(); } bool CopyInstanceDialog::shouldCopySaves() const { - return m_copySaves; + return m_copySaves; } void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_copySaves = false; - } - else if(state == Qt::Checked) - { - m_copySaves = true; - } + if(state == Qt::Unchecked) + { + m_copySaves = false; + } + else if(state == Qt::Checked) + { + m_copySaves = true; + } } diff --git a/application/dialogs/CopyInstanceDialog.h b/application/dialogs/CopyInstanceDialog.h index 809552eb..d46e647c 100644 --- a/application/dialogs/CopyInstanceDialog.h +++ b/application/dialogs/CopyInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,28 +28,28 @@ class CopyInstanceDialog; class CopyInstanceDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0); - ~CopyInstanceDialog(); + explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0); + ~CopyInstanceDialog(); - void updateDialogState(); + void updateDialogState(); - QString instName() const; - QString instGroup() const; - QString iconKey() const; - bool shouldCopySaves() const; + QString instName() const; + QString instGroup() const; + QString iconKey() const; + bool shouldCopySaves() const; private slots: - void on_iconButton_clicked(); - void on_instNameTextBox_textChanged(const QString &arg1); - void on_copySavesCheckbox_stateChanged(int state); + void on_iconButton_clicked(); + void on_instNameTextBox_textChanged(const QString &arg1); + void on_copySavesCheckbox_stateChanged(int state); private: - Ui::CopyInstanceDialog *ui; - QString InstIconKey; - InstancePtr m_original; - bool m_copySaves = true; + Ui::CopyInstanceDialog *ui; + QString InstIconKey; + InstancePtr m_original; + bool m_copySaves = true; }; diff --git a/application/dialogs/CustomMessageBox.cpp b/application/dialogs/CustomMessageBox.cpp index a7d75263..db60b7f3 100644 --- a/application/dialogs/CustomMessageBox.cpp +++ b/application/dialogs/CustomMessageBox.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,18 @@ namespace CustomMessageBox { QMessageBox *selectable(QWidget *parent, const QString &title, const QString &text, - QMessageBox::Icon icon, QMessageBox::StandardButtons buttons, - QMessageBox::StandardButton defaultButton) + QMessageBox::Icon icon, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) { - QMessageBox *messageBox = new QMessageBox(parent); - messageBox->setWindowTitle(title); - messageBox->setText(text); - messageBox->setStandardButtons(buttons); - messageBox->setDefaultButton(defaultButton); - messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); - messageBox->setIcon(icon); - messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + QMessageBox *messageBox = new QMessageBox(parent); + messageBox->setWindowTitle(title); + messageBox->setText(text); + messageBox->setStandardButtons(buttons); + messageBox->setDefaultButton(defaultButton); + messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBox->setIcon(icon); + messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); - return messageBox; + return messageBox; } } diff --git a/application/dialogs/CustomMessageBox.h b/application/dialogs/CustomMessageBox.h index f9c0ad4e..92291e17 100644 --- a/application/dialogs/CustomMessageBox.h +++ b/application/dialogs/CustomMessageBox.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ namespace CustomMessageBox { QMessageBox *selectable(QWidget *parent, const QString &title, const QString &text, - QMessageBox::Icon icon = QMessageBox::NoIcon, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + QMessageBox::Icon icon = QMessageBox::NoIcon, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); } diff --git a/application/dialogs/EditAccountDialog.cpp b/application/dialogs/EditAccountDialog.cpp index e43be1d8..9ae0c745 100644 --- a/application/dialogs/EditAccountDialog.cpp +++ b/application/dialogs/EditAccountDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,43 +19,43 @@ #include EditAccountDialog::EditAccountDialog(const QString &text, QWidget *parent, int flags) - : QDialog(parent), ui(new Ui::EditAccountDialog) + : QDialog(parent), ui(new Ui::EditAccountDialog) { - ui->setupUi(this); + ui->setupUi(this); - ui->label->setText(text); - ui->label->setVisible(!text.isEmpty()); + ui->label->setText(text); + ui->label->setVisible(!text.isEmpty()); - ui->userTextBox->setEnabled(flags & UsernameField); - ui->passTextBox->setEnabled(flags & PasswordField); + ui->userTextBox->setEnabled(flags & UsernameField); + ui->passTextBox->setEnabled(flags & PasswordField); } EditAccountDialog::~EditAccountDialog() { - delete ui; + delete ui; } void EditAccountDialog::on_label_linkActivated(const QString &link) { - DesktopServices::openUrl(QUrl(link)); + DesktopServices::openUrl(QUrl(link)); } void EditAccountDialog::setUsername(const QString & user) const { - ui->userTextBox->setText(user); + ui->userTextBox->setText(user); } QString EditAccountDialog::username() const { - return ui->userTextBox->text(); + return ui->userTextBox->text(); } void EditAccountDialog::setPassword(const QString & pass) const { - ui->passTextBox->setText(pass); + ui->passTextBox->setText(pass); } QString EditAccountDialog::password() const { - return ui->passTextBox->text(); + return ui->passTextBox->text(); } diff --git a/application/dialogs/EditAccountDialog.h b/application/dialogs/EditAccountDialog.h index f121a111..4f80284a 100644 --- a/application/dialogs/EditAccountDialog.h +++ b/application/dialogs/EditAccountDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,33 +24,33 @@ class EditAccountDialog; class EditAccountDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit EditAccountDialog(const QString &text = "", QWidget *parent = 0, - int flags = UsernameField | PasswordField); - ~EditAccountDialog(); + explicit EditAccountDialog(const QString &text = "", QWidget *parent = 0, + int flags = UsernameField | PasswordField); + ~EditAccountDialog(); - void setUsername(const QString & user) const; - void setPassword(const QString & pass) const; + void setUsername(const QString & user) const; + void setPassword(const QString & pass) const; - QString username() const; - QString password() const; + QString username() const; + QString password() const; - enum Flags - { - NoFlags = 0, + enum Flags + { + NoFlags = 0, - //! Specifies that the dialog should have a username field. - UsernameField, + //! Specifies that the dialog should have a username field. + UsernameField, - //! Specifies that the dialog should have a password field. - PasswordField, - }; + //! Specifies that the dialog should have a password field. + PasswordField, + }; private slots: void on_label_linkActivated(const QString &link); private: - Ui::EditAccountDialog *ui; + Ui::EditAccountDialog *ui; }; diff --git a/application/dialogs/ExportInstanceDialog.cpp b/application/dialogs/ExportInstanceDialog.cpp index 0e19b758..49c082e9 100644 --- a/application/dialogs/ExportInstanceDialog.cpp +++ b/application/dialogs/ExportInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,456 +33,450 @@ class PackIgnoreProxy : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT public: - PackIgnoreProxy(InstancePtr instance, QObject *parent) : QSortFilterProxyModel(parent) - { - m_instance = instance; - } - // NOTE: Sadly, we have to do sorting ourselves. - bool lessThan(const QModelIndex &left, const QModelIndex &right) const - { - QFileSystemModel *fsm = qobject_cast(sourceModel()); - if (!fsm) - { - return QSortFilterProxyModel::lessThan(left, right); - } - bool asc = sortOrder() == Qt::AscendingOrder ? true : false; - - QFileInfo leftFileInfo = fsm->fileInfo(left); - QFileInfo rightFileInfo = fsm->fileInfo(right); - - if (!leftFileInfo.isDir() && rightFileInfo.isDir()) - { - return !asc; - } - if (leftFileInfo.isDir() && !rightFileInfo.isDir()) - { - return asc; - } - - // sort and proxy model breaks the original model... - if (sortColumn() == 0) - { - return Strings::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), - Qt::CaseInsensitive) < 0; - } - if (sortColumn() == 1) - { - auto leftSize = leftFileInfo.size(); - auto rightSize = rightFileInfo.size(); - if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) - { - return Strings::naturalCompare(leftFileInfo.fileName(), - rightFileInfo.fileName(), - Qt::CaseInsensitive) < 0 - ? asc - : !asc; - } - return leftSize < rightSize; - } - return QSortFilterProxyModel::lessThan(left, right); - } - - virtual Qt::ItemFlags flags(const QModelIndex &index) const - { - if (!index.isValid()) - return Qt::NoItemFlags; - - auto sourceIndex = mapToSource(index); - Qt::ItemFlags flags = sourceIndex.flags(); - if (index.column() == 0) - { - flags |= Qt::ItemIsUserCheckable; - if (sourceIndex.model()->hasChildren(sourceIndex)) - { - flags |= Qt::ItemIsTristate; - } - } - - return flags; - } - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const - { - QModelIndex sourceIndex = mapToSource(index); - - if (index.column() == 0 && role == Qt::CheckStateRole) - { - QFileSystemModel *fsm = qobject_cast(sourceModel()); - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto cover = blocked.cover(blockedPath); - if (!cover.isNull()) - { - return QVariant(Qt::Unchecked); - } - else if (blocked.exists(blockedPath)) - { - return QVariant(Qt::PartiallyChecked); - } - else - { - return QVariant(Qt::Checked); - } - } - - return sourceIndex.data(role); - } - - virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole) - { - if (index.column() == 0 && role == Qt::CheckStateRole) - { - Qt::CheckState state = static_cast(value.toInt()); - return setFilterState(index, state); - } - - QModelIndex sourceIndex = mapToSource(index); - return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); - } - - QString relPath(const QString &path) const - { - QString prefix = QDir().absoluteFilePath(m_instance->instanceRoot()); - prefix += '/'; - if (!path.startsWith(prefix)) - { - return QString(); - } - return path.mid(prefix.size()); - } - - bool setFilterState(QModelIndex index, Qt::CheckState state) - { - QFileSystemModel *fsm = qobject_cast(sourceModel()); - - if (!fsm) - { - return false; - } - - QModelIndex sourceIndex = mapToSource(index); - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - bool changed = false; - if (state == Qt::Unchecked) - { - // blocking a path - auto &node = blocked.insert(blockedPath); - // get rid of all blocked nodes below - node.clear(); - changed = true; - } - else if (state == Qt::Checked || state == Qt::PartiallyChecked) - { - if (!blocked.remove(blockedPath)) - { - auto cover = blocked.cover(blockedPath); - qDebug() << "Blocked by cover" << cover; - // uncover - blocked.remove(cover); - // block all contents, except for any cover - QModelIndex rootIndex = - fsm->index(FS::PathCombine(m_instance->instanceRoot(), cover)); - QModelIndex doing = rootIndex; - int row = 0; - QStack todo; - while (1) - { - auto node = doing.child(row, 0); - if (!node.isValid()) - { - if (!todo.size()) - { - break; - } - else - { - doing = todo.pop(); - row = 0; - continue; - } - } - auto relpath = relPath(fsm->filePath(node)); - if (blockedPath.startsWith(relpath)) // cover found? - { - // continue processing cover later - todo.push(node); - } - else - { - // or just block this one. - blocked.insert(relpath); - } - row++; - } - } - changed = true; - } - if (changed) - { - // update the thing - emit dataChanged(index, index, {Qt::CheckStateRole}); - // update everything above index - QModelIndex up = index.parent(); - while (1) - { - if (!up.isValid()) - break; - emit dataChanged(up, up, {Qt::CheckStateRole}); - up = up.parent(); - } - // and everything below the index - QModelIndex doing = index; - int row = 0; - QStack todo; - while (1) - { - auto node = doing.child(row, 0); - if (!node.isValid()) - { - if (!todo.size()) - { - break; - } - else - { - doing = todo.pop(); - row = 0; - continue; - } - } - emit dataChanged(node, node, {Qt::CheckStateRole}); - todo.push(node); - row++; - } - // siblings and unrelated nodes are ignored - } - return true; - } - - bool shouldExpand(QModelIndex index) - { - QModelIndex sourceIndex = mapToSource(index); - QFileSystemModel *fsm = qobject_cast(sourceModel()); - if (!fsm) - { - return false; - } - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto found = blocked.find(blockedPath); - if(found) - { - return !found->leaf(); - } - return false; - } - - void setBlockedPaths(QStringList paths) - { - beginResetModel(); - blocked.clear(); - blocked.insert(paths); - endResetModel(); - } - - const SeparatorPrefixTree<'/'> & blockedPaths() const - { - return blocked; - } + PackIgnoreProxy(InstancePtr instance, QObject *parent) : QSortFilterProxyModel(parent) + { + m_instance = instance; + } + // NOTE: Sadly, we have to do sorting ourselves. + bool lessThan(const QModelIndex &left, const QModelIndex &right) const + { + QFileSystemModel *fsm = qobject_cast(sourceModel()); + if (!fsm) + { + return QSortFilterProxyModel::lessThan(left, right); + } + bool asc = sortOrder() == Qt::AscendingOrder ? true : false; + + QFileInfo leftFileInfo = fsm->fileInfo(left); + QFileInfo rightFileInfo = fsm->fileInfo(right); + + if (!leftFileInfo.isDir() && rightFileInfo.isDir()) + { + return !asc; + } + if (leftFileInfo.isDir() && !rightFileInfo.isDir()) + { + return asc; + } + + // sort and proxy model breaks the original model... + if (sortColumn() == 0) + { + return Strings::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), + Qt::CaseInsensitive) < 0; + } + if (sortColumn() == 1) + { + auto leftSize = leftFileInfo.size(); + auto rightSize = rightFileInfo.size(); + if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) + { + return Strings::naturalCompare(leftFileInfo.fileName(), + rightFileInfo.fileName(), + Qt::CaseInsensitive) < 0 + ? asc + : !asc; + } + return leftSize < rightSize; + } + return QSortFilterProxyModel::lessThan(left, right); + } + + virtual Qt::ItemFlags flags(const QModelIndex &index) const + { + if (!index.isValid()) + return Qt::NoItemFlags; + + auto sourceIndex = mapToSource(index); + Qt::ItemFlags flags = sourceIndex.flags(); + if (index.column() == 0) + { + flags |= Qt::ItemIsUserCheckable; + if (sourceIndex.model()->hasChildren(sourceIndex)) + { + flags |= Qt::ItemIsTristate; + } + } + + return flags; + } + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + QModelIndex sourceIndex = mapToSource(index); + + if (index.column() == 0 && role == Qt::CheckStateRole) + { + QFileSystemModel *fsm = qobject_cast(sourceModel()); + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + auto cover = blocked.cover(blockedPath); + if (!cover.isNull()) + { + return QVariant(Qt::Unchecked); + } + else if (blocked.exists(blockedPath)) + { + return QVariant(Qt::PartiallyChecked); + } + else + { + return QVariant(Qt::Checked); + } + } + + return sourceIndex.data(role); + } + + virtual bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) + { + if (index.column() == 0 && role == Qt::CheckStateRole) + { + Qt::CheckState state = static_cast(value.toInt()); + return setFilterState(index, state); + } + + QModelIndex sourceIndex = mapToSource(index); + return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); + } + + QString relPath(const QString &path) const + { + QString prefix = QDir().absoluteFilePath(m_instance->instanceRoot()); + prefix += '/'; + if (!path.startsWith(prefix)) + { + return QString(); + } + return path.mid(prefix.size()); + } + + bool setFilterState(QModelIndex index, Qt::CheckState state) + { + QFileSystemModel *fsm = qobject_cast(sourceModel()); + + if (!fsm) + { + return false; + } + + QModelIndex sourceIndex = mapToSource(index); + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + bool changed = false; + if (state == Qt::Unchecked) + { + // blocking a path + auto &node = blocked.insert(blockedPath); + // get rid of all blocked nodes below + node.clear(); + changed = true; + } + else if (state == Qt::Checked || state == Qt::PartiallyChecked) + { + if (!blocked.remove(blockedPath)) + { + auto cover = blocked.cover(blockedPath); + qDebug() << "Blocked by cover" << cover; + // uncover + blocked.remove(cover); + // block all contents, except for any cover + QModelIndex rootIndex = + fsm->index(FS::PathCombine(m_instance->instanceRoot(), cover)); + QModelIndex doing = rootIndex; + int row = 0; + QStack todo; + while (1) + { + auto node = doing.child(row, 0); + if (!node.isValid()) + { + if (!todo.size()) + { + break; + } + else + { + doing = todo.pop(); + row = 0; + continue; + } + } + auto relpath = relPath(fsm->filePath(node)); + if (blockedPath.startsWith(relpath)) // cover found? + { + // continue processing cover later + todo.push(node); + } + else + { + // or just block this one. + blocked.insert(relpath); + } + row++; + } + } + changed = true; + } + if (changed) + { + // update the thing + emit dataChanged(index, index, {Qt::CheckStateRole}); + // update everything above index + QModelIndex up = index.parent(); + while (1) + { + if (!up.isValid()) + break; + emit dataChanged(up, up, {Qt::CheckStateRole}); + up = up.parent(); + } + // and everything below the index + QModelIndex doing = index; + int row = 0; + QStack todo; + while (1) + { + auto node = doing.child(row, 0); + if (!node.isValid()) + { + if (!todo.size()) + { + break; + } + else + { + doing = todo.pop(); + row = 0; + continue; + } + } + emit dataChanged(node, node, {Qt::CheckStateRole}); + todo.push(node); + row++; + } + // siblings and unrelated nodes are ignored + } + return true; + } + + bool shouldExpand(QModelIndex index) + { + QModelIndex sourceIndex = mapToSource(index); + QFileSystemModel *fsm = qobject_cast(sourceModel()); + if (!fsm) + { + return false; + } + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + auto found = blocked.find(blockedPath); + if(found) + { + return !found->leaf(); + } + return false; + } + + void setBlockedPaths(QStringList paths) + { + beginResetModel(); + blocked.clear(); + blocked.insert(paths); + endResetModel(); + } + + const SeparatorPrefixTree<'/'> & blockedPaths() const + { + return blocked; + } protected: - bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const - { - Q_UNUSED(source_parent) + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const + { + Q_UNUSED(source_parent) - // adjust the columns you want to filter out here - // return false for those that will be hidden - if (source_column == 2 || source_column == 3) - return false; + // adjust the columns you want to filter out here + // return false for those that will be hidden + if (source_column == 2 || source_column == 3) + return false; - return true; - } + return true; + } private: - InstancePtr m_instance; - SeparatorPrefixTree<'/'> blocked; + InstancePtr m_instance; + SeparatorPrefixTree<'/'> blocked; }; ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent) - : QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance) + : QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance) { - ui->setupUi(this); - auto model = new QFileSystemModel(this); - proxyModel = new PackIgnoreProxy(m_instance, this); - loadPackIgnore(); - proxyModel->setSourceModel(model); - auto root = instance->instanceRoot(); - ui->treeView->setModel(proxyModel); - ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); - ui->treeView->sortByColumn(0, Qt::AscendingOrder); - - connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); - - model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); - model->setRootPath(root); - auto headerView = ui->treeView->header(); - headerView->setSectionResizeMode(QHeaderView::ResizeToContents); - headerView->setSectionResizeMode(0, QHeaderView::Stretch); + ui->setupUi(this); + auto model = new QFileSystemModel(this); + proxyModel = new PackIgnoreProxy(m_instance, this); + loadPackIgnore(); + proxyModel->setSourceModel(model); + auto root = instance->instanceRoot(); + ui->treeView->setModel(proxyModel); + ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); + ui->treeView->sortByColumn(0, Qt::AscendingOrder); + + connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); + + model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); + model->setRootPath(root); + auto headerView = ui->treeView->header(); + headerView->setSectionResizeMode(QHeaderView::ResizeToContents); + headerView->setSectionResizeMode(0, QHeaderView::Stretch); } ExportInstanceDialog::~ExportInstanceDialog() { - delete ui; + delete ui; } /// Save icon to instance's folder is needed void SaveIcon(InstancePtr m_instance) { - auto iconKey = m_instance->iconKey(); - auto iconList = MMC->icons(); - auto mmcIcon = iconList->icon(iconKey); - if(mmcIcon) - { - bool saveIcon = false; - switch(mmcIcon->type()) - { - case IconType::FileBased: - case IconType::Transient: - saveIcon = true; - default: - break; - } - if(saveIcon) - { - auto & image = mmcIcon->m_images[mmcIcon->type()]; - auto & icon = image.icon; - auto sizes = icon.availableSizes(); - if(sizes.size() == 0) - { - return; - } - auto areaOf = [](QSize size) - { - return size.width() * size.height(); - }; - QSize largest = sizes[0]; - // find variant with largest area - for(auto size: sizes) - { - if(areaOf(largest) < areaOf(size)) - { - largest = size; - } - } - auto pixmap = icon.pixmap(largest); - pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); - } - } + auto iconKey = m_instance->iconKey(); + auto iconList = MMC->icons(); + auto mmcIcon = iconList->icon(iconKey); + if(!mmcIcon || mmcIcon->isBuiltIn()) { + return; + } + auto path = mmcIcon->getFilePath(); + if(!path.isNull()) { + QFileInfo inInfo (path); + FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) (); + return; + } + auto & image = mmcIcon->m_images[mmcIcon->type()]; + auto & icon = image.icon; + auto sizes = icon.availableSizes(); + if(sizes.size() == 0) + { + return; + } + auto areaOf = [](QSize size) + { + return size.width() * size.height(); + }; + QSize largest = sizes[0]; + // find variant with largest area + for(auto size: sizes) + { + if(areaOf(largest) < areaOf(size)) + { + largest = size; + } + } + auto pixmap = icon.pixmap(largest); + pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); } bool ExportInstanceDialog::doExport() { - auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); - - const QString output = QFileDialog::getSaveFileName( - this, tr("Export %1").arg(m_instance->name()), - FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite); - if (output.isEmpty()) - { - return false; - } - if (QFile::exists(output)) - { - int ret = - QMessageBox::question(this, tr("Overwrite?"), - tr("This file already exists. Do you want to overwrite it?"), - QMessageBox::No, QMessageBox::Yes); - if (ret == QMessageBox::No) - { - return false; - } - } - - SaveIcon(m_instance); - - auto & blocked = proxyModel->blockedPaths(); - using std::placeholders::_1; - if (!JlCompress::compressDir(output, m_instance->instanceRoot(), name, std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) - { - QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); - return false; - } - return true; + auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); + + const QString output = QFileDialog::getSaveFileName( + this, tr("Export %1").arg(m_instance->name()), + FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite); + if (output.isEmpty()) + { + return false; + } + if (QFile::exists(output)) + { + int ret = + QMessageBox::question(this, tr("Overwrite?"), + tr("This file already exists. Do you want to overwrite it?"), + QMessageBox::No, QMessageBox::Yes); + if (ret == QMessageBox::No) + { + return false; + } + } + + SaveIcon(m_instance); + + auto & blocked = proxyModel->blockedPaths(); + using std::placeholders::_1; + if (!JlCompress::compressDir(output, m_instance->instanceRoot(), name, std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) + { + QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); + return false; + } + return true; } void ExportInstanceDialog::done(int result) { - savePackIgnore(); - if (result == QDialog::Accepted) - { - if (doExport()) - { - QDialog::done(QDialog::Accepted); - return; - } - else - { - return; - } - } - QDialog::done(result); + savePackIgnore(); + if (result == QDialog::Accepted) + { + if (doExport()) + { + QDialog::done(QDialog::Accepted); + return; + } + else + { + return; + } + } + QDialog::done(result); } void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) { - //WARNING: possible off-by-one? - for(int i = top; i < bottom; i++) - { - auto node = parent.child(i, 0); - if(proxyModel->shouldExpand(node)) - { - auto expNode = node.parent(); - if(!expNode.isValid()) - { - continue; - } - ui->treeView->expand(node); - } - } + //WARNING: possible off-by-one? + for(int i = top; i < bottom; i++) + { + auto node = parent.child(i, 0); + if(proxyModel->shouldExpand(node)) + { + auto expNode = node.parent(); + if(!expNode.isValid()) + { + continue; + } + ui->treeView->expand(node); + } + } } QString ExportInstanceDialog::ignoreFileName() { - return FS::PathCombine(m_instance->instanceRoot(), ".packignore"); + return FS::PathCombine(m_instance->instanceRoot(), ".packignore"); } void ExportInstanceDialog::loadPackIgnore() { - auto filename = ignoreFileName(); - QFile ignoreFile(filename); - if(!ignoreFile.open(QIODevice::ReadOnly)) - { - return; - } - auto data = ignoreFile.readAll(); - auto string = QString::fromUtf8(data); - proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); + auto filename = ignoreFileName(); + QFile ignoreFile(filename); + if(!ignoreFile.open(QIODevice::ReadOnly)) + { + return; + } + auto data = ignoreFile.readAll(); + auto string = QString::fromUtf8(data); + proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); } void ExportInstanceDialog::savePackIgnore() { - auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); - auto filename = ignoreFileName(); - try - { - FS::write(filename, data); - } - catch (Exception & e) - { - qWarning() << e.cause(); - } + auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); + auto filename = ignoreFileName(); + try + { + FS::write(filename, data); + } + catch (const Exception &e) + { + qWarning() << e.cause(); + } } #include "ExportInstanceDialog.moc" diff --git a/application/dialogs/ExportInstanceDialog.h b/application/dialogs/ExportInstanceDialog.h index 7b9c6726..aec4cbfb 100644 --- a/application/dialogs/ExportInstanceDialog.h +++ b/application/dialogs/ExportInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,25 +30,25 @@ class ExportInstanceDialog; class ExportInstanceDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0); - ~ExportInstanceDialog(); + explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0); + ~ExportInstanceDialog(); - virtual void done(int result); + virtual void done(int result); private: - bool doExport(); - void loadPackIgnore(); - void savePackIgnore(); - QString ignoreFileName(); + bool doExport(); + void loadPackIgnore(); + void savePackIgnore(); + QString ignoreFileName(); private: - Ui::ExportInstanceDialog *ui; - InstancePtr m_instance; - PackIgnoreProxy * proxyModel; + Ui::ExportInstanceDialog *ui; + InstancePtr m_instance; + PackIgnoreProxy * proxyModel; private slots: - void rowsInserted(QModelIndex parent, int top, int bottom); + void rowsInserted(QModelIndex parent, int top, int bottom); }; diff --git a/application/dialogs/IconPickerDialog.cpp b/application/dialogs/IconPickerDialog.cpp index 4ffd12bc..7f930717 100644 --- a/application/dialogs/IconPickerDialog.cpp +++ b/application/dialogs/IconPickerDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,138 +25,139 @@ #include "groupview/InstanceDelegate.h" #include "icons/IconList.h" +#include "icons/IconUtils.h" #include IconPickerDialog::IconPickerDialog(QWidget *parent) - : QDialog(parent), ui(new Ui::IconPickerDialog) + : QDialog(parent), ui(new Ui::IconPickerDialog) { - ui->setupUi(this); - setWindowModality(Qt::WindowModal); - - auto contentsWidget = ui->iconView; - contentsWidget->setViewMode(QListView::IconMode); - contentsWidget->setFlow(QListView::LeftToRight); - contentsWidget->setIconSize(QSize(48, 48)); - contentsWidget->setMovement(QListView::Static); - contentsWidget->setResizeMode(QListView::Adjust); - contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); - contentsWidget->setSpacing(5); - contentsWidget->setWordWrap(false); - contentsWidget->setWrapping(true); - contentsWidget->setUniformItemSizes(true); - contentsWidget->setTextElideMode(Qt::ElideRight); - contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - contentsWidget->setItemDelegate(new ListViewDelegate()); - - // contentsWidget->setAcceptDrops(true); - contentsWidget->setDropIndicatorShown(true); - contentsWidget->viewport()->setAcceptDrops(true); - contentsWidget->setDragDropMode(QAbstractItemView::DropOnly); - contentsWidget->setDefaultDropAction(Qt::CopyAction); - - contentsWidget->installEventFilter(this); - - contentsWidget->setModel(MMC->icons().get()); - - // NOTE: ResetRole forces the button to be on the left, while the OK/Cancel ones are on the right. We win. - auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"), QDialogButtonBox::ResetRole); - auto buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole); - - connect(buttonAdd, SIGNAL(clicked(bool)), SLOT(addNewIcon())); - connect(buttonRemove, SIGNAL(clicked(bool)), SLOT(removeSelectedIcon())); - - connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); - - connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection))); - - auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); - connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); + ui->setupUi(this); + setWindowModality(Qt::WindowModal); + + auto contentsWidget = ui->iconView; + contentsWidget->setViewMode(QListView::IconMode); + contentsWidget->setFlow(QListView::LeftToRight); + contentsWidget->setIconSize(QSize(48, 48)); + contentsWidget->setMovement(QListView::Static); + contentsWidget->setResizeMode(QListView::Adjust); + contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); + contentsWidget->setSpacing(5); + contentsWidget->setWordWrap(false); + contentsWidget->setWrapping(true); + contentsWidget->setUniformItemSizes(true); + contentsWidget->setTextElideMode(Qt::ElideRight); + contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + contentsWidget->setItemDelegate(new ListViewDelegate()); + + // contentsWidget->setAcceptDrops(true); + contentsWidget->setDropIndicatorShown(true); + contentsWidget->viewport()->setAcceptDrops(true); + contentsWidget->setDragDropMode(QAbstractItemView::DropOnly); + contentsWidget->setDefaultDropAction(Qt::CopyAction); + + contentsWidget->installEventFilter(this); + + contentsWidget->setModel(MMC->icons().get()); + + // NOTE: ResetRole forces the button to be on the left, while the OK/Cancel ones are on the right. We win. + auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"), QDialogButtonBox::ResetRole); + auto buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole); + + connect(buttonAdd, SIGNAL(clicked(bool)), SLOT(addNewIcon())); + connect(buttonRemove, SIGNAL(clicked(bool)), SLOT(removeSelectedIcon())); + + connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); + + connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection))); + + auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); + connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); } bool IconPickerDialog::eventFilter(QObject *obj, QEvent *evt) { - if (obj != ui->iconView) - return QDialog::eventFilter(obj, evt); - if (evt->type() != QEvent::KeyPress) - { - return QDialog::eventFilter(obj, evt); - } - QKeyEvent *keyEvent = static_cast(evt); - switch (keyEvent->key()) - { - case Qt::Key_Delete: - removeSelectedIcon(); - return true; - case Qt::Key_Plus: - addNewIcon(); - return true; - default: - break; - } - return QDialog::eventFilter(obj, evt); + if (obj != ui->iconView) + return QDialog::eventFilter(obj, evt); + if (evt->type() != QEvent::KeyPress) + { + return QDialog::eventFilter(obj, evt); + } + QKeyEvent *keyEvent = static_cast(evt); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + removeSelectedIcon(); + return true; + case Qt::Key_Plus: + addNewIcon(); + return true; + default: + break; + } + return QDialog::eventFilter(obj, evt); } void IconPickerDialog::addNewIcon() { - //: The title of the select icons open file dialog - QString selectIcons = tr("Select Icons"); - //: The type of icon files - QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), - tr("Icons") + "(*.png *.jpg *.jpeg *.ico *.svg)"); - MMC->icons()->installIcons(fileNames); + //: The title of the select icons open file dialog + QString selectIcons = tr("Select Icons"); + //: The type of icon files + auto filter = IconUtils::getIconFilter(); + QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), tr("Icons %1").arg(filter)); + MMC->icons()->installIcons(fileNames); } void IconPickerDialog::removeSelectedIcon() { - MMC->icons()->deleteIcon(selectedIconKey); + MMC->icons()->deleteIcon(selectedIconKey); } void IconPickerDialog::activated(QModelIndex index) { - selectedIconKey = index.data(Qt::UserRole).toString(); - accept(); + selectedIconKey = index.data(Qt::UserRole).toString(); + accept(); } void IconPickerDialog::selectionChanged(QItemSelection selected, QItemSelection deselected) { - if (selected.empty()) - return; + if (selected.empty()) + return; - QString key = selected.first().indexes().first().data(Qt::UserRole).toString(); - if (!key.isEmpty()) - selectedIconKey = key; + QString key = selected.first().indexes().first().data(Qt::UserRole).toString(); + if (!key.isEmpty()) + selectedIconKey = key; } int IconPickerDialog::execWithSelection(QString selection) { - auto list = MMC->icons(); - auto contentsWidget = ui->iconView; - selectedIconKey = selection; - - int index_nr = list->getIconIndex(selection); - auto model_index = list->index(index_nr); - contentsWidget->selectionModel()->select( - model_index, QItemSelectionModel::Current | QItemSelectionModel::Select); - - QMetaObject::invokeMethod(this, "delayed_scroll", Qt::QueuedConnection, - Q_ARG(QModelIndex, model_index)); - return QDialog::exec(); + auto list = MMC->icons(); + auto contentsWidget = ui->iconView; + selectedIconKey = selection; + + int index_nr = list->getIconIndex(selection); + auto model_index = list->index(index_nr); + contentsWidget->selectionModel()->select( + model_index, QItemSelectionModel::Current | QItemSelectionModel::Select); + + QMetaObject::invokeMethod(this, "delayed_scroll", Qt::QueuedConnection, + Q_ARG(QModelIndex, model_index)); + return QDialog::exec(); } void IconPickerDialog::delayed_scroll(QModelIndex model_index) { - auto contentsWidget = ui->iconView; - contentsWidget->scrollTo(model_index); + auto contentsWidget = ui->iconView; + contentsWidget->scrollTo(model_index); } IconPickerDialog::~IconPickerDialog() { - delete ui; + delete ui; } void IconPickerDialog::openFolder() { - DesktopServices::openDirectory(MMC->icons()->getDirectory(), true); + DesktopServices::openDirectory(MMC->icons()->getDirectory(), true); } diff --git a/application/dialogs/IconPickerDialog.h b/application/dialogs/IconPickerDialog.h index 9053ec61..cb870f44 100644 --- a/application/dialogs/IconPickerDialog.h +++ b/application/dialogs/IconPickerDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,26 +24,26 @@ class IconPickerDialog; class IconPickerDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit IconPickerDialog(QWidget *parent = 0); - ~IconPickerDialog(); - int execWithSelection(QString selection); - QString selectedIconKey; + explicit IconPickerDialog(QWidget *parent = 0); + ~IconPickerDialog(); + int execWithSelection(QString selection); + QString selectedIconKey; protected: - virtual bool eventFilter(QObject *, QEvent *); + virtual bool eventFilter(QObject *, QEvent *); private: - Ui::IconPickerDialog *ui; + Ui::IconPickerDialog *ui; private slots: - void selectionChanged(QItemSelection, QItemSelection); - void activated(QModelIndex); - void delayed_scroll(QModelIndex); - void addNewIcon(); - void removeSelectedIcon(); - void openFolder(); + void selectionChanged(QItemSelection, QItemSelection); + void activated(QModelIndex); + void delayed_scroll(QModelIndex); + void addNewIcon(); + void removeSelectedIcon(); + void openFolder(); }; diff --git a/application/dialogs/LoginDialog.cpp b/application/dialogs/LoginDialog.cpp index b2020372..484cb8e2 100644 --- a/application/dialogs/LoginDialog.cpp +++ b/application/dialogs/LoginDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,89 +22,89 @@ LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog) { - ui->setupUi(this); - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->setupUi(this); + ui->progressBar->setVisible(false); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } LoginDialog::~LoginDialog() { - delete ui; + delete ui; } // Stage 1: User interaction void LoginDialog::accept() { - setUserInputsEnabled(false); - ui->progressBar->setVisible(true); - - // Setup the login task and start it - m_account = MojangAccount::createFromUsername(ui->userTextBox->text()); - m_loginTask = m_account->login(nullptr, ui->passTextBox->text()); - connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); - connect(m_loginTask.get(), &Task::succeeded, this, - &LoginDialog::onTaskSucceeded); - connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus); - connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress); - m_loginTask->start(); + setUserInputsEnabled(false); + ui->progressBar->setVisible(true); + + // Setup the login task and start it + m_account = MojangAccount::createFromUsername(ui->userTextBox->text()); + m_loginTask = m_account->login(nullptr, ui->passTextBox->text()); + connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); + connect(m_loginTask.get(), &Task::succeeded, this, + &LoginDialog::onTaskSucceeded); + connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus); + connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress); + m_loginTask->start(); } void LoginDialog::setUserInputsEnabled(bool enable) { - ui->userTextBox->setEnabled(enable); - ui->passTextBox->setEnabled(enable); - ui->buttonBox->setEnabled(enable); + ui->userTextBox->setEnabled(enable); + ui->passTextBox->setEnabled(enable); + ui->buttonBox->setEnabled(enable); } // Enable the OK button only when both textboxes contain something. void LoginDialog::on_userTextBox_textEdited(const QString &newText) { - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty()); + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty()); } void LoginDialog::on_passTextBox_textEdited(const QString &newText) { - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty()); + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty()); } void LoginDialog::onTaskFailed(const QString &reason) { - // Set message - ui->label->setText("" + reason + ""); + // Set message + ui->label->setText("" + reason + ""); - // Re-enable user-interaction - setUserInputsEnabled(true); - ui->progressBar->setVisible(false); + // Re-enable user-interaction + setUserInputsEnabled(true); + ui->progressBar->setVisible(false); } void LoginDialog::onTaskSucceeded() { - QDialog::accept(); + QDialog::accept(); } void LoginDialog::onTaskStatus(const QString &status) { - ui->label->setText(status); + ui->label->setText(status); } void LoginDialog::onTaskProgress(qint64 current, qint64 total) { - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); + ui->progressBar->setMaximum(total); + ui->progressBar->setValue(current); } // Public interface MojangAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg) { - LoginDialog dlg(parent); - dlg.ui->label->setText(msg); - if (dlg.exec() == QDialog::Accepted) - { - return dlg.m_account; - } - return 0; + LoginDialog dlg(parent); + dlg.ui->label->setText(msg); + if (dlg.exec() == QDialog::Accepted) + { + return dlg.m_account; + } + return 0; } diff --git a/application/dialogs/LoginDialog.h b/application/dialogs/LoginDialog.h index 27b97cb0..30fcb760 100644 --- a/application/dialogs/LoginDialog.h +++ b/application/dialogs/LoginDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,32 +27,32 @@ class LoginDialog; class LoginDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - ~LoginDialog(); + ~LoginDialog(); - static MojangAccountPtr newAccount(QWidget *parent, QString message); + static MojangAccountPtr newAccount(QWidget *parent, QString message); private: - explicit LoginDialog(QWidget *parent = 0); + explicit LoginDialog(QWidget *parent = 0); - void setUserInputsEnabled(bool enable); + void setUserInputsEnabled(bool enable); protected slots: - void accept(); + void accept(); - void onTaskFailed(const QString &reason); - void onTaskSucceeded(); - void onTaskStatus(const QString &status); - void onTaskProgress(qint64 current, qint64 total); + void onTaskFailed(const QString &reason); + void onTaskSucceeded(); + void onTaskStatus(const QString &status); + void onTaskProgress(qint64 current, qint64 total); - void on_userTextBox_textEdited(const QString &newText); - void on_passTextBox_textEdited(const QString &newText); + void on_userTextBox_textEdited(const QString &newText); + void on_passTextBox_textEdited(const QString &newText); private: - Ui::LoginDialog *ui; - MojangAccountPtr m_account; - std::shared_ptr m_loginTask; + Ui::LoginDialog *ui; + MojangAccountPtr m_account; + std::shared_ptr m_loginTask; }; diff --git a/application/dialogs/ModEditDialogCommon.cpp b/application/dialogs/ModEditDialogCommon.cpp deleted file mode 100644 index 4201bdad..00000000 --- a/application/dialogs/ModEditDialogCommon.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "ModEditDialogCommon.h" -#include "CustomMessageBox.h" -#include - -bool lastfirst(QModelIndexList &list, int &first, int &last) -{ - if (list.isEmpty()) - return false; - first = last = list[0].row(); - for (auto item : list) - { - int row = item.row(); - if (row < first) - first = row; - if (row > last) - last = row; - } - return true; -} - -void showWebsiteForMod(QWidget *parentDlg, Mod &m) -{ - QString url = m.homeurl(); - if (url.size()) - { - // catch the cases where the protocol is missing - if (!url.startsWith("http")) - { - url = "http://" + url; - } - DesktopServices::openUrl(url); - } - else - { - CustomMessageBox::selectable( - parentDlg, QObject::tr("How sad!"), - QObject::tr("The mod author didn't provide a website link for this mod."), - QMessageBox::Warning); - } -} diff --git a/application/dialogs/ModEditDialogCommon.h b/application/dialogs/ModEditDialogCommon.h deleted file mode 100644 index fc5e3c2b..00000000 --- a/application/dialogs/ModEditDialogCommon.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include -#include -#include -#include - -bool lastfirst(QModelIndexList &list, int &first, int &last); - -void showWebsiteForMod(QWidget *parentDlg, Mod &m); diff --git a/application/dialogs/NewComponentDialog.cpp b/application/dialogs/NewComponentDialog.cpp index 514aa938..d0d1d24b 100644 --- a/application/dialogs/NewComponentDialog.cpp +++ b/application/dialogs/NewComponentDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,72 +35,72 @@ #include NewComponentDialog::NewComponentDialog(const QString & initialName, const QString & initialUid, QWidget *parent) - : QDialog(parent), ui(new Ui::NewComponentDialog) + : QDialog(parent), ui(new Ui::NewComponentDialog) { - ui->setupUi(this); - resize(minimumSizeHint()); + ui->setupUi(this); + resize(minimumSizeHint()); - ui->nameTextBox->setText(initialName); - ui->uidTextBox->setText(initialUid); + ui->nameTextBox->setText(initialName); + ui->uidTextBox->setText(initialUid); - connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); - connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); + connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); + connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); - auto groups = MMC->instances()->getGroups().toSet(); - ui->nameTextBox->setFocus(); + auto groups = MMC->instances()->getGroups().toSet(); + ui->nameTextBox->setFocus(); - originalPlaceholderText = ui->uidTextBox->placeholderText(); - updateDialogState(); + originalPlaceholderText = ui->uidTextBox->placeholderText(); + updateDialogState(); } NewComponentDialog::~NewComponentDialog() { - delete ui; + delete ui; } void NewComponentDialog::updateDialogState() { - auto protoUid = ui->nameTextBox->text().toLower(); - protoUid.remove(QRegularExpression("[^a-z]")); - if(protoUid.isEmpty()) - { - ui->uidTextBox->setPlaceholderText(originalPlaceholderText); - } - else - { - QString suggestedUid = "org.multimc.custom." + protoUid; - ui->uidTextBox->setPlaceholderText(suggestedUid); - } - bool allowOK = !name().isEmpty() && !uid().isEmpty() && !uidBlacklist.contains(uid()); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowOK); + auto protoUid = ui->nameTextBox->text().toLower(); + protoUid.remove(QRegularExpression("[^a-z]")); + if(protoUid.isEmpty()) + { + ui->uidTextBox->setPlaceholderText(originalPlaceholderText); + } + else + { + QString suggestedUid = "org.multimc.custom." + protoUid; + ui->uidTextBox->setPlaceholderText(suggestedUid); + } + bool allowOK = !name().isEmpty() && !uid().isEmpty() && !uidBlacklist.contains(uid()); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowOK); } QString NewComponentDialog::name() const { - auto result = ui->nameTextBox->text(); - if(result.size()) - { - return result.trimmed(); - } - return QString(); + auto result = ui->nameTextBox->text(); + if(result.size()) + { + return result.trimmed(); + } + return QString(); } QString NewComponentDialog::uid() const { - auto result = ui->uidTextBox->text(); - if(result.size()) - { - return result.trimmed(); - } - result = ui->uidTextBox->placeholderText(); - if(result.size() && result != originalPlaceholderText) - { - return result.trimmed(); - } - return QString(); + auto result = ui->uidTextBox->text(); + if(result.size()) + { + return result.trimmed(); + } + result = ui->uidTextBox->placeholderText(); + if(result.size() && result != originalPlaceholderText) + { + return result.trimmed(); + } + return QString(); } void NewComponentDialog::setBlacklist(QStringList badUids) { - uidBlacklist = badUids; + uidBlacklist = badUids; } diff --git a/application/dialogs/NewComponentDialog.h b/application/dialogs/NewComponentDialog.h index 70caec0f..518f342d 100644 --- a/application/dialogs/NewComponentDialog.h +++ b/application/dialogs/NewComponentDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,22 +27,22 @@ class NewComponentDialog; class NewComponentDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit NewComponentDialog(const QString & initialName = QString(), const QString & initialUid = QString(), QWidget *parent = 0); - virtual ~NewComponentDialog(); - void setBlacklist(QStringList badUids); + explicit NewComponentDialog(const QString & initialName = QString(), const QString & initialUid = QString(), QWidget *parent = 0); + virtual ~NewComponentDialog(); + void setBlacklist(QStringList badUids); - QString name() const; - QString uid() const; + QString name() const; + QString uid() const; private slots: - void updateDialogState(); + void updateDialogState(); private: - Ui::NewComponentDialog *ui; + Ui::NewComponentDialog *ui; - QString originalPlaceholderText; - QStringList uidBlacklist; + QString originalPlaceholderText; + QStringList uidBlacklist; }; diff --git a/application/dialogs/NewComponentDialog.ui b/application/dialogs/NewComponentDialog.ui index b30c2431..03b0d222 100644 --- a/application/dialogs/NewComponentDialog.ui +++ b/application/dialogs/NewComponentDialog.ui @@ -14,7 +14,7 @@ - Copy Instance + Add Empty Component diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp index b3ce5c6c..3533763d 100644 --- a/application/dialogs/NewInstanceDialog.cpp +++ b/application/dialogs/NewInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,178 +39,209 @@ #include #include -NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) - : QDialog(parent), ui(new Ui::NewInstanceDialog) -{ - ui->setupUi(this); - - setWindowIcon(MMC->getThemedIcon("new")); - - InstIconKey = "default"; - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - - auto groups = MMC->instances()->getGroups().toSet(); - auto groupList = QStringList(groups.toList()); - groupList.sort(Qt::CaseInsensitive); - groupList.removeOne(""); - groupList.push_front(initialGroup); - groupList.push_front(""); - ui->groupBox->addItems(groupList); - int index = groupList.indexOf(initialGroup); - if(index == -1) - { - index = 0; - } - ui->groupBox->setCurrentIndex(index); - ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); - - m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok); - m_buttons->button(QDialogButtonBox::Ok)->setDefault(true); - - m_container = new PageContainer(this); - m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); - m_container->layout()->setContentsMargins(0, 0, 0, 0); - ui->verticalLayout->insertWidget(2, m_container); - - m_container->addButtons(m_buttons); - m_buttons->setFocus(); - - if(!url.isEmpty()) - { - m_container->selectPage("import"); - importPage->setUrl(url); - } - - connect(m_buttons->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &NewInstanceDialog::accept); - connect(m_buttons->button(QDialogButtonBox::Help), &QPushButton::clicked, m_container, &PageContainer::help); - - updateDialogState(); - - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("NewInstanceGeometry").toByteArray())); +NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) + : QDialog(parent), ui(new Ui::NewInstanceDialog) +{ + ui->setupUi(this); + + setWindowIcon(MMC->getThemedIcon("new")); + + InstIconKey = "default"; + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + + auto groups = MMC->instances()->getGroups().toSet(); + auto groupList = QStringList(groups.toList()); + groupList.sort(Qt::CaseInsensitive); + groupList.removeOne(""); + groupList.push_front(initialGroup); + groupList.push_front(""); + ui->groupBox->addItems(groupList); + int index = groupList.indexOf(initialGroup); + if(index == -1) + { + index = 0; + } + ui->groupBox->setCurrentIndex(index); + ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); + + + // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below. + m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + m_container = new PageContainer(this); + m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); + m_container->layout()->setContentsMargins(0, 0, 0, 0); + ui->verticalLayout->insertWidget(2, m_container); + + m_container->addButtons(m_buttons); + + // Bonk Qt over its stupid head and make sure it understands which button is the default one... + // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button + auto OkButton = m_buttons->button(QDialogButtonBox::Ok); + OkButton->setDefault(true); + OkButton->setAutoDefault(true); + connect(OkButton, &QPushButton::clicked, this, &NewInstanceDialog::accept); + + auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); + CancelButton->setDefault(false); + CancelButton->setAutoDefault(false); + connect(CancelButton, &QPushButton::clicked, this, &NewInstanceDialog::reject); + + auto HelpButton = m_buttons->button(QDialogButtonBox::Help); + HelpButton->setDefault(false); + HelpButton->setAutoDefault(false); + connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); + + if(!url.isEmpty()) + { + QUrl actualUrl(url); + if(actualUrl.host() == "www.curseforge.com") { + m_container->selectPage("twitch"); + twitchPage->setUrl(url); + } + else { + m_container->selectPage("import"); + importPage->setUrl(url); + } + } + + updateDialogState(); + + restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("NewInstanceGeometry").toByteArray())); } void NewInstanceDialog::reject() { - MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); - QDialog::reject(); + MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); + QDialog::reject(); } void NewInstanceDialog::accept() { - MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); - importIconNow(); - QDialog::accept(); + MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); + importIconNow(); + QDialog::accept(); } QList NewInstanceDialog::getPages() { - importPage = new ImportPage(this); - return - { - new VanillaPage(this), - new FTBPage(this), - importPage, - new TwitchPage(this), - new TechnicPage(this) - }; + importPage = new ImportPage(this); + twitchPage = new TwitchPage(this); + return + { + new VanillaPage(this), + importPage, + twitchPage, + new FTBPage(this) + }; } QString NewInstanceDialog::dialogTitle() { - return tr("New Instance"); + return tr("New Instance"); } NewInstanceDialog::~NewInstanceDialog() { - delete ui; + delete ui; } void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task) { - creationTask.reset(task); - ui->instNameTextBox->setPlaceholderText(name); + creationTask.reset(task); + ui->instNameTextBox->setPlaceholderText(name); + + if(!task) + { + ui->iconButton->setIcon(MMC->icons()->getIcon("default")); + importIcon = false; + } - auto allowOK = task && !instName().isEmpty(); - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); + auto allowOK = task && !instName().isEmpty(); + m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); } void NewInstanceDialog::setSuggestedIconFromFile(const QString &path, const QString &name) { - importIcon = true; - importIconPath = path; - importIconName = name; + importIcon = true; + importIconPath = path; + importIconName = name; - //Hmm, for some reason they can be to small - ui->iconButton->setIcon(QIcon(path)); + //Hmm, for some reason they can be to small + ui->iconButton->setIcon(QIcon(path)); } InstanceTask * NewInstanceDialog::extractTask() { - InstanceTask * extracted = creationTask.get(); - creationTask.release(); - extracted->setName(instName()); - extracted->setGroup(instGroup()); - extracted->setIcon(iconKey()); - return extracted; + InstanceTask * extracted = creationTask.get(); + creationTask.release(); + extracted->setName(instName()); + extracted->setGroup(instGroup()); + extracted->setIcon(iconKey()); + return extracted; } void NewInstanceDialog::updateDialogState() { - auto allowOK = creationTask && !instName().isEmpty(); - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); + auto allowOK = creationTask && !instName().isEmpty(); + auto OkButton = m_buttons->button(QDialogButtonBox::Ok); + if(OkButton->isEnabled() != allowOK) + { + OkButton->setEnabled(allowOK); + } } QString NewInstanceDialog::instName() const { - auto result = ui->instNameTextBox->text(); - if(result.size()) - { - return result.trimmed(); - } - result = ui->instNameTextBox->placeholderText(); - if(result.size()) - { - return result.trimmed(); - } - return QString(); + auto result = ui->instNameTextBox->text().trimmed(); + if(result.size()) + { + return result; + } + result = ui->instNameTextBox->placeholderText().trimmed(); + if(result.size()) + { + return result; + } + return QString(); } QString NewInstanceDialog::instGroup() const { - return ui->groupBox->currentText(); + return ui->groupBox->currentText(); } QString NewInstanceDialog::iconKey() const { - return InstIconKey; + return InstIconKey; } void NewInstanceDialog::on_iconButton_clicked() { - importIconNow(); //so the user can switch back - IconPickerDialog dlg(this); - dlg.execWithSelection(InstIconKey); + importIconNow(); //so the user can switch back + IconPickerDialog dlg(this); + dlg.execWithSelection(InstIconKey); - if (dlg.result() == QDialog::Accepted) - { - InstIconKey = dlg.selectedIconKey; - ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); - importIcon = false; - } + if (dlg.result() == QDialog::Accepted) + { + InstIconKey = dlg.selectedIconKey; + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); + importIcon = false; + } } void NewInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) { - updateDialogState(); + updateDialogState(); } void NewInstanceDialog::importIconNow() { - if(importIcon) { - MMC->icons()->installIcon(importIconPath, importIconName); - InstIconKey = importIconName; - importIcon = false; - } - MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); + if(importIcon) { + MMC->icons()->installIcon(importIconPath, importIconName); + InstIconKey = importIconName; + importIcon = false; + } + MMC->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); } diff --git a/application/dialogs/NewInstanceDialog.h b/application/dialogs/NewInstanceDialog.h index 1448d225..0b8b2fb8 100644 --- a/application/dialogs/NewInstanceDialog.h +++ b/application/dialogs/NewInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,49 +29,51 @@ class NewInstanceDialog; class PageContainer; class QDialogButtonBox; class ImportPage; +class TwitchPage; class NewInstanceDialog : public QDialog, public BasePageProvider { - Q_OBJECT + Q_OBJECT public: - explicit NewInstanceDialog(const QString & initialGroup, const QString & url = QString(), QWidget *parent = 0); - ~NewInstanceDialog(); + explicit NewInstanceDialog(const QString & initialGroup, const QString & url = QString(), QWidget *parent = 0); + ~NewInstanceDialog(); - void updateDialogState(); + void updateDialogState(); - void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr); - void setSuggestedIconFromFile(const QString &path, const QString &name); + void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr); + void setSuggestedIconFromFile(const QString &path, const QString &name); - InstanceTask * extractTask(); + InstanceTask * extractTask(); - QString dialogTitle() override; - QList getPages() override; + QString dialogTitle() override; + QList getPages() override; - QString instName() const; - QString instGroup() const; - QString iconKey() const; + QString instName() const; + QString instGroup() const; + QString iconKey() const; public slots: - void accept() override; - void reject() override; + void accept() override; + void reject() override; private slots: - void on_iconButton_clicked(); - void on_instNameTextBox_textChanged(const QString &arg1); + void on_iconButton_clicked(); + void on_instNameTextBox_textChanged(const QString &arg1); private: - Ui::NewInstanceDialog *ui = nullptr; - PageContainer * m_container = nullptr; - QDialogButtonBox * m_buttons = nullptr; + Ui::NewInstanceDialog *ui = nullptr; + PageContainer * m_container = nullptr; + QDialogButtonBox * m_buttons = nullptr; - QString InstIconKey; - ImportPage *importPage = nullptr; - std::unique_ptr creationTask; + QString InstIconKey; + ImportPage *importPage = nullptr; + TwitchPage *twitchPage = nullptr; + std::unique_ptr creationTask; - bool importIcon = false; - QString importIconPath; - QString importIconName; + bool importIcon = false; + QString importIconPath; + QString importIconName; - void importIconNow(); + void importIconNow(); }; diff --git a/application/dialogs/NotificationDialog.cpp b/application/dialogs/NotificationDialog.cpp index 86b03bda..f2a35ae2 100644 --- a/application/dialogs/NotificationDialog.cpp +++ b/application/dialogs/NotificationDialog.cpp @@ -2,85 +2,85 @@ #include "ui_NotificationDialog.h" #include -#include +#include NotificationDialog::NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent) : - QDialog(parent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::CustomizeWindowHint), - ui(new Ui::NotificationDialog) + QDialog(parent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::CustomizeWindowHint), + ui(new Ui::NotificationDialog) { - ui->setupUi(this); + ui->setupUi(this); - QStyle::StandardPixmap icon; - switch (entry.type) - { - case NotificationChecker::NotificationEntry::Critical: - icon = QStyle::SP_MessageBoxCritical; - break; - case NotificationChecker::NotificationEntry::Warning: - icon = QStyle::SP_MessageBoxWarning; - break; - default: - case NotificationChecker::NotificationEntry::Information: - icon = QStyle::SP_MessageBoxInformation; - break; - } - ui->iconLabel->setPixmap(style()->standardPixmap(icon, 0, this)); - ui->messageLabel->setText(entry.message); + QStyle::StandardPixmap icon; + switch (entry.type) + { + case NotificationChecker::NotificationEntry::Critical: + icon = QStyle::SP_MessageBoxCritical; + break; + case NotificationChecker::NotificationEntry::Warning: + icon = QStyle::SP_MessageBoxWarning; + break; + default: + case NotificationChecker::NotificationEntry::Information: + icon = QStyle::SP_MessageBoxInformation; + break; + } + ui->iconLabel->setPixmap(style()->standardPixmap(icon, 0, this)); + ui->messageLabel->setText(entry.message); - m_dontShowAgainText = tr("Don't show again"); - m_closeText = tr("Close"); + m_dontShowAgainText = tr("Don't show again"); + m_closeText = tr("Close"); - ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); - ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); + ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); + ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); - startTimer(1000); + startTimer(1000); } NotificationDialog::~NotificationDialog() { - delete ui; + delete ui; } void NotificationDialog::timerEvent(QTimerEvent *event) { - if (m_dontShowAgainTime > 0) - { - m_dontShowAgainTime--; - if (m_dontShowAgainTime == 0) - { - ui->dontShowAgainBtn->setText(m_dontShowAgainText); - ui->dontShowAgainBtn->setEnabled(true); - } - else - { - ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); - } - } - if (m_closeTime > 0) - { - m_closeTime--; - if (m_closeTime == 0) - { - ui->closeBtn->setText(m_closeText); - ui->closeBtn->setEnabled(true); - } - else - { - ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); - } - } + if (m_dontShowAgainTime > 0) + { + m_dontShowAgainTime--; + if (m_dontShowAgainTime == 0) + { + ui->dontShowAgainBtn->setText(m_dontShowAgainText); + ui->dontShowAgainBtn->setEnabled(true); + } + else + { + ui->dontShowAgainBtn->setText(m_dontShowAgainText + QString(" (%1)").arg(m_dontShowAgainTime)); + } + } + if (m_closeTime > 0) + { + m_closeTime--; + if (m_closeTime == 0) + { + ui->closeBtn->setText(m_closeText); + ui->closeBtn->setEnabled(true); + } + else + { + ui->closeBtn->setText(m_closeText + QString(" (%1)").arg(m_closeTime)); + } + } - if (m_closeTime == 0 && m_dontShowAgainTime == 0) - { - killTimer(event->timerId()); - } + if (m_closeTime == 0 && m_dontShowAgainTime == 0) + { + killTimer(event->timerId()); + } } void NotificationDialog::on_dontShowAgainBtn_clicked() { - done(DontShowAgain); + done(DontShowAgain); } void NotificationDialog::on_closeBtn_clicked() { - done(Normal); + done(Normal); } diff --git a/application/dialogs/NotificationDialog.h b/application/dialogs/NotificationDialog.h index 27b9e853..e1cbb9fa 100644 --- a/application/dialogs/NotificationDialog.h +++ b/application/dialogs/NotificationDialog.h @@ -11,34 +11,34 @@ class NotificationDialog; class NotificationDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent = 0); - ~NotificationDialog(); + explicit NotificationDialog(const NotificationChecker::NotificationEntry &entry, QWidget *parent = 0); + ~NotificationDialog(); - enum ExitCode - { - Normal, - DontShowAgain - }; + enum ExitCode + { + Normal, + DontShowAgain + }; protected: - void timerEvent(QTimerEvent *event); + void timerEvent(QTimerEvent *event); private: - Ui::NotificationDialog *ui; + Ui::NotificationDialog *ui; - int m_dontShowAgainTime = 10; - int m_closeTime = 5; + int m_dontShowAgainTime = 10; + int m_closeTime = 5; - QString m_dontShowAgainText; - QString m_closeText; + QString m_dontShowAgainText; + QString m_closeText; private slots: - void on_dontShowAgainBtn_clicked(); - void on_closeBtn_clicked(); + void on_dontShowAgainBtn_clicked(); + void on_closeBtn_clicked(); }; #endif // NOTIFICATIONDIALOG_H diff --git a/application/dialogs/ProfileSelectDialog.cpp b/application/dialogs/ProfileSelectDialog.cpp index f1b335f9..b64de01a 100644 --- a/application/dialogs/ProfileSelectDialog.cpp +++ b/application/dialogs/ProfileSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,91 +26,91 @@ #include ProfileSelectDialog::ProfileSelectDialog(const QString &message, int flags, QWidget *parent) - : QDialog(parent), ui(new Ui::ProfileSelectDialog) + : QDialog(parent), ui(new Ui::ProfileSelectDialog) { - ui->setupUi(this); - - m_accounts = MMC->accounts(); - auto view = ui->listView; - //view->setModel(m_accounts.get()); - //view->hideColumn(MojangAccountList::ActiveColumn); - view->setColumnCount(1); - view->setRootIsDecorated(false); - if(QTreeWidgetItem* header = view->headerItem()) - { - header->setText(0, tr("Name")); - } - else - { - view->setHeaderLabel(tr("Name")); - } - QList items; - for (int i = 0; i < m_accounts->count(); i++) - { - MojangAccountPtr account = m_accounts->at(i); - for (auto profile : account->profiles()) - { - auto profileLabel = profile.name; - if(account->isInUse()) - { - profileLabel += tr(" (in use)"); - } - auto item = new QTreeWidgetItem(view); - item->setText(0, profileLabel); - item->setIcon(0, SkinUtils::getFaceFromCache(profile.id)); - item->setData(0, MojangAccountList::PointerRole, QVariant::fromValue(account)); - items.append(item); - } - } - view->addTopLevelItems(items); - - // Set the message label. - ui->msgLabel->setVisible(!message.isEmpty()); - ui->msgLabel->setText(message); - - // Flags... - ui->globalDefaultCheck->setVisible(flags & GlobalDefaultCheckbox); - ui->instDefaultCheck->setVisible(flags & InstanceDefaultCheckbox); - qDebug() << flags; - - // Select the first entry in the list. - ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0)); - - connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), SLOT(on_buttonBox_accepted())); + ui->setupUi(this); + + m_accounts = MMC->accounts(); + auto view = ui->listView; + //view->setModel(m_accounts.get()); + //view->hideColumn(MojangAccountList::ActiveColumn); + view->setColumnCount(1); + view->setRootIsDecorated(false); + if(QTreeWidgetItem* header = view->headerItem()) + { + header->setText(0, tr("Name")); + } + else + { + view->setHeaderLabel(tr("Name")); + } + QList items; + for (int i = 0; i < m_accounts->count(); i++) + { + MojangAccountPtr account = m_accounts->at(i); + for (auto profile : account->profiles()) + { + auto profileLabel = profile.name; + if(account->isInUse()) + { + profileLabel += tr(" (in use)"); + } + auto item = new QTreeWidgetItem(view); + item->setText(0, profileLabel); + item->setIcon(0, SkinUtils::getFaceFromCache(profile.id)); + item->setData(0, MojangAccountList::PointerRole, QVariant::fromValue(account)); + items.append(item); + } + } + view->addTopLevelItems(items); + + // Set the message label. + ui->msgLabel->setVisible(!message.isEmpty()); + ui->msgLabel->setText(message); + + // Flags... + ui->globalDefaultCheck->setVisible(flags & GlobalDefaultCheckbox); + ui->instDefaultCheck->setVisible(flags & InstanceDefaultCheckbox); + qDebug() << flags; + + // Select the first entry in the list. + ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0)); + + connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), SLOT(on_buttonBox_accepted())); } ProfileSelectDialog::~ProfileSelectDialog() { - delete ui; + delete ui; } MojangAccountPtr ProfileSelectDialog::selectedAccount() const { - return m_selected; + return m_selected; } bool ProfileSelectDialog::useAsGlobalDefault() const { - return ui->globalDefaultCheck->isChecked(); + return ui->globalDefaultCheck->isChecked(); } bool ProfileSelectDialog::useAsInstDefaullt() const { - return ui->instDefaultCheck->isChecked(); + return ui->instDefaultCheck->isChecked(); } void ProfileSelectDialog::on_buttonBox_accepted() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - m_selected = selected.data(MojangAccountList::PointerRole).value(); - } - close(); + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + m_selected = selected.data(MojangAccountList::PointerRole).value(); + } + close(); } void ProfileSelectDialog::on_buttonBox_rejected() { - close(); + close(); } diff --git a/application/dialogs/ProfileSelectDialog.h b/application/dialogs/ProfileSelectDialog.h index b1268743..40fa0843 100644 --- a/application/dialogs/ProfileSelectDialog.h +++ b/application/dialogs/ProfileSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,63 +28,63 @@ class ProfileSelectDialog; class ProfileSelectDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - enum Flags - { - NoFlags = 0, - - /*! - * Shows a check box on the dialog that allows the user to specify that the account - * they've selected should be used as the global default for all instances. - */ - GlobalDefaultCheckbox, - - /*! - * Shows a check box on the dialog that allows the user to specify that the account - * they've selected should be used as the default for the instance they are currently launching. - * This is not currently implemented. - */ - InstanceDefaultCheckbox, - }; - - /*! - * Constructs a new account select dialog with the given parent and message. - * The message will be shown at the top of the dialog. It is an empty string by default. - */ - explicit ProfileSelectDialog(const QString& message="", int flags=0, QWidget *parent = 0); - ~ProfileSelectDialog(); - - /*! - * Gets a pointer to the account that the user selected. - * This is null if the user clicked cancel or hasn't clicked OK yet. - */ - MojangAccountPtr selectedAccount() const; - - /*! - * Returns true if the user checked the "use as global default" checkbox. - * If the checkbox wasn't shown, this function returns false. - */ - bool useAsGlobalDefault() const; - - /*! - * Returns true if the user checked the "use as instance default" checkbox. - * If the checkbox wasn't shown, this function returns false. - */ - bool useAsInstDefaullt() const; + enum Flags + { + NoFlags = 0, + + /*! + * Shows a check box on the dialog that allows the user to specify that the account + * they've selected should be used as the global default for all instances. + */ + GlobalDefaultCheckbox, + + /*! + * Shows a check box on the dialog that allows the user to specify that the account + * they've selected should be used as the default for the instance they are currently launching. + * This is not currently implemented. + */ + InstanceDefaultCheckbox, + }; + + /*! + * Constructs a new account select dialog with the given parent and message. + * The message will be shown at the top of the dialog. It is an empty string by default. + */ + explicit ProfileSelectDialog(const QString& message="", int flags=0, QWidget *parent = 0); + ~ProfileSelectDialog(); + + /*! + * Gets a pointer to the account that the user selected. + * This is null if the user clicked cancel or hasn't clicked OK yet. + */ + MojangAccountPtr selectedAccount() const; + + /*! + * Returns true if the user checked the "use as global default" checkbox. + * If the checkbox wasn't shown, this function returns false. + */ + bool useAsGlobalDefault() const; + + /*! + * Returns true if the user checked the "use as instance default" checkbox. + * If the checkbox wasn't shown, this function returns false. + */ + bool useAsInstDefaullt() const; public slots: - void on_buttonBox_accepted(); + void on_buttonBox_accepted(); - void on_buttonBox_rejected(); + void on_buttonBox_rejected(); protected: - std::shared_ptr m_accounts; + std::shared_ptr m_accounts; - //! The account that was selected when the user clicked OK. - MojangAccountPtr m_selected; + //! The account that was selected when the user clicked OK. + MojangAccountPtr m_selected; private: - Ui::ProfileSelectDialog *ui; + Ui::ProfileSelectDialog *ui; }; diff --git a/application/dialogs/ProgressDialog.cpp b/application/dialogs/ProgressDialog.cpp index 056a0ffb..0e186fc2 100644 --- a/application/dialogs/ProgressDialog.cpp +++ b/application/dialogs/ProgressDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,117 +23,117 @@ ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ProgressDialog) { - ui->setupUi(this); - this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); - setSkipButton(false); - changeProgress(0, 100); + ui->setupUi(this); + this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + setSkipButton(false); + changeProgress(0, 100); } void ProgressDialog::setSkipButton(bool present, QString label) { - ui->skipButton->setAutoDefault(false); - ui->skipButton->setDefault(false); - ui->skipButton->setFocusPolicy(Qt::ClickFocus); - ui->skipButton->setEnabled(present); - ui->skipButton->setVisible(present); - ui->skipButton->setText(label); - updateSize(); + ui->skipButton->setAutoDefault(false); + ui->skipButton->setDefault(false); + ui->skipButton->setFocusPolicy(Qt::ClickFocus); + ui->skipButton->setEnabled(present); + ui->skipButton->setVisible(present); + ui->skipButton->setText(label); + updateSize(); } void ProgressDialog::on_skipButton_clicked(bool checked) { - Q_UNUSED(checked); - task->abort(); + Q_UNUSED(checked); + task->abort(); } ProgressDialog::~ProgressDialog() { - delete ui; + delete ui; } void ProgressDialog::updateSize() { QSize qSize = QSize(480, minimumSizeHint().height()); - resize(qSize); + resize(qSize); setFixedSize(qSize); } int ProgressDialog::execWithTask(Task *task) { - this->task = task; - QDialog::DialogCode result; - - if(!task) - { - qDebug() << "Programmer error: progress dialog created with null task."; - return Accepted; - } - - if(handleImmediateResult(result)) - { - return result; - } - - // Connect signals. - connect(task, SIGNAL(started()), SLOT(onTaskStarted())); - connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString))); - connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded())); - connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &))); - connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); - - // if this didn't connect to an already running task, invoke start - if(!task->isRunning()) - { - task->start(); - } - if(task->isRunning()) - { - changeProgress(task->getProgress(), task->getTotalProgress()); - changeStatus(task->getStatus()); - return QDialog::exec(); - } - else if(handleImmediateResult(result)) - { - return result; - } - else - { - return QDialog::Rejected; - } + this->task = task; + QDialog::DialogCode result; + + if(!task) + { + qDebug() << "Programmer error: progress dialog created with null task."; + return Accepted; + } + + if(handleImmediateResult(result)) + { + return result; + } + + // Connect signals. + connect(task, SIGNAL(started()), SLOT(onTaskStarted())); + connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString))); + connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded())); + connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &))); + connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); + + // if this didn't connect to an already running task, invoke start + if(!task->isRunning()) + { + task->start(); + } + if(task->isRunning()) + { + changeProgress(task->getProgress(), task->getTotalProgress()); + changeStatus(task->getStatus()); + return QDialog::exec(); + } + else if(handleImmediateResult(result)) + { + return result; + } + else + { + return QDialog::Rejected; + } } // TODO: only provide the unique_ptr overloads int ProgressDialog::execWithTask(std::unique_ptr &&task) { - connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); - return execWithTask(task.release()); + connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); + return execWithTask(task.release()); } int ProgressDialog::execWithTask(std::unique_ptr &task) { - connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); - return execWithTask(task.release()); + connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater); + return execWithTask(task.release()); } bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result) { - if(task->isFinished()) - { - if(task->wasSuccessful()) - { - result = QDialog::Accepted; - } - else - { - result = QDialog::Rejected; - } - return true; - } - return false; + if(task->isFinished()) + { + if(task->wasSuccessful()) + { + result = QDialog::Accepted; + } + else + { + result = QDialog::Rejected; + } + return true; + } + return false; } Task *ProgressDialog::getTask() { - return task; + return task; } void ProgressDialog::onTaskStarted() @@ -142,55 +142,55 @@ void ProgressDialog::onTaskStarted() void ProgressDialog::onTaskFailed(QString failure) { - reject(); + reject(); } void ProgressDialog::onTaskSucceeded() { - accept(); + accept(); } void ProgressDialog::changeStatus(const QString &status) { - ui->statusLabel->setText(status); - updateSize(); + ui->statusLabel->setText(status); + updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) { - ui->taskProgressBar->setMaximum(total); - ui->taskProgressBar->setValue(current); + ui->taskProgressBar->setMaximum(total); + ui->taskProgressBar->setValue(current); } void ProgressDialog::keyPressEvent(QKeyEvent *e) { - if(ui->skipButton->isVisible()) - { - if (e->key() == Qt::Key_Escape) - { - on_skipButton_clicked(true); - return; - } - else if(e->key() == Qt::Key_Tab) - { - ui->skipButton->setFocusPolicy(Qt::StrongFocus); - ui->skipButton->setFocus(); - ui->skipButton->setAutoDefault(true); - ui->skipButton->setDefault(true); - return; - } - } - QDialog::keyPressEvent(e); + if(ui->skipButton->isVisible()) + { + if (e->key() == Qt::Key_Escape) + { + on_skipButton_clicked(true); + return; + } + else if(e->key() == Qt::Key_Tab) + { + ui->skipButton->setFocusPolicy(Qt::StrongFocus); + ui->skipButton->setFocus(); + ui->skipButton->setAutoDefault(true); + ui->skipButton->setDefault(true); + return; + } + } + QDialog::keyPressEvent(e); } void ProgressDialog::closeEvent(QCloseEvent *e) { - if (task && task->isRunning()) - { - e->ignore(); - } - else - { - QDialog::closeEvent(e); - } + if (task && task->isRunning()) + { + e->ignore(); + } + else + { + QDialog::closeEvent(e); + } } diff --git a/application/dialogs/ProgressDialog.h b/application/dialogs/ProgressDialog.h index f27b71e1..a3779e3f 100644 --- a/application/dialogs/ProgressDialog.h +++ b/application/dialogs/ProgressDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,45 +27,45 @@ class ProgressDialog; class ProgressDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit ProgressDialog(QWidget *parent = 0); - ~ProgressDialog(); + explicit ProgressDialog(QWidget *parent = 0); + ~ProgressDialog(); - void updateSize(); + void updateSize(); - int execWithTask(Task *task); - int execWithTask(std::unique_ptr &&task); - int execWithTask(std::unique_ptr &task); + int execWithTask(Task *task); + int execWithTask(std::unique_ptr &&task); + int execWithTask(std::unique_ptr &task); - void setSkipButton(bool present, QString label = QString()); + void setSkipButton(bool present, QString label = QString()); - Task *getTask(); + Task *getTask(); public slots: - void onTaskStarted(); - void onTaskFailed(QString failure); - void onTaskSucceeded(); + void onTaskStarted(); + void onTaskFailed(QString failure); + void onTaskSucceeded(); - void changeStatus(const QString &status); - void changeProgress(qint64 current, qint64 total); + void changeStatus(const QString &status); + void changeProgress(qint64 current, qint64 total); private slots: - void on_skipButton_clicked(bool checked); + void on_skipButton_clicked(bool checked); protected: - virtual void keyPressEvent(QKeyEvent *e); - virtual void closeEvent(QCloseEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + virtual void closeEvent(QCloseEvent *e); private: - bool handleImmediateResult(QDialog::DialogCode &result); + bool handleImmediateResult(QDialog::DialogCode &result); private: - Ui::ProgressDialog *ui; + Ui::ProgressDialog *ui; - Task *task; + Task *task; }; diff --git a/application/dialogs/SkinUploadDialog.cpp b/application/dialogs/SkinUploadDialog.cpp index 93414c6e..7d2ff829 100644 --- a/application/dialogs/SkinUploadDialog.cpp +++ b/application/dialogs/SkinUploadDialog.cpp @@ -9,106 +9,106 @@ void SkinUploadDialog::on_buttonBox_rejected() { - close(); + close(); } void SkinUploadDialog::on_buttonBox_accepted() { - AuthSessionPtr session = std::make_shared(); - auto login = m_acct->login(session); - ProgressDialog prog(this); - if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted) - { - //FIXME: recover with password prompt - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to login!"), QMessageBox::Warning)->exec(); - close(); - return; - } - QString fileName; - QString input = ui->skinPathTextBox->text(); - QRegExp urlPrefixMatcher("^([a-z]+)://.+$"); - bool isLocalFile = false; - // it has an URL prefix -> it is an URL - if(urlPrefixMatcher.exactMatch(input)) - { - QUrl fileURL = input; - if(fileURL.isValid()) - { - // local? - if(fileURL.isLocalFile()) - { - isLocalFile = true; - fileName = fileURL.toLocalFile(); - } - else - { - CustomMessageBox::selectable( - this, - tr("Skin Upload"), - tr("Using remote URLs for setting skins is not implemented yet."), - QMessageBox::Warning - )->exec(); - close(); - return; - } - } - else - { - CustomMessageBox::selectable( - this, - tr("Skin Upload"), - tr("You cannot use an invalid URL for uploading skins."), - QMessageBox::Warning - )->exec(); - close(); - return; - } - } - else - { - // just assume it's a path then - isLocalFile = true; - fileName = ui->skinPathTextBox->text(); - } - if (isLocalFile && !QFile::exists(fileName)) - { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); - close(); - return; - } - SkinUpload::Model model = SkinUpload::STEVE; - if (ui->steveBtn->isChecked()) - { - model = SkinUpload::STEVE; - } - else if (ui->alexBtn->isChecked()) - { - model = SkinUpload::ALEX; - } - SkinUploadPtr upload = std::make_shared(this, session, FS::read(fileName), model); - if (prog.execWithTask((Task*)upload.get()) != QDialog::Accepted) - { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); - close(); - return; - } - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec(); - close(); + AuthSessionPtr session = std::make_shared(); + auto login = m_acct->login(session); + ProgressDialog prog(this); + if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted) + { + //FIXME: recover with password prompt + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to login!"), QMessageBox::Warning)->exec(); + close(); + return; + } + QString fileName; + QString input = ui->skinPathTextBox->text(); + QRegExp urlPrefixMatcher("^([a-z]+)://.+$"); + bool isLocalFile = false; + // it has an URL prefix -> it is an URL + if(urlPrefixMatcher.exactMatch(input)) + { + QUrl fileURL = input; + if(fileURL.isValid()) + { + // local? + if(fileURL.isLocalFile()) + { + isLocalFile = true; + fileName = fileURL.toLocalFile(); + } + else + { + CustomMessageBox::selectable( + this, + tr("Skin Upload"), + tr("Using remote URLs for setting skins is not implemented yet."), + QMessageBox::Warning + )->exec(); + close(); + return; + } + } + else + { + CustomMessageBox::selectable( + this, + tr("Skin Upload"), + tr("You cannot use an invalid URL for uploading skins."), + QMessageBox::Warning + )->exec(); + close(); + return; + } + } + else + { + // just assume it's a path then + isLocalFile = true; + fileName = ui->skinPathTextBox->text(); + } + if (isLocalFile && !QFile::exists(fileName)) + { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); + close(); + return; + } + SkinUpload::Model model = SkinUpload::STEVE; + if (ui->steveBtn->isChecked()) + { + model = SkinUpload::STEVE; + } + else if (ui->alexBtn->isChecked()) + { + model = SkinUpload::ALEX; + } + SkinUploadPtr upload = std::make_shared(this, session, FS::read(fileName), model); + if (prog.execWithTask((Task*)upload.get()) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); + close(); + return; + } + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec(); + close(); } void SkinUploadDialog::on_skinBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png"); - if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) - { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - ui->skinPathTextBox->setText(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png"); + if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) + { + return; + } + QString cooked_path = FS::NormalizePath(raw_path); + ui->skinPathTextBox->setText(cooked_path); } SkinUploadDialog::SkinUploadDialog(MojangAccountPtr acct, QWidget *parent) - :QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog) + :QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog) { - ui->setupUi(this); + ui->setupUi(this); } diff --git a/application/dialogs/SkinUploadDialog.h b/application/dialogs/SkinUploadDialog.h index 514eabc8..deb44eac 100644 --- a/application/dialogs/SkinUploadDialog.h +++ b/application/dialogs/SkinUploadDialog.h @@ -5,25 +5,25 @@ namespace Ui { - class SkinUploadDialog; + class SkinUploadDialog; } class SkinUploadDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit SkinUploadDialog(MojangAccountPtr acct, QWidget *parent = 0); - virtual ~SkinUploadDialog() {}; + explicit SkinUploadDialog(MojangAccountPtr acct, QWidget *parent = 0); + virtual ~SkinUploadDialog() {}; public slots: - void on_buttonBox_accepted(); + void on_buttonBox_accepted(); - void on_buttonBox_rejected(); + void on_buttonBox_rejected(); - void on_skinBrowseBtn_clicked(); + void on_skinBrowseBtn_clicked(); protected: - MojangAccountPtr m_acct; + MojangAccountPtr m_acct; private: - Ui::SkinUploadDialog *ui; + Ui::SkinUploadDialog *ui; }; diff --git a/application/dialogs/UpdateDialog.cpp b/application/dialogs/UpdateDialog.cpp index 30c7173d..242a5b70 100644 --- a/application/dialogs/UpdateDialog.cpp +++ b/application/dialogs/UpdateDialog.cpp @@ -10,20 +10,20 @@ UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog) { - ui->setupUi(this); - auto channel = MMC->settings()->get("UpdateChannel").toString(); - if(hasUpdate) - { - ui->label->setText(tr("A new %1 update is available!").arg(channel)); - } - else - { - ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel)); - ui->btnUpdateNow->setHidden(true); - ui->btnUpdateLater->setText(tr("Close")); - } - loadChangelog(); - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("UpdateDialogGeometry").toByteArray())); + ui->setupUi(this); + auto channel = MMC->settings()->get("UpdateChannel").toString(); + if(hasUpdate) + { + ui->label->setText(tr("A new %1 update is available!").arg(channel)); + } + else + { + ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel)); + ui->btnUpdateNow->setHidden(true); + ui->btnUpdateLater->setText(tr("Close")); + } + loadChangelog(); + restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("UpdateDialogGeometry").toByteArray())); } UpdateDialog::~UpdateDialog() @@ -32,150 +32,150 @@ UpdateDialog::~UpdateDialog() void UpdateDialog::loadChangelog() { - auto channel = MMC->settings()->get("UpdateChannel").toString(); - dljob.reset(new NetJob("Changelog")); - QString url; - if(channel == "stable") - { - url = QString("https://raw.githubusercontent.com/MultiMC/MultiMC5/%1/changelog.md").arg(channel); - m_changelogType = CHANGELOG_MARKDOWN; - } - else - { - url = QString("https://api.github.com/repos/MultiMC/MultiMC5/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); - m_changelogType = CHANGELOG_COMMITS; - } - dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); - connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded); - connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed); - dljob->start(); + auto channel = MMC->settings()->get("UpdateChannel").toString(); + dljob.reset(new NetJob("Changelog")); + QString url; + if(channel == "stable") + { + url = QString("https://raw.githubusercontent.com/MultiMC/MultiMC5/%1/changelog.md").arg(channel); + m_changelogType = CHANGELOG_MARKDOWN; + } + else + { + url = QString("https://api.github.com/repos/MultiMC/MultiMC5/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); + m_changelogType = CHANGELOG_COMMITS; + } + dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); + connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded); + connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed); + dljob->start(); } QString reprocessMarkdown(QByteArray markdown) { - HoeDown hoedown; - QString output = hoedown.process(markdown); + HoeDown hoedown; + QString output = hoedown.process(markdown); - // HACK: easier than customizing hoedown - output.replace(QRegExp("GH-([0-9]+)"), "GH-\\1"); - qDebug() << output; - return output; + // HACK: easier than customizing hoedown + output.replace(QRegExp("GH-([0-9]+)"), "GH-\\1"); + qDebug() << output; + return output; } QString reprocessCommits(QByteArray json) { - auto channel = MMC->settings()->get("UpdateChannel").toString(); - try - { - QString result; - auto document = Json::requireDocument(json); - auto rootobject = Json::requireObject(document); - auto status = Json::requireString(rootobject, "status"); - auto diff_url = Json::requireString(rootobject, "html_url"); + auto channel = MMC->settings()->get("UpdateChannel").toString(); + try + { + QString result; + auto document = Json::requireDocument(json); + auto rootobject = Json::requireObject(document); + auto status = Json::requireString(rootobject, "status"); + auto diff_url = Json::requireString(rootobject, "html_url"); - auto print_commits = [&]() - { - result += ""; - auto commitarray = Json::requireArray(rootobject, "commits"); - for(int i = commitarray.size() - 1; i >= 0; i--) - { - const auto & commitval = commitarray[i]; - auto commitobj = Json::requireObject(commitval); - auto parents_info = Json::ensureArray(commitobj, "parents"); - // NOTE: this ignores merge commits, because they have more than one parent - if(parents_info.size() > 1) - { - continue; - } - auto commit_url = Json::requireString(commitobj, "html_url"); - auto commit_info = Json::requireObject(commitobj, "commit"); - auto commit_message = Json::requireString(commit_info, "message"); - auto lines = commit_message.split('\n'); - QRegularExpression regexp("(?(GH-(?[0-9]+))|(NOISSUE)|(SCRATCH))? *(?.*) *"); - auto match = regexp.match(lines.takeFirst(), 0, QRegularExpression::NormalMatch); - auto issuenr = match.captured("issuenr"); - auto prefix = match.captured("prefix"); - auto rest = match.captured("rest"); - result += ""; - lines.prepend(rest); - result += ""; - } - result += "
"; - if(issuenr.length()) - { - result += QString("GH-%2").arg(issuenr, issuenr); - } - else if(prefix.length()) - { - result += QString("%2").arg(commit_url, prefix); - } - else - { - result += QString("NOISSUE").arg(commit_url); - } - result += "

" + lines.join("
") + "

"; - }; + auto print_commits = [&]() + { + result += ""; + auto commitarray = Json::requireArray(rootobject, "commits"); + for(int i = commitarray.size() - 1; i >= 0; i--) + { + const auto & commitval = commitarray[i]; + auto commitobj = Json::requireObject(commitval); + auto parents_info = Json::ensureArray(commitobj, "parents"); + // NOTE: this ignores merge commits, because they have more than one parent + if(parents_info.size() > 1) + { + continue; + } + auto commit_url = Json::requireString(commitobj, "html_url"); + auto commit_info = Json::requireObject(commitobj, "commit"); + auto commit_message = Json::requireString(commit_info, "message"); + auto lines = commit_message.split('\n'); + QRegularExpression regexp("(?(GH-(?[0-9]+))|(NOISSUE)|(SCRATCH))? *(?.*) *"); + auto match = regexp.match(lines.takeFirst(), 0, QRegularExpression::NormalMatch); + auto issuenr = match.captured("issuenr"); + auto prefix = match.captured("prefix"); + auto rest = match.captured("rest"); + result += ""; + lines.prepend(rest); + result += ""; + } + result += "
"; + if(issuenr.length()) + { + result += QString("GH-%2").arg(issuenr, issuenr); + } + else if(prefix.length()) + { + result += QString("%2").arg(commit_url, prefix); + } + else + { + result += QString("NOISSUE").arg(commit_url); + } + result += "

" + lines.join("
") + "

"; + }; - if(status == "identical") - { - return QObject::tr("

There are no code changes between your current version and latest %1.

").arg(channel); - } - else if(status == "ahead") - { - result += QObject::tr("

Following commits were added since last update:

"); - print_commits(); - } - else if(status == "diverged") - { - auto commit_ahead = Json::requireInteger(rootobject, "ahead_by"); - auto commit_behind = Json::requireInteger(rootobject, "behind_by"); - result += QObject::tr("

The update removes %1 commits and adds the following %2:

").arg(commit_behind).arg(commit_ahead); - print_commits(); - } - result += QObject::tr("

You can look at the changes on github.

").arg(diff_url); - return result; - } - catch (JSONValidationError & e) - { - qWarning() << "Got an unparseable commit log from github:" << e.what(); - qDebug() << json; - } - return QString(); + if(status == "identical") + { + return QObject::tr("

There are no code changes between your current version and latest %1.

").arg(channel); + } + else if(status == "ahead") + { + result += QObject::tr("

Following commits were added since last update:

"); + print_commits(); + } + else if(status == "diverged") + { + auto commit_ahead = Json::requireInteger(rootobject, "ahead_by"); + auto commit_behind = Json::requireInteger(rootobject, "behind_by"); + result += QObject::tr("

The update removes %1 commits and adds the following %2:

").arg(commit_behind).arg(commit_ahead); + print_commits(); + } + result += QObject::tr("

You can look at the changes on github.

").arg(diff_url); + return result; + } + catch (const JSONValidationError &e) + { + qWarning() << "Got an unparseable commit log from github:" << e.what(); + qDebug() << json; + } + return QString(); } void UpdateDialog::changelogLoaded() { - QString result; - switch(m_changelogType) - { - case CHANGELOG_COMMITS: - result = reprocessCommits(changelogData); - break; - case CHANGELOG_MARKDOWN: - result = reprocessMarkdown(changelogData); - break; - } - changelogData.clear(); - ui->changelogBrowser->setHtml(result); + QString result; + switch(m_changelogType) + { + case CHANGELOG_COMMITS: + result = reprocessCommits(changelogData); + break; + case CHANGELOG_MARKDOWN: + result = reprocessMarkdown(changelogData); + break; + } + changelogData.clear(); + ui->changelogBrowser->setHtml(result); } void UpdateDialog::changelogFailed(QString reason) { - ui->changelogBrowser->setHtml(tr("

Failed to fetch changelog... Error: %1

").arg(reason)); + ui->changelogBrowser->setHtml(tr("

Failed to fetch changelog... Error: %1

").arg(reason)); } void UpdateDialog::on_btnUpdateLater_clicked() { - reject(); + reject(); } void UpdateDialog::on_btnUpdateNow_clicked() { - done(UPDATE_NOW); + done(UPDATE_NOW); } void UpdateDialog::closeEvent(QCloseEvent* evt) { - MMC->settings()->set("UpdateDialogGeometry", saveGeometry().toBase64()); - QDialog::closeEvent(evt); + MMC->settings()->set("UpdateDialogGeometry", saveGeometry().toBase64()); + QDialog::closeEvent(evt); } diff --git a/application/dialogs/UpdateDialog.h b/application/dialogs/UpdateDialog.h index 78960c99..6a871b1c 100644 --- a/application/dialogs/UpdateDialog.h +++ b/application/dialogs/UpdateDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,43 +25,43 @@ class UpdateDialog; enum UpdateAction { - UPDATE_LATER = QDialog::Rejected, - UPDATE_NOW = QDialog::Accepted, + UPDATE_LATER = QDialog::Rejected, + UPDATE_NOW = QDialog::Accepted, }; enum ChangelogType { - CHANGELOG_MARKDOWN, - CHANGELOG_COMMITS + CHANGELOG_MARKDOWN, + CHANGELOG_COMMITS }; class UpdateDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit UpdateDialog(bool hasUpdate = true, QWidget *parent = 0); - ~UpdateDialog(); + explicit UpdateDialog(bool hasUpdate = true, QWidget *parent = 0); + ~UpdateDialog(); public slots: - void on_btnUpdateNow_clicked(); - void on_btnUpdateLater_clicked(); + void on_btnUpdateNow_clicked(); + void on_btnUpdateLater_clicked(); - /// Starts loading the changelog - void loadChangelog(); + /// Starts loading the changelog + void loadChangelog(); - /// Slot for when the chengelog loads successfully. - void changelogLoaded(); + /// Slot for when the chengelog loads successfully. + void changelogLoaded(); - /// Slot for when the chengelog fails to load... - void changelogFailed(QString reason); + /// Slot for when the chengelog fails to load... + void changelogFailed(QString reason); protected: - void closeEvent(QCloseEvent * ) override; + void closeEvent(QCloseEvent * ) override; private: - Ui::UpdateDialog *ui; - QByteArray changelogData; - NetJobPtr dljob; - ChangelogType m_changelogType = CHANGELOG_MARKDOWN; + Ui::UpdateDialog *ui; + QByteArray changelogData; + NetJobPtr dljob; + ChangelogType m_changelogType = CHANGELOG_MARKDOWN; }; diff --git a/application/dialogs/VersionSelectDialog.cpp b/application/dialogs/VersionSelectDialog.cpp index c7009497..0b6ba87e 100644 --- a/application/dialogs/VersionSelectDialog.cpp +++ b/application/dialogs/VersionSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,109 +33,109 @@ #include VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable) - : QDialog(parent) + : QDialog(parent) { - setObjectName(QStringLiteral("VersionSelectDialog")); - resize(400, 347); - m_verticalLayout = new QVBoxLayout(this); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + setObjectName(QStringLiteral("VersionSelectDialog")); + resize(400, 347); + m_verticalLayout = new QVBoxLayout(this); + m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - m_versionWidget = new VersionSelectWidget(parent); - m_verticalLayout->addWidget(m_versionWidget); + m_versionWidget = new VersionSelectWidget(parent); + m_verticalLayout->addWidget(m_versionWidget); - m_horizontalLayout = new QHBoxLayout(); - m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + m_horizontalLayout = new QHBoxLayout(); + m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - m_refreshButton = new QPushButton(this); - m_refreshButton->setObjectName(QStringLiteral("refreshButton")); - m_horizontalLayout->addWidget(m_refreshButton); + m_refreshButton = new QPushButton(this); + m_refreshButton->setObjectName(QStringLiteral("refreshButton")); + m_horizontalLayout->addWidget(m_refreshButton); - m_buttonBox = new QDialogButtonBox(this); - m_buttonBox->setObjectName(QStringLiteral("buttonBox")); - m_buttonBox->setOrientation(Qt::Horizontal); - m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - m_horizontalLayout->addWidget(m_buttonBox); + m_buttonBox = new QDialogButtonBox(this); + m_buttonBox->setObjectName(QStringLiteral("buttonBox")); + m_buttonBox->setOrientation(Qt::Horizontal); + m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + m_horizontalLayout->addWidget(m_buttonBox); - m_verticalLayout->addLayout(m_horizontalLayout); + m_verticalLayout->addLayout(m_horizontalLayout); - retranslate(); + retranslate(); - QObject::connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - QObject::connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + QObject::connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + QObject::connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - QMetaObject::connectSlotsByName(this); - setWindowModality(Qt::WindowModal); - setWindowTitle(title); + QMetaObject::connectSlotsByName(this); + setWindowModality(Qt::WindowModal); + setWindowTitle(title); - m_vlist = vlist; + m_vlist = vlist; - if (!cancelable) - { - m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); - } + if (!cancelable) + { + m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); + } } void VersionSelectDialog::retranslate() { - // FIXME: overrides custom title given in constructor! - setWindowTitle(tr("Choose Version")); - m_refreshButton->setToolTip(tr("Reloads the version list.")); - m_refreshButton->setText(tr("&Refresh")); + // FIXME: overrides custom title given in constructor! + setWindowTitle(tr("Choose Version")); + m_refreshButton->setToolTip(tr("Reloads the version list.")); + m_refreshButton->setText(tr("&Refresh")); } void VersionSelectDialog::setCurrentVersion(const QString& version) { - m_currentVersion = version; - m_versionWidget->setCurrentVersion(version); + m_currentVersion = version; + m_versionWidget->setCurrentVersion(version); } void VersionSelectDialog::setEmptyString(QString emptyString) { - m_versionWidget->setEmptyString(emptyString); + m_versionWidget->setEmptyString(emptyString); } void VersionSelectDialog::setEmptyErrorString(QString emptyErrorString) { - m_versionWidget->setEmptyErrorString(emptyErrorString); + m_versionWidget->setEmptyErrorString(emptyErrorString); } void VersionSelectDialog::setResizeOn(int column) { - resizeOnColumn = column; + resizeOnColumn = column; } int VersionSelectDialog::exec() { - QDialog::open(); - m_versionWidget->initialize(m_vlist); - if(resizeOnColumn != -1) - { - m_versionWidget->setResizeOn(resizeOnColumn); - } - return QDialog::exec(); + QDialog::open(); + m_versionWidget->initialize(m_vlist); + if(resizeOnColumn != -1) + { + m_versionWidget->setResizeOn(resizeOnColumn); + } + return QDialog::exec(); } void VersionSelectDialog::selectRecommended() { - m_versionWidget->selectRecommended(); + m_versionWidget->selectRecommended(); } BaseVersionPtr VersionSelectDialog::selectedVersion() const { - return m_versionWidget->selectedVersion(); + return m_versionWidget->selectedVersion(); } void VersionSelectDialog::on_refreshButton_clicked() { - m_versionWidget->loadList(); + m_versionWidget->loadList(); } void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter) { - m_versionWidget->setExactFilter(role, filter); + m_versionWidget->setExactFilter(role, filter); } void VersionSelectDialog::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter) { - m_versionWidget->setFuzzyFilter(role, filter); + m_versionWidget->setFuzzyFilter(role, filter); } diff --git a/application/dialogs/VersionSelectDialog.h b/application/dialogs/VersionSelectDialog.h index 127a0ee9..14bc4d76 100644 --- a/application/dialogs/VersionSelectDialog.h +++ b/application/dialogs/VersionSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,43 +36,43 @@ class VersionProxyModel; class VersionSelectDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0, bool cancelable = true); - virtual ~VersionSelectDialog() {}; + explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0, bool cancelable = true); + virtual ~VersionSelectDialog() {}; - int exec() override; + int exec() override; - BaseVersionPtr selectedVersion() const; + BaseVersionPtr selectedVersion() const; - void setCurrentVersion(const QString & version); - void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); - void setExactFilter(BaseVersionList::ModelRoles role, QString filter); - void setEmptyString(QString emptyString); - void setEmptyErrorString(QString emptyErrorString); - void setResizeOn(int column); + void setCurrentVersion(const QString & version); + void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); + void setExactFilter(BaseVersionList::ModelRoles role, QString filter); + void setEmptyString(QString emptyString); + void setEmptyErrorString(QString emptyErrorString); + void setResizeOn(int column); private slots: - void on_refreshButton_clicked(); + void on_refreshButton_clicked(); private: - void retranslate(); - void selectRecommended(); + void retranslate(); + void selectRecommended(); private: - QString m_currentVersion; - VersionSelectWidget *m_versionWidget = nullptr; - QVBoxLayout *m_verticalLayout = nullptr; - QHBoxLayout *m_horizontalLayout = nullptr; - QPushButton *m_refreshButton = nullptr; - QDialogButtonBox *m_buttonBox = nullptr; + QString m_currentVersion; + VersionSelectWidget *m_versionWidget = nullptr; + QVBoxLayout *m_verticalLayout = nullptr; + QHBoxLayout *m_horizontalLayout = nullptr; + QPushButton *m_refreshButton = nullptr; + QDialogButtonBox *m_buttonBox = nullptr; - BaseVersionList *m_vlist = nullptr; + BaseVersionList *m_vlist = nullptr; - VersionProxyModel *m_proxyModel = nullptr; + VersionProxyModel *m_proxyModel = nullptr; - int resizeOnColumn = -1; + int resizeOnColumn = -1; - Task * loadTask = nullptr; + Task * loadTask = nullptr; }; diff --git a/application/groupview/AccessibleGroupView.cpp b/application/groupview/AccessibleGroupView.cpp new file mode 100644 index 00000000..9a1bb821 --- /dev/null +++ b/application/groupview/AccessibleGroupView.cpp @@ -0,0 +1,774 @@ +#include "GroupView.h" +#include "AccessibleGroupView.h" +#include "AccessibleGroupView_p.h" + +#include +#include +#include + +QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object) +{ + QAccessibleInterface *iface = 0; + if (!object || !object->isWidgetType()) + return iface; + + QWidget *widget = static_cast(object); + + if (classname == QLatin1String("GroupView")) { + iface = new AccessibleGroupView((GroupView *)widget); + } + return iface; +} + + +QAbstractItemView *AccessibleGroupView::view() const +{ + return qobject_cast(object()); +} + +int AccessibleGroupView::logicalIndex(const QModelIndex &index) const +{ + if (!view()->model() || !index.isValid()) + return -1; + return index.row() * (index.model()->columnCount()) + index.column(); +} + +AccessibleGroupView::AccessibleGroupView(QWidget *w) + : QAccessibleObject(w) +{ + Q_ASSERT(view()); +} + +bool AccessibleGroupView::isValid() const +{ + return view(); +} + +AccessibleGroupView::~AccessibleGroupView() +{ + for (QAccessible::Id id : childToId) { + QAccessible::deleteAccessibleInterface(id); + } +} + +QAccessibleInterface *AccessibleGroupView::cellAt(int row, int column) const +{ + if (!view()->model()) { + return 0; + } + + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (Q_UNLIKELY(!index.isValid())) { + qWarning() << "AccessibleGroupView::cellAt: invalid index: " << index << " for " << view(); + return 0; + } + + return child(logicalIndex(index)); +} + +QAccessibleInterface *AccessibleGroupView::caption() const +{ + return 0; +} + +QString AccessibleGroupView::columnDescription(int column) const +{ + if (!view()->model()) + return QString(); + + return view()->model()->headerData(column, Qt::Horizontal).toString(); +} + +int AccessibleGroupView::columnCount() const +{ + if (!view()->model()) + return 0; + return 1; +} + +int AccessibleGroupView::rowCount() const +{ + if (!view()->model()) + return 0; + return view()->model()->rowCount(); +} + +int AccessibleGroupView::selectedCellCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedIndexes().count(); +} + +int AccessibleGroupView::selectedColumnCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedColumns().count(); +} + +int AccessibleGroupView::selectedRowCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedRows().count(); +} + +QString AccessibleGroupView::rowDescription(int row) const +{ + if (!view()->model()) + return QString(); + return view()->model()->headerData(row, Qt::Vertical).toString(); +} + +QList AccessibleGroupView::selectedCells() const +{ + QList cells; + if (!view()->selectionModel()) + return cells; + const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes(); + cells.reserve(selectedIndexes.size()); + for (const QModelIndex &index : selectedIndexes) + cells.append(child(logicalIndex(index))); + return cells; +} + +QList AccessibleGroupView::selectedColumns() const +{ + if (!view()->selectionModel()) { + return QList(); + } + + const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns(); + + QList columns; + columns.reserve(selectedColumns.size()); + for (const QModelIndex &index : selectedColumns) { + columns.append(index.column()); + } + + return columns; +} + +QList AccessibleGroupView::selectedRows() const +{ + if (!view()->selectionModel()) { + return QList(); + } + + QList rows; + + const QModelIndexList selectedRows = view()->selectionModel()->selectedRows(); + + rows.reserve(selectedRows.size()); + for (const QModelIndex &index : selectedRows) { + rows.append(index.row()); + } + + return rows; +} + +QAccessibleInterface *AccessibleGroupView::summary() const +{ + return 0; +} + +bool AccessibleGroupView::isColumnSelected(int column) const +{ + if (!view()->selectionModel()) { + return false; + } + + return view()->selectionModel()->isColumnSelected(column, QModelIndex()); +} + +bool AccessibleGroupView::isRowSelected(int row) const +{ + if (!view()->selectionModel()) { + return false; + } + + return view()->selectionModel()->isRowSelected(row, QModelIndex()); +} + +bool AccessibleGroupView::selectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) { + return false; + } + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: { + return false; + } + case QAbstractItemView::SingleSelection: { + if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 ) + return false; + view()->clearSelection(); + break; + } + case QAbstractItemView::ContiguousSelection: { + if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) { + view()->clearSelection(); + } + break; + } + default: { + break; + } + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); + return true; +} + +bool AccessibleGroupView::selectColumn(int column) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + QModelIndex index = view()->model()->index(0, column, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) { + return false; + } + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: { + return false; + } + case QAbstractItemView::SingleSelection: { + if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) { + return false; + } + // fallthrough intentional + } + case QAbstractItemView::ContiguousSelection: { + if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { + view()->clearSelection(); + } + break; + } + default: { + break; + } + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns); + return true; +} + +bool AccessibleGroupView::unselectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + if (!index.isValid()) { + return false; + } + + QItemSelection selection(index, index); + auto selectionModel = view()->selectionModel(); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: + // no unselect + if (selectedRowCount() == 1) { + return false; + } + break; + case QAbstractItemView::ContiguousSelection: { + // no unselect + if (selectedRowCount() == 1) { + return false; + } + + + if ((!row || selectionModel->isRowSelected(row - 1, view()->rootIndex())) && selectionModel->isRowSelected(row + 1, view()->rootIndex())) { + //If there are rows selected both up the current row and down the current rown, + //the ones which are down the current row will be deselected + selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex())); + } + } + default: { + break; + } + } + + selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); + return true; +} + +bool AccessibleGroupView::unselectColumn(int column) +{ + auto model = view()->model(); + if (!model || !view()->selectionModel()) { + return false; + } + + QModelIndex index = model->index(0, column, view()->rootIndex()); + if (!index.isValid()) { + return false; + } + + QItemSelection selection(index, index); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: { + //In SingleSelection and ContiguousSelection once an item + //is selected, there's no way for the user to unselect all items + if (selectedColumnCount() == 1) { + return false; + } + break; + } + case QAbstractItemView::ContiguousSelection: + if (selectedColumnCount() == 1) { + return false; + } + + if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) + && view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { + //If there are columns selected both at the left of the current row and at the right + //of the current row, the ones which are at the right will be deselected + selection = QItemSelection(index, model->index(0, columnCount() - 1, view()->rootIndex())); + } + default: + break; + } + + view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns); + return true; +} + +QAccessible::Role AccessibleGroupView::role() const +{ + return QAccessible::List; +} + +QAccessible::State AccessibleGroupView::state() const +{ + return QAccessible::State(); +} + +QAccessibleInterface *AccessibleGroupView::childAt(int x, int y) const +{ + QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0)); + QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset); + // FIXME: if indexPosition < 0 in one coordinate, return header + + QModelIndex index = view()->indexAt(indexPosition); + if (index.isValid()) { + return child(logicalIndex(index)); + } + return 0; +} + +int AccessibleGroupView::childCount() const +{ + if (!view()->model()) { + return 0; + } + return (view()->model()->rowCount()) * (view()->model()->columnCount()); +} + +int AccessibleGroupView::indexOfChild(const QAccessibleInterface *iface) const +{ + if (!view()->model()) + return -1; + QAccessibleInterface *parent = iface->parent(); + if (parent->object() != view()) + return -1; + + Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + const AccessibleGroupViewItem* cell = static_cast(iface); + return logicalIndex(cell->m_index); + } else if (iface->role() == QAccessible::Pane) { + return 0; // corner button + } else { + qWarning() << "AccessibleGroupView::indexOfChild has a child with unknown role..." << iface->role() << iface->text(QAccessible::Name); + } + // FIXME: we are in denial of our children. this should stop. + return -1; +} + +QString AccessibleGroupView::text(QAccessible::Text t) const +{ + if (t == QAccessible::Description) + return view()->accessibleDescription(); + return view()->accessibleName(); +} + +QRect AccessibleGroupView::rect() const +{ + if (!view()->isVisible()) + return QRect(); + QPoint pos = view()->mapToGlobal(QPoint(0, 0)); + return QRect(pos.x(), pos.y(), view()->width(), view()->height()); +} + +QAccessibleInterface *AccessibleGroupView::parent() const +{ + if (view() && view()->parent()) { + if (qstrcmp("QComboBoxPrivateContainer", view()->parent()->metaObject()->className()) == 0) { + return QAccessible::queryAccessibleInterface(view()->parent()->parent()); + } + return QAccessible::queryAccessibleInterface(view()->parent()); + } + return 0; +} + +QAccessibleInterface *AccessibleGroupView::child(int logicalIndex) const +{ + if (!view()->model()) + return 0; + + auto id = childToId.constFind(logicalIndex); + if (id != childToId.constEnd()) + return QAccessible::accessibleInterface(id.value()); + + int columns = view()->model()->columnCount(); + + int row = logicalIndex / columns; + int column = logicalIndex % columns; + + QAccessibleInterface *iface = 0; + + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (Q_UNLIKELY(!index.isValid())) { + qWarning("AccessibleGroupView::child: Invalid index at: %d %d", row, column); + return 0; + } + iface = new AccessibleGroupViewItem(view(), index); + + QAccessible::registerAccessibleInterface(iface); + childToId.insert(logicalIndex, QAccessible::uniqueId(iface)); + return iface; +} + +void *AccessibleGroupView::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableInterface) + return static_cast(this); + return 0; +} + +void AccessibleGroupView::modelChange(QAccessibleTableModelChangeEvent *event) +{ + // if there is no cache yet, we don't update anything + if (childToId.isEmpty()) + return; + + switch (event->modelChangeType()) { + case QAccessibleTableModelChangeEvent::ModelReset: + for (QAccessible::Id id : childToId) + QAccessible::deleteAccessibleInterface(id); + childToId.clear(); + break; + + // rows are inserted: move every row after that + case QAccessibleTableModelChangeEvent::RowsInserted: + case QAccessibleTableModelChangeEvent::ColumnsInserted: { + + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (indexOfChild(iface) >= 0) { + newCache.insert(indexOfChild(iface), id); + } else { + // ### This should really not happen, + // but it might if the view has a root index set. + // This needs to be fixed. + QAccessible::deleteAccessibleInterface(id); + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::ColumnsRemoved: + case QAccessibleTableModelChangeEvent::RowsRemoved: { + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + Q_ASSERT(iface->tableCellInterface()); + AccessibleGroupViewItem *cell = static_cast(iface->tableCellInterface()); + // Since it is a QPersistentModelIndex, we only need to check if it is valid + if (cell->m_index.isValid()) + newCache.insert(indexOfChild(cell), id); + else + QAccessible::deleteAccessibleInterface(id); + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::DataChanged: + // nothing to do in this case + break; + } +} + +// TABLE CELL + +AccessibleGroupViewItem::AccessibleGroupViewItem(QAbstractItemView *view_, const QModelIndex &index_) + : view(view_), m_index(index_) +{ + if (Q_UNLIKELY(!index_.isValid())) + qWarning() << "AccessibleGroupViewItem::AccessibleGroupViewItem with invalid index: " << index_; +} + +void *AccessibleGroupViewItem::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableCellInterface) + return static_cast(this); + if (t == QAccessible::ActionInterface) + return static_cast(this); + return 0; +} + +int AccessibleGroupViewItem::columnExtent() const { return 1; } +int AccessibleGroupViewItem::rowExtent() const { return 1; } + +QList AccessibleGroupViewItem::rowHeaderCells() const +{ + return {}; +} + +QList AccessibleGroupViewItem::columnHeaderCells() const +{ + return {}; +} + +int AccessibleGroupViewItem::columnIndex() const +{ + if (!isValid()) { + return -1; + } + + return m_index.column(); +} + +int AccessibleGroupViewItem::rowIndex() const +{ + if (!isValid()) { + return -1; + } + + return m_index.row(); +} + +bool AccessibleGroupViewItem::isSelected() const +{ + if (!isValid()) { + return false; + } + + return view->selectionModel()->isSelected(m_index); +} + +QStringList AccessibleGroupViewItem::actionNames() const +{ + QStringList names; + names << toggleAction(); + return names; +} + +void AccessibleGroupViewItem::doAction(const QString& actionName) +{ + if (actionName == toggleAction()) { + if (isSelected()) { + unselectCell(); + } + else { + selectCell(); + } + } +} + +QStringList AccessibleGroupViewItem::keyBindingsForAction(const QString &) const +{ + return QStringList(); +} + + +void AccessibleGroupViewItem::selectCell() +{ + if (!isValid()) { + return; + } + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (selectionMode == QAbstractItemView::NoSelection) { + return; + } + + Q_ASSERT(table()); + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->selectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->selectRow(m_index.row()); + return; + } + + if (selectionMode == QAbstractItemView::SingleSelection) { + view->clearSelection(); + } + + view->selectionModel()->select(m_index, QItemSelectionModel::Select); +} + +void AccessibleGroupViewItem::unselectCell() +{ + if (!isValid()) + return; + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (selectionMode == QAbstractItemView::NoSelection) + return; + + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->unselectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->unselectRow(m_index.row()); + return; + } + + //If the mode is not MultiSelection or ExtendedSelection and only + //one cell is selected it cannot be unselected by the user + if ((selectionMode != QAbstractItemView::MultiSelection) && (selectionMode != QAbstractItemView::ExtendedSelection) && (view->selectionModel()->selectedIndexes().count() <= 1)) + return; + + view->selectionModel()->select(m_index, QItemSelectionModel::Deselect); +} + +QAccessibleInterface *AccessibleGroupViewItem::table() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessible::Role AccessibleGroupViewItem::role() const +{ + return QAccessible::ListItem; +} + +QAccessible::State AccessibleGroupViewItem::state() const +{ + QAccessible::State st; + if (!isValid()) + return st; + + QRect globalRect = view->rect(); + globalRect.translate(view->mapToGlobal(QPoint(0,0))); + if (!globalRect.intersects(rect())) + st.invisible = true; + + if (view->selectionModel()->isSelected(m_index)) + st.selected = true; + if (view->selectionModel()->currentIndex() == m_index) + st.focused = true; + if (m_index.model()->data(m_index, Qt::CheckStateRole).toInt() == Qt::Checked) + st.checked = true; + + Qt::ItemFlags flags = m_index.flags(); + if (flags & Qt::ItemIsSelectable) { + st.selectable = true; + st.focusable = true; + if (view->selectionMode() == QAbstractItemView::MultiSelection) + st.multiSelectable = true; + if (view->selectionMode() == QAbstractItemView::ExtendedSelection) + st.extSelectable = true; + } + return st; +} + + +QRect AccessibleGroupViewItem::rect() const +{ + QRect r; + if (!isValid()) + return r; + r = view->visualRect(m_index); + + if (!r.isNull()) { + r.translate(view->viewport()->mapTo(view, QPoint(0,0))); + r.translate(view->mapToGlobal(QPoint(0, 0))); + } + return r; +} + +QString AccessibleGroupViewItem::text(QAccessible::Text t) const +{ + QString value; + if (!isValid()) + return value; + QAbstractItemModel *model = view->model(); + switch (t) { + case QAccessible::Name: + value = model->data(m_index, Qt::AccessibleTextRole).toString(); + if (value.isEmpty()) + value = model->data(m_index, Qt::DisplayRole).toString(); + break; + case QAccessible::Description: + value = model->data(m_index, Qt::AccessibleDescriptionRole).toString(); + break; + default: + break; + } + return value; +} + +void AccessibleGroupViewItem::setText(QAccessible::Text /*t*/, const QString &text) +{ + if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable)) + return; + view->model()->setData(m_index, text); +} + +bool AccessibleGroupViewItem::isValid() const +{ + return view && view->model() && m_index.isValid(); +} + +QAccessibleInterface *AccessibleGroupViewItem::parent() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessibleInterface *AccessibleGroupViewItem::child(int) const +{ + return 0; +} diff --git a/application/groupview/AccessibleGroupView.h b/application/groupview/AccessibleGroupView.h new file mode 100644 index 00000000..9bfd1745 --- /dev/null +++ b/application/groupview/AccessibleGroupView.h @@ -0,0 +1,6 @@ +#pragma once + +#include +class QAccessibleInterface; + +QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object); diff --git a/application/groupview/AccessibleGroupView_p.h b/application/groupview/AccessibleGroupView_p.h new file mode 100644 index 00000000..cdec1c0a --- /dev/null +++ b/application/groupview/AccessibleGroupView_p.h @@ -0,0 +1,116 @@ +#pragma once + +#include "GroupView.h" +#include "QtCore/qpointer.h" +#include +#include +#include +// #include + +class QAccessibleTableCell; +class QAccessibleTableHeaderCell; + +class AccessibleGroupView :public QAccessibleTableInterface, public QAccessibleObject +{ +public: + explicit AccessibleGroupView(QWidget *w); + bool isValid() const override; + + QAccessible::Role role() const override; + QAccessible::State state() const override; + QString text(QAccessible::Text t) const override; + QRect rect() const override; + + QAccessibleInterface *childAt(int x, int y) const override; + int childCount() const override; + int indexOfChild(const QAccessibleInterface *) const override; + + QAccessibleInterface *parent() const override; + QAccessibleInterface *child(int index) const override; + + void *interface_cast(QAccessible::InterfaceType t) override; + + // table interface + QAccessibleInterface *cellAt(int row, int column) const override; + QAccessibleInterface *caption() const override; + QAccessibleInterface *summary() const override; + QString columnDescription(int column) const override; + QString rowDescription(int row) const override; + int columnCount() const override; + int rowCount() const override; + + // selection + int selectedCellCount() const override; + int selectedColumnCount() const override; + int selectedRowCount() const override; + QList selectedCells() const override; + QList selectedColumns() const override; + QList selectedRows() const override; + bool isColumnSelected(int column) const override; + bool isRowSelected(int row) const override; + bool selectRow(int row) override; + bool selectColumn(int column) override; + bool unselectRow(int row) override; + bool unselectColumn(int column) override; + + QAbstractItemView *view() const; + + void modelChange(QAccessibleTableModelChangeEvent *event) override; + +protected: + // maybe vector + typedef QHash ChildCache; + mutable ChildCache childToId; + + virtual ~AccessibleGroupView(); + +private: + inline int logicalIndex(const QModelIndex &index) const; +}; + +class AccessibleGroupViewItem: public QAccessibleInterface, public QAccessibleTableCellInterface, public QAccessibleActionInterface +{ +public: + AccessibleGroupViewItem(QAbstractItemView *view, const QModelIndex &m_index); + + void *interface_cast(QAccessible::InterfaceType t) override; + QObject *object() const override { return nullptr; } + QAccessible::Role role() const override; + QAccessible::State state() const override; + QRect rect() const override; + bool isValid() const override; + + QAccessibleInterface *childAt(int, int) const override { return nullptr; } + int childCount() const override { return 0; } + int indexOfChild(const QAccessibleInterface *) const override { return -1; } + + QString text(QAccessible::Text t) const override; + void setText(QAccessible::Text t, const QString &text) override; + + QAccessibleInterface *parent() const override; + QAccessibleInterface *child(int) const override; + + // cell interface + int columnExtent() const override; + QList columnHeaderCells() const override; + int columnIndex() const override; + int rowExtent() const override; + QList rowHeaderCells() const override; + int rowIndex() const override; + bool isSelected() const override; + QAccessibleInterface* table() const override; + + //action interface + QStringList actionNames() const override; + void doAction(const QString &actionName) override; + QStringList keyBindingsForAction(const QString &actionName) const override; + +private: + QPointer view; + QPersistentModelIndex m_index; + + void selectCell(); + void unselectCell(); + + friend class AccessibleGroupView; +}; diff --git a/application/groupview/GroupView.cpp b/application/groupview/GroupView.cpp index 0d6aa49e..bc7ef6c0 100644 --- a/application/groupview/GroupView.cpp +++ b/application/groupview/GroupView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,943 +25,984 @@ #include #include #include +#include #include "VisualGroup.h" #include template bool listsIntersect(const QList &l1, const QList t2) { - for (auto &item : l1) - { - if (t2.contains(item)) - { - return true; - } - } - return false; + for (auto &item : l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; } GroupView::GroupView(QWidget *parent) - : QAbstractItemView(parent) + : QAbstractItemView(parent) { - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - setAcceptDrops(true); - setAutoScroll(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setAcceptDrops(true); + setAutoScroll(true); } GroupView::~GroupView() { - qDeleteAll(m_groups); - m_groups.clear(); + qDeleteAll(m_groups); + m_groups.clear(); } void GroupView::setModel(QAbstractItemModel *model) { - QAbstractItemView::setModel(model); - connect(model, &QAbstractItemModel::modelReset, this, &GroupView::modelReset); - connect(model, &QAbstractItemModel::rowsRemoved, this, &GroupView::rowsRemoved); + QAbstractItemView::setModel(model); + connect(model, &QAbstractItemModel::modelReset, this, &GroupView::modelReset); + connect(model, &QAbstractItemModel::rowsRemoved, this, &GroupView::rowsRemoved); } void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles) + const QVector &roles) { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::modelReset() { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } void GroupView::rowsRemoved() { - scheduleDelayedItemsLayout(); + scheduleDelayedItemsLayout(); } +void GroupView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + QAbstractItemView::currentChanged(current, previous); + // TODO: for accessibility support, implement+register a factory, steal QAccessibleTable from Qt and return an instance of it for GroupView. + if (QAccessible::isActive() && current.isValid()) { + QAccessibleEvent event(this, QAccessible::Focus); + event.setChild(current.row()); + QAccessible::updateAccessibility(&event); + } +} + + class LocaleString : public QString { public: - LocaleString(const char *s) : QString(s) - { - } - LocaleString(const QString &s) : QString(s) - { - } + LocaleString(const char *s) : QString(s) + { + } + LocaleString(const QString &s) : QString(s) + { + } }; inline bool operator<(const LocaleString &lhs, const LocaleString &rhs) { - return (QString::localeAwareCompare(lhs, rhs) < 0); + return (QString::localeAwareCompare(lhs, rhs) < 0); } void GroupView::updateScrollbar() { - int previousScroll = verticalScrollBar()->value(); - if (m_groups.isEmpty()) - { - verticalScrollBar()->setRange(0, 0); - } - else - { - int totalHeight = 0; - // top margin - totalHeight += m_categoryMargin; - int itemScroll = 0; - for (auto category : m_groups) - { - category->m_verticalPosition = totalHeight; - totalHeight += category->totalHeight() + m_categoryMargin; - if(!itemScroll && category->totalHeight() != 0) - { - itemScroll = category->contentHeight() / category->numRows(); - } - } - // do not divide by zero - if(itemScroll == 0) - itemScroll = 64; - - totalHeight += m_bottomMargin; - verticalScrollBar()->setSingleStep ( itemScroll ); - const int rowsPerPage = qMax ( viewport()->height() / itemScroll, 1 ); - verticalScrollBar()->setPageStep ( rowsPerPage * itemScroll ); - - verticalScrollBar()->setRange(0, totalHeight - height()); - } - - verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); + int previousScroll = verticalScrollBar()->value(); + if (m_groups.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + // top margin + totalHeight += m_categoryMargin; + int itemScroll = 0; + for (auto category : m_groups) + { + category->m_verticalPosition = totalHeight; + totalHeight += category->totalHeight() + m_categoryMargin; + if(!itemScroll && category->totalHeight() != 0) + { + itemScroll = category->contentHeight() / category->numRows(); + } + } + // do not divide by zero + if(itemScroll == 0) + itemScroll = 64; + + totalHeight += m_bottomMargin; + verticalScrollBar()->setSingleStep ( itemScroll ); + const int rowsPerPage = qMax ( viewport()->height() / itemScroll, 1 ); + verticalScrollBar()->setPageStep ( rowsPerPage * itemScroll ); + + verticalScrollBar()->setRange(0, totalHeight - height()); + } + + verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); } void GroupView::updateGeometries() { - geometryCache.clear(); - - QMap cats; - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QString groupName = model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); - if (!cats.contains(groupName)) - { - VisualGroup *old = this->category(groupName); - if (old) - { - auto cat = new VisualGroup(old); - cats.insert(groupName, cat); - cat->update(); - } - else - { - auto cat = new VisualGroup(groupName, this); - cats.insert(groupName, cat); - cat->update(); - } - } - } - - qDeleteAll(m_groups); - m_groups = cats.values(); - updateScrollbar(); - viewport()->update(); + geometryCache.clear(); + + QMap cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString groupName = model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); + if (!cats.contains(groupName)) + { + VisualGroup *old = this->category(groupName); + if (old) + { + auto cat = new VisualGroup(old); + cats.insert(groupName, cat); + cat->update(); + } + else + { + auto cat = new VisualGroup(groupName, this); + cats.insert(groupName, cat); + cat->update(); + } + } + } + + qDeleteAll(m_groups); + m_groups = cats.values(); + updateScrollbar(); + viewport()->update(); } bool GroupView::isIndexHidden(const QModelIndex &index) const { - VisualGroup *cat = category(index); - if (cat) - { - return cat->collapsed; - } - else - { - return false; - } + VisualGroup *cat = category(index); + if (cat) + { + return cat->collapsed; + } + else + { + return false; + } } VisualGroup *GroupView::category(const QModelIndex &index) const { - return category(index.data(GroupViewRoles::GroupRole).toString()); + return category(index.data(GroupViewRoles::GroupRole).toString()); } VisualGroup *GroupView::category(const QString &cat) const { - for (auto group : m_groups) - { - if (group->text == cat) - { - return group; - } - } - return nullptr; + for (auto group : m_groups) + { + if (group->text == cat) + { + return group; + } + } + return nullptr; } VisualGroup *GroupView::categoryAt(const QPoint &pos, VisualGroup::HitResults & result) const { - for (auto group : m_groups) - { - result = group->hitScan(pos); - if(result != VisualGroup::NoHit) - { - return group; - } - } - result = VisualGroup::NoHit; - return nullptr; + for (auto group : m_groups) + { + result = group->hitScan(pos); + if(result != VisualGroup::NoHit) + { + return group; + } + } + result = VisualGroup::NoHit; + return nullptr; } QString GroupView::groupNameAt(const QPoint &point) { - VisualGroup::HitResults hitresult; - auto group = categoryAt(point + offset(), hitresult); - if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) - { - return group->text; - } - return QString(); + executeDelayedItemsLayout(); + + VisualGroup::HitResults hitresult; + auto group = categoryAt(point + offset(), hitresult); + if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) + { + return group->text; + } + return QString(); } int GroupView::calculateItemsPerRow() const { - return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); } int GroupView::contentWidth() const { - return width() - m_leftMargin - m_rightMargin; + return width() - m_leftMargin - m_rightMargin; } int GroupView::itemWidth() const { - return m_itemWidth; + return m_itemWidth; } void GroupView::mousePressEvent(QMouseEvent *event) { - // endCategoryEditor(); - - QPoint visualPos = event->pos(); - QPoint geometryPos = event->pos() + offset(); - - QPersistentModelIndex index = indexAt(visualPos); - - m_pressedIndex = index; - m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); - m_pressedPosition = geometryPos; - - VisualGroup::HitResults hitresult; - m_pressedCategory = categoryAt(geometryPos, hitresult); - if (m_pressedCategory && hitresult & VisualGroup::CheckboxHit) - { - setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); - event->accept(); - return; - } - - if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) - { - if(index != currentIndex()) - { - // FIXME: better! - m_currentCursorColumn = -1; - } - // we disable scrollTo for mouse press so the item doesn't change position - // when the user is interacting with it (ie. clicking on it) - bool autoScroll = hasAutoScroll(); - setAutoScroll(false); - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - - setAutoScroll(autoScroll); - QRect rect(visualPos, visualPos); - setSelection(rect, QItemSelectionModel::ClearAndSelect); - - // signal handlers may change the model - emit pressed(index); - } - else - { - // Forces a finalize() even if mouse is pressed, but not on a item - selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); - } + executeDelayedItemsLayout(); + + QPoint visualPos = event->pos(); + QPoint geometryPos = event->pos() + offset(); + + QPersistentModelIndex index = indexAt(visualPos); + + m_pressedIndex = index; + m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); + m_pressedPosition = geometryPos; + + VisualGroup::HitResults hitresult; + m_pressedCategory = categoryAt(geometryPos, hitresult); + if (m_pressedCategory && hitresult & VisualGroup::CheckboxHit) + { + setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); + event->accept(); + return; + } + + if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) + { + if(index != currentIndex()) + { + // FIXME: better! + m_currentCursorColumn = -1; + } + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = hasAutoScroll(); + setAutoScroll(false); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + + setAutoScroll(autoScroll); + QRect rect(visualPos, visualPos); + setSelection(rect, QItemSelectionModel::ClearAndSelect); + + // signal handlers may change the model + emit pressed(index); + } + else + { + // Forces a finalize() even if mouse is pressed, but not on a item + selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); + } } void GroupView::mouseMoveEvent(QMouseEvent *event) { - QPoint topLeft; - QPoint visualPos = event->pos(); - QPoint geometryPos = event->pos() + offset(); - - if (state() == ExpandingState || state() == CollapsingState) - { - return; - } - - if (state() == DraggingState) - { - topLeft = m_pressedPosition - offset(); - if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) - { - m_pressedIndex = QModelIndex(); - startDrag(model()->supportedDragActions()); - setState(NoState); - stopAutoScroll(); - } - return; - } - - if (selectionMode() != SingleSelection) - { - topLeft = m_pressedPosition - offset(); - } - else - { - topLeft = geometryPos; - } - - if (m_pressedIndex.isValid() && (state() != DragSelectingState) && - (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) - { - setState(DraggingState); - return; - } - - if ((event->buttons() & Qt::LeftButton) && selectionModel()) - { - setState(DragSelectingState); - - setSelection(QRect(visualPos, visualPos), QItemSelectionModel::ClearAndSelect); - QModelIndex index = indexAt(visualPos); - - // set at the end because it might scroll the view - if (index.isValid() && (index != selectionModel()->currentIndex()) && - (index.flags() & Qt::ItemIsEnabled)) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - } - } + executeDelayedItemsLayout(); + + QPoint topLeft; + QPoint visualPos = event->pos(); + QPoint geometryPos = event->pos() + offset(); + + if (state() == ExpandingState || state() == CollapsingState) + { + return; + } + + if (state() == DraggingState) + { + topLeft = m_pressedPosition - offset(); + if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) + { + m_pressedIndex = QModelIndex(); + startDrag(model()->supportedDragActions()); + setState(NoState); + stopAutoScroll(); + } + return; + } + + if (selectionMode() != SingleSelection) + { + topLeft = m_pressedPosition - offset(); + } + else + { + topLeft = geometryPos; + } + + if (m_pressedIndex.isValid() && (state() != DragSelectingState) && + (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + + setSelection(QRect(visualPos, visualPos), QItemSelectionModel::ClearAndSelect); + QModelIndex index = indexAt(visualPos); + + // set at the end because it might scroll the view + if (index.isValid() && (index != selectionModel()->currentIndex()) && + (index.flags() & Qt::ItemIsEnabled)) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + } } void GroupView::mouseReleaseEvent(QMouseEvent *event) { - QPoint visualPos = event->pos(); - QPoint geometryPos = event->pos() + offset(); - QPersistentModelIndex index = indexAt(visualPos); - - VisualGroup::HitResults hitresult; - - bool click = (index == m_pressedIndex && index.isValid()) || - (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos, hitresult)); - - if (click && m_pressedCategory) - { - if (state() == ExpandingState) - { - m_pressedCategory->collapsed = false; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - else if (state() == CollapsingState) - { - m_pressedCategory->collapsed = true; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - } - - m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; - - setState(NoState); - - if (click) - { - if (event->button() == Qt::LeftButton) - { - emit clicked(index); - } - QStyleOptionViewItem option = viewOptions(); - if (m_pressedAlreadySelected) - { - option.state |= QStyle::State_Selected; - } - if ((model()->flags(index) & Qt::ItemIsEnabled) && - style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) - { - emit activated(index); - } - } + executeDelayedItemsLayout(); + + QPoint visualPos = event->pos(); + QPoint geometryPos = event->pos() + offset(); + QPersistentModelIndex index = indexAt(visualPos); + + VisualGroup::HitResults hitresult; + + bool click = (index == m_pressedIndex && index.isValid()) || + (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos, hitresult)); + + if (click && m_pressedCategory) + { + if (state() == ExpandingState) + { + m_pressedCategory->collapsed = false; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + else if (state() == CollapsingState) + { + m_pressedCategory->collapsed = true; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; + + setState(NoState); + + if (click) + { + if (event->button() == Qt::LeftButton) + { + emit clicked(index); + } + QStyleOptionViewItem option = viewOptions(); + if (m_pressedAlreadySelected) + { + option.state |= QStyle::State_Selected; + } + if ((model()->flags(index) & Qt::ItemIsEnabled) && + style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } + } } void GroupView::mouseDoubleClickEvent(QMouseEvent *event) { - QModelIndex index = indexAt(event->pos()); - if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) - { - QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), - event->screenPos(), event->button(), event->buttons(), - event->modifiers()); - mousePressEvent(&me); - return; - } - // signal handlers may change the model - QPersistentModelIndex persistent = index; - emit doubleClicked(persistent); + executeDelayedItemsLayout(); + + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) + { + QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), + event->screenPos(), event->button(), event->buttons(), + event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); + + QStyleOptionViewItem option = viewOptions(); + if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } } void GroupView::paintEvent(QPaintEvent *event) { - executeDelayedItemsLayout(); - - QPainter painter(this->viewport()); - - QStyleOptionViewItem option(viewOptions()); - option.widget = this; - - int wpWidth = viewport()->width(); - option.rect.setWidth(wpWidth); - for (int i = 0; i < m_groups.size(); ++i) - { - VisualGroup *category = m_groups.at(i); - int y = category->verticalPosition(); - y -= verticalOffset(); - QRect backup = option.rect; - int height = category->totalHeight(); - option.rect.setTop(y); - option.rect.setHeight(height); - option.rect.setLeft(m_leftMargin); - option.rect.setRight(wpWidth - m_rightMargin); - category->drawHeader(&painter, option); - y += category->totalHeight() + m_categoryMargin; - option.rect = backup; - } - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QModelIndex index = model()->index(i, 0); - if (isIndexHidden(index)) - { - continue; - } - Qt::ItemFlags flags = index.flags(); - option.rect = visualRect(index); - option.features |= QStyleOptionViewItem::WrapText; - if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) - { - option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected - : QStyle::State_None; - } - else - { - option.state &= ~QStyle::State_Selected; - } - option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; - if (!(flags & Qt::ItemIsEnabled)) - { - option.state &= ~QStyle::State_Enabled; - } - itemDelegate()->paint(&painter, option, index); - } - - /* - * Drop indicators for manual reordering... - */ + executeDelayedItemsLayout(); + + QPainter painter(this->viewport()); + + QStyleOptionViewItem option(viewOptions()); + option.widget = this; + + int wpWidth = viewport()->width(); + option.rect.setWidth(wpWidth); + for (int i = 0; i < m_groups.size(); ++i) + { + VisualGroup *category = m_groups.at(i); + int y = category->verticalPosition(); + y -= verticalOffset(); + QRect backup = option.rect; + int height = category->totalHeight(); + option.rect.setTop(y); + option.rect.setHeight(height); + option.rect.setLeft(m_leftMargin); + option.rect.setRight(wpWidth - m_rightMargin); + category->drawHeader(&painter, option); + y += category->totalHeight() + m_categoryMargin; + option.rect = backup; + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QModelIndex index = model()->index(i, 0); + if (isIndexHidden(index)) + { + continue; + } + Qt::ItemFlags flags = index.flags(); + option.rect = visualRect(index); + option.features |= QStyleOptionViewItem::WrapText; + if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) + { + option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected + : QStyle::State_None; + } + else + { + option.state &= ~QStyle::State_Selected; + } + option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; + if (!(flags & Qt::ItemIsEnabled)) + { + option.state &= ~QStyle::State_Enabled; + } + itemDelegate()->paint(&painter, option, index); + } + + /* + * Drop indicators for manual reordering... + */ #if 0 - if (!m_lastDragPosition.isNull()) - { - QPair pair = rowDropPos(m_lastDragPosition); - Group *category = pair.first; - int row = pair.second; - if (category) - { - int internalRow = row - category->firstItemIndex; - QLine line; - if (internalRow >= category->numItems()) - { - QRect toTheRightOfRect = visualRect(category->lastItem()); - line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); - } - else - { - QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); - line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); - } - painter.save(); - painter.setPen(QPen(Qt::black, 3)); - painter.drawLine(line); - painter.restore(); - } - } + if (!m_lastDragPosition.isNull()) + { + QPair pair = rowDropPos(m_lastDragPosition); + Group *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - category->firstItemIndex; + QLine line; + if (internalRow >= category->numItems()) + { + QRect toTheRightOfRect = visualRect(category->lastItem()); + line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); + } + else + { + QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); + line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); + } + painter.save(); + painter.setPen(QPen(Qt::black, 3)); + painter.drawLine(line); + painter.restore(); + } + } #endif } void GroupView::resizeEvent(QResizeEvent *event) { - int newItemsPerRow = calculateItemsPerRow(); - if(newItemsPerRow != m_currentItemsPerRow) - { - m_currentCursorColumn = -1; - m_currentItemsPerRow = newItemsPerRow; - updateGeometries(); - } - else - { - updateScrollbar(); - } + int newItemsPerRow = calculateItemsPerRow(); + if(newItemsPerRow != m_currentItemsPerRow) + { + m_currentCursorColumn = -1; + m_currentItemsPerRow = newItemsPerRow; + updateGeometries(); + } + else + { + updateScrollbar(); + } } void GroupView::dragEnterEvent(QDragEnterEvent *event) { - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); + executeDelayedItemsLayout(); + + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); } void GroupView::dragMoveEvent(QDragMoveEvent *event) { - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); + executeDelayedItemsLayout(); + + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos() + offset(); + viewport()->update(); + event->accept(); } void GroupView::dragLeaveEvent(QDragLeaveEvent *event) { - m_lastDragPosition = QPoint(); - viewport()->update(); + executeDelayedItemsLayout(); + + m_lastDragPosition = QPoint(); + viewport()->update(); } void GroupView::dropEvent(QDropEvent *event) { - m_lastDragPosition = QPoint(); - - stopAutoScroll(); - setState(NoState); - - if (event->source() == this) - { - if(event->possibleActions() & Qt::MoveAction) - { - QPair dropPos = rowDropPos(event->pos() + offset()); - const VisualGroup *category = dropPos.first; - const int row = dropPos.second; - - if (row == -1) - { - viewport()->update(); - return; - } - - const QString categoryText = category->text; - if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) - { - model()->setData(model()->index(row, 0), categoryText, - GroupViewRoles::GroupRole); - event->setDropAction(Qt::MoveAction); - event->accept(); - } - updateGeometries(); - viewport()->update(); - } - } - auto mimedata = event->mimeData(); - - // check if the action is supported - if (!mimedata) - { - return; - } - - // files dropped from outside? - if (mimedata->hasUrls()) - { - auto urls = mimedata->urls(); - event->accept(); - emit droppedURLs(urls); - } + executeDelayedItemsLayout(); + + m_lastDragPosition = QPoint(); + + stopAutoScroll(); + setState(NoState); + + if (event->source() == this) + { + if(event->possibleActions() & Qt::MoveAction) + { + QPair dropPos = rowDropPos(event->pos() + offset()); + const VisualGroup *category = dropPos.first; + const int row = dropPos.second; + + if (row == -1) + { + viewport()->update(); + return; + } + + const QString categoryText = category->text; + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) + { + model()->setData(model()->index(row, 0), categoryText, + GroupViewRoles::GroupRole); + event->setDropAction(Qt::MoveAction); + event->accept(); + } + updateGeometries(); + viewport()->update(); + } + } + auto mimedata = event->mimeData(); + + // check if the action is supported + if (!mimedata) + { + return; + } + + // files dropped from outside? + if (mimedata->hasUrls()) + { + auto urls = mimedata->urls(); + event->accept(); + emit droppedURLs(urls); + } } void GroupView::startDrag(Qt::DropActions supportedActions) { - QModelIndexList indexes = selectionModel()->selectedIndexes(); - if(indexes.count() == 0) - return; - - QMimeData *data = model()->mimeData(indexes); - if (!data) - { - return; - } - QRect rect; - QPixmap pixmap = renderToPixmap(indexes, &rect); - //rect.translate(offset()); - // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); - QDrag *drag = new QDrag(this); - drag->setPixmap(pixmap); - drag->setMimeData(data); - Qt::DropAction defaultDropAction = Qt::IgnoreAction; - if (this->defaultDropAction() != Qt::IgnoreAction && - (supportedActions & this->defaultDropAction())) - { - defaultDropAction = this->defaultDropAction(); - } - if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) - { - const QItemSelection selection = selectionModel()->selection(); - - for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) - { - QModelIndex parent = (*it).parent(); - if ((*it).left() != 0) - { - continue; - } - if ((*it).right() != (model()->columnCount(parent) - 1)) - { - continue; - } - int count = (*it).bottom() - (*it).top() + 1; - model()->removeRows((*it).top(), count, parent); - } - } + executeDelayedItemsLayout(); + + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if(indexes.count() == 0) + return; + + QMimeData *data = model()->mimeData(indexes); + if (!data) + { + return; + } + QRect rect; + QPixmap pixmap = renderToPixmap(indexes, &rect); + //rect.translate(offset()); + // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (this->defaultDropAction() != Qt::IgnoreAction && + (supportedActions & this->defaultDropAction())) + { + defaultDropAction = this->defaultDropAction(); + } + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) + { + const QItemSelection selection = selectionModel()->selection(); + + for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) + { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + { + continue; + } + if ((*it).right() != (model()->columnCount(parent) - 1)) + { + continue; + } + int count = (*it).bottom() - (*it).top() + 1; + model()->removeRows((*it).top(), count, parent); + } + } } QRect GroupView::visualRect(const QModelIndex &index) const { - return geometryRect(index).translated(-offset()); + const_cast(this)->executeDelayedItemsLayout(); + + return geometryRect(index).translated(-offset()); } QRect GroupView::geometryRect(const QModelIndex &index) const { - if (!index.isValid() || isIndexHidden(index) || index.column() > 0) - { - return QRect(); - } + const_cast(this)->executeDelayedItemsLayout(); + + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } - int row = index.row(); - if(geometryCache.contains(row)) - { - return *geometryCache[row]; - } + int row = index.row(); + if(geometryCache.contains(row)) + { + return *geometryCache[row]; + } - const VisualGroup *cat = category(index); - QPair pos = cat->positionOf(index); - int x = pos.first; - // int y = pos.second; + const VisualGroup *cat = category(index); + QPair pos = cat->positionOf(index); + int x = pos.first; + // int y = pos.second; - QRect out; - out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); - out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); - out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); - geometryCache.insert(row, new QRect(out)); - return out; + QRect out; + out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); + out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); + geometryCache.insert(row, new QRect(out)); + return out; } QModelIndex GroupView::indexAt(const QPoint &point) const { - const_cast(this)->executeDelayedItemsLayout(); + const_cast(this)->executeDelayedItemsLayout(); - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(point)) - { - return index; - } - } - return QModelIndex(); + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(point)) + { + return index; + } + } + return QModelIndex(); } -void GroupView::setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) +void GroupView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) { - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - QRect itemRect = visualRect(index); - if (itemRect.intersects(rect)) - { - selectionModel()->select(index, commands); - update(itemRect.translated(-offset())); - } - } + executeDelayedItemsLayout(); + + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + QRect itemRect = visualRect(index); + if (itemRect.intersects(rect)) + { + selectionModel()->select(index, commands); + update(itemRect.translated(-offset())); + } + } } QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const { - Q_ASSERT(r); - auto paintPairs = draggablePaintPairs(indices, r); - if (paintPairs.isEmpty()) - { - return QPixmap(); - } - QPixmap pixmap(r->size()); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - QStyleOptionViewItem option = viewOptions(); - option.state |= QStyle::State_Selected; - for (int j = 0; j < paintPairs.count(); ++j) - { - option.rect = paintPairs.at(j).first.translated(-r->topLeft()); - const QModelIndex ¤t = paintPairs.at(j).second; - itemDelegate()->paint(&painter, option, current); - } - return pixmap; -} - -QList> GroupView::draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const -{ - Q_ASSERT(r); - QRect &rect = *r; - QList> ret; - for (int i = 0; i < indices.count(); ++i) - { - const QModelIndex &index = indices.at(i); - const QRect current = geometryRect(index); - ret += qMakePair(current, index); - rect |= current; - } - return ret; + Q_ASSERT(r); + auto paintPairs = draggablePaintPairs(indices, r); + if (paintPairs.isEmpty()) + { + return QPixmap(); + } + QPixmap pixmap(r->size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QStyleOptionViewItem option = viewOptions(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < paintPairs.count(); ++j) + { + option.rect = paintPairs.at(j).first.translated(-r->topLeft()); + const QModelIndex ¤t = paintPairs.at(j).second; + itemDelegate()->paint(&painter, option, current); + } + return pixmap; +} + +QList> GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + QList> ret; + for (int i = 0; i < indices.count(); ++i) + { + const QModelIndex &index = indices.at(i); + const QRect current = geometryRect(index); + ret += qMakePair(current, index); + rect |= current; + } + return ret; } bool GroupView::isDragEventAccepted(QDropEvent *event) { - return true; + return true; } QPair GroupView::rowDropPos(const QPoint &pos) { - return qMakePair(nullptr, -1); + return qMakePair(nullptr, -1); } QPoint GroupView::offset() const { - return QPoint(horizontalOffset(), verticalOffset()); + return QPoint(horizontalOffset(), verticalOffset()); } QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const { - QRegion region; - for (auto &range : selection) - { - int start_row = range.top(); - int end_row = range.bottom(); - for (int row = start_row; row <= end_row; ++row) - { - int start_column = range.left(); - int end_column = range.right(); - for (int column = start_column; column <= end_column; ++column) - { - QModelIndex index = model()->index(row, column, rootIndex()); - region += visualRect(index); // OK - } - } - } - return region; + QRegion region; + for (auto &range : selection) + { + int start_row = range.top(); + int end_row = range.bottom(); + for (int row = start_row; row <= end_row; ++row) + { + int start_column = range.left(); + int end_column = range.right(); + for (int column = start_column; column <= end_column; ++column) + { + QModelIndex index = model()->index(row, column, rootIndex()); + region += visualRect(index); // OK + } + } + } + return region; } QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, - Qt::KeyboardModifiers modifiers) -{ - auto current = currentIndex(); - if(!current.isValid()) - { - return current; - } - auto cat = category(current); - int group_index = m_groups.indexOf(cat); - if(group_index < 0) - return current; - - auto real_group = m_groups[group_index]; - int beginning_row = 0; - for(auto group: m_groups) - { - if(group == real_group) - break; - beginning_row += group->numRows(); - } - - QPair pos = cat->positionOf(current); - int column = pos.first; - int row = pos.second; - if(m_currentCursorColumn < 0) - { - m_currentCursorColumn = column; - } - switch(cursorAction) - { - case MoveUp: - { - if(row == 0) - { - int prevgroupindex = group_index-1; - while(prevgroupindex >= 0) - { - auto prevgroup = m_groups[prevgroupindex]; - if(prevgroup->collapsed) - { - prevgroupindex--; - continue; - } - int newRow = prevgroup->numRows() - 1; - int newRowSize = prevgroup->rows[newRow].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return prevgroup->rows[newRow][newColumn]; - } - } - else - { - int newRow = row - 1; - int newRowSize = cat->rows[newRow].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return cat->rows[newRow][newColumn]; - } - return current; - } - case MoveDown: - { - if(row == cat->rows.size() - 1) - { - int nextgroupindex = group_index+1; - while (nextgroupindex < m_groups.size()) - { - auto nextgroup = m_groups[nextgroupindex]; - if(nextgroup->collapsed) - { - nextgroupindex++; - continue; - } - int newRowSize = nextgroup->rows[0].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return nextgroup->rows[0][newColumn]; - } - } - else - { - int newRow = row + 1; - int newRowSize = cat->rows[newRow].size(); - int newColumn = m_currentCursorColumn; - if (m_currentCursorColumn >= newRowSize) - { - newColumn = newRowSize - 1; - } - return cat->rows[newRow][newColumn]; - } - return current; - } - case MoveLeft: - { - if(column > 0) - { - m_currentCursorColumn = column - 1; - return cat->rows[row][column - 1]; - } - // TODO: moving to previous line - return current; - } - case MoveRight: - { - if(column < cat->rows[row].size() - 1) - { - m_currentCursorColumn = column + 1; - return cat->rows[row][column + 1]; - } - // TODO: moving to next line - return current; - } - case MoveHome: - { - m_currentCursorColumn = 0; - return cat->rows[row][0]; - } - case MoveEnd: - { - auto last = cat->rows[row].size() - 1; - m_currentCursorColumn = last; - return cat->rows[row][last]; - } - default: - break; - } - return current; + Qt::KeyboardModifiers modifiers) +{ + auto current = currentIndex(); + if(!current.isValid()) + { + return current; + } + auto cat = category(current); + int group_index = m_groups.indexOf(cat); + if(group_index < 0) + return current; + + auto real_group = m_groups[group_index]; + int beginning_row = 0; + for(auto group: m_groups) + { + if(group == real_group) + break; + beginning_row += group->numRows(); + } + + QPair pos = cat->positionOf(current); + int column = pos.first; + int row = pos.second; + if(m_currentCursorColumn < 0) + { + m_currentCursorColumn = column; + } + switch(cursorAction) + { + case MoveUp: + { + if(row == 0) + { + int prevgroupindex = group_index-1; + while(prevgroupindex >= 0) + { + auto prevgroup = m_groups[prevgroupindex]; + if(prevgroup->collapsed) + { + prevgroupindex--; + continue; + } + int newRow = prevgroup->numRows() - 1; + int newRowSize = prevgroup->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return prevgroup->rows[newRow][newColumn]; + } + } + else + { + int newRow = row - 1; + int newRowSize = cat->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return cat->rows[newRow][newColumn]; + } + return current; + } + case MoveDown: + { + if(row == cat->rows.size() - 1) + { + int nextgroupindex = group_index+1; + while (nextgroupindex < m_groups.size()) + { + auto nextgroup = m_groups[nextgroupindex]; + if(nextgroup->collapsed) + { + nextgroupindex++; + continue; + } + int newRowSize = nextgroup->rows[0].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return nextgroup->rows[0][newColumn]; + } + } + else + { + int newRow = row + 1; + int newRowSize = cat->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return cat->rows[newRow][newColumn]; + } + return current; + } + case MoveLeft: + { + if(column > 0) + { + m_currentCursorColumn = column - 1; + return cat->rows[row][column - 1]; + } + // TODO: moving to previous line + return current; + } + case MoveRight: + { + if(column < cat->rows[row].size() - 1) + { + m_currentCursorColumn = column + 1; + return cat->rows[row][column + 1]; + } + // TODO: moving to next line + return current; + } + case MoveHome: + { + m_currentCursorColumn = 0; + return cat->rows[row][0]; + } + case MoveEnd: + { + auto last = cat->rows[row].size() - 1; + m_currentCursorColumn = last; + return cat->rows[row][last]; + } + default: + break; + } + return current; } int GroupView::horizontalOffset() const { - return horizontalScrollBar()->value(); + return horizontalScrollBar()->value(); } int GroupView::verticalOffset() const { - return verticalScrollBar()->value(); + return verticalScrollBar()->value(); } void GroupView::scrollContentsBy(int dx, int dy) { - scrollDirtyRegion(dx, dy); - viewport()->scroll(dx, dy); + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); } void GroupView::scrollTo(const QModelIndex &index, ScrollHint hint) { - if (!index.isValid()) - return; + if (!index.isValid()) + return; - const QRect rect = visualRect(index); - if (hint == EnsureVisible && viewport()->rect().contains(rect)) - { - viewport()->update(rect); - return; - } + const QRect rect = visualRect(index); + if (hint == EnsureVisible && viewport()->rect().contains(rect)) + { + viewport()->update(rect); + return; + } - verticalScrollBar()->setValue(verticalScrollToValue(index, rect, hint)); + verticalScrollBar()->setValue(verticalScrollToValue(index, rect, hint)); } int GroupView::verticalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const { - const QRect area = viewport()->rect(); - const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); - const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); - - int verticalValue = verticalScrollBar()->value(); - QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); - if (hint == QListView::PositionAtTop || above) - verticalValue += adjusted.top(); - else if (hint == QListView::PositionAtBottom || below) - verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); - else if (hint == QListView::PositionAtCenter) - verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); - return verticalValue; + const QRect area = viewport()->rect(); + const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); + const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); + + int verticalValue = verticalScrollBar()->value(); + QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); + if (hint == QListView::PositionAtTop || above) + verticalValue += adjusted.top(); + else if (hint == QListView::PositionAtBottom || below) + verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); + else if (hint == QListView::PositionAtCenter) + verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); + return verticalValue; } diff --git a/application/groupview/GroupView.h b/application/groupview/GroupView.h index 07c65bb5..db29a0d4 100644 --- a/application/groupview/GroupView.h +++ b/application/groupview/GroupView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,126 +23,127 @@ struct GroupViewRoles { - enum - { - GroupRole = Qt::UserRole, - ProgressValueRole, - ProgressMaximumRole - }; + enum + { + GroupRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; }; class GroupView : public QAbstractItemView { - Q_OBJECT + Q_OBJECT public: - GroupView(QWidget *parent = 0); - ~GroupView(); + GroupView(QWidget *parent = 0); + ~GroupView(); - void setModel(QAbstractItemModel *model) override; + void setModel(QAbstractItemModel *model) override; - /// return geometry rectangle occupied by the specified model item - QRect geometryRect(const QModelIndex &index) const; - /// return visual rectangle occupied by the specified model item - virtual QRect visualRect(const QModelIndex &index) const override; - /// get the model index at the specified visual point - virtual QModelIndex indexAt(const QPoint &point) const override; - QString groupNameAt(const QPoint &point); - void setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) override; + /// return geometry rectangle occupied by the specified model item + QRect geometryRect(const QModelIndex &index) const; + /// return visual rectangle occupied by the specified model item + virtual QRect visualRect(const QModelIndex &index) const override; + /// get the model index at the specified visual point + virtual QModelIndex indexAt(const QPoint &point) const override; + QString groupNameAt(const QPoint &point); + void setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) override; - virtual int horizontalOffset() const override; - virtual int verticalOffset() const override; - virtual void scrollContentsBy(int dx, int dy) override; - virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override; + virtual int horizontalOffset() const override; + virtual int verticalOffset() const override; + virtual void scrollContentsBy(int dx, int dy) override; + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override; - virtual QModelIndex moveCursor(CursorAction cursorAction, - Qt::KeyboardModifiers modifiers) override; + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) override; - virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - int spacing() const - { - return m_spacing; - }; + int spacing() const + { + return m_spacing; + }; public slots: - virtual void updateGeometries() override; + virtual void updateGeometries() override; protected slots: - virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles) override; - virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; - virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; - void modelReset(); - void rowsRemoved(); + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; + void modelReset(); + void rowsRemoved(); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; signals: - void droppedURLs(QList urls); + void droppedURLs(QList urls); protected: - virtual bool isIndexHidden(const QModelIndex &index) const override; - void mousePressEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void resizeEvent(QResizeEvent *event) override; + virtual bool isIndexHidden(const QModelIndex &index) const override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; - void dragEnterEvent(QDragEnterEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dropEvent(QDropEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; - void startDrag(Qt::DropActions supportedActions) override; + void startDrag(Qt::DropActions supportedActions) override; - void updateScrollbar(); + void updateScrollbar(); private: - friend struct VisualGroup; - QList m_groups; - - // geometry - int m_leftMargin = 5; - int m_rightMargin = 5; - int m_bottomMargin = 5; - int m_categoryMargin = 5; - int m_spacing = 5; - int m_itemWidth = 100; - int m_currentItemsPerRow = -1; - int m_currentCursorColumn= -1; - mutable QCache geometryCache; - - // point where the currently active mouse action started in geometry coordinates - QPoint m_pressedPosition; - QPersistentModelIndex m_pressedIndex; - bool m_pressedAlreadySelected; - VisualGroup *m_pressedCategory; - QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; - QPoint m_lastDragPosition; - - VisualGroup *category(const QModelIndex &index) const; - VisualGroup *category(const QString &cat) const; - VisualGroup *categoryAt(const QPoint &pos, VisualGroup::HitResults & result) const; - - int itemsPerRow() const - { - return m_currentItemsPerRow; - }; - int contentWidth() const; + friend struct VisualGroup; + QList m_groups; + + // geometry + int m_leftMargin = 5; + int m_rightMargin = 5; + int m_bottomMargin = 5; + int m_categoryMargin = 5; + int m_spacing = 5; + int m_itemWidth = 100; + int m_currentItemsPerRow = -1; + int m_currentCursorColumn= -1; + mutable QCache geometryCache; + + // point where the currently active mouse action started in geometry coordinates + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + VisualGroup *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + + VisualGroup *category(const QModelIndex &index) const; + VisualGroup *category(const QString &cat) const; + VisualGroup *categoryAt(const QPoint &pos, VisualGroup::HitResults & result) const; + + int itemsPerRow() const + { + return m_currentItemsPerRow; + }; + int contentWidth() const; private: /* methods */ - int itemWidth() const; - int calculateItemsPerRow() const; - int verticalScrollToValue(const QModelIndex &index, const QRect &rect, - QListView::ScrollHint hint) const; - QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; - QList> draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const; + int itemWidth() const; + int calculateItemsPerRow() const; + int verticalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) const; + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; + QList> draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const; - bool isDragEventAccepted(QDropEvent *event); + bool isDragEventAccepted(QDropEvent *event); - QPair rowDropPos(const QPoint &pos); + QPair rowDropPos(const QPoint &pos); - QPoint offset() const; + QPoint offset() const; }; diff --git a/application/groupview/GroupedProxyModel.cpp b/application/groupview/GroupedProxyModel.cpp index c13f2411..d5fbd93c 100644 --- a/application/groupview/GroupedProxyModel.cpp +++ b/application/groupview/GroupedProxyModel.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,25 +24,25 @@ GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(pa bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); - const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); - if (leftCategory == rightCategory) - { - return subSortLessThan(left, right); - } - else - { - // FIXME: real group sorting happens in GroupView::updateGeometries(), see LocaleString - auto result = leftCategory.localeAwareCompare(rightCategory); - if(result == 0) - { - return subSortLessThan(left, right); - } - return result < 0; - } + const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); + const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); + if (leftCategory == rightCategory) + { + return subSortLessThan(left, right); + } + else + { + // FIXME: real group sorting happens in GroupView::updateGeometries(), see LocaleString + auto result = leftCategory.localeAwareCompare(rightCategory); + if(result == 0) + { + return subSortLessThan(left, right); + } + return result < 0; + } } bool GroupedProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const { - return left.row() < right.row(); + return left.row() < right.row(); } diff --git a/application/groupview/GroupedProxyModel.h b/application/groupview/GroupedProxyModel.h index babeb308..810657e7 100644 --- a/application/groupview/GroupedProxyModel.h +++ b/application/groupview/GroupedProxyModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,12 @@ class GroupedProxyModel : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT public: - GroupedProxyModel(QObject *parent = 0); + GroupedProxyModel(QObject *parent = 0); protected: - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; }; diff --git a/application/groupview/InstanceDelegate.cpp b/application/groupview/InstanceDelegate.cpp index 0855c04a..39830d1a 100644 --- a/application/groupview/InstanceDelegate.cpp +++ b/application/groupview/InstanceDelegate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,33 +19,35 @@ #include #include #include +#include #include "GroupView.h" #include "BaseInstance.h" #include "InstanceList.h" #include +#include // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) + qreal &widthUsed) { - height = 0; - widthUsed = 0; - textLayout.beginLayout(); - QString str = textLayout.text(); - while (true) - { - QTextLine line = textLayout.createLine(); - if (!line.isValid()) - break; - if (line.textLength() == 0) - break; - line.setLineWidth(lineWidth); - line.setPosition(QPointF(0, height)); - height += line.height(); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); - } - textLayout.endLayout(); + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); } ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) @@ -53,291 +55,374 @@ ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent } void drawSelectionRect(QPainter *painter, const QStyleOptionViewItem &option, - const QRect &rect) + const QRect &rect) { - if ((option.state & QStyle::State_Selected)) - painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); - else - { - QColor backgroundColor = option.palette.color(QPalette::Background); - backgroundColor.setAlpha(160); - painter->fillRect(rect, QBrush(backgroundColor)); - } + if ((option.state & QStyle::State_Selected)) + painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); + else + { + QColor backgroundColor = option.palette.color(QPalette::Background); + backgroundColor.setAlpha(160); + painter->fillRect(rect, QBrush(backgroundColor)); + } } void drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) { - if (!(option.state & QStyle::State_HasFocus)) - return; - QStyleOptionFocusRect opt; - opt.direction = option.direction; - opt.fontMetrics = option.fontMetrics; - opt.palette = option.palette; - opt.rect = rect; - // opt.state = option.state | QStyle::State_KeyboardFocusChange | - // QStyle::State_Item; - auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; - opt.backgroundColor = option.palette.color(col); - // Apparently some widget styles expect this hint to not be set - painter->setRenderHint(QPainter::Antialiasing, false); - - QStyle *style = option.widget ? option.widget->style() : QApplication::style(); - - style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); - - painter->setRenderHint(QPainter::Antialiasing); + if (!(option.state & QStyle::State_HasFocus)) + return; + QStyleOptionFocusRect opt; + opt.direction = option.direction; + opt.fontMetrics = option.fontMetrics; + opt.palette = option.palette; + opt.rect = rect; + // opt.state = option.state | QStyle::State_KeyboardFocusChange | + // QStyle::State_Item; + auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; + opt.backgroundColor = option.palette.color(col); + // Apparently some widget styles expect this hint to not be set + painter->setRenderHint(QPainter::Antialiasing, false); + + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + + style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); + + painter->setRenderHint(QPainter::Antialiasing); } // TODO this can be made a lot prettier void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItem &option, - const int value, const int maximum) + const int value, const int maximum) { - if (maximum == 0 || value == maximum) - { - return; - } + if (maximum == 0 || value == maximum) + { + return; + } - painter->save(); + painter->save(); - qreal percent = (qreal)value / (qreal)maximum; - QColor color = option.palette.color(QPalette::Dark); - color.setAlphaF(0.70f); - painter->setBrush(color); - painter->setPen(QPen(QBrush(), 0)); - painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); + qreal percent = (qreal)value / (qreal)maximum; + QColor color = option.palette.color(QPalette::Dark); + color.setAlphaF(0.70f); + painter->setBrush(color); + painter->setPen(QPen(QBrush(), 0)); + painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); - painter->restore(); + painter->restore(); } void drawBadges(QPainter *painter, const QStyleOptionViewItem &option, BaseInstance *instance, QIcon::Mode mode, QIcon::State state) { - QList pixmaps; - if (instance->isRunning()) - { - pixmaps.append("status-running"); - } - else if (instance->hasCrashed() || instance->hasVersionBroken()) - { - pixmaps.append("status-bad"); - } - if (instance->hasUpdateAvailable()) - { - pixmaps.append("checkupdate"); - } - - static const int itemSide = 24; - static const int spacing = 1; - const int itemsPerRow = qMax(1, qFloor(double(option.rect.width() + spacing) / double(itemSide + spacing))); - const int rows = qCeil((double)pixmaps.size() / (double)itemsPerRow); - QListIterator it(pixmaps); - painter->translate(option.rect.topLeft()); - for (int y = 0; y < rows; ++y) - { - for (int x = 0; x < itemsPerRow; ++x) - { - if (!it.hasNext()) - { - return; - } - // FIXME: inject this. - auto icon = XdgIcon::fromTheme(it.next()); - // opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); - const QPixmap pixmap; - // itemSide - QRect badgeRect( - option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide, - y * itemSide + qMax(y - 1, 0) * spacing, - itemSide, - itemSide - ); - icon.paint(painter, badgeRect, Qt::AlignCenter, mode, state); - } - } - painter->translate(-option.rect.topLeft()); + QList pixmaps; + if (instance->isRunning()) + { + pixmaps.append("status-running"); + } + else if (instance->hasCrashed() || instance->hasVersionBroken()) + { + pixmaps.append("status-bad"); + } + if (instance->hasUpdateAvailable()) + { + pixmaps.append("checkupdate"); + } + + static const int itemSide = 24; + static const int spacing = 1; + const int itemsPerRow = qMax(1, qFloor(double(option.rect.width() + spacing) / double(itemSide + spacing))); + const int rows = qCeil((double)pixmaps.size() / (double)itemsPerRow); + QListIterator it(pixmaps); + painter->translate(option.rect.topLeft()); + for (int y = 0; y < rows; ++y) + { + for (int x = 0; x < itemsPerRow; ++x) + { + if (!it.hasNext()) + { + return; + } + // FIXME: inject this. + auto icon = XdgIcon::fromTheme(it.next()); + // opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); + const QPixmap pixmap; + // itemSide + QRect badgeRect( + option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide, + y * itemSide + qMax(y - 1, 0) * spacing, + itemSide, + itemSide + ); + icon.paint(painter, badgeRect, Qt::AlignCenter, mode, state); + } + } + painter->translate(-option.rect.topLeft()); } static QSize viewItemTextSize(const QStyleOptionViewItem *option) { - QStyle *style = option->widget ? option->widget->style() : QApplication::style(); - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(option->font); - textLayout.setText(option->text); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; - QRect bounds(0, 0, 100 - 2 * textMargin, 600); - qreal height = 0, widthUsed = 0; - viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); - const QSize size(qCeil(widthUsed), qCeil(height)); - return QSize(size.width() + 2 * textMargin, size.height()); + QStyle *style = option->widget ? option->widget->style() : QApplication::style(); + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option->font); + textLayout.setText(option->text); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; + QRect bounds(0, 0, 100 - 2 * textMargin, 600); + qreal height = 0, widthUsed = 0; + viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); + const QSize size(qCeil(widthUsed), qCeil(height)); + return QSize(size.width() + 2 * textMargin, size.height()); } void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const + const QModelIndex &index) const { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - painter->save(); - painter->setClipRect(opt.rect); - - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - - // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); - const int iconSize = 48; - QRect iconbox = opt.rect; - const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; - QRect textRect = opt.rect; - QRect textHighlightRect = textRect; - // clip the decoration on top, remove width padding - textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); - - textHighlightRect.adjust(0, iconSize + 5, 0, 0); - - // draw background - { - // FIXME: unused - // QSize textSize = viewItemTextSize ( &opt ); - QPalette::ColorGroup cg; - QStyleOptionViewItem opt2(opt); - - if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) - { - if (!(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - else - cg = QPalette::Normal; - } - else - { - cg = QPalette::Disabled; - } - opt2.palette.setCurrentColorGroup(cg); - - // fill in background, if any - - if (opt.backgroundBrush.style() != Qt::NoBrush) - { - QPointF oldBO = painter->brushOrigin(); - painter->setBrushOrigin(opt.rect.topLeft()); - painter->fillRect(opt.rect, opt.backgroundBrush); - painter->setBrushOrigin(oldBO); - } - - drawSelectionRect(painter, opt2, textHighlightRect); - - /* - if (opt.showDecorationSelected) - { - drawSelectionRect(painter, opt2, opt.rect); - drawFocusRect(painter, opt2, opt.rect); - // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); - } - else - { - - // if ( opt.state & QStyle::State_Selected ) - { - // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, - // opt.widget ); - // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, - // QPalette::Highlight ) ); - drawSelectionRect(painter, opt2, textHighlightRect); - drawFocusRect(painter, opt2, textHighlightRect); - } - } - */ - } - - // icon mode and state, also used for badges - QIcon::Mode mode = QIcon::Normal; - if (!(opt.state & QStyle::State_Enabled)) - mode = QIcon::Disabled; - else if (opt.state & QStyle::State_Selected) - mode = QIcon::Selected; - QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; - - // draw the icon - { - iconbox.setHeight(iconSize); - opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); - } - // set the text colors - QPalette::ColorGroup cg = - opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; - if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) - cg = QPalette::Inactive; - if (opt.state & QStyle::State_Selected) - { - painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); - } - else - { - painter->setPen(opt.palette.color(cg, QPalette::Text)); - } - - // draw the text - QTextOption textOption; - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - textOption.setTextDirection(opt.direction); - textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); - QTextLayout textLayout; - textLayout.setTextOption(textOption); - textLayout.setFont(opt.font); - textLayout.setText(opt.text); - - qreal width, height; - viewItemTextLayout(textLayout, textRect.width(), height, width); - - const int lineCount = textLayout.lineCount(); - - const QRect layoutRect = QStyle::alignedRect( - opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); - const QPointF position = layoutRect.topLeft(); - for (int i = 0; i < lineCount; ++i) - { - const QTextLine line = textLayout.lineAt(i); - line.draw(painter, position); - } - - // FIXME: this really has no business of being here. Make generic. - auto instance = (BaseInstance*)index.data(InstanceList::InstancePointerRole) - .value(); - if (instance) - { - drawBadges(painter, opt, instance, mode, state); - } - - drawProgressOverlay(painter, opt, index.data(GroupViewRoles::ProgressValueRole).toInt(), - index.data(GroupViewRoles::ProgressMaximumRole).toInt()); - - painter->restore(); + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + painter->save(); + painter->setClipRect(opt.rect); + + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + + // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); + const int iconSize = 48; + QRect iconbox = opt.rect; + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; + QRect textRect = opt.rect; + QRect textHighlightRect = textRect; + // clip the decoration on top, remove width padding + textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); + + textHighlightRect.adjust(0, iconSize + 5, 0, 0); + + // draw background + { + // FIXME: unused + // QSize textSize = viewItemTextSize ( &opt ); + drawSelectionRect(painter, opt, textHighlightRect); + /* + QPalette::ColorGroup cg; + QStyleOptionViewItem opt2(opt); + + if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) + { + if (!(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + else + cg = QPalette::Normal; + } + else + { + cg = QPalette::Disabled; + } + */ + /* + opt2.palette.setCurrentColorGroup(cg); + + // fill in background, if any + + + if (opt.backgroundBrush.style() != Qt::NoBrush) + { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(opt.rect.topLeft()); + painter->fillRect(opt.rect, opt.backgroundBrush); + painter->setBrushOrigin(oldBO); + } + + drawSelectionRect(painter, opt2, textHighlightRect); + */ + + /* + if (opt.showDecorationSelected) + { + drawSelectionRect(painter, opt2, opt.rect); + drawFocusRect(painter, opt2, opt.rect); + // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); + } + else + { + + // if ( opt.state & QStyle::State_Selected ) + { + // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, + // opt.widget ); + // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, + // QPalette::Highlight ) ); + drawSelectionRect(painter, opt2, textHighlightRect); + drawFocusRect(painter, opt2, textHighlightRect); + } + } + */ + } + + // icon mode and state, also used for badges + QIcon::Mode mode = QIcon::Normal; + if (!(opt.state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (opt.state & QStyle::State_Selected) + mode = QIcon::Selected; + QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; + + // draw the icon + { + iconbox.setHeight(iconSize); + opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); + } + // set the text colors + QPalette::ColorGroup cg = + opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (opt.state & QStyle::State_Selected) + { + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + } + else + { + painter->setPen(opt.palette.color(cg, QPalette::Text)); + } + + // draw the text + QTextOption textOption; + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + textOption.setTextDirection(opt.direction); + textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(opt.font); + textLayout.setText(opt.text); + + qreal width, height; + viewItemTextLayout(textLayout, textRect.width(), height, width); + + const int lineCount = textLayout.lineCount(); + + const QRect layoutRect = QStyle::alignedRect( + opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); + const QPointF position = layoutRect.topLeft(); + for (int i = 0; i < lineCount; ++i) + { + const QTextLine line = textLayout.lineAt(i); + line.draw(painter, position); + } + + // FIXME: this really has no business of being here. Make generic. + auto instance = (BaseInstance*)index.data(InstanceList::InstancePointerRole) + .value(); + if (instance) + { + drawBadges(painter, opt, instance, mode, state); + } + + drawProgressOverlay(painter, opt, index.data(GroupViewRoles::ProgressValueRole).toInt(), + index.data(GroupViewRoles::ProgressMaximumRole).toInt()); + + painter->restore(); } QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const + const QModelIndex &index) const { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - opt.features |= QStyleOptionViewItem::WrapText; - opt.text = index.data().toString(); - opt.textElideMode = Qt::ElideRight; - opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; - - QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; - int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables - QSize szz = viewItemTextSize(&opt); - height += szz.height(); - // FIXME: maybe the icon items could scale and keep proportions? - QSize sz(100, height); - return sz; + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + opt.features |= QStyleOptionViewItem::WrapText; + opt.text = index.data().toString(); + opt.textElideMode = Qt::ElideRight; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; + int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables + QSize szz = viewItemTextSize(&opt); + height += szz.height(); + // FIXME: maybe the icon items could scale and keep proportions? + QSize sz(100, height); + return sz; } +class NoReturnTextEdit: public QTextEdit +{ + Q_OBJECT +public: + explicit NoReturnTextEdit(QWidget *parent) : QTextEdit(parent) + { + setTextInteractionFlags(Qt::TextEditorInteraction); + setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + } + bool event(QEvent * event) override + { + auto eventType = event->type(); + if(eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease) + { + QKeyEvent *keyEvent = static_cast(event); + auto key = keyEvent->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter) + { + emit editingDone(); + return true; + } + if(key == Qt::Key_Tab) + { + return true; + } + } + return QTextEdit::event(event); + } +signals: + void editingDone(); +}; + +void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + const int iconSize = 48; + QRect textRect = option.rect; + // QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + textRect.adjust(0, iconSize + 5, 0, 0); + editor->setGeometry(textRect); +} + +void ListViewDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + auto text = index.data(Qt::EditRole).toString(); + QTextEdit * realeditor = qobject_cast(editor); + realeditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop); + realeditor->append(text); + realeditor->selectAll(); + realeditor->document()->clearUndoRedoStacks(); +} + +void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + QTextEdit * realeditor = qobject_cast(editor); + QString text = realeditor->toPlainText(); + text.replace(QChar('\n'), QChar(' ')); + text = text.trimmed(); + if(text.size() != 0) + { + model->setData(index, text); + } +} + +QWidget * ListViewDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + auto editor = new NoReturnTextEdit(parent); + connect(editor, &NoReturnTextEdit::editingDone, this, &ListViewDelegate::editingDone); + return editor; +} + +void ListViewDelegate::editingDone() +{ + NoReturnTextEdit *editor = qobject_cast(sender()); + emit commitData(editor); + emit closeEditor(editor); +} + +#include "InstanceDelegate.moc" diff --git a/application/groupview/InstanceDelegate.h b/application/groupview/InstanceDelegate.h index c0148570..be49f943 100644 --- a/application/groupview/InstanceDelegate.h +++ b/application/groupview/InstanceDelegate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,20 @@ class ListViewDelegate : public QStyledItemDelegate { + Q_OBJECT + public: - explicit ListViewDelegate(QObject *parent = 0); + explicit ListViewDelegate(QObject *parent = 0); + virtual ~ListViewDelegate() {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + + void setEditorData(QWidget * editor, const QModelIndex & index) const override; + void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; -protected: - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +private slots: + void editingDone(); }; diff --git a/application/groupview/VisualGroup.cpp b/application/groupview/VisualGroup.cpp index 940c7a8b..f3e6751d 100644 --- a/application/groupview/VisualGroup.cpp +++ b/application/groupview/VisualGroup.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,290 +28,290 @@ VisualGroup::VisualGroup(const QString &text, GroupView *view) : view(view), tex } VisualGroup::VisualGroup(const VisualGroup *other) - : view(other->view), text(other->text), collapsed(other->collapsed) + : view(other->view), text(other->text), collapsed(other->collapsed) { } void VisualGroup::update() { - auto temp_items = items(); - auto itemsPerRow = view->itemsPerRow(); - - int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow)); - rows = QVector(numRows); - - int maxRowHeight = 0; - int positionInRow = 0; - int currentRow = 0; - int offsetFromTop = 0; - for (auto item: temp_items) - { - if(positionInRow == itemsPerRow) - { - rows[currentRow].height = maxRowHeight; - rows[currentRow].top = offsetFromTop; - currentRow ++; - offsetFromTop += maxRowHeight + 5; - positionInRow = 0; - maxRowHeight = 0; - } - auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height(); - if(itemHeight > maxRowHeight) - { - maxRowHeight = itemHeight; - } - rows[currentRow].items.append(item); - positionInRow++; - } - rows[currentRow].height = maxRowHeight; - rows[currentRow].top = offsetFromTop; + auto temp_items = items(); + auto itemsPerRow = view->itemsPerRow(); + + int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow)); + rows = QVector(numRows); + + int maxRowHeight = 0; + int positionInRow = 0; + int currentRow = 0; + int offsetFromTop = 0; + for (auto item: temp_items) + { + if(positionInRow == itemsPerRow) + { + rows[currentRow].height = maxRowHeight; + rows[currentRow].top = offsetFromTop; + currentRow ++; + offsetFromTop += maxRowHeight + 5; + positionInRow = 0; + maxRowHeight = 0; + } + auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height(); + if(itemHeight > maxRowHeight) + { + maxRowHeight = itemHeight; + } + rows[currentRow].items.append(item); + positionInRow++; + } + rows[currentRow].height = maxRowHeight; + rows[currentRow].top = offsetFromTop; } QPair VisualGroup::positionOf(const QModelIndex &index) const { - int y = 0; - for (auto & row: rows) - { - for(auto x = 0; x < row.items.size(); x++) - { - if(row.items[x] == index) - { - return qMakePair(x,y); - } - } - y++; - } - qWarning() << "Item" << index.row() << index.data(Qt::DisplayRole).toString() << "not found in visual group" << text; - return qMakePair(0, 0); + int y = 0; + for (auto & row: rows) + { + for(auto x = 0; x < row.items.size(); x++) + { + if(row.items[x] == index) + { + return qMakePair(x,y); + } + } + y++; + } + qWarning() << "Item" << index.row() << index.data(Qt::DisplayRole).toString() << "not found in visual group" << text; + return qMakePair(0, 0); } int VisualGroup::rowTopOf(const QModelIndex &index) const { - auto position = positionOf(index); - return rows[position.second].top; + auto position = positionOf(index); + return rows[position.second].top; } int VisualGroup::rowHeightOf(const QModelIndex &index) const { - auto position = positionOf(index); - return rows[position.second].height; + auto position = positionOf(index); + return rows[position.second].height; } VisualGroup::HitResults VisualGroup::hitScan(const QPoint &pos) const { - VisualGroup::HitResults results = VisualGroup::NoHit; - int y_start = verticalPosition(); - int body_start = y_start + headerHeight(); - int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? - int y = pos.y(); - // int x = pos.x(); - if (y < y_start) - { - results = VisualGroup::NoHit; - } - else if (y < body_start) - { - results = VisualGroup::HeaderHit; - int collapseSize = headerHeight() - 4; - - // the icon - QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); - if (iconRect.contains(pos)) - { - results |= VisualGroup::CheckboxHit; - } - } - else if (y < body_end) - { - results |= VisualGroup::BodyHit; - } - return results; + VisualGroup::HitResults results = VisualGroup::NoHit; + int y_start = verticalPosition(); + int body_start = y_start + headerHeight(); + int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? + int y = pos.y(); + // int x = pos.x(); + if (y < y_start) + { + results = VisualGroup::NoHit; + } + else if (y < body_start) + { + results = VisualGroup::HeaderHit; + int collapseSize = headerHeight() - 4; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); + if (iconRect.contains(pos)) + { + results |= VisualGroup::CheckboxHit; + } + } + else if (y < body_end) + { + results |= VisualGroup::BodyHit; + } + return results; } void VisualGroup::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) { - painter->setRenderHint(QPainter::Antialiasing); - - const QRect optRect = option.rect; - QFont font(QApplication::font()); - font.setBold(true); - const QFontMetrics fontMetrics = QFontMetrics(font); - - QColor outlineColor = option.palette.text().color(); - outlineColor.setAlphaF(0.35); - - //BEGIN: top left corner - { - painter->save(); - painter->setPen(outlineColor); - const QPointF topLeft(optRect.topLeft()); - QRectF arc(topLeft, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 1440, 1440); - painter->restore(); - } - //END: top left corner - - //BEGIN: left vertical line - { - QPoint start(optRect.topLeft()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topLeft()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: left vertical line - - //BEGIN: horizontal line - { - QPoint start(optRect.topLeft()); - start.rx() += 3; - QPoint horizontalGradTop(optRect.topLeft()); - horizontalGradTop.rx() += optRect.width() - 6; - painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); - } - //END: horizontal line - - //BEGIN: top right corner - { - painter->save(); - painter->setPen(outlineColor); - QPointF topRight(optRect.topRight()); - topRight.rx() -= 4; - QRectF arc(topRight, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 0, 1440); - painter->restore(); - } - //END: top right corner - - //BEGIN: right vertical line - { - QPoint start(optRect.topRight()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topRight()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: right vertical line - - //BEGIN: checkboxy thing - { - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, false); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - QRect iconSubRect(option.rect); - iconSubRect.setTop(iconSubRect.top() + 7); - iconSubRect.setLeft(iconSubRect.left() + 7); - - int sizing = fontMetrics.height(); - int even = ( (sizing - 1) % 2 ); - - iconSubRect.setHeight(sizing - even); - iconSubRect.setWidth(sizing - even); - painter->drawRect(iconSubRect); - - - /* - if(collapsed) - painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); - else - painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); - */ - painter->setBrush(option.palette.text()); - painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, - iconSubRect.width(), 2, penColor); - if (collapsed) - { - painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, - iconSubRect.height(), penColor); - } - - painter->restore(); - } - //END: checkboxy thing - - //BEGIN: text - { - QRect textRect(option.rect); - textRect.setTop(textRect.top() + 7); - textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7); - textRect.setHeight(fontMetrics.height()); - textRect.setRight(textRect.right() - 7); - - painter->save(); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); - painter->restore(); - } - //END: text + painter->setRenderHint(QPainter::Antialiasing); + + const QRect optRect = option.rect; + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + + QColor outlineColor = option.palette.text().color(); + outlineColor.setAlphaF(0.35); + + //BEGIN: top left corner + { + painter->save(); + painter->setPen(outlineColor); + const QPointF topLeft(optRect.topLeft()); + QRectF arc(topLeft, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 1440, 1440); + painter->restore(); + } + //END: top left corner + + //BEGIN: left vertical line + { + QPoint start(optRect.topLeft()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topLeft()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: left vertical line + + //BEGIN: horizontal line + { + QPoint start(optRect.topLeft()); + start.rx() += 3; + QPoint horizontalGradTop(optRect.topLeft()); + horizontalGradTop.rx() += optRect.width() - 6; + painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); + } + //END: horizontal line + + //BEGIN: top right corner + { + painter->save(); + painter->setPen(outlineColor); + QPointF topRight(optRect.topRight()); + topRight.rx() -= 4; + QRectF arc(topRight, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 0, 1440); + painter->restore(); + } + //END: top right corner + + //BEGIN: right vertical line + { + QPoint start(optRect.topRight()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topRight()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: right vertical line + + //BEGIN: checkboxy thing + { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + QRect iconSubRect(option.rect); + iconSubRect.setTop(iconSubRect.top() + 7); + iconSubRect.setLeft(iconSubRect.left() + 7); + + int sizing = fontMetrics.height(); + int even = ( (sizing - 1) % 2 ); + + iconSubRect.setHeight(sizing - even); + iconSubRect.setWidth(sizing - even); + painter->drawRect(iconSubRect); + + + /* + if(collapsed) + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); + else + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); + */ + painter->setBrush(option.palette.text()); + painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, + iconSubRect.width(), 2, penColor); + if (collapsed) + { + painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, + iconSubRect.height(), penColor); + } + + painter->restore(); + } + //END: checkboxy thing + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7); + textRect.setHeight(fontMetrics.height()); + textRect.setRight(textRect.right() - 7); + + painter->save(); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); + painter->restore(); + } + //END: text } int VisualGroup::totalHeight() const { - return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? + return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? } int VisualGroup::headerHeight() const { - QFont font(QApplication::font()); + QFont font(QApplication::font()); font.setBold(true); QFontMetrics fontMetrics(font); const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ + 11 /* top and bottom separation */; return height; - /* - int raw = view->viewport()->fontMetrics().height() + 4; - // add english. maybe. depends on font height. - if (raw % 2 == 0) - raw++; - return std::min(raw, 25); - */ + /* + int raw = view->viewport()->fontMetrics().height() + 4; + // add english. maybe. depends on font height. + if (raw % 2 == 0) + raw++; + return std::min(raw, 25); + */ } int VisualGroup::contentHeight() const { - if (collapsed) - { - return 0; - } - auto last = rows[numRows() - 1]; - return last.top + last.height; + if (collapsed) + { + return 0; + } + auto last = rows[numRows() - 1]; + return last.top + last.height; } int VisualGroup::numRows() const { - return rows.size(); + return rows.size(); } int VisualGroup::verticalPosition() const { - return m_verticalPosition; + return m_verticalPosition; } QList VisualGroup::items() const { - QList indices; - for (int i = 0; i < view->model()->rowCount(); ++i) - { - const QModelIndex index = view->model()->index(i, 0); - if (index.data(GroupViewRoles::GroupRole).toString() == text) - { - indices.append(index); - } - } - return indices; + QList indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(GroupViewRoles::GroupRole).toString() == text) + { + indices.append(index); + } + } + return indices; } diff --git a/application/groupview/VisualGroup.h b/application/groupview/VisualGroup.h index 2caac49f..356a6da2 100644 --- a/application/groupview/VisualGroup.h +++ b/application/groupview/VisualGroup.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,81 +26,81 @@ class QModelIndex; struct VisualRow { - QList items; - int height = 0; - int top = 0; - inline int size() const - { - return items.size(); - } - inline QModelIndex &operator[](int i) - { - return items[i]; - } + QList items; + int height = 0; + int top = 0; + inline int size() const + { + return items.size(); + } + inline QModelIndex &operator[](int i) + { + return items[i]; + } }; struct VisualGroup { /* constructors */ - VisualGroup(const QString &text, GroupView *view); - VisualGroup(const VisualGroup *other); + VisualGroup(const QString &text, GroupView *view); + VisualGroup(const VisualGroup *other); /* data */ - GroupView *view = nullptr; - QString text; - bool collapsed = false; - QVector rows; - int firstItemIndex = 0; - int m_verticalPosition = 0; + GroupView *view = nullptr; + QString text; + bool collapsed = false; + QVector rows; + int firstItemIndex = 0; + int m_verticalPosition = 0; /* logic */ - /// update the internal list of items and flow them into the rows. - void update(); + /// update the internal list of items and flow them into the rows. + void update(); - /// draw the header at y-position. - void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); + /// draw the header at y-position. + void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); - /// height of the group, in total. includes a small bit of padding. - int totalHeight() const; + /// height of the group, in total. includes a small bit of padding. + int totalHeight() const; - /// height of the group header, in pixels - int headerHeight() const; + /// height of the group header, in pixels + int headerHeight() const; - /// height of the group content, in pixels - int contentHeight() const; + /// height of the group content, in pixels + int contentHeight() const; - /// the number of visual rows this group has - int numRows() const; + /// the number of visual rows this group has + int numRows() const; - /// actually calculate the above value - int calculateNumRows() const; + /// actually calculate the above value + int calculateNumRows() const; - /// the height at which this group starts, in pixels - int verticalPosition() const; + /// the height at which this group starts, in pixels + int verticalPosition() const; - /// relative geometry - top of the row of the given item - int rowTopOf(const QModelIndex &index) const; + /// relative geometry - top of the row of the given item + int rowTopOf(const QModelIndex &index) const; - /// height of the row of the given item - int rowHeightOf(const QModelIndex &index) const; + /// height of the row of the given item + int rowHeightOf(const QModelIndex &index) const; - /// x/y position of the given item inside the group (in items!) - QPair positionOf(const QModelIndex &index) const; + /// x/y position of the given item inside the group (in items!) + QPair positionOf(const QModelIndex &index) const; - enum HitResult - { - NoHit = 0x0, - TextHit = 0x1, - CheckboxHit = 0x2, - HeaderHit = 0x4, - BodyHit = 0x8 - }; - Q_DECLARE_FLAGS(HitResults, HitResult) + enum HitResult + { + NoHit = 0x0, + TextHit = 0x1, + CheckboxHit = 0x2, + HeaderHit = 0x4, + BodyHit = 0x8 + }; + Q_DECLARE_FLAGS(HitResults, HitResult) - /// shoot! BANG! what did we hit? - HitResults hitScan (const QPoint &pos) const; + /// shoot! BANG! what did we hit? + HitResults hitScan (const QPoint &pos) const; - QList items() const; + QList items() const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(VisualGroup::HitResults) diff --git a/application/install_prereqs.cmake.in b/application/install_prereqs.cmake.in index 2906a4ec..e4408d16 100644 --- a/application/install_prereqs.cmake.in +++ b/application/install_prereqs.cmake.in @@ -1,20 +1,20 @@ set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@") -file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@") +file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@") function(gp_resolved_file_type_override resolved_file type_var) - if(resolved_file MATCHES "^/(usr/)?lib/libQt") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libxcb-") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libicu") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libpng") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libproxy") - set(${type_var} other PARENT_SCOPE) - elseif((resolved_file MATCHES "^/(usr/)?lib(.+)?/libstdc\\+\\+") AND (UNIX AND NOT APPLE)) - set(${type_var} other PARENT_SCOPE) - endif() + if(resolved_file MATCHES "^/(usr/)?lib/libQt") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libxcb-") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libicu") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libpng") + set(${type_var} other PARENT_SCOPE) + elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libproxy") + set(${type_var} other PARENT_SCOPE) + elseif((resolved_file MATCHES "^/(usr/)?lib(.+)?/libstdc\\+\\+") AND (UNIX AND NOT APPLE)) + set(${type_var} other PARENT_SCOPE) + endif() endfunction() set(gp_tool "@CMAKE_GP_TOOL@") diff --git a/application/main.cpp b/application/main.cpp index 2f90f2b3..c871bf1b 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -16,46 +16,47 @@ int main(int argc, char *argv[]) { #ifdef BREAK_INFINITE_LOOP - while(true) - { - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - } + while(true) + { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } #endif #ifdef BREAK_EXCEPTION - throw 42; + throw 42; #endif #ifdef BREAK_RETURN - return 42; + return 42; #endif #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif - // initialize Qt - MultiMC app(argc, argv); + // initialize Qt + MultiMC app(argc, argv); - switch (app.status()) - { - case MultiMC::StartingUp: - case MultiMC::Initialized: - { - Q_INIT_RESOURCE(multimc); - Q_INIT_RESOURCE(backgrounds); - Q_INIT_RESOURCE(assets); + switch (app.status()) + { + case MultiMC::StartingUp: + case MultiMC::Initialized: + { + Q_INIT_RESOURCE(multimc); + Q_INIT_RESOURCE(backgrounds); + Q_INIT_RESOURCE(assets); - Q_INIT_RESOURCE(pe_dark); - Q_INIT_RESOURCE(pe_light); - Q_INIT_RESOURCE(pe_blue); - Q_INIT_RESOURCE(pe_colored); - Q_INIT_RESOURCE(OSX); - Q_INIT_RESOURCE(iOS); - Q_INIT_RESOURCE(flat); - return app.exec(); - } - case MultiMC::Failed: - return 1; - case MultiMC::Succeeded: - return 0; - } + Q_INIT_RESOURCE(pe_dark); + Q_INIT_RESOURCE(pe_light); + Q_INIT_RESOURCE(pe_blue); + Q_INIT_RESOURCE(pe_colored); + Q_INIT_RESOURCE(OSX); + Q_INIT_RESOURCE(iOS); + Q_INIT_RESOURCE(flat); + return app.exec(); + } + case MultiMC::Failed: + return 1; + case MultiMC::Succeeded: + return 0; + } } diff --git a/application/package/linux/MultiMC b/application/package/linux/MultiMC index 2d4be9f0..da6373bc 100755 --- a/application/package/linux/MultiMC +++ b/application/package/linux/MultiMC @@ -2,10 +2,10 @@ # Basic start script for running MultiMC with the libs packaged with it. function printerror { - printf "$1" - if which zenity >/dev/null; then zenity --error --text="$1" &>/dev/null; - elif which kdialog >/dev/null; then kdialog --error "$1" &>/dev/null; - fi + printf "$1" + if which zenity >/dev/null; then zenity --error --text="$1" &>/dev/null; + elif which kdialog >/dev/null; then kdialog --error "$1" &>/dev/null; + fi } if [[ $EUID -eq 0 ]]; then @@ -28,66 +28,66 @@ export QT_FONTPATH="${MMC_DIR}/fonts" # Detect missing dependencies... DEPS_LIST=`ldd "${MMC_DIR}"/plugins/*/*.so 2>/dev/null | grep "not found" | sort -u | awk -vORS=", " '{ print $1 }'` if [ "x$DEPS_LIST" = "x" ]; then - # We have all our dependencies. Run MultiMC. - echo "No missing dependencies found." + # We have all our dependencies. Run MultiMC. + echo "No missing dependencies found." - # Just to be sure... - chmod +x "${MMC_DIR}/bin/MultiMC" + # Just to be sure... + chmod +x "${MMC_DIR}/bin/MultiMC" - # Run MultiMC - "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" + # Run MultiMC + "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" - # Run MultiMC in valgrind - # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" + # Run MultiMC in valgrind + # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" - # Run MultiMC with callgrind, delay instrumentation - # valgrind --log-file="valgrind.log" --tool=callgrind --instr-atstart=no "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" - # use callgrind_control -i on/off to profile actions + # Run MultiMC with callgrind, delay instrumentation + # valgrind --log-file="valgrind.log" --tool=callgrind --instr-atstart=no "${MMC_DIR}/bin/MultiMC" -d "${MMC_DIR}" "$@" + # use callgrind_control -i on/off to profile actions - # Exit with MultiMC's exit code. - exit $? + # Exit with MultiMC's exit code. + exit $? else - # apt - if which apt-file &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do apt-file -l search $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo apt-get install $COMMAND_LIBS" - # pacman - elif which pkgfile &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pkgfile $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo pacman -S $COMMAND_LIBS" - # dnf - elif which dnf &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do dnf whatprovides -q $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | grep -v 'Repo' | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo dnf install $COMMAND_LIBS" - # yum - elif which yum &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do yum whatprovides $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo yum install $COMMAND_LIBS" - # zypper - elif which zypper &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do zypper wp $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo zypper install $COMMAND_LIBS" - # emerge - elif which pfl &>/dev/null; then - LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` - COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pfl $LIBRARY; done` - COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` - INSTALL_CMD="sudo emerge $COMMAND_LIBS" - fi + # apt + if which apt-file &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do apt-file -l search $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo apt-get install $COMMAND_LIBS" + # pacman + elif which pkgfile &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pkgfile $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo pacman -S $COMMAND_LIBS" + # dnf + elif which dnf &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do dnf whatprovides -q $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | grep -v 'Repo' | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo dnf install $COMMAND_LIBS" + # yum + elif which yum &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do yum whatprovides $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo yum install $COMMAND_LIBS" + # zypper + elif which zypper &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do zypper wp $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo zypper install $COMMAND_LIBS" + # emerge + elif which pfl &>/dev/null; then + LIBRARIES=`echo "$DEPS_LIST" | grep -oP "[^, ]*" | sort -u` + COMMAND_LIBS=`for LIBRARY in $LIBRARIES; do pfl $LIBRARY; done` + COMMAND_LIBS=`echo "$COMMAND_LIBS" | sort -u | awk -vORS=" " '{ print $1 }'` + INSTALL_CMD="sudo emerge $COMMAND_LIBS" + fi - MESSAGE="Error: MultiMC is missing the following libraries that it needs to work correctly:\n\t${DEPS_LIST}\nPlease install them from your distribution's package manager." - MESSAGE="$MESSAGE\n\nHint (please apply common sense): $INSTALL_CMD\n" + MESSAGE="Error: MultiMC is missing the following libraries that it needs to work correctly:\n\t${DEPS_LIST}\nPlease install them from your distribution's package manager." + MESSAGE="$MESSAGE\n\nHint (please apply common sense): $INSTALL_CMD\n" - printerror "$MESSAGE" - exit 1 + printerror "$MESSAGE" + exit 1 fi diff --git a/application/package/linux/multimc.desktop b/application/package/linux/multimc.desktop index 514b330f..770f24f1 100755 --- a/application/package/linux/multimc.desktop +++ b/application/package/linux/multimc.desktop @@ -8,3 +8,4 @@ Terminal=false Exec=multimc Icon=multimc Categories=Game +Keywords=game;minecraft; diff --git a/application/package/ubuntu/multimc/DEBIAN/control b/application/package/ubuntu/multimc/DEBIAN/control new file mode 100644 index 00000000..34c223a5 --- /dev/null +++ b/application/package/ubuntu/multimc/DEBIAN/control @@ -0,0 +1,12 @@ +Package: multimc +Version: 1.3-1 +Architecture: all +Maintainer: Petr Mrázek +Section: games +Priority: optional +Installed-Size: 75 +Depends: zenity, desktop-file-utils, qt5-default +Recommends: openjdk-8-jre +Homepage: http://multimc.org +Description: A local install wrapper for MultiMC + diff --git a/application/package/ubuntu/multimc/DEBIAN/postrm b/application/package/ubuntu/multimc/DEBIAN/postrm new file mode 100755 index 00000000..f9bbc8a7 --- /dev/null +++ b/application/package/ubuntu/multimc/DEBIAN/postrm @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +update-desktop-database diff --git a/application/package/ubuntu/multimc/opt/multimc/icon.svg b/application/package/ubuntu/multimc/opt/multimc/icon.svg new file mode 100644 index 00000000..8bb0e289 --- /dev/null +++ b/application/package/ubuntu/multimc/opt/multimc/icon.svg @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/package/ubuntu/multimc/opt/multimc/run.sh b/application/package/ubuntu/multimc/opt/multimc/run.sh new file mode 100755 index 00000000..c493a513 --- /dev/null +++ b/application/package/ubuntu/multimc/opt/multimc/run.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +INSTDIR="${XDG_DATA_HOME-$HOME/.local/share}/multimc" + +if [ `getconf LONG_BIT` = "64" ] +then + PACKAGE="mmc-stable-lin64.tar.gz" +else + PACKAGE="mmc-stable-lin32.tar.gz" +fi + +deploy() { + mkdir -p $INSTDIR + cd ${INSTDIR} + + wget --progress=dot:force "https://files.multimc.org/downloads/${PACKAGE}" 2>&1 | sed -u 's/.* \([0-9]\+%\)\ \+\([0-9.]\+.\) \(.*\)/\1\n# Downloading at \2\/s, ETA \3/' | zenity --progress --auto-close --auto-kill --title="Downloading MultiMC..." + + tar -xzf ${PACKAGE} --transform='s,MultiMC/,,' + rm ${PACKAGE} + chmod +x MultiMC +} + +runmmc() { + cd ${INSTDIR} + ./MultiMC "$@" +} + +if [[ ! -f ${INSTDIR}/MultiMC ]]; then + deploy + runmmc "$@" +else + runmmc "$@" +fi diff --git a/application/package/ubuntu/multimc/usr/share/applications/multimc.desktop b/application/package/ubuntu/multimc/usr/share/applications/multimc.desktop new file mode 100755 index 00000000..e0456f89 --- /dev/null +++ b/application/package/ubuntu/multimc/usr/share/applications/multimc.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Categories=Game; +Exec=/opt/multimc/run.sh +Icon=/opt/multimc/icon.svg +Keywords=game;Minecraft; +MimeType= +Name=MultiMC 5 +Path= +StartupNotify=true +Terminal=false +TerminalOptions= +Type=Application +X-DBUS-ServiceName= +X-DBUS-StartupType= +X-KDE-SubstituteUID=false +X-KDE-Username= diff --git a/application/package/ubuntu/readme.md b/application/package/ubuntu/readme.md new file mode 100644 index 00000000..5b0d6b27 --- /dev/null +++ b/application/package/ubuntu/readme.md @@ -0,0 +1,12 @@ +# What is this? +A simple ubuntu package for MultiMC that wraps the contains a script that downloads and installs real MultiMC on ubuntu based systems. + +It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting. + +# How to build this? +You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run: +``` +fakeroot dpkg-deb --build multimc_1.3-1 +``` + +Replace the version with whatever is appropriate. diff --git a/application/pagedialog/PageDialog.cpp b/application/pagedialog/PageDialog.cpp index 1b5284d2..a1a78d78 100644 --- a/application/pagedialog/PageDialog.cpp +++ b/application/pagedialog/PageDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,35 +26,36 @@ #include "widgets/PageContainer.h" PageDialog::PageDialog(BasePageProvider *pageProvider, QString defaultId, QWidget *parent) - : QDialog(parent) + : QDialog(parent) { - setWindowTitle(pageProvider->dialogTitle()); - m_container = new PageContainer(pageProvider, defaultId, this); + setWindowTitle(pageProvider->dialogTitle()); + m_container = new PageContainer(pageProvider, defaultId, this); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(m_container); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); - setLayout(mainLayout); + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(m_container); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0, 0, 0, 0); + setLayout(mainLayout); - QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); - buttons->button(QDialogButtonBox::Close)->setDefault(true); - m_container->addButtons(buttons); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); + buttons->button(QDialogButtonBox::Close)->setDefault(true); + buttons->setContentsMargins(6, 0, 6, 0); + m_container->addButtons(buttons); - connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close())); - connect(buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), m_container, SLOT(help())); + connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close())); + connect(buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), m_container, SLOT(help())); - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("PagedGeometry").toByteArray())); + restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("PagedGeometry").toByteArray())); } void PageDialog::closeEvent(QCloseEvent *event) { - qDebug() << "Paged dialog close requested"; - if (m_container->prepareToClose()) - { - qDebug() << "Paged dialog close approved"; - MMC->settings()->set("PagedGeometry", saveGeometry().toBase64()); - qDebug() << "Paged dialog geometry saved"; - QDialog::closeEvent(event); - } + qDebug() << "Paged dialog close requested"; + if (m_container->prepareToClose()) + { + qDebug() << "Paged dialog close approved"; + MMC->settings()->set("PagedGeometry", saveGeometry().toBase64()); + qDebug() << "Paged dialog geometry saved"; + QDialog::closeEvent(event); + } } diff --git a/application/pagedialog/PageDialog.h b/application/pagedialog/PageDialog.h index a287f9c9..b92d90b5 100644 --- a/application/pagedialog/PageDialog.h +++ b/application/pagedialog/PageDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,15 @@ class PageContainer; class PageDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit PageDialog(BasePageProvider *pageProvider, QString defaultId = QString(), QWidget *parent = 0); - virtual ~PageDialog() {} + explicit PageDialog(BasePageProvider *pageProvider, QString defaultId = QString(), QWidget *parent = 0); + virtual ~PageDialog() {} private slots: - virtual void closeEvent(QCloseEvent *event); + virtual void closeEvent(QCloseEvent *event); private: - PageContainer * m_container; + PageContainer * m_container; }; diff --git a/application/pages/BasePage.h b/application/pages/BasePage.h index d4547770..2ad56d71 100644 --- a/application/pages/BasePage.h +++ b/application/pages/BasePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,35 +24,35 @@ class BasePage { public: - virtual ~BasePage() {} - virtual QString id() const = 0; - virtual QString displayName() const = 0; - virtual QIcon icon() const = 0; - virtual bool apply() { return true; } - virtual bool shouldDisplay() const { return true; } - virtual QString helpPage() const { return QString(); } - void opened() - { - isOpened = true; - openedImpl(); - } - void closed() - { - isOpened = false; - closedImpl(); - } - virtual void openedImpl() {} - virtual void closedImpl() {} - virtual void setParentContainer(BasePageContainer * container) - { - m_container = container; - }; + virtual ~BasePage() {} + virtual QString id() const = 0; + virtual QString displayName() const = 0; + virtual QIcon icon() const = 0; + virtual bool apply() { return true; } + virtual bool shouldDisplay() const { return true; } + virtual QString helpPage() const { return QString(); } + void opened() + { + isOpened = true; + openedImpl(); + } + void closed() + { + isOpened = false; + closedImpl(); + } + virtual void openedImpl() {} + virtual void closedImpl() {} + virtual void setParentContainer(BasePageContainer * container) + { + m_container = container; + }; public: - int stackIndex = -1; - int listIndex = -1; + int stackIndex = -1; + int listIndex = -1; protected: - BasePageContainer * m_container = nullptr; - bool isOpened = false; + BasePageContainer * m_container = nullptr; + bool isOpened = false; }; typedef std::shared_ptr BasePagePtr; diff --git a/application/pages/BasePageContainer.h b/application/pages/BasePageContainer.h index ff7315c2..f8c7adeb 100644 --- a/application/pages/BasePageContainer.h +++ b/application/pages/BasePageContainer.h @@ -3,8 +3,8 @@ class BasePageContainer { public: - virtual ~BasePageContainer(){}; - virtual bool selectPage(QString pageId) = 0; - virtual void refreshContainer() = 0; - virtual bool requestClose() = 0; + virtual ~BasePageContainer(){}; + virtual bool selectPage(QString pageId) = 0; + virtual void refreshContainer() = 0; + virtual bool requestClose() = 0; }; diff --git a/application/pages/BasePageProvider.h b/application/pages/BasePageProvider.h index 1cc7458a..ad29f163 100644 --- a/application/pages/BasePageProvider.h +++ b/application/pages/BasePageProvider.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,46 +22,47 @@ class BasePageProvider { public: - virtual QList getPages() = 0; - virtual QString dialogTitle() = 0; + virtual QList getPages() = 0; + virtual QString dialogTitle() = 0; }; class GenericPageProvider : public BasePageProvider { - typedef std::function PageCreator; + typedef std::function PageCreator; public: - explicit GenericPageProvider(const QString &dialogTitle) - : m_dialogTitle(dialogTitle) - { - } + explicit GenericPageProvider(const QString &dialogTitle) + : m_dialogTitle(dialogTitle) + { + } + virtual ~GenericPageProvider() {} - QList getPages() override - { - QList pages; - for (PageCreator creator : m_creators) - { - pages.append(creator()); - } - return pages; - } - QString dialogTitle() override { return m_dialogTitle; } + QList getPages() override + { + QList pages; + for (PageCreator creator : m_creators) + { + pages.append(creator()); + } + return pages; + } + QString dialogTitle() override { return m_dialogTitle; } - void setDialogTitle(const QString &title) - { - m_dialogTitle = title; - } - void addPageCreator(PageCreator page) - { - m_creators.append(page); - } + void setDialogTitle(const QString &title) + { + m_dialogTitle = title; + } + void addPageCreator(PageCreator page) + { + m_creators.append(page); + } - template - void addPage() - { - addPageCreator([](){return new PageClass();}); - } + template + void addPage() + { + addPageCreator([](){return new PageClass();}); + } private: - QList m_creators; - QString m_dialogTitle; + QList m_creators; + QString m_dialogTitle; }; diff --git a/application/pages/global/AccountListPage.cpp b/application/pages/global/AccountListPage.cpp index 63943174..c14134f3 100644 --- a/application/pages/global/AccountListPage.cpp +++ b/application/pages/global/AccountListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ #include "ui_AccountListPage.h" #include +#include #include @@ -34,120 +35,159 @@ #include "MultiMC.h" AccountListPage::AccountListPage(QWidget *parent) - : QWidget(parent), ui(new Ui::AccountListPage) + : QMainWindow(parent), ui(new Ui::AccountListPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->listView->setEmptyString(tr( + "Welcome!\n" + "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account." + )); + ui->listView->setEmptyMode(VersionListView::String); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); - m_accounts = MMC->accounts(); + m_accounts = MMC->accounts(); - ui->listView->setModel(m_accounts.get()); - ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui->listView->setModel(m_accounts.get()); + ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui->listView->setSelectionMode(QAbstractItemView::SingleSelection); - // Expand the account column - ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); + // Expand the account column + ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); - QItemSelectionModel *selectionModel = ui->listView->selectionModel(); + QItemSelectionModel *selectionModel = ui->listView->selectionModel(); - connect(selectionModel, &QItemSelectionModel::selectionChanged, - [this](const QItemSelection &sel, const QItemSelection &dsel) - { updateButtonStates(); }); + connect(selectionModel, &QItemSelectionModel::selectionChanged, [this](const QItemSelection &sel, const QItemSelection &dsel) { + updateButtonStates(); + }); + connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu); - connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged())); - connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged())); + connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged())); + connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged())); - updateButtonStates(); + updateButtonStates(); } AccountListPage::~AccountListPage() { - delete ui; + delete ui; } +void AccountListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->listView->mapToGlobal(pos)); + delete menu; +} + +void AccountListPage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + QMainWindow::changeEvent(event); +} + +QMenu * AccountListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->toolBar->toggleViewAction() ); + return filteredMenu; +} + + void AccountListPage::listChanged() { - updateButtonStates(); + updateButtonStates(); } -void AccountListPage::on_addAccountBtn_clicked() +void AccountListPage::on_actionAdd_triggered() { - addAccount(tr("Please enter your Mojang or Minecraft account username and password to add " - "your account.")); + addAccount(tr("Please enter your Mojang or Minecraft account username and password to add " + "your account.")); } -void AccountListPage::on_rmAccountBtn_clicked() +void AccountListPage::on_actionRemove_triggered() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - m_accounts->removeAccount(selected); - } + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + m_accounts->removeAccount(selected); + } } -void AccountListPage::on_setDefaultBtn_clicked() +void AccountListPage::on_actionSetDefault_triggered() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - MojangAccountPtr account = - selected.data(MojangAccountList::PointerRole).value(); - m_accounts->setActiveAccount(account->username()); - } + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + MojangAccountPtr account = + selected.data(MojangAccountList::PointerRole).value(); + m_accounts->setActiveAccount(account->username()); + } } -void AccountListPage::on_noDefaultBtn_clicked() +void AccountListPage::on_actionNoDefault_triggered() { - m_accounts->setActiveAccount(""); + m_accounts->setActiveAccount(""); } void AccountListPage::updateButtonStates() { - // If there is no selection, disable buttons that require something selected. - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - - ui->rmAccountBtn->setEnabled(selection.size() > 0); - ui->setDefaultBtn->setEnabled(selection.size() > 0); - ui->uploadSkinBtn->setEnabled(selection.size() > 0); + // If there is no selection, disable buttons that require something selected. + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + + ui->actionRemove->setEnabled(selection.size() > 0); + ui->actionSetDefault->setEnabled(selection.size() > 0); + ui->actionUploadSkin->setEnabled(selection.size() > 0); + + if(m_accounts->activeAccount().get() == nullptr) { + ui->actionNoDefault->setEnabled(false); + ui->actionNoDefault->setChecked(true); + } + else { + ui->actionNoDefault->setEnabled(true); + ui->actionNoDefault->setChecked(false); + } - ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr); } void AccountListPage::addAccount(const QString &errMsg) { - // TODO: The login dialog isn't quite done yet - MojangAccountPtr account = LoginDialog::newAccount(this, errMsg); - - if (account != nullptr) - { - m_accounts->addAccount(account); - if (m_accounts->count() == 1) - m_accounts->setActiveAccount(account->username()); - - // Grab associated player skins - auto job = new NetJob("Player skins: " + account->username()); - - for (AccountProfile profile : account->profiles()) - { - auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); - auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta); - job->addNetAction(action); - meta->setStale(true); - } - - job->start(); - } + // TODO: The login dialog isn't quite done yet + MojangAccountPtr account = LoginDialog::newAccount(this, errMsg); + + if (account != nullptr) + { + m_accounts->addAccount(account); + if (m_accounts->count() == 1) + m_accounts->setActiveAccount(account->username()); + + // Grab associated player skins + auto job = new NetJob("Player skins: " + account->username()); + + for (AccountProfile profile : account->profiles()) + { + auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); + auto action = Net::Download::makeCached(QUrl(URLConstants::SKINS_BASE + profile.id + ".png"), meta); + job->addNetAction(action); + meta->setStale(true); + } + + job->start(); + } } -void AccountListPage::on_uploadSkinBtn_clicked() +void AccountListPage::on_actionUploadSkin_triggered() { - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) - { - QModelIndex selected = selection.first(); - MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value(); - SkinUploadDialog dialog(account, this); - dialog.exec(); - } + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) + { + QModelIndex selected = selection.first(); + MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value(); + SkinUploadDialog dialog(account, this); + dialog.exec(); + } } diff --git a/application/pages/global/AccountListPage.h b/application/pages/global/AccountListPage.h index fa5561fe..4c8bc00b 100644 --- a/application/pages/global/AccountListPage.h +++ b/application/pages/global/AccountListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include +#include #include #include "pages/BasePage.h" @@ -30,59 +30,64 @@ class AccountListPage; class AuthenticateTask; -class AccountListPage : public QWidget, public BasePage +class AccountListPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit AccountListPage(QWidget *parent = 0); - ~AccountListPage(); - - QString displayName() const override - { - return tr("Accounts"); - } - QIcon icon() const override - { - auto icon = MMC->getThemedIcon("accounts"); - if(icon.isNull()) - { - icon = MMC->getThemedIcon("noaccount"); - } - return icon; - } - QString id() const override - { - return "accounts"; - } - QString helpPage() const override - { - return "Getting-Started#adding-an-account"; - } + explicit AccountListPage(QWidget *parent = 0); + ~AccountListPage(); + + QString displayName() const override + { + return tr("Accounts"); + } + QIcon icon() const override + { + auto icon = MMC->getThemedIcon("accounts"); + if(icon.isNull()) + { + icon = MMC->getThemedIcon("noaccount"); + } + return icon; + } + QString id() const override + { + return "accounts"; + } + QString helpPage() const override + { + return "Getting-Started#adding-an-account"; + } + +private: + void changeEvent(QEvent * event) override; + QMenu * createPopupMenu() override; public slots: - void on_addAccountBtn_clicked(); + void on_actionAdd_triggered(); - void on_rmAccountBtn_clicked(); + void on_actionRemove_triggered(); - void on_setDefaultBtn_clicked(); + void on_actionSetDefault_triggered(); - void on_noDefaultBtn_clicked(); + void on_actionNoDefault_triggered(); - void on_uploadSkinBtn_clicked(); + void on_actionUploadSkin_triggered(); - void listChanged(); + void listChanged(); - //! Updates the states of the dialog's buttons. - void updateButtonStates(); + //! Updates the states of the dialog's buttons. + void updateButtonStates(); protected: - std::shared_ptr m_accounts; + std::shared_ptr m_accounts; protected slots: - void addAccount(const QString& errMsg=""); + void ShowContextMenu(const QPoint &pos); + void addAccount(const QString& errMsg=""); private: - Ui::AccountListPage *ui; + Ui::AccountListPage *ui; }; diff --git a/application/pages/global/AccountListPage.ui b/application/pages/global/AccountListPage.ui index f4e87680..433c1f7f 100644 --- a/application/pages/global/AccountListPage.ui +++ b/application/pages/global/AccountListPage.ui @@ -1,122 +1,94 @@ AccountListPage - + 0 0 - 694 - 609 + 800 + 600 - - - 0 + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + toolBar + + + RightToolBarArea + + + false + + + + + + + + + + Add + + + + + Remove + + + + + Set Default - - 0 + + + + true - - 0 + + No Default - - 0 + + + + Upload Skin - - - - 0 - - - - Tab 1 - - - - - - <html><head/><body><p>Welcome! If you're new here, you can click the &quot;Add&quot; button to add your Mojang or Minecraft account.</p></body></html> - - - true - - - - - - - - - - - - - - &Add - - - - - - - &Remove - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - <html><head/><body><p>Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).</p></body></html> - - - &Set Default - - - - - - - Set no default account. This will cause MultiMC to prompt you to select an account every time you launch an instance that doesn't have its own default set. - - - &No Default - - - - - - - Opens a dialog to select and upload a skin image to the selected account. - - - &Upload Skin - - - - - - - - - - - - + + + + VersionListView + QTreeView +
widgets/VersionListView.h
+
+ + WideBar + QToolBar +
widgets/WideBar.h
+
+
diff --git a/application/pages/global/CustomCommandsPage.cpp b/application/pages/global/CustomCommandsPage.cpp index 1352b6be..3b182319 100644 --- a/application/pages/global/CustomCommandsPage.cpp +++ b/application/pages/global/CustomCommandsPage.cpp @@ -6,17 +6,18 @@ CustomCommandsPage::CustomCommandsPage(QWidget* parent): QWidget(parent) { - auto verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - verticalLayout->setContentsMargins(0, 0, 0, 0); + auto verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + verticalLayout->setContentsMargins(0, 0, 0, 0); - auto tabWidget = new QTabWidget(this); - tabWidget->setObjectName(QStringLiteral("tabWidget")); - commands = new CustomCommands(this); - tabWidget->addTab(commands, "Foo"); - tabWidget->tabBar()->hide(); - verticalLayout->addWidget(tabWidget); - loadSettings(); + auto tabWidget = new QTabWidget(this); + tabWidget->setObjectName(QStringLiteral("tabWidget")); + commands = new CustomCommands(this); + commands->setContentsMargins(6, 6, 6, 6); + tabWidget->addTab(commands, "Foo"); + tabWidget->tabBar()->hide(); + verticalLayout->addWidget(tabWidget); + loadSettings(); } CustomCommandsPage::~CustomCommandsPage() @@ -25,26 +26,26 @@ CustomCommandsPage::~CustomCommandsPage() bool CustomCommandsPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void CustomCommandsPage::applySettings() { - auto s = MMC->settings(); - s->set("PreLaunchCommand", commands->prelaunchCommand()); - s->set("WrapperCommand", commands->wrapperCommand()); - s->set("PostExitCommand", commands->postexitCommand()); + auto s = MMC->settings(); + s->set("PreLaunchCommand", commands->prelaunchCommand()); + s->set("WrapperCommand", commands->wrapperCommand()); + s->set("PostExitCommand", commands->postexitCommand()); } void CustomCommandsPage::loadSettings() { - auto s = MMC->settings(); - commands->initialize( - false, - true, - s->get("PreLaunchCommand").toString(), - s->get("WrapperCommand").toString(), - s->get("PostExitCommand").toString() - ); + auto s = MMC->settings(); + commands->initialize( + false, + true, + s->get("PreLaunchCommand").toString(), + s->get("WrapperCommand").toString(), + s->get("PostExitCommand").toString() + ); } diff --git a/application/pages/global/CustomCommandsPage.h b/application/pages/global/CustomCommandsPage.h index 52256ed3..a9047e63 100644 --- a/application/pages/global/CustomCommandsPage.h +++ b/application/pages/global/CustomCommandsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2018 MultiMC Contributors +/* Copyright 2018-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,32 +24,32 @@ class CustomCommandsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit CustomCommandsPage(QWidget *parent = 0); - ~CustomCommandsPage(); + explicit CustomCommandsPage(QWidget *parent = 0); + ~CustomCommandsPage(); - QString displayName() const override - { - return tr("Custom Commands"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("custom-commands"); - } - QString id() const override - { - return "custom-commands"; - } - QString helpPage() const override - { - return "Custom-commands"; - } - bool apply() override; + QString displayName() const override + { + return tr("Custom Commands"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("custom-commands"); + } + QString id() const override + { + return "custom-commands"; + } + QString helpPage() const override + { + return "Custom-commands"; + } + bool apply() override; private: - void applySettings(); - void loadSettings(); - CustomCommands * commands; + void applySettings(); + void loadSettings(); + CustomCommands * commands; }; diff --git a/application/pages/global/ExternalToolsPage.cpp b/application/pages/global/ExternalToolsPage.cpp index 20887b54..7de6f858 100644 --- a/application/pages/global/ExternalToolsPage.cpp +++ b/application/pages/global/ExternalToolsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include #include #include +#include #include "settings/SettingsObject.h" #include "tools/BaseProfiler.h" @@ -28,206 +29,206 @@ #include ExternalToolsPage::ExternalToolsPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::ExternalToolsPage) + QWidget(parent), + ui(new Ui::ExternalToolsPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); - #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - ui->jsonEditorTextBox->setClearButtonEnabled(true); - #endif + #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) + ui->jsonEditorTextBox->setClearButtonEnabled(true); + #endif - ui->mceditLink->setOpenExternalLinks(true); - ui->jvisualvmLink->setOpenExternalLinks(true); - ui->jprofilerLink->setOpenExternalLinks(true); - loadSettings(); + ui->mceditLink->setOpenExternalLinks(true); + ui->jvisualvmLink->setOpenExternalLinks(true); + ui->jprofilerLink->setOpenExternalLinks(true); + loadSettings(); } ExternalToolsPage::~ExternalToolsPage() { - delete ui; + delete ui; } void ExternalToolsPage::loadSettings() { - auto s = MMC->settings(); - ui->jprofilerPathEdit->setText(s->get("JProfilerPath").toString()); - ui->jvisualvmPathEdit->setText(s->get("JVisualVMPath").toString()); - ui->mceditPathEdit->setText(s->get("MCEditPath").toString()); + auto s = MMC->settings(); + ui->jprofilerPathEdit->setText(s->get("JProfilerPath").toString()); + ui->jvisualvmPathEdit->setText(s->get("JVisualVMPath").toString()); + ui->mceditPathEdit->setText(s->get("MCEditPath").toString()); - // Editors - ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString()); + // Editors + ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString()); } void ExternalToolsPage::applySettings() { - auto s = MMC->settings(); - - s->set("JProfilerPath", ui->jprofilerPathEdit->text()); - s->set("JVisualVMPath", ui->jvisualvmPathEdit->text()); - s->set("MCEditPath", ui->mceditPathEdit->text()); - - // Editors - QString jsonEditor = ui->jsonEditorTextBox->text(); - if (!jsonEditor.isEmpty() && - (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable())) - { - QString found = QStandardPaths::findExecutable(jsonEditor); - if (!found.isEmpty()) - { - jsonEditor = found; - } - } - s->set("JsonEditor", jsonEditor); + auto s = MMC->settings(); + + s->set("JProfilerPath", ui->jprofilerPathEdit->text()); + s->set("JVisualVMPath", ui->jvisualvmPathEdit->text()); + s->set("MCEditPath", ui->mceditPathEdit->text()); + + // Editors + QString jsonEditor = ui->jsonEditorTextBox->text(); + if (!jsonEditor.isEmpty() && + (!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable())) + { + QString found = QStandardPaths::findExecutable(jsonEditor); + if (!found.isEmpty()) + { + jsonEditor = found; + } + } + s->set("JsonEditor", jsonEditor); } void ExternalToolsPage::on_jprofilerPathBtn_clicked() { - QString raw_dir = ui->jprofilerPathEdit->text(); - QString error; - do - { - raw_dir = QFileDialog::getExistingDirectory(this, tr("JProfiler Folder"), raw_dir); - if (raw_dir.isEmpty()) - { - break; - } - QString cooked_dir = FS::NormalizePath(raw_dir); - if (!MMC->profilers()["jprofiler"]->check(cooked_dir, &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); - continue; - } - else - { - ui->jprofilerPathEdit->setText(cooked_dir); - break; - } - } while (1); + QString raw_dir = ui->jprofilerPathEdit->text(); + QString error; + do + { + raw_dir = QFileDialog::getExistingDirectory(this, tr("JProfiler Folder"), raw_dir); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = FS::NormalizePath(raw_dir); + if (!MMC->profilers()["jprofiler"]->check(cooked_dir, &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); + continue; + } + else + { + ui->jprofilerPathEdit->setText(cooked_dir); + break; + } + } while (1); } void ExternalToolsPage::on_jprofilerCheckBtn_clicked() { - QString error; - if (!MMC->profilers()["jprofiler"]->check(ui->jprofilerPathEdit->text(), &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); - } - else - { - QMessageBox::information(this, tr("OK"), tr("JProfiler setup seems to be OK")); - } + QString error; + if (!MMC->profilers()["jprofiler"]->check(ui->jprofilerPathEdit->text(), &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("JProfiler setup seems to be OK")); + } } void ExternalToolsPage::on_jvisualvmPathBtn_clicked() { - QString raw_dir = ui->jvisualvmPathEdit->text(); - QString error; - do - { - raw_dir = QFileDialog::getOpenFileName(this, tr("JVisualVM Executable"), raw_dir); - if (raw_dir.isEmpty()) - { - break; - } - QString cooked_dir = FS::NormalizePath(raw_dir); - if (!MMC->profilers()["jvisualvm"]->check(cooked_dir, &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); - continue; - } - else - { - ui->jvisualvmPathEdit->setText(cooked_dir); - break; - } - } while (1); + QString raw_dir = ui->jvisualvmPathEdit->text(); + QString error; + do + { + raw_dir = QFileDialog::getOpenFileName(this, tr("JVisualVM Executable"), raw_dir); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = FS::NormalizePath(raw_dir); + if (!MMC->profilers()["jvisualvm"]->check(cooked_dir, &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); + continue; + } + else + { + ui->jvisualvmPathEdit->setText(cooked_dir); + break; + } + } while (1); } void ExternalToolsPage::on_jvisualvmCheckBtn_clicked() { - QString error; - if (!MMC->profilers()["jvisualvm"]->check(ui->jvisualvmPathEdit->text(), &error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); - } - else - { - QMessageBox::information(this, tr("OK"), tr("JVisualVM setup seems to be OK")); - } + QString error; + if (!MMC->profilers()["jvisualvm"]->check(ui->jvisualvmPathEdit->text(), &error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("JVisualVM setup seems to be OK")); + } } void ExternalToolsPage::on_mceditPathBtn_clicked() { - QString raw_dir = ui->mceditPathEdit->text(); - QString error; - do - { + QString raw_dir = ui->mceditPathEdit->text(); + QString error; + do + { #ifdef Q_OS_OSX - raw_dir = QFileDialog::getOpenFileName(this, tr("MCEdit Application"), raw_dir); + raw_dir = QFileDialog::getOpenFileName(this, tr("MCEdit Application"), raw_dir); #else - raw_dir = QFileDialog::getExistingDirectory(this, tr("MCEdit Folder"), raw_dir); + raw_dir = QFileDialog::getExistingDirectory(this, tr("MCEdit Folder"), raw_dir); #endif - if (raw_dir.isEmpty()) - { - break; - } - QString cooked_dir = FS::NormalizePath(raw_dir); - if (!MMC->mcedit()->check(cooked_dir, error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); - continue; - } - else - { - ui->mceditPathEdit->setText(cooked_dir); - break; - } - } while (1); + if (raw_dir.isEmpty()) + { + break; + } + QString cooked_dir = FS::NormalizePath(raw_dir); + if (!MMC->mcedit()->check(cooked_dir, error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); + continue; + } + else + { + ui->mceditPathEdit->setText(cooked_dir); + break; + } + } while (1); } void ExternalToolsPage::on_mceditCheckBtn_clicked() { - QString error; - if (!MMC->mcedit()->check(ui->mceditPathEdit->text(), error)) - { - QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); - } - else - { - QMessageBox::information(this, tr("OK"), tr("MCEdit setup seems to be OK")); - } + QString error; + if (!MMC->mcedit()->check(ui->mceditPathEdit->text(), error)) + { + QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error)); + } + else + { + QMessageBox::information(this, tr("OK"), tr("MCEdit setup seems to be OK")); + } } void ExternalToolsPage::on_jsonEditorBrowseBtn_clicked() { - QString raw_file = QFileDialog::getOpenFileName( - this, tr("JSON Editor"), - ui->jsonEditorTextBox->text().isEmpty() + QString raw_file = QFileDialog::getOpenFileName( + this, tr("JSON Editor"), + ui->jsonEditorTextBox->text().isEmpty() #if defined(Q_OS_LINUX) - ? QString("/usr/bin") + ? QString("/usr/bin") #else - ? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first() + ? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first() #endif - : ui->jsonEditorTextBox->text()); - - if (raw_file.isEmpty()) - { - return; - } - QString cooked_file = FS::NormalizePath(raw_file); - - // it has to exist and be an executable - if (QFileInfo(cooked_file).exists() && QFileInfo(cooked_file).isExecutable()) - { - ui->jsonEditorTextBox->setText(cooked_file); - } - else - { - QMessageBox::warning(this, tr("Invalid"), - tr("The file chosen does not seem to be an executable")); - } + : ui->jsonEditorTextBox->text()); + + if (raw_file.isEmpty()) + { + return; + } + QString cooked_file = FS::NormalizePath(raw_file); + + // it has to exist and be an executable + if (QFileInfo(cooked_file).exists() && QFileInfo(cooked_file).isExecutable()) + { + ui->jsonEditorTextBox->setText(cooked_file); + } + else + { + QMessageBox::warning(this, tr("Invalid"), + tr("The file chosen does not seem to be an executable")); + } } bool ExternalToolsPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } diff --git a/application/pages/global/ExternalToolsPage.h b/application/pages/global/ExternalToolsPage.h index de46d8a6..06e64273 100644 --- a/application/pages/global/ExternalToolsPage.h +++ b/application/pages/global/ExternalToolsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,49 +26,49 @@ class ExternalToolsPage; class ExternalToolsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ExternalToolsPage(QWidget *parent = 0); - ~ExternalToolsPage(); + explicit ExternalToolsPage(QWidget *parent = 0); + ~ExternalToolsPage(); - QString displayName() const override - { - return tr("External Tools"); - } - QIcon icon() const override - { - auto icon = MMC->getThemedIcon("externaltools"); - if(icon.isNull()) - { - icon = MMC->getThemedIcon("loadermods"); - } - return icon; - } - QString id() const override - { - return "external-tools"; - } - QString helpPage() const override - { - return "Tools"; - } - virtual bool apply() override; + QString displayName() const override + { + return tr("External Tools"); + } + QIcon icon() const override + { + auto icon = MMC->getThemedIcon("externaltools"); + if(icon.isNull()) + { + icon = MMC->getThemedIcon("loadermods"); + } + return icon; + } + QString id() const override + { + return "external-tools"; + } + QString helpPage() const override + { + return "Tools"; + } + virtual bool apply() override; private: - void loadSettings(); - void applySettings(); + void loadSettings(); + void applySettings(); private: - Ui::ExternalToolsPage *ui; + Ui::ExternalToolsPage *ui; private slots: - void on_jprofilerPathBtn_clicked(); - void on_jprofilerCheckBtn_clicked(); - void on_jvisualvmPathBtn_clicked(); - void on_jvisualvmCheckBtn_clicked(); - void on_mceditPathBtn_clicked(); - void on_mceditCheckBtn_clicked(); - void on_jsonEditorBrowseBtn_clicked(); + void on_jprofilerPathBtn_clicked(); + void on_jprofilerCheckBtn_clicked(); + void on_jvisualvmPathBtn_clicked(); + void on_jvisualvmCheckBtn_clicked(); + void on_mceditPathBtn_clicked(); + void on_mceditCheckBtn_clicked(); + void on_jsonEditorBrowseBtn_clicked(); }; diff --git a/application/pages/global/ExternalToolsPage.ui b/application/pages/global/ExternalToolsPage.ui index 5f19898b..e79e9388 100644 --- a/application/pages/global/ExternalToolsPage.ui +++ b/application/pages/global/ExternalToolsPage.ui @@ -63,7 +63,7 @@ - <html><head/><body><p><a href="http://www.ej-technologies.com/products/jprofiler/overview.html">http://www.ej-technologies.com/products/jprofiler/overview.html</a></p></body></html> + <html><head/><body><p><a href="https://www.ej-technologies.com/products/jprofiler/overview.html">https://www.ej-technologies.com/products/jprofiler/overview.html</a></p></body></html> @@ -137,7 +137,7 @@ - <html><head/><body><p><a href="http://www.mcedit.net/">http://www.mcedit.net/</a></p></body></html> + <html><head/><body><p><a href="https://www.mcedit.net/">https://www.mcedit.net/</a></p></body></html> diff --git a/application/pages/global/JavaPage.cpp b/application/pages/global/JavaPage.cpp index ce733778..211dc929 100644 --- a/application/pages/global/JavaPage.cpp +++ b/application/pages/global/JavaPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include #include #include +#include #include "dialogs/VersionSelectDialog.h" @@ -34,120 +35,120 @@ JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); - auto sysMB = Sys::getSystemRam() / Sys::megabyte; - ui->maxMemSpinBox->setMaximum(sysMB); - loadSettings(); + auto sysMB = Sys::getSystemRam() / Sys::megabyte; + ui->maxMemSpinBox->setMaximum(sysMB); + loadSettings(); } JavaPage::~JavaPage() { - delete ui; + delete ui; } bool JavaPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void JavaPage::applySettings() { - auto s = MMC->settings(); - - // Memory - int min = ui->minMemSpinBox->value(); - int max = ui->maxMemSpinBox->value(); - if(min < max) - { - s->set("MinMemAlloc", min); - s->set("MaxMemAlloc", max); - } - else - { - s->set("MinMemAlloc", max); - s->set("MaxMemAlloc", min); - } - s->set("PermGen", ui->permGenSpinBox->value()); - - // Java Settings - s->set("JavaPath", ui->javaPathTextBox->text()); - s->set("JvmArgs", ui->jvmArgsTextBox->text()); - JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); + auto s = MMC->settings(); + + // Memory + int min = ui->minMemSpinBox->value(); + int max = ui->maxMemSpinBox->value(); + if(min < max) + { + s->set("MinMemAlloc", min); + s->set("MaxMemAlloc", max); + } + else + { + s->set("MinMemAlloc", max); + s->set("MaxMemAlloc", min); + } + s->set("PermGen", ui->permGenSpinBox->value()); + + // Java Settings + s->set("JavaPath", ui->javaPathTextBox->text()); + s->set("JvmArgs", ui->jvmArgsTextBox->text()); + JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); } void JavaPage::loadSettings() { - auto s = MMC->settings(); - // Memory - int min = s->get("MinMemAlloc").toInt(); - int max = s->get("MaxMemAlloc").toInt(); - if(min < max) - { - ui->minMemSpinBox->setValue(min); - ui->maxMemSpinBox->setValue(max); - } - else - { - ui->minMemSpinBox->setValue(max); - ui->maxMemSpinBox->setValue(min); - } - ui->permGenSpinBox->setValue(s->get("PermGen").toInt()); - - // Java Settings - ui->javaPathTextBox->setText(s->get("JavaPath").toString()); - ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString()); + auto s = MMC->settings(); + // Memory + int min = s->get("MinMemAlloc").toInt(); + int max = s->get("MaxMemAlloc").toInt(); + if(min < max) + { + ui->minMemSpinBox->setValue(min); + ui->maxMemSpinBox->setValue(max); + } + else + { + ui->minMemSpinBox->setValue(max); + ui->maxMemSpinBox->setValue(min); + } + ui->permGenSpinBox->setValue(s->get("PermGen").toInt()); + + // Java Settings + ui->javaPathTextBox->setText(s->get("JavaPath").toString()); + ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString()); } void JavaPage::on_javaDetectBtn_clicked() { - JavaInstallPtr java; + JavaInstallPtr java; - VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); - vselect.setResizeOn(2); - vselect.exec(); + VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); + vselect.setResizeOn(2); + vselect.exec(); - if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) - { - java = std::dynamic_pointer_cast(vselect.selectedVersion()); - ui->javaPathTextBox->setText(java->path); - } + if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) + { + java = std::dynamic_pointer_cast(vselect.selectedVersion()); + ui->javaPathTextBox->setText(java->path); + } } void JavaPage::on_javaBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if(raw_path.isEmpty()) - { - return; - } - - QString cooked_path = FS::NormalizePath(raw_path); - QFileInfo javaInfo(cooked_path);; - if(!javaInfo.exists() || !javaInfo.isExecutable()) - { - return; - } - ui->javaPathTextBox->setText(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if(raw_path.isEmpty()) + { + return; + } + + QString cooked_path = FS::NormalizePath(raw_path); + QFileInfo javaInfo(cooked_path);; + if(!javaInfo.exists() || !javaInfo.isExecutable()) + { + return; + } + ui->javaPathTextBox->setText(cooked_path); } void JavaPage::on_javaTestBtn_clicked() { - if(checker) - { - return; - } - checker.reset(new JavaCommon::TestCheck( - this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(), - ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); - connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); - checker->run(); + if(checker) + { + return; + } + checker.reset(new JavaCommon::TestCheck( + this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(), + ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); + connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); + checker->run(); } void JavaPage::checkerFinished() { - checker.reset(); + checker.reset(); } diff --git a/application/pages/global/JavaPage.h b/application/pages/global/JavaPage.h index e3a9f37f..3efebb78 100644 --- a/application/pages/global/JavaPage.h +++ b/application/pages/global/JavaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,42 +31,42 @@ class JavaPage; class JavaPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit JavaPage(QWidget *parent = 0); - ~JavaPage(); + explicit JavaPage(QWidget *parent = 0); + ~JavaPage(); - QString displayName() const override - { - return tr("Java"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("java"); - } - QString id() const override - { - return "java-settings"; - } - QString helpPage() const override - { - return "Java-settings"; - } - bool apply() override; + QString displayName() const override + { + return tr("Java"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("java"); + } + QString id() const override + { + return "java-settings"; + } + QString helpPage() const override + { + return "Java-settings"; + } + bool apply() override; private: - void applySettings(); - void loadSettings(); + void applySettings(); + void loadSettings(); private slots: - void on_javaDetectBtn_clicked(); - void on_javaTestBtn_clicked(); - void on_javaBrowseBtn_clicked(); - void checkerFinished(); + void on_javaDetectBtn_clicked(); + void on_javaTestBtn_clicked(); + void on_javaBrowseBtn_clicked(); + void checkerFinished(); private: - Ui::JavaPage *ui; - unique_qobject_ptr checker; + Ui::JavaPage *ui; + unique_qobject_ptr checker; }; diff --git a/application/pages/global/LanguagePage.cpp b/application/pages/global/LanguagePage.cpp new file mode 100644 index 00000000..ae3168cc --- /dev/null +++ b/application/pages/global/LanguagePage.cpp @@ -0,0 +1,51 @@ +#include "LanguagePage.h" + +#include "widgets/LanguageSelectionWidget.h" +#include + +LanguagePage::LanguagePage(QWidget* parent) : + QWidget(parent) +{ + setObjectName(QStringLiteral("languagePage")); + auto layout = new QVBoxLayout(this); + mainWidget = new LanguageSelectionWidget(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(mainWidget); + retranslate(); +} + +LanguagePage::~LanguagePage() +{ +} + +bool LanguagePage::apply() +{ + applySettings(); + return true; +} + +void LanguagePage::applySettings() +{ + auto settings = MMC->settings(); + QString key = mainWidget->getSelectedLanguageKey(); + settings->set("Language", key); +} + +void LanguagePage::loadSettings() +{ + // NIL +} + +void LanguagePage::retranslate() +{ + mainWidget->retranslate(); +} + +void LanguagePage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + retranslate(); + } + QWidget::changeEvent(event); +} diff --git a/application/pages/global/LanguagePage.h b/application/pages/global/LanguagePage.h new file mode 100644 index 00000000..c4df2ea9 --- /dev/null +++ b/application/pages/global/LanguagePage.h @@ -0,0 +1,60 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "pages/BasePage.h" +#include +#include + +class LanguageSelectionWidget; + +class LanguagePage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit LanguagePage(QWidget *parent = 0); + virtual ~LanguagePage(); + + QString displayName() const override + { + return tr("Language"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("language"); + } + QString id() const override + { + return "language-settings"; + } + QString helpPage() const override + { + return "Language-settings"; + } + bool apply() override; + + void changeEvent(QEvent * ) override; + +private: + void applySettings(); + void loadSettings(); + void retranslate(); + +private: + LanguageSelectionWidget *mainWidget; +}; diff --git a/application/pages/global/MinecraftPage.cpp b/application/pages/global/MinecraftPage.cpp index 4efc2fa0..5e0d9625 100644 --- a/application/pages/global/MinecraftPage.cpp +++ b/application/pages/global/MinecraftPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ #include #include +#include #include "settings/SettingsObject.h" #include "MultiMC.h" @@ -25,52 +26,52 @@ MinecraftPage::MinecraftPage(QWidget *parent) : QWidget(parent), ui(new Ui::MinecraftPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - loadSettings(); - updateCheckboxStuff(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + loadSettings(); + updateCheckboxStuff(); } MinecraftPage::~MinecraftPage() { - delete ui; + delete ui; } bool MinecraftPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void MinecraftPage::updateCheckboxStuff() { - ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); - ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); + ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); + ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); } void MinecraftPage::on_maximizedCheckBox_clicked(bool checked) { - Q_UNUSED(checked); - updateCheckboxStuff(); + Q_UNUSED(checked); + updateCheckboxStuff(); } void MinecraftPage::applySettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); - // Window Size - s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); - s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); - s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); + // Window Size + s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); + s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); + s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); } void MinecraftPage::loadSettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); - // Window Size - ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool()); - ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); - ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); + // Window Size + ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool()); + ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); + ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); } diff --git a/application/pages/global/MinecraftPage.h b/application/pages/global/MinecraftPage.h index d1abd6fe..39265fbe 100644 --- a/application/pages/global/MinecraftPage.h +++ b/application/pages/global/MinecraftPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,40 +31,40 @@ class MinecraftPage; class MinecraftPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit MinecraftPage(QWidget *parent = 0); - ~MinecraftPage(); + explicit MinecraftPage(QWidget *parent = 0); + ~MinecraftPage(); - QString displayName() const override - { - return tr("Minecraft"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("minecraft"); - } - QString id() const override - { - return "minecraft-settings"; - } - QString helpPage() const override - { - return "Minecraft-settings"; - } - bool apply() override; + QString displayName() const override + { + return tr("Minecraft"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("minecraft"); + } + QString id() const override + { + return "minecraft-settings"; + } + QString helpPage() const override + { + return "Minecraft-settings"; + } + bool apply() override; private: - void updateCheckboxStuff(); - void applySettings(); - void loadSettings(); + void updateCheckboxStuff(); + void applySettings(); + void loadSettings(); private slots: - void on_maximizedCheckBox_clicked(bool checked); + void on_maximizedCheckBox_clicked(bool checked); private: - Ui::MinecraftPage *ui; + Ui::MinecraftPage *ui; }; diff --git a/application/pages/global/MultiMCPage.cpp b/application/pages/global/MultiMCPage.cpp index 21e8123c..d10d4caa 100644 --- a/application/pages/global/MultiMCPage.cpp +++ b/application/pages/global/MultiMCPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,208 +32,190 @@ // FIXME: possibly move elsewhere enum InstSortMode { - // Sort alphabetically by name. - Sort_Name, - // Sort by which instance was launched most recently. - Sort_LastLaunch + // Sort alphabetically by name. + Sort_Name, + // Sort by which instance was launched most recently. + Sort_LastLaunch }; MultiMCPage::MultiMCPage(QWidget *parent) : QWidget(parent), ui(new Ui::MultiMCPage) { - ui->setupUi(this); - auto origForeground = ui->fontPreview->palette().color(ui->fontPreview->foregroundRole()); - auto origBackground = ui->fontPreview->palette().color(ui->fontPreview->backgroundRole()); - m_colors.reset(new LogColorCache(origForeground, origBackground)); - - ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); - ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); - - defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); - - m_languageModel = MMC->translations(); - loadSettings(); - - if(BuildConfig.UPDATER_ENABLED) - { - QObject::connect(MMC->updateChecker().get(), &UpdateChecker::channelListLoaded, this, - &MultiMCPage::refreshUpdateChannelList); - - if (MMC->updateChecker()->hasChannels()) - { - refreshUpdateChannelList(); - } - else - { - MMC->updateChecker()->updateChanList(false); - } - } - else - { - ui->updateSettingsBox->setHidden(true); - } -/* // Analytics - if(BuildConfig.ANALYTICS_ID.isEmpty()) - { - ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->analyticsTab)); - } -*/ connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); - connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); - connect(ui->languageBox, SIGNAL(currentIndexChanged(int)), SLOT(languageIndexChanged(int))); + ui->setupUi(this); + auto origForeground = ui->fontPreview->palette().color(ui->fontPreview->foregroundRole()); + auto origBackground = ui->fontPreview->palette().color(ui->fontPreview->backgroundRole()); + m_colors.reset(new LogColorCache(origForeground, origBackground)); + + ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); + ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); + + defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat()); + + m_languageModel = MMC->translations(); + loadSettings(); + + if(BuildConfig.UPDATER_ENABLED) + { + QObject::connect(MMC->updateChecker().get(), &UpdateChecker::channelListLoaded, this, + &MultiMCPage::refreshUpdateChannelList); + + if (MMC->updateChecker()->hasChannels()) + { + refreshUpdateChannelList(); + } + else + { + MMC->updateChecker()->updateChanList(false); + } + } + else + { + ui->updateSettingsBox->setHidden(true); + } + connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); + connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); } MultiMCPage::~MultiMCPage() { - delete ui; + delete ui; } bool MultiMCPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void MultiMCPage::on_instDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) - { - QString cooked_dir = FS::NormalizePath(raw_dir); - if (FS::checkProblemticPathJava(QDir(cooked_dir))) - { - QMessageBox warning; - warning.setText(tr("You're trying to specify an instance folder which\'s path " - "contains at least one \'!\'. " - "Java is known to cause problems if that is the case, your " - "instances (probably) won't start!")); - warning.setInformativeText( - tr("Do you really want to use this path? " - "Selecting \"No\" will close this and not alter your instance path.")); - warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - int result = warning.exec(); - if (result == QMessageBox::Yes) - { - ui->instDirTextBox->setText(cooked_dir); - } - } - else - { - ui->instDirTextBox->setText(cooked_dir); - } - } + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) + { + QString cooked_dir = FS::NormalizePath(raw_dir); + if (FS::checkProblemticPathJava(QDir(cooked_dir))) + { + QMessageBox warning; + warning.setText(tr("You're trying to specify an instance folder which\'s path " + "contains at least one \'!\'. " + "Java is known to cause problems if that is the case, your " + "instances (probably) won't start!")); + warning.setInformativeText( + tr("Do you really want to use this path? " + "Selecting \"No\" will close this and not alter your instance path.")); + warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + int result = warning.exec(); + if (result == QMessageBox::Yes) + { + ui->instDirTextBox->setText(cooked_dir); + } + } + else + { + ui->instDirTextBox->setText(cooked_dir); + } + } } void MultiMCPage::on_iconsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) - { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->iconsDirTextBox->setText(cooked_dir); - } + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) + { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->iconsDirTextBox->setText(cooked_dir); + } } void MultiMCPage::on_modsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) - { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->modsDirTextBox->setText(cooked_dir); - } -} - -void MultiMCPage::languageIndexChanged(int index) -{ - auto languageCode = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); - if(languageCode.isEmpty()) - { - qWarning() << "Unknown language at index" << index; - return; - } - auto translations = MMC->translations(); - translations->selectLanguage(languageCode); - translations->updateLanguage(languageCode); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) + { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->modsDirTextBox->setText(cooked_dir); + } } void MultiMCPage::refreshUpdateChannelList() { - // Stop listening for selection changes. It's going to change a lot while we update it and - // we don't need to update the - // description label constantly. - QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, - SLOT(updateChannelSelectionChanged(int))); - - QList channelList = MMC->updateChecker()->getChannelList(); - ui->updateChannelComboBox->clear(); - int selection = -1; - for (int i = 0; i < channelList.count(); i++) - { - UpdateChecker::ChannelListEntry entry = channelList.at(i); - - // When it comes to selection, we'll rely on the indexes of a channel entry being the - // same in the - // combo box as it is in the update checker's channel list. - // This probably isn't very safe, but the channel list doesn't change often enough (or - // at all) for - // this to be a big deal. Hope it doesn't break... - ui->updateChannelComboBox->addItem(entry.name); - - // If the update channel we just added was the selected one, set the current index in - // the combo box to it. - if (entry.id == m_currentUpdateChannel) - { - qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel; - selection = i; - } - } - - ui->updateChannelComboBox->setCurrentIndex(selection); - - // Start listening for selection changes again and update the description label. - QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, - SLOT(updateChannelSelectionChanged(int))); - refreshUpdateChannelDesc(); - - // Now that we've updated the channel list, we can enable the combo box. - // It starts off disabled so that if the channel list hasn't been loaded, it will be - // disabled. - ui->updateChannelComboBox->setEnabled(true); + // Stop listening for selection changes. It's going to change a lot while we update it and + // we don't need to update the + // description label constantly. + QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateChannelSelectionChanged(int))); + + QList channelList = MMC->updateChecker()->getChannelList(); + ui->updateChannelComboBox->clear(); + int selection = -1; + for (int i = 0; i < channelList.count(); i++) + { + UpdateChecker::ChannelListEntry entry = channelList.at(i); + + // When it comes to selection, we'll rely on the indexes of a channel entry being the + // same in the + // combo box as it is in the update checker's channel list. + // This probably isn't very safe, but the channel list doesn't change often enough (or + // at all) for + // this to be a big deal. Hope it doesn't break... + ui->updateChannelComboBox->addItem(entry.name); + + // If the update channel we just added was the selected one, set the current index in + // the combo box to it. + if (entry.id == m_currentUpdateChannel) + { + qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel; + selection = i; + } + } + + ui->updateChannelComboBox->setCurrentIndex(selection); + + // Start listening for selection changes again and update the description label. + QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateChannelSelectionChanged(int))); + refreshUpdateChannelDesc(); + + // Now that we've updated the channel list, we can enable the combo box. + // It starts off disabled so that if the channel list hasn't been loaded, it will be + // disabled. + ui->updateChannelComboBox->setEnabled(true); } void MultiMCPage::updateChannelSelectionChanged(int index) { - refreshUpdateChannelDesc(); + refreshUpdateChannelDesc(); } void MultiMCPage::refreshUpdateChannelDesc() { - // Get the channel list. - QList channelList = MMC->updateChecker()->getChannelList(); - int selectedIndex = ui->updateChannelComboBox->currentIndex(); - if (selectedIndex < 0) - { - return; - } - if (selectedIndex < channelList.count()) - { - // Find the channel list entry with the given index. - UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex); - - // Set the description text. - ui->updateChannelDescLabel->setText(selected.description); - - // Set the currently selected channel ID. - m_currentUpdateChannel = selected.id; - } + // Get the channel list. + QList channelList = MMC->updateChecker()->getChannelList(); + int selectedIndex = ui->updateChannelComboBox->currentIndex(); + if (selectedIndex < 0) + { + return; + } + if (selectedIndex < channelList.count()) + { + // Find the channel list entry with the given index. + UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex); + + // Set the description text. + ui->updateChannelDescLabel->setText(selected.description); + + // Set the currently selected channel ID. + m_currentUpdateChannel = selected.id; + } } void MultiMCPage::applySettings() { +<<<<<<< HEAD auto s = MMC->settings(); // Language @@ -434,39 +416,232 @@ void MultiMCPage::loadSettings() { ui->analyticsCheck->setChecked(s->get("Analytics").toBool()); } */ +======= + auto s = MMC->settings(); + + if (ui->resetNotificationsBtn->isChecked()) + { + s->set("ShownNotifications", QString()); + } + + // Updates + s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); + s->set("UpdateChannel", m_currentUpdateChannel); + auto original = s->get("IconTheme").toString(); + //FIXME: make generic + switch (ui->themeComboBox->currentIndex()) + { + case 1: + s->set("IconTheme", "pe_dark"); + break; + case 2: + s->set("IconTheme", "pe_light"); + break; + case 3: + s->set("IconTheme", "pe_blue"); + break; + case 4: + s->set("IconTheme", "pe_colored"); + break; + case 5: + s->set("IconTheme", "OSX"); + break; + case 6: + s->set("IconTheme", "iOS"); + break; + case 7: + s->set("IconTheme", "flat"); + break; + case 8: + s->set("IconTheme", "custom"); + break; + case 0: + default: + s->set("IconTheme", "multimc"); + break; + } + + if(original != s->get("IconTheme")) + { + MMC->setIconTheme(s->get("IconTheme").toString()); + } + + auto originalAppTheme = s->get("ApplicationTheme").toString(); + auto newAppTheme = ui->themeComboBoxColors->currentData().toString(); + if(originalAppTheme != newAppTheme) + { + s->set("ApplicationTheme", newAppTheme); + MMC->setApplicationTheme(newAppTheme, false); + } + + // Console settings + s->set("ShowConsole", ui->showConsoleCheck->isChecked()); + s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); + s->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); + QString consoleFontFamily = ui->consoleFont->currentFont().family(); + s->set("ConsoleFont", consoleFontFamily); + s->set("ConsoleFontSize", ui->fontSizeBox->value()); + s->set("ConsoleMaxLines", ui->lineLimitSpinBox->value()); + s->set("ConsoleOverflowStop", ui->checkStopLogging->checkState() != Qt::Unchecked); + + // Folders + // TODO: Offer to move instances to new instance folder. + s->set("InstanceDir", ui->instDirTextBox->text()); + s->set("CentralModsDir", ui->modsDirTextBox->text()); + s->set("IconsDir", ui->iconsDirTextBox->text()); + + auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); + switch (sortMode) + { + case Sort_LastLaunch: + s->set("InstSortMode", "LastLaunch"); + break; + case Sort_Name: + default: + s->set("InstSortMode", "Name"); + break; + } + + // Analytics + if(!BuildConfig.ANALYTICS_ID.isEmpty()) + { + s->set("Analytics", ui->analyticsCheck->isChecked()); + } +} +void MultiMCPage::loadSettings() +{ + auto s = MMC->settings(); + // Updates + ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); + m_currentUpdateChannel = s->get("UpdateChannel").toString(); + //FIXME: make generic + auto theme = s->get("IconTheme").toString(); + if (theme == "pe_dark") + { + ui->themeComboBox->setCurrentIndex(1); + } + else if (theme == "pe_light") + { + ui->themeComboBox->setCurrentIndex(2); + } + else if (theme == "pe_blue") + { + ui->themeComboBox->setCurrentIndex(3); + } + else if (theme == "pe_colored") + { + ui->themeComboBox->setCurrentIndex(4); + } + else if (theme == "OSX") + { + ui->themeComboBox->setCurrentIndex(5); + } + else if (theme == "iOS") + { + ui->themeComboBox->setCurrentIndex(6); + } + else if (theme == "flat") + { + ui->themeComboBox->setCurrentIndex(7); + } + else if (theme == "custom") + { + ui->themeComboBox->setCurrentIndex(8); + } + else + { + ui->themeComboBox->setCurrentIndex(0); + } + + { + auto currentTheme = s->get("ApplicationTheme").toString(); + auto themes = MMC->getValidApplicationThemes(); + int idx = 0; + for(auto &theme: themes) + { + ui->themeComboBoxColors->addItem(theme->name(), theme->id()); + if(currentTheme == theme->id()) + { + ui->themeComboBoxColors->setCurrentIndex(idx); + } + idx++; + } + } + + // Console settings + ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); + ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool()); + ui->showConsoleErrorCheck->setChecked(s->get("ShowConsoleOnError").toBool()); + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + QFont consoleFont(fontFamily); + ui->consoleFont->setCurrentFont(consoleFont); + + bool conversionOk = true; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + ui->fontSizeBox->setValue(fontSize); + refreshFontPreview(); + ui->lineLimitSpinBox->setValue(s->get("ConsoleMaxLines").toInt()); + ui->checkStopLogging->setChecked(s->get("ConsoleOverflowStop").toBool()); + + // Folders + ui->instDirTextBox->setText(s->get("InstanceDir").toString()); + ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); + ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); + + QString sortMode = s->get("InstSortMode").toString(); + + if (sortMode == "LastLaunch") + { + ui->sortLastLaunchedBtn->setChecked(true); + } + else + { + ui->sortByNameBtn->setChecked(true); + } + + // Analytics + if(!BuildConfig.ANALYTICS_ID.isEmpty()) + { + ui->analyticsCheck->setChecked(s->get("Analytics").toBool()); + } +>>>>>>> origin/stable } void MultiMCPage::refreshFontPreview() { - int fontSize = ui->fontSizeBox->value(); - QString fontFamily = ui->consoleFont->currentFont().family(); - ui->fontPreview->clear(); - defaultFormat->setFont(QFont(fontFamily, fontSize)); - { - QTextCharFormat format(*defaultFormat); - format.setForeground(m_colors->getFront(MessageLevel::Error)); - // append a paragraph/line - auto workCursor = ui->fontPreview->textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(tr("[Something/ERROR] A spooky error!"), format); - workCursor.insertBlock(); - } - { - QTextCharFormat format(*defaultFormat); - format.setForeground(m_colors->getFront(MessageLevel::Message)); - // append a paragraph/line - auto workCursor = ui->fontPreview->textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(tr("[Test/INFO] A harmless message..."), format); - workCursor.insertBlock(); - } - { - QTextCharFormat format(*defaultFormat); - format.setForeground(m_colors->getFront(MessageLevel::Warning)); - // append a paragraph/line - auto workCursor = ui->fontPreview->textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(tr("[Something/WARN] A not so spooky warning."), format); - workCursor.insertBlock(); - } + int fontSize = ui->fontSizeBox->value(); + QString fontFamily = ui->consoleFont->currentFont().family(); + ui->fontPreview->clear(); + defaultFormat->setFont(QFont(fontFamily, fontSize)); + { + QTextCharFormat format(*defaultFormat); + format.setForeground(m_colors->getFront(MessageLevel::Error)); + // append a paragraph/line + auto workCursor = ui->fontPreview->textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(tr("[Something/ERROR] A spooky error!"), format); + workCursor.insertBlock(); + } + { + QTextCharFormat format(*defaultFormat); + format.setForeground(m_colors->getFront(MessageLevel::Message)); + // append a paragraph/line + auto workCursor = ui->fontPreview->textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(tr("[Test/INFO] A harmless message..."), format); + workCursor.insertBlock(); + } + { + QTextCharFormat format(*defaultFormat); + format.setForeground(m_colors->getFront(MessageLevel::Warning)); + // append a paragraph/line + auto workCursor = ui->fontPreview->textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(tr("[Something/WARN] A not so spooky warning."), format); + workCursor.insertBlock(); + } } diff --git a/application/pages/global/MultiMCPage.h b/application/pages/global/MultiMCPage.h index d5194c0e..27a801be 100644 --- a/application/pages/global/MultiMCPage.h +++ b/application/pages/global/MultiMCPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,71 +34,69 @@ class MultiMCPage; class MultiMCPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit MultiMCPage(QWidget *parent = 0); - ~MultiMCPage(); - - QString displayName() const override - { - return "MultiMC"; - } - QIcon icon() const override - { - return MMC->getThemedIcon("multimc"); - } - QString id() const override - { - return "multimc-settings"; - } - QString helpPage() const override - { - return "MultiMC-settings"; - } - bool apply() override; + explicit MultiMCPage(QWidget *parent = 0); + ~MultiMCPage(); + + QString displayName() const override + { + return "MultiMC"; + } + QIcon icon() const override + { + return MMC->getThemedIcon("multimc"); + } + QString id() const override + { + return "multimc-settings"; + } + QString helpPage() const override + { + return "MultiMC-settings"; + } + bool apply() override; private: - void applySettings(); - void loadSettings(); + void applySettings(); + void loadSettings(); private slots: - void on_instDirBrowseBtn_clicked(); - void on_modsDirBrowseBtn_clicked(); - void on_iconsDirBrowseBtn_clicked(); + void on_instDirBrowseBtn_clicked(); + void on_modsDirBrowseBtn_clicked(); + void on_iconsDirBrowseBtn_clicked(); - void languageIndexChanged(int index); + /*! + * Updates the list of update channels in the combo box. + */ + void refreshUpdateChannelList(); - /*! - * Updates the list of update channels in the combo box. - */ - void refreshUpdateChannelList(); + /*! + * Updates the channel description label. + */ + void refreshUpdateChannelDesc(); - /*! - * Updates the channel description label. - */ - void refreshUpdateChannelDesc(); + /*! + * Updates the font preview + */ + void refreshFontPreview(); - /*! - * Updates the font preview - */ - void refreshFontPreview(); - - void updateChannelSelectionChanged(int index); + void updateChannelSelectionChanged(int index); private: - Ui::MultiMCPage *ui; + Ui::MultiMCPage *ui; - /*! - * Stores the currently selected update channel. - */ - QString m_currentUpdateChannel; + /*! + * Stores the currently selected update channel. + */ + QString m_currentUpdateChannel; - // default format for the font preview... - QTextCharFormat *defaultFormat; + // default format for the font preview... + QTextCharFormat *defaultFormat; - std::unique_ptr m_colors; + std::unique_ptr m_colors; - std::shared_ptr m_languageModel; + std::shared_ptr m_languageModel; }; diff --git a/application/pages/global/MultiMCPage.ui b/application/pages/global/MultiMCPage.ui index 124401c3..ea034919 100644 --- a/application/pages/global/MultiMCPage.ui +++ b/application/pages/global/MultiMCPage.ui @@ -6,7 +6,7 @@ 0 0 - 467 + 514 629
@@ -228,18 +228,6 @@ - - - - Language: - - - - - - - - @@ -570,7 +558,6 @@ resetNotificationsBtn sortLastLaunchedBtn sortByNameBtn - languageBox themeComboBox themeComboBoxColors showConsoleCheck diff --git a/application/pages/global/PackagesPage.cpp b/application/pages/global/PackagesPage.cpp index 7cf1e3f5..1bbbed7a 100644 --- a/application/pages/global/PackagesPage.cpp +++ b/application/pages/global/PackagesPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,192 +33,192 @@ using namespace Meta; static QString formatRequires(const VersionPtr &version) { - QStringList lines; - auto & reqs = version->requires(); - auto iter = reqs.begin(); - while (iter != reqs.end()) - { - auto &uid = iter->uid; - auto &version = iter->equalsVersion; - const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid; - if(!version.isEmpty()) - { - lines.append(QString("%1 (%2)").arg(readable, version)); - } - else - { - lines.append(QString("%1").arg(readable)); - } - iter++; - } - return lines.join('\n'); + QStringList lines; + auto & reqs = version->requires(); + auto iter = reqs.begin(); + while (iter != reqs.end()) + { + auto &uid = iter->uid; + auto &version = iter->equalsVersion; + const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid; + if(!version.isEmpty()) + { + lines.append(QString("%1 (%2)").arg(readable, version)); + } + else + { + lines.append(QString("%1").arg(readable)); + } + iter++; + } + return lines.join('\n'); } PackagesPage::PackagesPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::PackagesPage) + QWidget(parent), + ui(new Ui::PackagesPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - - m_fileProxy = new QSortFilterProxyModel(this); - m_fileProxy->setSortRole(Qt::DisplayRole); - m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterRole(Qt::DisplayRole); - m_fileProxy->setFilterKeyColumn(0); - m_fileProxy->sort(0); - m_fileProxy->setSourceModel(ENV.metadataIndex().get()); - ui->indexView->setModel(m_fileProxy); - - m_filterProxy = new QSortFilterProxyModel(this); - m_filterProxy->setSortRole(VersionList::SortRole); - m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterProxy->setFilterRole(Qt::DisplayRole); - m_filterProxy->setFilterKeyColumn(0); - m_filterProxy->sort(0, Qt::DescendingOrder); - ui->versionsView->setModel(m_filterProxy); - - m_versionProxy = new VersionProxyModel(this); - m_filterProxy->setSourceModel(m_versionProxy); - - connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList); - connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion); - connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged); - - updateCurrentVersionList(QModelIndex()); - updateVersion(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + + m_fileProxy = new QSortFilterProxyModel(this); + m_fileProxy->setSortRole(Qt::DisplayRole); + m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive); + m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_fileProxy->setFilterRole(Qt::DisplayRole); + m_fileProxy->setFilterKeyColumn(0); + m_fileProxy->sort(0); + m_fileProxy->setSourceModel(ENV.metadataIndex().get()); + ui->indexView->setModel(m_fileProxy); + + m_filterProxy = new QSortFilterProxyModel(this); + m_filterProxy->setSortRole(VersionList::SortRole); + m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_filterProxy->setFilterRole(Qt::DisplayRole); + m_filterProxy->setFilterKeyColumn(0); + m_filterProxy->sort(0, Qt::DescendingOrder); + ui->versionsView->setModel(m_filterProxy); + + m_versionProxy = new VersionProxyModel(this); + m_filterProxy->setSourceModel(m_versionProxy); + + connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList); + connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion); + connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged); + + updateCurrentVersionList(QModelIndex()); + updateVersion(); } PackagesPage::~PackagesPage() { - delete ui; + delete ui; } QIcon PackagesPage::icon() const { - return MMC->getThemedIcon("packages"); + return MMC->getThemedIcon("packages"); } void PackagesPage::on_refreshIndexBtn_clicked() { - ENV.metadataIndex()->load(Net::Mode::Online); + ENV.metadataIndex()->load(Net::Mode::Online); } void PackagesPage::on_refreshFileBtn_clicked() { - VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value(); - if (!list) - { - return; - } - list->load(Net::Mode::Online); + VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value(); + if (!list) + { + return; + } + list->load(Net::Mode::Online); } void PackagesPage::on_refreshVersionBtn_clicked() { - VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value(); - if (!version) - { - return; - } - version->load(Net::Mode::Online); + VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value(); + if (!version) + { + return; + } + version->load(Net::Mode::Online); } void PackagesPage::on_fileSearchEdit_textChanged(const QString &search) { - if (search.isEmpty()) - { - m_fileProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } + if (search.isEmpty()) + { + m_fileProxy->setFilterFixedString(QString()); + } + else + { + QStringList parts = search.split(' '); + std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); + m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); + } } void PackagesPage::on_versionSearchEdit_textChanged(const QString &search) { - if (search.isEmpty()) - { - m_filterProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } + if (search.isEmpty()) + { + m_filterProxy->setFilterFixedString(QString()); + } + else + { + QStringList parts = search.split(' '); + std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); + m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); + } } void PackagesPage::updateCurrentVersionList(const QModelIndex &index) { - if (index.isValid()) - { - VersionListPtr list = index.data(Index::ListPtrRole).value(); - ui->versionsBox->setEnabled(true); - ui->refreshFileBtn->setEnabled(true); - ui->fileUidLabel->setEnabled(true); - ui->fileUid->setText(list->uid()); - ui->fileNameLabel->setEnabled(true); - ui->fileName->setText(list->name()); - m_versionProxy->setSourceModel(list.get()); - ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable())); - list->load(Net::Mode::Offline); - } - else - { - ui->versionsBox->setEnabled(false); - ui->refreshFileBtn->setEnabled(false); - ui->fileUidLabel->setEnabled(false); - ui->fileUid->clear(); - ui->fileNameLabel->setEnabled(false); - ui->fileName->clear(); - m_versionProxy->setSourceModel(nullptr); - ui->refreshFileBtn->setText(tr("Refresh")); - } + if (index.isValid()) + { + VersionListPtr list = index.data(Index::ListPtrRole).value(); + ui->versionsBox->setEnabled(true); + ui->refreshFileBtn->setEnabled(true); + ui->fileUidLabel->setEnabled(true); + ui->fileUid->setText(list->uid()); + ui->fileNameLabel->setEnabled(true); + ui->fileName->setText(list->name()); + m_versionProxy->setSourceModel(list.get()); + ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable())); + list->load(Net::Mode::Offline); + } + else + { + ui->versionsBox->setEnabled(false); + ui->refreshFileBtn->setEnabled(false); + ui->fileUidLabel->setEnabled(false); + ui->fileUid->clear(); + ui->fileNameLabel->setEnabled(false); + ui->fileName->clear(); + m_versionProxy->setSourceModel(nullptr); + ui->refreshFileBtn->setText(tr("Refresh")); + } } void PackagesPage::versionListDataChanged(const QModelIndex &tl, const QModelIndex &br) { - if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex())) - { - updateVersion(); - } + if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex())) + { + updateVersion(); + } } void PackagesPage::updateVersion() { - VersionPtr version = std::dynamic_pointer_cast( - ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value()); - if (version) - { - ui->refreshVersionBtn->setEnabled(true); - ui->versionVersionLabel->setEnabled(true); - ui->versionVersion->setText(version->version()); - ui->versionTimeLabel->setEnabled(true); - ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm")); - ui->versionTypeLabel->setEnabled(true); - ui->versionType->setText(version->type()); - ui->versionRequiresLabel->setEnabled(true); - ui->versionRequires->setText(formatRequires(version)); - ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version())); - } - else - { - ui->refreshVersionBtn->setEnabled(false); - ui->versionVersionLabel->setEnabled(false); - ui->versionVersion->clear(); - ui->versionTimeLabel->setEnabled(false); - ui->versionTime->clear(); - ui->versionTypeLabel->setEnabled(false); - ui->versionType->clear(); - ui->versionRequiresLabel->setEnabled(false); - ui->versionRequires->clear(); - ui->refreshVersionBtn->setText(tr("Refresh")); - } + VersionPtr version = std::dynamic_pointer_cast( + ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value()); + if (version) + { + ui->refreshVersionBtn->setEnabled(true); + ui->versionVersionLabel->setEnabled(true); + ui->versionVersion->setText(version->version()); + ui->versionTimeLabel->setEnabled(true); + ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm")); + ui->versionTypeLabel->setEnabled(true); + ui->versionType->setText(version->type()); + ui->versionRequiresLabel->setEnabled(true); + ui->versionRequires->setText(formatRequires(version)); + ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version())); + } + else + { + ui->refreshVersionBtn->setEnabled(false); + ui->versionVersionLabel->setEnabled(false); + ui->versionVersion->clear(); + ui->versionTimeLabel->setEnabled(false); + ui->versionTime->clear(); + ui->versionTypeLabel->setEnabled(false); + ui->versionType->clear(); + ui->versionRequiresLabel->setEnabled(false); + ui->versionRequires->clear(); + ui->refreshVersionBtn->setText(tr("Refresh")); + } } void PackagesPage::openedImpl() { - ENV.metadataIndex()->load(Net::Mode::Offline); + ENV.metadataIndex()->load(Net::Mode::Offline); } diff --git a/application/pages/global/PackagesPage.h b/application/pages/global/PackagesPage.h index ad155d9e..872961a5 100644 --- a/application/pages/global/PackagesPage.h +++ b/application/pages/global/PackagesPage.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,30 +28,30 @@ class VersionProxyModel; class PackagesPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit PackagesPage(QWidget *parent = 0); - ~PackagesPage(); + explicit PackagesPage(QWidget *parent = 0); + ~PackagesPage(); - QString id() const override { return "packages-global"; } - QString displayName() const override { return tr("Packages"); } - QIcon icon() const override; - void openedImpl() override; + QString id() const override { return "packages-global"; } + QString displayName() const override { return tr("Packages"); } + QIcon icon() const override; + void openedImpl() override; private slots: - void on_refreshIndexBtn_clicked(); - void on_refreshFileBtn_clicked(); - void on_refreshVersionBtn_clicked(); - void on_fileSearchEdit_textChanged(const QString &search); - void on_versionSearchEdit_textChanged(const QString &search); - void updateCurrentVersionList(const QModelIndex &index); - void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br); + void on_refreshIndexBtn_clicked(); + void on_refreshFileBtn_clicked(); + void on_refreshVersionBtn_clicked(); + void on_fileSearchEdit_textChanged(const QString &search); + void on_versionSearchEdit_textChanged(const QString &search); + void updateCurrentVersionList(const QModelIndex &index); + void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br); private: - Ui::PackagesPage *ui; - QSortFilterProxyModel *m_fileProxy; - QSortFilterProxyModel *m_filterProxy; - VersionProxyModel *m_versionProxy; + Ui::PackagesPage *ui; + QSortFilterProxyModel *m_fileProxy; + QSortFilterProxyModel *m_filterProxy; + VersionProxyModel *m_versionProxy; - void updateVersion(); + void updateVersion(); }; diff --git a/application/pages/global/PasteEEPage.cpp b/application/pages/global/PasteEEPage.cpp index 83dd6411..5355d63a 100644 --- a/application/pages/global/PasteEEPage.cpp +++ b/application/pages/global/PasteEEPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include #include #include +#include #include "settings/SettingsObject.h" #include "tools/BaseProfiler.h" @@ -26,56 +27,56 @@ #include PasteEEPage::PasteEEPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::PasteEEPage) + QWidget(parent), + ui(new Ui::PasteEEPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide();\ - connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited); - loadSettings(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide();\ + connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited); + loadSettings(); } PasteEEPage::~PasteEEPage() { - delete ui; + delete ui; } void PasteEEPage::loadSettings() { - auto s = MMC->settings(); - QString keyToUse = s->get("PasteEEAPIKey").toString(); - if(keyToUse == "multimc") - { - ui->multimcButton->setChecked(true); - } - else - { - ui->customButton->setChecked(true); - ui->customAPIkeyEdit->setText(keyToUse); - } + auto s = MMC->settings(); + QString keyToUse = s->get("PasteEEAPIKey").toString(); + if(keyToUse == "multimc") + { + ui->multimcButton->setChecked(true); + } + else + { + ui->customButton->setChecked(true); + ui->customAPIkeyEdit->setText(keyToUse); + } } void PasteEEPage::applySettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); - QString pasteKeyToUse; - if (ui->customButton->isChecked()) - pasteKeyToUse = ui->customAPIkeyEdit->text(); - else - { - pasteKeyToUse = "multimc"; - } - s->set("PasteEEAPIKey", pasteKeyToUse); + QString pasteKeyToUse; + if (ui->customButton->isChecked()) + pasteKeyToUse = ui->customAPIkeyEdit->text(); + else + { + pasteKeyToUse = "multimc"; + } + s->set("PasteEEAPIKey", pasteKeyToUse); } bool PasteEEPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void PasteEEPage::textEdited(const QString& text) { - ui->customButton->setChecked(true); + ui->customButton->setChecked(true); } diff --git a/application/pages/global/PasteEEPage.h b/application/pages/global/PasteEEPage.h index 1b152577..78cf41f4 100644 --- a/application/pages/global/PasteEEPage.h +++ b/application/pages/global/PasteEEPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,37 +26,37 @@ class PasteEEPage; class PasteEEPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit PasteEEPage(QWidget *parent = 0); - ~PasteEEPage(); - - QString displayName() const override - { - return tr("Log Upload"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("log"); - } - QString id() const override - { - return "log-upload"; - } - QString helpPage() const override - { - return "Log-Upload"; - } - virtual bool apply() override; + explicit PasteEEPage(QWidget *parent = 0); + ~PasteEEPage(); + + QString displayName() const override + { + return tr("Log Upload"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("log"); + } + QString id() const override + { + return "log-upload"; + } + QString helpPage() const override + { + return "Log-Upload"; + } + virtual bool apply() override; private: - void loadSettings(); - void applySettings(); + void loadSettings(); + void applySettings(); private slots: - void textEdited(const QString &text); + void textEdited(const QString &text); private: - Ui::PasteEEPage *ui; + Ui::PasteEEPage *ui; }; diff --git a/application/pages/global/ProxyPage.cpp b/application/pages/global/ProxyPage.cpp index 7dbcb860..ee56a54e 100644 --- a/application/pages/global/ProxyPage.cpp +++ b/application/pages/global/ProxyPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,81 +16,86 @@ #include "ProxyPage.h" #include "ui_ProxyPage.h" +#include + #include "settings/SettingsObject.h" #include "MultiMC.h" -#include +#include "Env.h" ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - loadSettings(); - updateCheckboxStuff(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + loadSettings(); + updateCheckboxStuff(); - connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); + connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); } ProxyPage::~ProxyPage() { - delete ui; + delete ui; } bool ProxyPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void ProxyPage::updateCheckboxStuff() { - ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && - !ui->proxyDefaultBtn->isChecked()); - ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && - !ui->proxyDefaultBtn->isChecked()); + ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && + !ui->proxyDefaultBtn->isChecked()); + ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && + !ui->proxyDefaultBtn->isChecked()); } void ProxyPage::proxyChanged(int) { - updateCheckboxStuff(); + updateCheckboxStuff(); } void ProxyPage::applySettings() { - auto s = MMC->settings(); + auto s = MMC->settings(); + + // Proxy + QString proxyType = "None"; + if (ui->proxyDefaultBtn->isChecked()) + proxyType = "Default"; + else if (ui->proxyNoneBtn->isChecked()) + proxyType = "None"; + else if (ui->proxySOCKS5Btn->isChecked()) + proxyType = "SOCKS5"; + else if (ui->proxyHTTPBtn->isChecked()) + proxyType = "HTTP"; - // Proxy - QString proxyType = "None"; - if (ui->proxyDefaultBtn->isChecked()) - proxyType = "Default"; - else if (ui->proxyNoneBtn->isChecked()) - proxyType = "None"; - else if (ui->proxySOCKS5Btn->isChecked()) - proxyType = "SOCKS5"; - else if (ui->proxyHTTPBtn->isChecked()) - proxyType = "HTTP"; + s->set("ProxyType", proxyType); + s->set("ProxyAddr", ui->proxyAddrEdit->text()); + s->set("ProxyPort", ui->proxyPortEdit->value()); + s->set("ProxyUser", ui->proxyUserEdit->text()); + s->set("ProxyPass", ui->proxyPassEdit->text()); - s->set("ProxyType", proxyType); - s->set("ProxyAddr", ui->proxyAddrEdit->text()); - s->set("ProxyPort", ui->proxyPortEdit->value()); - s->set("ProxyUser", ui->proxyUserEdit->text()); - s->set("ProxyPass", ui->proxyPassEdit->text()); + ENV.updateProxySettings(proxyType, ui->proxyAddrEdit->text(), ui->proxyPortEdit->value(), + ui->proxyUserEdit->text(), ui->proxyPassEdit->text()); } void ProxyPage::loadSettings() { - auto s = MMC->settings(); - // Proxy - QString proxyType = s->get("ProxyType").toString(); - if (proxyType == "Default") - ui->proxyDefaultBtn->setChecked(true); - else if (proxyType == "None") - ui->proxyNoneBtn->setChecked(true); - else if (proxyType == "SOCKS5") - ui->proxySOCKS5Btn->setChecked(true); - else if (proxyType == "HTTP") - ui->proxyHTTPBtn->setChecked(true); + auto s = MMC->settings(); + // Proxy + QString proxyType = s->get("ProxyType").toString(); + if (proxyType == "Default") + ui->proxyDefaultBtn->setChecked(true); + else if (proxyType == "None") + ui->proxyNoneBtn->setChecked(true); + else if (proxyType == "SOCKS5") + ui->proxySOCKS5Btn->setChecked(true); + else if (proxyType == "HTTP") + ui->proxyHTTPBtn->setChecked(true); - ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString()); - ui->proxyPortEdit->setValue(s->get("ProxyPort").value()); - ui->proxyUserEdit->setText(s->get("ProxyUser").toString()); - ui->proxyPassEdit->setText(s->get("ProxyPass").toString()); + ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString()); + ui->proxyPortEdit->setValue(s->get("ProxyPort").value()); + ui->proxyUserEdit->setText(s->get("ProxyUser").toString()); + ui->proxyPassEdit->setText(s->get("ProxyPass").toString()); } diff --git a/application/pages/global/ProxyPage.h b/application/pages/global/ProxyPage.h index 565c2857..d87bc120 100644 --- a/application/pages/global/ProxyPage.h +++ b/application/pages/global/ProxyPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,39 +28,39 @@ class ProxyPage; class ProxyPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ProxyPage(QWidget *parent = 0); - ~ProxyPage(); + explicit ProxyPage(QWidget *parent = 0); + ~ProxyPage(); - QString displayName() const override - { - return tr("Proxy"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("proxy"); - } - QString id() const override - { - return "proxy-settings"; - } - QString helpPage() const override - { - return "Proxy-settings"; - } - bool apply() override; + QString displayName() const override + { + return tr("Proxy"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("proxy"); + } + QString id() const override + { + return "proxy-settings"; + } + QString helpPage() const override + { + return "Proxy-settings"; + } + bool apply() override; private: - void updateCheckboxStuff(); - void applySettings(); - void loadSettings(); + void updateCheckboxStuff(); + void applySettings(); + void loadSettings(); private slots: - void proxyChanged(int); + void proxyChanged(int); private: - Ui::ProxyPage *ui; + Ui::ProxyPage *ui; }; diff --git a/application/pages/instance/GameOptionsPage.cpp b/application/pages/instance/GameOptionsPage.cpp new file mode 100644 index 00000000..5555fc79 --- /dev/null +++ b/application/pages/instance/GameOptionsPage.cpp @@ -0,0 +1,40 @@ +#include "GameOptionsPage.h" +#include "ui_GameOptionsPage.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/gameoptions/GameOptions.h" + +GameOptionsPage::GameOptionsPage(MinecraftInstance * inst, QWidget* parent) + : QWidget(parent), ui(new Ui::GameOptionsPage) +{ + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + m_model = inst->gameOptionsModel(); + ui->optionsView->setModel(m_model.get()); + auto head = ui->optionsView->header(); + if(head->count()) + { + head->setSectionResizeMode(0, QHeaderView::ResizeToContents); + for(int i = 1; i < head->count(); i++) + { + head->setSectionResizeMode(i, QHeaderView::Stretch); + } + } +} + +GameOptionsPage::~GameOptionsPage() +{ + // m_model->save(); +} + +void GameOptionsPage::openedImpl() +{ + // m_model->observe(); +} + +void GameOptionsPage::closedImpl() +{ + // m_model->unobserve(); +} + +#include "GameOptionsPage.moc" + diff --git a/application/pages/instance/GameOptionsPage.h b/application/pages/instance/GameOptionsPage.h new file mode 100644 index 00000000..ae47747f --- /dev/null +++ b/application/pages/instance/GameOptionsPage.h @@ -0,0 +1,63 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "pages/BasePage.h" +#include + +namespace Ui +{ +class GameOptionsPage; +} + +class GameOptions; +class MinecraftInstance; + +class GameOptionsPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit GameOptionsPage(MinecraftInstance *inst, QWidget *parent = 0); + virtual ~GameOptionsPage(); + + void openedImpl() override; + void closedImpl() override; + + virtual QString displayName() const override + { + return tr("Game Options"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("settings"); + } + virtual QString id() const override + { + return "gameoptions"; + } + virtual QString helpPage() const override + { + return "Game-Options-management"; + } + +private: // data + Ui::GameOptionsPage *ui = nullptr; + std::shared_ptr m_model; +}; diff --git a/application/pages/instance/GameOptionsPage.ui b/application/pages/instance/GameOptionsPage.ui new file mode 100644 index 00000000..f0a5ce0e --- /dev/null +++ b/application/pages/instance/GameOptionsPage.ui @@ -0,0 +1,88 @@ + + + GameOptionsPage + + + + 0 + 0 + 706 + 575 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + 0 + + + + Tab 1 + + + + + + + 0 + 0 + + + + true + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + 64 + 64 + + + + false + + + false + + + + + + + + + + + tabWidget + optionsView + + + + diff --git a/application/pages/instance/InstanceSettingsPage.cpp b/application/pages/instance/InstanceSettingsPage.cpp index 71e90a32..b7b0a863 100644 --- a/application/pages/instance/InstanceSettingsPage.cpp +++ b/application/pages/instance/InstanceSettingsPage.cpp @@ -15,237 +15,271 @@ #include InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) - : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) + : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) { - m_settings = inst->settings(); - ui->setupUi(this); - auto sysMB = Sys::getSystemRam() / Sys::megabyte; - ui->maxMemSpinBox->setMaximum(sysMB); - loadSettings(); + m_settings = inst->settings(); + ui->setupUi(this); + auto sysMB = Sys::getSystemRam() / Sys::megabyte; + ui->maxMemSpinBox->setMaximum(sysMB); + connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); + connect(MMC, &MultiMC::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); + connect(MMC, &MultiMC::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); + loadSettings(); } bool InstanceSettingsPage::shouldDisplay() const { - return !m_instance->isRunning(); + return !m_instance->isRunning(); } InstanceSettingsPage::~InstanceSettingsPage() { - delete ui; + delete ui; +} + +void InstanceSettingsPage::globalSettingsButtonClicked(bool) +{ + switch(ui->settingsTabs->currentIndex()) { + case 0: + MMC->ShowGlobalSettings(this, "java-settings"); + return; + case 1: + MMC->ShowGlobalSettings(this, "minecraft-settings"); + return; + case 2: + MMC->ShowGlobalSettings(this, "custom-commands"); + return; + } } bool InstanceSettingsPage::apply() { - applySettings(); - return true; + applySettings(); + return true; } void InstanceSettingsPage::applySettings() { - SettingsObject::Lock lock(m_settings); - - // Console - bool console = ui->consoleSettingsBox->isChecked(); - m_settings->set("OverrideConsole", console); - if (console) - { - m_settings->set("ShowConsole", ui->showConsoleCheck->isChecked()); - m_settings->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); - m_settings->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); - } - else - { - m_settings->reset("ShowConsole"); - m_settings->reset("AutoCloseConsole"); - m_settings->reset("ShowConsoleOnError"); - } - - // Window Size - bool window = ui->windowSizeGroupBox->isChecked(); - m_settings->set("OverrideWindow", window); - if (window) - { - m_settings->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); - m_settings->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); - m_settings->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); - } - else - { - m_settings->reset("LaunchMaximized"); - m_settings->reset("MinecraftWinWidth"); - m_settings->reset("MinecraftWinHeight"); - } - - // Memory - bool memory = ui->memoryGroupBox->isChecked(); - m_settings->set("OverrideMemory", memory); - if (memory) - { - int min = ui->minMemSpinBox->value(); - int max = ui->maxMemSpinBox->value(); - if(min < max) - { - m_settings->set("MinMemAlloc", min); - m_settings->set("MaxMemAlloc", max); - } - else - { - m_settings->set("MinMemAlloc", max); - m_settings->set("MaxMemAlloc", min); - } - m_settings->set("PermGen", ui->permGenSpinBox->value()); - } - else - { - m_settings->reset("MinMemAlloc"); - m_settings->reset("MaxMemAlloc"); - m_settings->reset("PermGen"); - } - - // Java Install Settings - bool javaInstall = ui->javaSettingsGroupBox->isChecked(); - m_settings->set("OverrideJavaLocation", javaInstall); - if (javaInstall) - { - m_settings->set("JavaPath", ui->javaPathTextBox->text()); - } - else - { - m_settings->reset("JavaPath"); - } - - // Java arguments - bool javaArgs = ui->javaArgumentsGroupBox->isChecked(); - m_settings->set("OverrideJavaArgs", javaArgs); - if(javaArgs) - { - m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); - JavaCommon::checkJVMArgs(m_settings->get("JvmArgs").toString(), this->parentWidget()); - } - else - { - m_settings->reset("JvmArgs"); - } - - // old generic 'override both' is removed. - m_settings->reset("OverrideJava"); - - // Custom Commands - bool custcmd = ui->customCommands->checked(); - m_settings->set("OverrideCommands", custcmd); - if (custcmd) - { - m_settings->set("PreLaunchCommand", ui->customCommands->prelaunchCommand()); - m_settings->set("WrapperCommand", ui->customCommands->wrapperCommand()); - m_settings->set("PostExitCommand", ui->customCommands->postexitCommand()); - } - else - { - m_settings->reset("PreLaunchCommand"); - m_settings->reset("WrapperCommand"); - m_settings->reset("PostExitCommand"); - } + SettingsObject::Lock lock(m_settings); + + // Console + bool console = ui->consoleSettingsBox->isChecked(); + m_settings->set("OverrideConsole", console); + if (console) + { + m_settings->set("ShowConsole", ui->showConsoleCheck->isChecked()); + m_settings->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); + m_settings->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); + } + else + { + m_settings->reset("ShowConsole"); + m_settings->reset("AutoCloseConsole"); + m_settings->reset("ShowConsoleOnError"); + } + + // Window Size + bool window = ui->windowSizeGroupBox->isChecked(); + m_settings->set("OverrideWindow", window); + if (window) + { + m_settings->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); + m_settings->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); + m_settings->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); + } + else + { + m_settings->reset("LaunchMaximized"); + m_settings->reset("MinecraftWinWidth"); + m_settings->reset("MinecraftWinHeight"); + } + + // Memory + bool memory = ui->memoryGroupBox->isChecked(); + m_settings->set("OverrideMemory", memory); + if (memory) + { + int min = ui->minMemSpinBox->value(); + int max = ui->maxMemSpinBox->value(); + if(min < max) + { + m_settings->set("MinMemAlloc", min); + m_settings->set("MaxMemAlloc", max); + } + else + { + m_settings->set("MinMemAlloc", max); + m_settings->set("MaxMemAlloc", min); + } + m_settings->set("PermGen", ui->permGenSpinBox->value()); + } + else + { + m_settings->reset("MinMemAlloc"); + m_settings->reset("MaxMemAlloc"); + m_settings->reset("PermGen"); + } + + // Java Install Settings + bool javaInstall = ui->javaSettingsGroupBox->isChecked(); + m_settings->set("OverrideJavaLocation", javaInstall); + if (javaInstall) + { + m_settings->set("JavaPath", ui->javaPathTextBox->text()); + } + else + { + m_settings->reset("JavaPath"); + } + + // Java arguments + bool javaArgs = ui->javaArgumentsGroupBox->isChecked(); + m_settings->set("OverrideJavaArgs", javaArgs); + if(javaArgs) + { + m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); + JavaCommon::checkJVMArgs(m_settings->get("JvmArgs").toString(), this->parentWidget()); + } + else + { + m_settings->reset("JvmArgs"); + } + + // old generic 'override both' is removed. + m_settings->reset("OverrideJava"); + + // Custom Commands + bool custcmd = ui->customCommands->checked(); + m_settings->set("OverrideCommands", custcmd); + if (custcmd) + { + m_settings->set("PreLaunchCommand", ui->customCommands->prelaunchCommand()); + m_settings->set("WrapperCommand", ui->customCommands->wrapperCommand()); + m_settings->set("PostExitCommand", ui->customCommands->postexitCommand()); + } + else + { + m_settings->reset("PreLaunchCommand"); + m_settings->reset("WrapperCommand"); + m_settings->reset("PostExitCommand"); + } } void InstanceSettingsPage::loadSettings() { - // Console - ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); - ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); - ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool()); - ui->showConsoleErrorCheck->setChecked(m_settings->get("ShowConsoleOnError").toBool()); - - // Window Size - ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool()); - ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool()); - ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt()); - ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt()); - - // Memory - ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool()); - int min = m_settings->get("MinMemAlloc").toInt(); - int max = m_settings->get("MaxMemAlloc").toInt(); - if(min < max) - { - ui->minMemSpinBox->setValue(min); - ui->maxMemSpinBox->setValue(max); - } - else - { - ui->minMemSpinBox->setValue(max); - ui->maxMemSpinBox->setValue(min); - } - ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); - - // Java Settings - bool overrideJava = m_settings->get("OverrideJava").toBool(); - bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava; - bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava; - - ui->javaSettingsGroupBox->setChecked(overrideLocation); - ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); - - ui->javaArgumentsGroupBox->setChecked(overrideArgs); - ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); - - // Custom commands - ui->customCommands->initialize( - true, - m_settings->get("OverrideCommands").toBool(), - m_settings->get("PreLaunchCommand").toString(), - m_settings->get("WrapperCommand").toString(), - m_settings->get("PostExitCommand").toString() - ); + // Console + ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); + ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); + ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool()); + ui->showConsoleErrorCheck->setChecked(m_settings->get("ShowConsoleOnError").toBool()); + + // Window Size + ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool()); + ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool()); + ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt()); + ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt()); + + // Memory + ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool()); + int min = m_settings->get("MinMemAlloc").toInt(); + int max = m_settings->get("MaxMemAlloc").toInt(); + if(min < max) + { + ui->minMemSpinBox->setValue(min); + ui->maxMemSpinBox->setValue(max); + } + else + { + ui->minMemSpinBox->setValue(max); + ui->maxMemSpinBox->setValue(min); + } + ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); + bool permGenVisible = m_settings->get("PermGenVisible").toBool(); + ui->permGenSpinBox->setVisible(permGenVisible); + ui->labelPermGen->setVisible(permGenVisible); + ui->labelPermgenNote->setVisible(permGenVisible); + + + // Java Settings + bool overrideJava = m_settings->get("OverrideJava").toBool(); + bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava; + bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava; + + ui->javaSettingsGroupBox->setChecked(overrideLocation); + ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); + + ui->javaArgumentsGroupBox->setChecked(overrideArgs); + ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); + + // Custom commands + ui->customCommands->initialize( + true, + m_settings->get("OverrideCommands").toBool(), + m_settings->get("PreLaunchCommand").toString(), + m_settings->get("WrapperCommand").toString(), + m_settings->get("PostExitCommand").toString() + ); } void InstanceSettingsPage::on_javaDetectBtn_clicked() { - JavaInstallPtr java; + JavaInstallPtr java; - VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); - vselect.setResizeOn(2); - vselect.exec(); + VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); + vselect.setResizeOn(2); + vselect.exec(); - if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) - { - java = std::dynamic_pointer_cast(vselect.selectedVersion()); - ui->javaPathTextBox->setText(java->path); - } + if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) + { + java = std::dynamic_pointer_cast(vselect.selectedVersion()); + ui->javaPathTextBox->setText(java->path); + bool visible = java->id.requiresPermGen() && m_settings->get("OverrideMemory").toBool(); + ui->permGenSpinBox->setVisible(visible); + ui->labelPermGen->setVisible(visible); + ui->labelPermgenNote->setVisible(visible); + m_settings->set("PermGenVisible", visible); + } } void InstanceSettingsPage::on_javaBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if(raw_path.isEmpty()) - { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - - QFileInfo javaInfo(cooked_path);; - if(!javaInfo.exists() || !javaInfo.isExecutable()) - { - return; - } - ui->javaPathTextBox->setText(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if(raw_path.isEmpty()) + { + return; + } + QString cooked_path = FS::NormalizePath(raw_path); + + QFileInfo javaInfo(cooked_path); + if(!javaInfo.exists() || !javaInfo.isExecutable()) + { + return; + } + ui->javaPathTextBox->setText(cooked_path); + + // custom Java could be anything... enable perm gen option + ui->permGenSpinBox->setVisible(true); + ui->labelPermGen->setVisible(true); + ui->labelPermgenNote->setVisible(true); + m_settings->set("PermGenVisible", true); } void InstanceSettingsPage::on_javaTestBtn_clicked() { - if(checker) - { - return; - } - checker.reset(new JavaCommon::TestCheck( - this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "), - ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); - connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); - checker->run(); + if(checker) + { + return; + } + checker.reset(new JavaCommon::TestCheck( + this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "), + ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); + connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); + checker->run(); } void InstanceSettingsPage::checkerFinished() { - checker.reset(); + checker.reset(); } diff --git a/application/pages/instance/InstanceSettingsPage.h b/application/pages/instance/InstanceSettingsPage.h index c5d7d3b6..c3c78fd5 100644 --- a/application/pages/instance/InstanceSettingsPage.h +++ b/application/pages/instance/InstanceSettingsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,43 +32,45 @@ class InstanceSettingsPage; class InstanceSettingsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit InstanceSettingsPage(BaseInstance *inst, QWidget *parent = 0); - virtual ~InstanceSettingsPage(); - virtual QString displayName() const override - { - return tr("Settings"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("instance-settings"); - } - virtual QString id() const override - { - return "settings"; - } - virtual bool apply() override; - virtual QString helpPage() const override - { - return "Instance-settings"; - } - virtual bool shouldDisplay() const override; + explicit InstanceSettingsPage(BaseInstance *inst, QWidget *parent = 0); + virtual ~InstanceSettingsPage(); + virtual QString displayName() const override + { + return tr("Settings"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("instance-settings"); + } + virtual QString id() const override + { + return "settings"; + } + virtual bool apply() override; + virtual QString helpPage() const override + { + return "Instance-settings"; + } + virtual bool shouldDisplay() const override; private slots: - void on_javaDetectBtn_clicked(); - void on_javaTestBtn_clicked(); - void on_javaBrowseBtn_clicked(); + void on_javaDetectBtn_clicked(); + void on_javaTestBtn_clicked(); + void on_javaBrowseBtn_clicked(); - void applySettings(); - void loadSettings(); + void applySettings(); + void loadSettings(); - void checkerFinished(); + void checkerFinished(); + + void globalSettingsButtonClicked(bool checked); private: - Ui::InstanceSettingsPage *ui; - BaseInstance *m_instance; - SettingsObjectPtr m_settings; - unique_qobject_ptr checker; + Ui::InstanceSettingsPage *ui; + BaseInstance *m_instance; + SettingsObjectPtr m_settings; + unique_qobject_ptr checker; }; diff --git a/application/pages/instance/InstanceSettingsPage.ui b/application/pages/instance/InstanceSettingsPage.ui index 0c180df3..d6de53ee 100644 --- a/application/pages/instance/InstanceSettingsPage.ui +++ b/application/pages/instance/InstanceSettingsPage.ui @@ -6,8 +6,8 @@ 0 0 - 553 - 522 + 738 + 804 @@ -23,6 +23,16 @@ 0 + + + + Open Global Settings + + + The settings here are overrides for global settings. + + + @@ -367,6 +377,7 @@ + openGlobalJavaSettingsButton settingsTabs javaSettingsGroupBox javaPathTextBox diff --git a/application/pages/instance/LegacyUpgradePage.cpp b/application/pages/instance/LegacyUpgradePage.cpp index f808ab88..af800b03 100644 --- a/application/pages/instance/LegacyUpgradePage.cpp +++ b/application/pages/instance/LegacyUpgradePage.cpp @@ -1,50 +1,50 @@ #include "LegacyUpgradePage.h" #include "ui_LegacyUpgradePage.h" +#include "InstanceList.h" #include "minecraft/legacy/LegacyInstance.h" #include "minecraft/legacy/LegacyUpgradeTask.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/ProgressDialog.h" LegacyUpgradePage::LegacyUpgradePage(InstancePtr inst, QWidget *parent) - : QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst) + : QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst) { - ui->setupUi(this); + ui->setupUi(this); } LegacyUpgradePage::~LegacyUpgradePage() { - delete ui; + delete ui; } void LegacyUpgradePage::runModalTask(Task *task) { - connect(task, &Task::failed, [this](QString reason) - { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show(); - }); - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - if(loadDialog.execWithTask(task) == QDialog::Accepted) - { - m_container->requestClose(); - } + connect(task, &Task::failed, [this](QString reason) + { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show(); + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + if(loadDialog.execWithTask(task) == QDialog::Accepted) + { + m_container->requestClose(); + } } void LegacyUpgradePage::on_upgradeButton_clicked() { - QString newName = tr("%1 (Migrated)").arg(m_inst->name()); - auto upgradeTask = new LegacyUpgradeTask(m_inst); - upgradeTask->setName(newName); - upgradeTask->setGroup(m_inst->group()); - upgradeTask->setIcon(m_inst->iconKey()); - std::unique_ptr task(MMC->folderProvider()->wrapInstanceTask(upgradeTask)); - runModalTask(task.get()); + QString newName = tr("%1 (Migrated)").arg(m_inst->name()); + auto upgradeTask = new LegacyUpgradeTask(m_inst); + upgradeTask->setName(newName); + upgradeTask->setGroup(MMC->instances()->getInstanceGroup(m_inst->id())); + upgradeTask->setIcon(m_inst->iconKey()); + unique_qobject_ptr task(MMC->instances()->wrapInstanceTask(upgradeTask)); + runModalTask(task.get()); } bool LegacyUpgradePage::shouldDisplay() const { - return !m_inst->isRunning(); + return !m_inst->isRunning(); } diff --git a/application/pages/instance/LegacyUpgradePage.h b/application/pages/instance/LegacyUpgradePage.h index 3e1abe93..4f86a721 100644 --- a/application/pages/instance/LegacyUpgradePage.h +++ b/application/pages/instance/LegacyUpgradePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,36 +29,36 @@ class LegacyUpgradePage; class LegacyUpgradePage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit LegacyUpgradePage(InstancePtr inst, QWidget *parent = 0); - virtual ~LegacyUpgradePage(); - virtual QString displayName() const override - { - return tr("Upgrade"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("checkupdate"); - } - virtual QString id() const override - { - return "upgrade"; - } - virtual QString helpPage() const override - { - return "Legacy-upgrade"; - } - virtual bool shouldDisplay() const override; + explicit LegacyUpgradePage(InstancePtr inst, QWidget *parent = 0); + virtual ~LegacyUpgradePage(); + virtual QString displayName() const override + { + return tr("Upgrade"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("checkupdate"); + } + virtual QString id() const override + { + return "upgrade"; + } + virtual QString helpPage() const override + { + return "Legacy-upgrade"; + } + virtual bool shouldDisplay() const override; private slots: - void on_upgradeButton_clicked(); + void on_upgradeButton_clicked(); private: - void runModalTask(Task *task); + void runModalTask(Task *task); private: - Ui::LegacyUpgradePage *ui; - InstancePtr m_inst; + Ui::LegacyUpgradePage *ui; + InstancePtr m_inst; }; diff --git a/application/pages/instance/LogPage.cpp b/application/pages/instance/LogPage.cpp index 0fa1ee67..94ada424 100644 --- a/application/pages/instance/LogPage.cpp +++ b/application/pages/instance/LogPage.cpp @@ -15,298 +15,298 @@ class LogFormatProxyModel : public QIdentityProxyModel { public: - LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) - { - } - QVariant data(const QModelIndex &index, int role) const override - { - switch(role) - { - case Qt::FontRole: - return m_font; - case Qt::TextColorRole: - { - MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); - return m_colors->getFront(level); - } - case Qt::BackgroundRole: - { - MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); - return m_colors->getBack(level); - } - default: - return QIdentityProxyModel::data(index, role); - } - } - - void setFont(QFont font) - { - m_font = font; - } - - void setColors(LogColorCache* colors) - { - m_colors.reset(colors); - } - - QModelIndex find(const QModelIndex &start, const QString &value, bool reverse) const - { - QModelIndex parentIndex = parent(start); - auto compare = [&](int r) -> QModelIndex - { - QModelIndex idx = index(r, start.column(), parentIndex); - if (!idx.isValid() || idx == start) - { - return QModelIndex(); - } - QVariant v = data(idx, Qt::DisplayRole); - QString t = v.toString(); - if (t.contains(value, Qt::CaseInsensitive)) - return idx; - return QModelIndex(); - }; - if(reverse) - { - int from = start.row(); - int to = 0; - - for (int i = 0; i < 2; ++i) - { - for (int r = from; (r >= to); --r) - { - auto idx = compare(r); - if(idx.isValid()) - return idx; - } - // prepare for the next iteration - from = rowCount() - 1; - to = start.row(); - } - } - else - { - int from = start.row(); - int to = rowCount(parentIndex); - - for (int i = 0; i < 2; ++i) - { - for (int r = from; (r < to); ++r) - { - auto idx = compare(r); - if(idx.isValid()) - return idx; - } - // prepare for the next iteration - from = 0; - to = start.row(); - } - } - return QModelIndex(); - } + LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) + { + } + QVariant data(const QModelIndex &index, int role) const override + { + switch(role) + { + case Qt::FontRole: + return m_font; + case Qt::TextColorRole: + { + MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); + return m_colors->getFront(level); + } + case Qt::BackgroundRole: + { + MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); + return m_colors->getBack(level); + } + default: + return QIdentityProxyModel::data(index, role); + } + } + + void setFont(QFont font) + { + m_font = font; + } + + void setColors(LogColorCache* colors) + { + m_colors.reset(colors); + } + + QModelIndex find(const QModelIndex &start, const QString &value, bool reverse) const + { + QModelIndex parentIndex = parent(start); + auto compare = [&](int r) -> QModelIndex + { + QModelIndex idx = index(r, start.column(), parentIndex); + if (!idx.isValid() || idx == start) + { + return QModelIndex(); + } + QVariant v = data(idx, Qt::DisplayRole); + QString t = v.toString(); + if (t.contains(value, Qt::CaseInsensitive)) + return idx; + return QModelIndex(); + }; + if(reverse) + { + int from = start.row(); + int to = 0; + + for (int i = 0; i < 2; ++i) + { + for (int r = from; (r >= to); --r) + { + auto idx = compare(r); + if(idx.isValid()) + return idx; + } + // prepare for the next iteration + from = rowCount() - 1; + to = start.row(); + } + } + else + { + int from = start.row(); + int to = rowCount(parentIndex); + + for (int i = 0; i < 2; ++i) + { + for (int r = from; (r < to); ++r) + { + auto idx = compare(r); + if(idx.isValid()) + return idx; + } + // prepare for the next iteration + from = 0; + to = start.row(); + } + } + return QModelIndex(); + } private: - QFont m_font; - std::unique_ptr m_colors; + QFont m_font; + std::unique_ptr m_colors; }; LogPage::LogPage(InstancePtr instance, QWidget *parent) - : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) + : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - - m_proxy = new LogFormatProxyModel(this); - // set up text colors in the log proxy and adapt them to the current theme foreground and background - { - auto origForeground = ui->text->palette().color(ui->text->foregroundRole()); - auto origBackground = ui->text->palette().color(ui->text->backgroundRole()); - m_proxy->setColors(new LogColorCache(origForeground, origBackground)); - } - - // set up fonts in the log proxy - { - QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if(!conversionOk) - { - fontSize = 11; - } - m_proxy->setFont(QFont(fontFamily, fontSize)); - } - - ui->text->setModel(m_proxy); - - // set up instance and launch process recognition - { - auto launchTask = m_instance->getLaunchTask(); - if(launchTask) - { - setInstanceLaunchTaskChanged(launchTask, true); - } - connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged); - } - - auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); - connect(findShortcut, SIGNAL(activated()), SLOT(findActivated())); - auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); - connect(findNextShortcut, SIGNAL(activated()), SLOT(findNextActivated())); - connect(ui->searchBar, SIGNAL(returnPressed()), SLOT(on_findButton_clicked())); - auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); - connect(findPreviousShortcut, SIGNAL(activated()), SLOT(findPreviousActivated())); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + + m_proxy = new LogFormatProxyModel(this); + // set up text colors in the log proxy and adapt them to the current theme foreground and background + { + auto origForeground = ui->text->palette().color(ui->text->foregroundRole()); + auto origBackground = ui->text->palette().color(ui->text->backgroundRole()); + m_proxy->setColors(new LogColorCache(origForeground, origBackground)); + } + + // set up fonts in the log proxy + { + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + m_proxy->setFont(QFont(fontFamily, fontSize)); + } + + ui->text->setModel(m_proxy); + + // set up instance and launch process recognition + { + auto launchTask = m_instance->getLaunchTask(); + if(launchTask) + { + setInstanceLaunchTaskChanged(launchTask, true); + } + connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged); + } + + auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); + connect(findShortcut, SIGNAL(activated()), SLOT(findActivated())); + auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); + connect(findNextShortcut, SIGNAL(activated()), SLOT(findNextActivated())); + connect(ui->searchBar, SIGNAL(returnPressed()), SLOT(on_findButton_clicked())); + auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); + connect(findPreviousShortcut, SIGNAL(activated()), SLOT(findPreviousActivated())); } LogPage::~LogPage() { - delete ui; + delete ui; } void LogPage::modelStateToUI() { - if(m_model->wrapLines()) - { - ui->text->setWordWrap(true); - ui->wrapCheckbox->setCheckState(Qt::Checked); - } - else - { - ui->text->setWordWrap(false); - ui->wrapCheckbox->setCheckState(Qt::Unchecked); - } - if(m_model->suspended()) - { - ui->trackLogCheckbox->setCheckState(Qt::Unchecked); - } - else - { - ui->trackLogCheckbox->setCheckState(Qt::Checked); - } + if(m_model->wrapLines()) + { + ui->text->setWordWrap(true); + ui->wrapCheckbox->setCheckState(Qt::Checked); + } + else + { + ui->text->setWordWrap(false); + ui->wrapCheckbox->setCheckState(Qt::Unchecked); + } + if(m_model->suspended()) + { + ui->trackLogCheckbox->setCheckState(Qt::Unchecked); + } + else + { + ui->trackLogCheckbox->setCheckState(Qt::Checked); + } } void LogPage::UIToModelState() { - if(!m_model) - { - return; - } - m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); - m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); + if(!m_model) + { + return; + } + m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); + m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); } -void LogPage::setInstanceLaunchTaskChanged(std::shared_ptr proc, bool initial) +void LogPage::setInstanceLaunchTaskChanged(shared_qobject_ptr proc, bool initial) { - m_process = proc; - if(m_process) - { - m_model = proc->getLogModel(); - m_proxy->setSourceModel(m_model.get()); - if(initial) - { - modelStateToUI(); - } - else - { - UIToModelState(); - } - } - else - { - m_proxy->setSourceModel(nullptr); - m_model.reset(); - } + m_process = proc; + if(m_process) + { + m_model = proc->getLogModel(); + m_proxy->setSourceModel(m_model.get()); + if(initial) + { + modelStateToUI(); + } + else + { + UIToModelState(); + } + } + else + { + m_proxy->setSourceModel(nullptr); + m_model.reset(); + } } -void LogPage::onInstanceLaunchTaskChanged(std::shared_ptr proc) +void LogPage::onInstanceLaunchTaskChanged(shared_qobject_ptr proc) { - setInstanceLaunchTaskChanged(proc, false); + setInstanceLaunchTaskChanged(proc, false); } bool LogPage::apply() { - return true; + return true; } bool LogPage::shouldDisplay() const { - return m_instance->isRunning() || m_proxy->rowCount() > 0; + return m_instance->isRunning() || m_proxy->rowCount() > 0; } void LogPage::on_btnPaste_clicked() { - if(!m_model) - return; - - //FIXME: turn this into a proper task and move the upload logic out of GuiUtil! - m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); - auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this); - if(!url.isEmpty()) - { - m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url)); - } - else - { - m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!")); - } + if(!m_model) + return; + + //FIXME: turn this into a proper task and move the upload logic out of GuiUtil! + m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); + auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this); + if(!url.isEmpty()) + { + m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url)); + } + else + { + m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!")); + } } void LogPage::on_btnCopy_clicked() { - if(!m_model) - return; - m_model->append(MessageLevel::MultiMC, QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); - GuiUtil::setClipboardText(m_model->toPlainText()); + if(!m_model) + return; + m_model->append(MessageLevel::MultiMC, QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); + GuiUtil::setClipboardText(m_model->toPlainText()); } void LogPage::on_btnClear_clicked() { - if(!m_model) - return; - m_model->clear(); - m_container->refreshContainer(); + if(!m_model) + return; + m_model->clear(); + m_container->refreshContainer(); } void LogPage::on_btnBottom_clicked() { - ui->text->scrollToBottom(); + ui->text->scrollToBottom(); } void LogPage::on_trackLogCheckbox_clicked(bool checked) { - if(!m_model) - return; - m_model->suspend(!checked); + if(!m_model) + return; + m_model->suspend(!checked); } void LogPage::on_wrapCheckbox_clicked(bool checked) { - ui->text->setWordWrap(checked); - if(!m_model) - return; - m_model->setLineWrap(checked); + ui->text->setWordWrap(checked); + if(!m_model) + return; + m_model->setLineWrap(checked); } void LogPage::on_findButton_clicked() { - auto modifiers = QApplication::keyboardModifiers(); - bool reverse = modifiers & Qt::ShiftModifier; - ui->text->findNext(ui->searchBar->text(), reverse); + auto modifiers = QApplication::keyboardModifiers(); + bool reverse = modifiers & Qt::ShiftModifier; + ui->text->findNext(ui->searchBar->text(), reverse); } void LogPage::findNextActivated() { - ui->text->findNext(ui->searchBar->text(), false); + ui->text->findNext(ui->searchBar->text(), false); } void LogPage::findPreviousActivated() { - ui->text->findNext(ui->searchBar->text(), true); + ui->text->findNext(ui->searchBar->text(), true); } void LogPage::findActivated() { - // focus the search bar if it doesn't have focus - if (!ui->searchBar->hasFocus()) - { - ui->searchBar->setFocus(); - ui->searchBar->selectAll(); - } + // focus the search bar if it doesn't have focus + if (!ui->searchBar->hasFocus()) + { + ui->searchBar->setFocus(); + ui->searchBar->selectAll(); + } } diff --git a/application/pages/instance/LogPage.h b/application/pages/instance/LogPage.h index 2229418d..9c3b56a9 100644 --- a/application/pages/instance/LogPage.h +++ b/application/pages/instance/LogPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,56 +31,56 @@ class LogFormatProxyModel; class LogPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit LogPage(InstancePtr instance, QWidget *parent = 0); - virtual ~LogPage(); - virtual QString displayName() const override - { - return tr("Minecraft Log"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("log"); - } - virtual QString id() const override - { - return "console"; - } - virtual bool apply() override; - virtual QString helpPage() const override - { - return "Minecraft-Logs"; - } - virtual bool shouldDisplay() const override; + explicit LogPage(InstancePtr instance, QWidget *parent = 0); + virtual ~LogPage(); + virtual QString displayName() const override + { + return tr("Minecraft Log"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("log"); + } + virtual QString id() const override + { + return "console"; + } + virtual bool apply() override; + virtual QString helpPage() const override + { + return "Minecraft-Logs"; + } + virtual bool shouldDisplay() const override; private slots: - void on_btnPaste_clicked(); - void on_btnCopy_clicked(); - void on_btnClear_clicked(); - void on_btnBottom_clicked(); + void on_btnPaste_clicked(); + void on_btnCopy_clicked(); + void on_btnClear_clicked(); + void on_btnBottom_clicked(); - void on_trackLogCheckbox_clicked(bool checked); - void on_wrapCheckbox_clicked(bool checked); + void on_trackLogCheckbox_clicked(bool checked); + void on_wrapCheckbox_clicked(bool checked); - void on_findButton_clicked(); - void findActivated(); - void findNextActivated(); - void findPreviousActivated(); + void on_findButton_clicked(); + void findActivated(); + void findNextActivated(); + void findPreviousActivated(); - void onInstanceLaunchTaskChanged(std::shared_ptr proc); + void onInstanceLaunchTaskChanged(shared_qobject_ptr proc); private: - void modelStateToUI(); - void UIToModelState(); - void setInstanceLaunchTaskChanged(std::shared_ptr proc, bool initial); + void modelStateToUI(); + void UIToModelState(); + void setInstanceLaunchTaskChanged(shared_qobject_ptr proc, bool initial); private: - Ui::LogPage *ui; - InstancePtr m_instance; - std::shared_ptr m_process; + Ui::LogPage *ui; + InstancePtr m_instance; + shared_qobject_ptr m_process; - LogFormatProxyModel * m_proxy; - shared_qobject_ptr m_model; + LogFormatProxyModel * m_proxy; + shared_qobject_ptr m_model; }; diff --git a/application/pages/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp index 90155df3..d449f8bf 100644 --- a/application/pages/instance/ModFolderPage.cpp +++ b/application/pages/instance/ModFolderPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,191 +20,324 @@ #include #include #include +#include #include "MultiMC.h" #include "dialogs/CustomMessageBox.h" -#include "dialogs/ModEditDialogCommon.h" #include -#include "minecraft/ModList.h" -#include "minecraft/Mod.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/Mod.h" #include "minecraft/VersionFilterData.h" #include "minecraft/ComponentList.h" #include -ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr mods, QString id, - QString iconName, QString displayName, QString helpPage, - QWidget *parent) - : QWidget(parent), ui(new Ui::ModFolderPage) -{ - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - m_inst = inst; - m_mods = mods; - m_id = id; - m_displayName = displayName; - m_iconName = iconName; - m_helpName = helpPage; - m_fileSelectionFilter = "%1 (*.zip *.jar)"; - m_filterModel = new QSortFilterProxyModel(this); - m_filterModel->setDynamicSortFilter(true); - m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSourceModel(m_mods.get()); - m_filterModel->setFilterKeyColumn(-1); - ui->modTreeView->setModel(m_filterModel); - ui->modTreeView->installEventFilter(this); - ui->modTreeView->sortByColumn(1, Qt::AscendingOrder); - auto smodel = ui->modTreeView->selectionModel(); - connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged ); +#include +#include "Version.h" + +namespace { + // FIXME: wasteful + void RemoveThePrefix(QString & string) { + QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +")); + string.remove(regex); + string = string.trimmed(); + } +} + +class ModSortProxy : public QSortFilterProxyModel +{ +public: + explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent) + { + } + +protected: + bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override + { + ModFolderModel *model = qobject_cast(sourceModel()); + if( + !model || + !source_left.isValid() || + !source_right.isValid() || + source_left.column() != source_right.column() + ) { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + + // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed. + + auto column = (ModFolderModel::Columns) source_left.column(); + bool invert = false; + switch(column) { + // GH-2550 - sort by enabled/disabled + case ModFolderModel::ActiveColumn: { + auto dataL = source_left.data(Qt::CheckStateRole).toBool(); + auto dataR = source_right.data(Qt::CheckStateRole).toBool(); + if(dataL != dataR) { + return dataL > dataR; + } + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2722 - sort mod names in a way that discards "The" prefixes + case ModFolderModel::NameColumn: { + auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataL); + auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataR); + + auto less = dataL.compare(dataR, sortCaseSensitivity()); + if(less != 0) { + return invert ? (less > 0) : (less < 0); + } + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2762 - sort versions by parsing them as versions + case ModFolderModel::VersionColumn: { + auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString()); + auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString()); + return invert ? (dataL > dataR) : (dataL < dataR); + } + default: { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + } + } +}; + +ModFolderPage::ModFolderPage( + BaseInstance *inst, + std::shared_ptr mods, + QString id, + QString iconName, + QString displayName, + QString helpPage, + QWidget *parent +) : + QMainWindow(parent), + ui(new Ui::ModFolderPage) +{ + ui->setupUi(this); + ui->actionsToolbar->insertSpacer(ui->actionView_configs); + + m_inst = inst; + on_RunningState_changed(m_inst && m_inst->isRunning()); + m_mods = mods; + m_id = id; + m_displayName = displayName; + m_iconName = iconName; + m_helpName = helpPage; + m_fileSelectionFilter = "%1 (*.zip *.jar)"; + m_filterModel = new ModSortProxy(this); + m_filterModel->setDynamicSortFilter(true); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSourceModel(m_mods.get()); + m_filterModel->setFilterKeyColumn(-1); + ui->modTreeView->setModel(m_filterModel); + ui->modTreeView->installEventFilter(this); + ui->modTreeView->sortByColumn(1, Qt::AscendingOrder); + ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu); + connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated); + + auto smodel = ui->modTreeView->selectionModel(); + connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent); + connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged ); + connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed); +} + +void ModFolderPage::modItemActivated(const QModelIndex&) +{ + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle); +} + +QMenu * ModFolderPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() ); + return filteredMenu; +} + +void ModFolderPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->modTreeView->mapToGlobal(pos)); + delete menu; } void ModFolderPage::openedImpl() { - m_mods->startWatching(); + m_mods->startWatching(); } void ModFolderPage::closedImpl() { - m_mods->stopWatching(); + m_mods->stopWatching(); } void ModFolderPage::on_filterTextChanged(const QString& newContents) { - m_viewFilter = newContents; - m_filterModel->setFilterFixedString(m_viewFilter); + m_viewFilter = newContents; + m_filterModel->setFilterFixedString(m_viewFilter); } -CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr mods, - QString id, QString iconName, QString displayName, - QString helpPage, QWidget *parent) - : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent) +CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr mods, + QString id, QString iconName, QString displayName, + QString helpPage, QWidget *parent) + : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent) { } ModFolderPage::~ModFolderPage() { - m_mods->stopWatching(); - delete ui; + m_mods->stopWatching(); + delete ui; +} + +void ModFolderPage::on_RunningState_changed(bool running) +{ + if(m_controlsEnabled == !running) { + return; + } + m_controlsEnabled = !running; + ui->actionAdd->setEnabled(m_controlsEnabled); + ui->actionDisable->setEnabled(m_controlsEnabled); + ui->actionEnable->setEnabled(m_controlsEnabled); + ui->actionRemove->setEnabled(m_controlsEnabled); } bool ModFolderPage::shouldDisplay() const { - if (m_inst) - return !m_inst->isRunning(); - return true; + return true; } bool CoreModFolderPage::shouldDisplay() const { - if (ModFolderPage::shouldDisplay()) - { - auto inst = dynamic_cast(m_inst); - if (!inst) - return true; - auto version = inst->getComponentList(); - if (!version) - return true; - if(!version->getComponent("net.minecraftforge")) - { - return false; - } - if(!version->getComponent("net.minecraft")) - { - return false; - } - if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) - { - return true; - } - } - return false; + if (ModFolderPage::shouldDisplay()) + { + auto inst = dynamic_cast(m_inst); + if (!inst) + return true; + auto version = inst->getComponentList(); + if (!version) + return true; + if(!version->getComponent("net.minecraftforge")) + { + return false; + } + if(!version->getComponent("net.minecraft")) + { + return false; + } + if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) + { + return true; + } + } + return false; } bool ModFolderPage::modListFilter(QKeyEvent *keyEvent) { - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; - } - return QWidget::eventFilter(ui->modTreeView, keyEvent); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_actionRemove_triggered(); + return true; + case Qt::Key_Plus: + on_actionAdd_triggered(); + return true; + default: + break; + } + return QWidget::eventFilter(ui->modTreeView, keyEvent); } bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev) { - if (ev->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast(ev); - if (obj == ui->modTreeView) - return modListFilter(keyEvent); - return QWidget::eventFilter(obj, ev); + if (ev->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast(ev); + if (obj == ui->modTreeView) + return modListFilter(keyEvent); + return QWidget::eventFilter(obj, ev); } -void ModFolderPage::on_addModBtn_clicked() +void ModFolderPage::on_actionAdd_triggered() { - auto list = GuiUtil::BrowseForFiles( - m_helpName, - tr("Select %1", - "Select whatever type of files the page contains. Example: 'Loader Mods'") - .arg(m_displayName), - m_fileSelectionFilter.arg(m_displayName), MMC->settings()->get("CentralModsDir").toString(), - this->parentWidget()); - if (!list.empty()) - { - for (auto filename : list) - { - m_mods->installMod(filename); - } - } + if(!m_controlsEnabled) { + return; + } + auto list = GuiUtil::BrowseForFiles( + m_helpName, + tr("Select %1", + "Select whatever type of files the page contains. Example: 'Loader Mods'") + .arg(m_displayName), + m_fileSelectionFilter.arg(m_displayName), MMC->settings()->get("CentralModsDir").toString(), + this->parentWidget()); + if (!list.empty()) + { + for (auto filename : list) + { + m_mods->installMod(filename); + } + } } -void ModFolderPage::on_enableModBtn_clicked() +void ModFolderPage::on_actionEnable_triggered() { - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), true); + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable); } -void ModFolderPage::on_disableModBtn_clicked() +void ModFolderPage::on_actionDisable_triggered() { - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), false); + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable); } -void ModFolderPage::on_rmModBtn_clicked() +void ModFolderPage::on_actionRemove_triggered() { - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->deleteMods(selection.indexes()); + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->deleteMods(selection.indexes()); } -void ModFolderPage::on_configFolderBtn_clicked() +void ModFolderPage::on_actionView_configs_triggered() { - DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); + DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); } -void ModFolderPage::on_viewModBtn_clicked() +void ModFolderPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_mods->dir().absolutePath(), true); + DesktopServices::openDirectory(m_mods->dir().absolutePath(), true); } void ModFolderPage::modCurrent(const QModelIndex ¤t, const QModelIndex &previous) { - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - auto sourceCurrent = m_filterModel->mapToSource(current); - int row = sourceCurrent.row(); - Mod &m = m_mods->operator[](row); - ui->frame->updateWithMod(m); + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + Mod &m = m_mods->operator[](row); + ui->frame->updateWithMod(m); } diff --git a/application/pages/instance/ModFolderPage.h b/application/pages/instance/ModFolderPage.h index 15e728cb..d49d25c3 100644 --- a/application/pages/instance/ModFolderPage.h +++ b/application/pages/instance/ModFolderPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,94 +15,105 @@ #pragma once -#include +#include #include "minecraft/MinecraftInstance.h" #include "pages/BasePage.h" #include -class ModList; +class ModFolderModel; namespace Ui { class ModFolderPage; } -class ModFolderPage : public QWidget, public BasePage +class ModFolderPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ModFolderPage(BaseInstance *inst, std::shared_ptr mods, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~ModFolderPage(); + explicit ModFolderPage( + BaseInstance *inst, + std::shared_ptr mods, + QString id, + QString iconName, + QString displayName, + QString helpPage = "", + QWidget *parent = 0 + ); + virtual ~ModFolderPage(); - void setFilter(const QString & filter) - { - m_fileSelectionFilter = filter; - } + void setFilter(const QString & filter) + { + m_fileSelectionFilter = filter; + } - virtual QString displayName() const override - { - return m_displayName; - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon(m_iconName); - } - virtual QString id() const override - { - return m_id; - } - virtual QString helpPage() const override - { - return m_helpName; - } - virtual bool shouldDisplay() const override; + virtual QString displayName() const override + { + return m_displayName; + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon(m_iconName); + } + virtual QString id() const override + { + return m_id; + } + virtual QString helpPage() const override + { + return m_helpName; + } + virtual bool shouldDisplay() const override; - virtual void openedImpl() override; - virtual void closedImpl() override; + virtual void openedImpl() override; + virtual void closedImpl() override; protected: - bool eventFilter(QObject *obj, QEvent *ev) override; - bool modListFilter(QKeyEvent *ev); + bool eventFilter(QObject *obj, QEvent *ev) override; + bool modListFilter(QKeyEvent *ev); + QMenu * createPopupMenu() override; protected: - BaseInstance *m_inst; + BaseInstance *m_inst = nullptr; protected: - Ui::ModFolderPage *ui; - std::shared_ptr m_mods; - QSortFilterProxyModel *m_filterModel; - QString m_iconName; - QString m_id; - QString m_displayName; - QString m_helpName; - QString m_fileSelectionFilter; - QString m_viewFilter; + Ui::ModFolderPage *ui = nullptr; + std::shared_ptr m_mods; + QSortFilterProxyModel *m_filterModel = nullptr; + QString m_iconName; + QString m_id; + QString m_displayName; + QString m_helpName; + QString m_fileSelectionFilter; + QString m_viewFilter; + bool m_controlsEnabled = true; public slots: - void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); + void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); private slots: - void on_filterTextChanged(const QString & newContents); - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - void on_enableModBtn_clicked(); - void on_disableModBtn_clicked(); - void on_configFolderBtn_clicked(); + void modItemActivated(const QModelIndex &index); + void on_filterTextChanged(const QString & newContents); + void on_RunningState_changed(bool running); + void on_actionAdd_triggered(); + void on_actionRemove_triggered(); + void on_actionEnable_triggered(); + void on_actionDisable_triggered(); + void on_actionView_Folder_triggered(); + void on_actionView_configs_triggered(); + void ShowContextMenu(const QPoint &pos); }; class CoreModFolderPage : public ModFolderPage { public: - explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr mods, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~CoreModFolderPage() - { - } - virtual bool shouldDisplay() const; + explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr mods, QString id, + QString iconName, QString displayName, QString helpPage = "", + QWidget *parent = 0); + virtual ~CoreModFolderPage() + { + } + virtual bool shouldDisplay() const; }; diff --git a/application/pages/instance/ModFolderPage.ui b/application/pages/instance/ModFolderPage.ui index b5597bdc..052df602 100644 --- a/application/pages/instance/ModFolderPage.ui +++ b/application/pages/instance/ModFolderPage.ui @@ -1,155 +1,144 @@ ModFolderPage - + 0 0 - 723 - 532 + 1042 + 501 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true + + + + + + + Filter: + + + + + + + + + + 0 + 0 + + + + + + - + 0 0 - - Tab 1 - - - - - - - - &Add - - - - - - - &Remove - - - - - - - Enable - - - - - - - Disable - - - - - - - Open the 'config' folder in the system file manager. - - - View configs - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - &View Folder - - - - - - - - - - 0 - 0 - - - - - - - - - - true - - - - - - - Filter: - - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::DropOnly - - - - - - + + true + + + QAbstractItemView::DropOnly + - - - + + + + + + Actions + + + Qt::ToolButtonTextOnly + + + RightToolBarArea + + + false + + + + + + + + + + + + &Add + + + Add mods + + + + + &Remove + + + Remove selected mods + + + + + &Enable + + + Enable selected mods + + + + + &Disable + + + Disable selected mods + + + + + View &Configs + + + Open the 'config' folder in the system file manager. + + + + + View &Folder + + @@ -163,17 +152,15 @@
widgets/MCModInfoFrame.h
1
+ + WideBar + QToolBar +
widgets/WideBar.h
+
- tabWidget modTreeView filterEdit - addModBtn - rmModBtn - enableModBtn - disableModBtn - configFolderBtn - viewModBtn diff --git a/application/pages/instance/NotesPage.cpp b/application/pages/instance/NotesPage.cpp index 3925fdfc..fa966c91 100644 --- a/application/pages/instance/NotesPage.cpp +++ b/application/pages/instance/NotesPage.cpp @@ -3,20 +3,19 @@ #include NotesPage::NotesPage(BaseInstance *inst, QWidget *parent) - : QWidget(parent), ui(new Ui::NotesPage), m_inst(inst) + : QWidget(parent), ui(new Ui::NotesPage), m_inst(inst) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - ui->noteEditor->setText(m_inst->notes()); + ui->setupUi(this); + ui->noteEditor->setText(m_inst->notes()); } NotesPage::~NotesPage() { - delete ui; + delete ui; } bool NotesPage::apply() { - m_inst->setNotes(ui->noteEditor->toPlainText()); - return true; + m_inst->setNotes(ui->noteEditor->toPlainText()); + return true; } diff --git a/application/pages/instance/NotesPage.h b/application/pages/instance/NotesPage.h index 4a25f9b1..f96e5374 100644 --- a/application/pages/instance/NotesPage.h +++ b/application/pages/instance/NotesPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,33 +28,33 @@ class NotesPage; class NotesPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit NotesPage(BaseInstance *inst, QWidget *parent = 0); - virtual ~NotesPage(); - virtual QString displayName() const override - { - return tr("Notes"); - } - virtual QIcon icon() const override - { - auto icon = MMC->getThemedIcon("notes"); - if(icon.isNull()) - icon = MMC->getThemedIcon("news"); - return icon; - } - virtual QString id() const override - { - return "notes"; - } - virtual bool apply() override; - virtual QString helpPage() const override - { - return "Notes"; - } + explicit NotesPage(BaseInstance *inst, QWidget *parent = 0); + virtual ~NotesPage(); + virtual QString displayName() const override + { + return tr("Notes"); + } + virtual QIcon icon() const override + { + auto icon = MMC->getThemedIcon("notes"); + if(icon.isNull()) + icon = MMC->getThemedIcon("news"); + return icon; + } + virtual QString id() const override + { + return "notes"; + } + virtual bool apply() override; + virtual QString helpPage() const override + { + return "Notes"; + } private: - Ui::NotesPage *ui; - BaseInstance *m_inst; + Ui::NotesPage *ui; + BaseInstance *m_inst; }; diff --git a/application/pages/instance/NotesPage.ui b/application/pages/instance/NotesPage.ui index 88cca92f..67cb261c 100644 --- a/application/pages/instance/NotesPage.ui +++ b/application/pages/instance/NotesPage.ui @@ -10,7 +10,7 @@ 538 - + 0 @@ -24,34 +24,26 @@ 0 - - - 0 + + + Qt::ScrollBarAlwaysOn + + + true + + + false + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - Tab 1 - - - - - - Qt::ScrollBarAlwaysOn - - - false - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - + + noteEditor +
diff --git a/application/pages/instance/OtherLogsPage.cpp b/application/pages/instance/OtherLogsPage.cpp index 10cb1145..387db74f 100644 --- a/application/pages/instance/OtherLogsPage.cpp +++ b/application/pages/instance/OtherLogsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,289 +25,289 @@ #include OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent) - : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter), - m_watcher(new RecursiveFileSystemWatcher(this)) + : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter), + m_watcher(new RecursiveFileSystemWatcher(this)) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); - m_watcher->setMatcher(fileFilter); - m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); + m_watcher->setMatcher(fileFilter); + m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); - connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox); - populateSelectLogBox(); + connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox); + populateSelectLogBox(); - auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); - connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated); + auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); + connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated); - auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); - connect(findNextShortcut, &QShortcut::activated, this, &OtherLogsPage::findNextActivated); + auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); + connect(findNextShortcut, &QShortcut::activated, this, &OtherLogsPage::findNextActivated); - auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); - connect(findPreviousShortcut, &QShortcut::activated, this, &OtherLogsPage::findPreviousActivated); + auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this); + connect(findPreviousShortcut, &QShortcut::activated, this, &OtherLogsPage::findPreviousActivated); - connect(ui->searchBar, &QLineEdit::returnPressed, this, &OtherLogsPage::on_findButton_clicked); + connect(ui->searchBar, &QLineEdit::returnPressed, this, &OtherLogsPage::on_findButton_clicked); } OtherLogsPage::~OtherLogsPage() { - delete ui; + delete ui; } void OtherLogsPage::openedImpl() { - m_watcher->enable(); + m_watcher->enable(); } void OtherLogsPage::closedImpl() { - m_watcher->disable(); + m_watcher->disable(); } void OtherLogsPage::populateSelectLogBox() { - ui->selectLogBox->clear(); - ui->selectLogBox->addItems(m_watcher->files()); - if (m_currentFile.isEmpty()) - { - setControlsEnabled(false); - ui->selectLogBox->setCurrentIndex(-1); - } - else - { - const int index = ui->selectLogBox->findText(m_currentFile); - if (index != -1) - { - ui->selectLogBox->setCurrentIndex(index); - setControlsEnabled(true); - } - else - { - setControlsEnabled(false); - } - } + ui->selectLogBox->clear(); + ui->selectLogBox->addItems(m_watcher->files()); + if (m_currentFile.isEmpty()) + { + setControlsEnabled(false); + ui->selectLogBox->setCurrentIndex(-1); + } + else + { + const int index = ui->selectLogBox->findText(m_currentFile); + if (index != -1) + { + ui->selectLogBox->setCurrentIndex(index); + setControlsEnabled(true); + } + else + { + setControlsEnabled(false); + } + } } void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) { - QString file; - if (index != -1) - { - file = ui->selectLogBox->itemText(index); - } + QString file; + if (index != -1) + { + file = ui->selectLogBox->itemText(index); + } - if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_path, file))) - { - m_currentFile = QString(); - ui->text->clear(); - setControlsEnabled(false); - } - else - { - m_currentFile = file; - on_btnReload_clicked(); - setControlsEnabled(true); - } + if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_path, file))) + { + m_currentFile = QString(); + ui->text->clear(); + setControlsEnabled(false); + } + else + { + m_currentFile = file; + on_btnReload_clicked(); + setControlsEnabled(true); + } } void OtherLogsPage::on_btnReload_clicked() { - if(m_currentFile.isEmpty()) - { - setControlsEnabled(false); - return; - } - QFile file(FS::PathCombine(m_path, m_currentFile)); - if (!file.open(QFile::ReadOnly)) - { - setControlsEnabled(false); - ui->btnReload->setEnabled(true); // allow reload - m_currentFile = QString(); - QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2") - .arg(m_currentFile, file.errorString())); - } - else - { - auto setPlainText = [&](const QString & text) - { - QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if(!conversionOk) - { - fontSize = 11; - } - QTextDocument *doc = ui->text->document(); - doc->setDefaultFont(QFont(fontFamily, fontSize)); - ui->text->setPlainText(text); - }; - auto showTooBig = [&]() - { - setPlainText( - tr("The file (%1) is too big. You may want to open it in a viewer optimized " - "for large files.").arg(file.fileName())); - }; - if(file.size() > (1024ll * 1024ll * 12ll)) - { - showTooBig(); - return; - } - QString content; - if(file.fileName().endsWith(".gz")) - { - QByteArray temp; - if(!GZip::unzip(file.readAll(), temp)) - { - setPlainText( - tr("The file (%1) is not readable.").arg(file.fileName())); - return; - } - content = QString::fromUtf8(temp); - } - else - { - content = QString::fromUtf8(file.readAll()); - } - if (content.size() >= 50000000ll) - { - showTooBig(); - return; - } - setPlainText(content); - } + if(m_currentFile.isEmpty()) + { + setControlsEnabled(false); + return; + } + QFile file(FS::PathCombine(m_path, m_currentFile)); + if (!file.open(QFile::ReadOnly)) + { + setControlsEnabled(false); + ui->btnReload->setEnabled(true); // allow reload + m_currentFile = QString(); + QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2") + .arg(m_currentFile, file.errorString())); + } + else + { + auto setPlainText = [&](const QString & text) + { + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + QTextDocument *doc = ui->text->document(); + doc->setDefaultFont(QFont(fontFamily, fontSize)); + ui->text->setPlainText(text); + }; + auto showTooBig = [&]() + { + setPlainText( + tr("The file (%1) is too big. You may want to open it in a viewer optimized " + "for large files.").arg(file.fileName())); + }; + if(file.size() > (1024ll * 1024ll * 12ll)) + { + showTooBig(); + return; + } + QString content; + if(file.fileName().endsWith(".gz")) + { + QByteArray temp; + if(!GZip::unzip(file.readAll(), temp)) + { + setPlainText( + tr("The file (%1) is not readable.").arg(file.fileName())); + return; + } + content = QString::fromUtf8(temp); + } + else + { + content = QString::fromUtf8(file.readAll()); + } + if (content.size() >= 50000000ll) + { + showTooBig(); + return; + } + setPlainText(content); + } } void OtherLogsPage::on_btnPaste_clicked() { - GuiUtil::uploadPaste(ui->text->toPlainText(), this); + GuiUtil::uploadPaste(ui->text->toPlainText(), this); } void OtherLogsPage::on_btnCopy_clicked() { - GuiUtil::setClipboardText(ui->text->toPlainText()); + GuiUtil::setClipboardText(ui->text->toPlainText()); } void OtherLogsPage::on_btnDelete_clicked() { - if(m_currentFile.isEmpty()) - { - setControlsEnabled(false); - return; - } - if (QMessageBox::question(this, tr("Delete"), - tr("Do you really want to delete %1?").arg(m_currentFile), - QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) - { - return; - } - QFile file(FS::PathCombine(m_path, m_currentFile)); - if (!file.remove()) - { - QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2") - .arg(m_currentFile, file.errorString())); - } + if(m_currentFile.isEmpty()) + { + setControlsEnabled(false); + return; + } + if (QMessageBox::question(this, tr("Delete"), + tr("Do you really want to delete %1?").arg(m_currentFile), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) + { + return; + } + QFile file(FS::PathCombine(m_path, m_currentFile)); + if (!file.remove()) + { + QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2") + .arg(m_currentFile, file.errorString())); + } } void OtherLogsPage::on_btnClean_clicked() { - auto toDelete = m_watcher->files(); - if(toDelete.isEmpty()) - { - return; - } - QMessageBox *messageBox = new QMessageBox(this); - messageBox->setWindowTitle(tr("Clean up")); - if(toDelete.size() > 5) - { - messageBox->setText(tr("Do you really want to delete all log files?")); - messageBox->setDetailedText(toDelete.join('\n')); - } - else - { - messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n'))); - } - messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - messageBox->setDefaultButton(QMessageBox::Ok); - messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); - messageBox->setIcon(QMessageBox::Question); - messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + auto toDelete = m_watcher->files(); + if(toDelete.isEmpty()) + { + return; + } + QMessageBox *messageBox = new QMessageBox(this); + messageBox->setWindowTitle(tr("Clean up")); + if(toDelete.size() > 5) + { + messageBox->setText(tr("Do you really want to delete all log files?")); + messageBox->setDetailedText(toDelete.join('\n')); + } + else + { + messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n'))); + } + messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + messageBox->setDefaultButton(QMessageBox::Ok); + messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBox->setIcon(QMessageBox::Question); + messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); - if (messageBox->exec() != QMessageBox::Ok) - { - return; - } - QStringList failed; - for(auto item: toDelete) - { - QFile file(FS::PathCombine(m_path, item)); - if (!file.remove()) - { - failed.push_back(item); - } - } - if(!failed.empty()) - { - QMessageBox *messageBox = new QMessageBox(this); - messageBox->setWindowTitle(tr("Error")); - if(failed.size() > 5) - { - messageBox->setText(tr("Couldn't delete some files!")); - messageBox->setDetailedText(failed.join('\n')); - } - else - { - messageBox->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n'))); - } - messageBox->setStandardButtons(QMessageBox::Ok); - messageBox->setDefaultButton(QMessageBox::Ok); - messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); - messageBox->setIcon(QMessageBox::Critical); - messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); - messageBox->exec(); - } + if (messageBox->exec() != QMessageBox::Ok) + { + return; + } + QStringList failed; + for(auto item: toDelete) + { + QFile file(FS::PathCombine(m_path, item)); + if (!file.remove()) + { + failed.push_back(item); + } + } + if(!failed.empty()) + { + QMessageBox *messageBox = new QMessageBox(this); + messageBox->setWindowTitle(tr("Error")); + if(failed.size() > 5) + { + messageBox->setText(tr("Couldn't delete some files!")); + messageBox->setDetailedText(failed.join('\n')); + } + else + { + messageBox->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n'))); + } + messageBox->setStandardButtons(QMessageBox::Ok); + messageBox->setDefaultButton(QMessageBox::Ok); + messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageBox->setIcon(QMessageBox::Critical); + messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); + messageBox->exec(); + } } void OtherLogsPage::setControlsEnabled(const bool enabled) { - ui->btnReload->setEnabled(enabled); - ui->btnDelete->setEnabled(enabled); - ui->btnCopy->setEnabled(enabled); - ui->btnPaste->setEnabled(enabled); - ui->text->setEnabled(enabled); - ui->btnClean->setEnabled(enabled); + ui->btnReload->setEnabled(enabled); + ui->btnDelete->setEnabled(enabled); + ui->btnCopy->setEnabled(enabled); + ui->btnPaste->setEnabled(enabled); + ui->text->setEnabled(enabled); + ui->btnClean->setEnabled(enabled); } // FIXME: HACK, use LogView instead? static void findNext(QPlainTextEdit * _this, const QString& what, bool reverse) { - _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); + _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); } void OtherLogsPage::on_findButton_clicked() { - auto modifiers = QApplication::keyboardModifiers(); - bool reverse = modifiers & Qt::ShiftModifier; - findNext(ui->text, ui->searchBar->text(), reverse); + auto modifiers = QApplication::keyboardModifiers(); + bool reverse = modifiers & Qt::ShiftModifier; + findNext(ui->text, ui->searchBar->text(), reverse); } void OtherLogsPage::findNextActivated() { - findNext(ui->text, ui->searchBar->text(), false); + findNext(ui->text, ui->searchBar->text(), false); } void OtherLogsPage::findPreviousActivated() { - findNext(ui->text, ui->searchBar->text(), true); + findNext(ui->text, ui->searchBar->text(), true); } void OtherLogsPage::findActivated() { - // focus the search bar if it doesn't have focus - if (!ui->searchBar->hasFocus()) - { - ui->searchBar->setFocus(); - ui->searchBar->selectAll(); - } + // focus the search bar if it doesn't have focus + if (!ui->searchBar->hasFocus()) + { + ui->searchBar->setFocus(); + ui->searchBar->selectAll(); + } } diff --git a/application/pages/instance/OtherLogsPage.h b/application/pages/instance/OtherLogsPage.h index ac01ef0a..226f3af3 100644 --- a/application/pages/instance/OtherLogsPage.h +++ b/application/pages/instance/OtherLogsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,52 +30,52 @@ class RecursiveFileSystemWatcher; class OtherLogsPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent = 0); - ~OtherLogsPage(); + explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent = 0); + ~OtherLogsPage(); - QString id() const override - { - return "logs"; - } - QString displayName() const override - { - return tr("Other logs"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("log"); - } - QString helpPage() const override - { - return "Minecraft-Logs"; - } - void openedImpl() override; - void closedImpl() override; + QString id() const override + { + return "logs"; + } + QString displayName() const override + { + return tr("Other logs"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("log"); + } + QString helpPage() const override + { + return "Minecraft-Logs"; + } + void openedImpl() override; + void closedImpl() override; private slots: - void populateSelectLogBox(); - void on_selectLogBox_currentIndexChanged(const int index); - void on_btnReload_clicked(); - void on_btnPaste_clicked(); - void on_btnCopy_clicked(); - void on_btnDelete_clicked(); - void on_btnClean_clicked(); + void populateSelectLogBox(); + void on_selectLogBox_currentIndexChanged(const int index); + void on_btnReload_clicked(); + void on_btnPaste_clicked(); + void on_btnCopy_clicked(); + void on_btnDelete_clicked(); + void on_btnClean_clicked(); - void on_findButton_clicked(); - void findActivated(); - void findNextActivated(); - void findPreviousActivated(); + void on_findButton_clicked(); + void findActivated(); + void findNextActivated(); + void findPreviousActivated(); private: - void setControlsEnabled(const bool enabled); + void setControlsEnabled(const bool enabled); private: - Ui::OtherLogsPage *ui; - QString m_path; - QString m_currentFile; - IPathMatcher::Ptr m_fileFilter; - RecursiveFileSystemWatcher *m_watcher; + Ui::OtherLogsPage *ui; + QString m_path; + QString m_currentFile; + IPathMatcher::Ptr m_fileFilter; + RecursiveFileSystemWatcher *m_watcher; }; diff --git a/application/pages/instance/ResourcePackPage.h b/application/pages/instance/ResourcePackPage.h index 19dc78da..e11c78a3 100644 --- a/application/pages/instance/ResourcePackPage.h +++ b/application/pages/instance/ResourcePackPage.h @@ -4,18 +4,19 @@ class ResourcePackPage : public ModFolderPage { + Q_OBJECT public: - explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", - "resourcepacks", tr("Resource packs"), "Resource-packs", parent) - { - ui->configFolderBtn->setHidden(true); - } + explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) + : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", + "resourcepacks", tr("Resource packs"), "Resource-packs", parent) + { + ui->actionView_configs->setVisible(false); + } - virtual ~ResourcePackPage() {} - virtual bool shouldDisplay() const override - { - return !m_inst->traits().contains("no-texturepacks") && - !m_inst->traits().contains("texturepacks"); - } + virtual ~ResourcePackPage() {} + virtual bool shouldDisplay() const override + { + return !m_inst->traits().contains("no-texturepacks") && + !m_inst->traits().contains("texturepacks"); + } }; diff --git a/application/pages/instance/ScreenshotsPage.cpp b/application/pages/instance/ScreenshotsPage.cpp index 71458386..efa0f9f2 100644 --- a/application/pages/instance/ScreenshotsPage.cpp +++ b/application/pages/instance/ScreenshotsPage.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -32,341 +33,390 @@ typedef std::shared_ptr SharedIconCachePtr; class ThumbnailingResult : public QObject { - Q_OBJECT + Q_OBJECT public slots: - inline void emitResultsReady(const QString &path) { emit resultsReady(path); } - inline void emitResultsFailed(const QString &path) { emit resultsFailed(path); } + inline void emitResultsReady(const QString &path) { emit resultsReady(path); } + inline void emitResultsFailed(const QString &path) { emit resultsFailed(path); } signals: - void resultsReady(const QString &path); - void resultsFailed(const QString &path); + void resultsReady(const QString &path); + void resultsFailed(const QString &path); }; class ThumbnailRunnable : public QRunnable { public: - ThumbnailRunnable(QString path, SharedIconCachePtr cache) - { - m_path = path; - m_cache = cache; - } - void run() - { - QFileInfo info(m_path); - if (info.isDir()) - return; - if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) - return; - int tries = 5; - while (tries) - { - if (!m_cache->stale(m_path)) - return; - QImage image(m_path); - if (image.isNull()) - { - QThread::msleep(500); - tries--; - continue; - } - QImage small; - if (image.width() > image.height()) - small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); - else - small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); - QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); - QImage square(QSize(256, 256), QImage::Format_ARGB32); - square.fill(Qt::transparent); - - QPainter painter(&square); - painter.drawImage(offset, small); - painter.end(); - - QIcon icon(QPixmap::fromImage(square)); - m_cache->add(m_path, icon); - m_resultEmitter.emitResultsReady(m_path); - return; - } - m_resultEmitter.emitResultsFailed(m_path); - } - QString m_path; - SharedIconCachePtr m_cache; - ThumbnailingResult m_resultEmitter; + ThumbnailRunnable(QString path, SharedIconCachePtr cache) + { + m_path = path; + m_cache = cache; + } + void run() + { + QFileInfo info(m_path); + if (info.isDir()) + return; + if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) + return; + int tries = 5; + while (tries) + { + if (!m_cache->stale(m_path)) + return; + QImage image(m_path); + if (image.isNull()) + { + QThread::msleep(500); + tries--; + continue; + } + QImage small; + if (image.width() > image.height()) + small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); + else + small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); + QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); + QImage square(QSize(256, 256), QImage::Format_ARGB32); + square.fill(Qt::transparent); + + QPainter painter(&square); + painter.drawImage(offset, small); + painter.end(); + + QIcon icon(QPixmap::fromImage(square)); + m_cache->add(m_path, icon); + m_resultEmitter.emitResultsReady(m_path); + return; + } + m_resultEmitter.emitResultsFailed(m_path); + } + QString m_path; + SharedIconCachePtr m_cache; + ThumbnailingResult m_resultEmitter; }; // this is about as elegant and well written as a bag of bricks with scribbles done by insane // asylum patients. class FilterModel : public QIdentityProxyModel { - Q_OBJECT + Q_OBJECT public: - explicit FilterModel(QObject *parent = 0) : QIdentityProxyModel(parent) - { - m_thumbnailingPool.setMaxThreadCount(4); - m_thumbnailCache = std::make_shared(); - m_thumbnailCache->add("placeholder", MMC->getThemedIcon("screenshot-placeholder")); - connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); - // FIXME: the watched file set is not updated when files are removed - } - virtual ~FilterModel() { m_thumbnailingPool.waitForDone(500); } - virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const - { - auto model = sourceModel(); - if (!model) - return QVariant(); - if (role == Qt::DisplayRole || role == Qt::EditRole) - { - QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - return result.toString().remove(QRegExp("\\.png$")); - } - if (role == Qt::DecorationRole) - { - QVariant result = - sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); - QString filePath = result.toString(); - QIcon temp; - if (!watched.contains(filePath)) - { - ((QFileSystemWatcher &)watcher).addPath(filePath); - ((QSet &)watched).insert(filePath); - } - if (m_thumbnailCache->get(filePath, temp)) - { - return temp; - } - if (!m_failed.contains(filePath)) - { - ((FilterModel *)this)->thumbnailImage(filePath); - } - return (m_thumbnailCache->get("placeholder")); - } - return sourceModel()->data(mapToSource(proxyIndex), role); - } - virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole) - { - auto model = sourceModel(); - if (!model) - return false; - if (role != Qt::EditRole) - return false; - // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't - // sort after renames - { - ((QFileSystemModel *)model)->setNameFilterDisables(true); - ((QFileSystemModel *)model)->setNameFilterDisables(false); - } - return model->setData(mapToSource(index), value.toString() + ".png", role); - } + explicit FilterModel(QObject *parent = 0) : QIdentityProxyModel(parent) + { + m_thumbnailingPool.setMaxThreadCount(4); + m_thumbnailCache = std::make_shared(); + m_thumbnailCache->add("placeholder", MMC->getThemedIcon("screenshot-placeholder")); + connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); + // FIXME: the watched file set is not updated when files are removed + } + virtual ~FilterModel() { m_thumbnailingPool.waitForDone(500); } + virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const + { + auto model = sourceModel(); + if (!model) + return QVariant(); + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + return result.toString().remove(QRegExp("\\.png$")); + } + if (role == Qt::DecorationRole) + { + QVariant result = + sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); + QString filePath = result.toString(); + QIcon temp; + if (!watched.contains(filePath)) + { + ((QFileSystemWatcher &)watcher).addPath(filePath); + ((QSet &)watched).insert(filePath); + } + if (m_thumbnailCache->get(filePath, temp)) + { + return temp; + } + if (!m_failed.contains(filePath)) + { + ((FilterModel *)this)->thumbnailImage(filePath); + } + return (m_thumbnailCache->get("placeholder")); + } + return sourceModel()->data(mapToSource(proxyIndex), role); + } + virtual bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) + { + auto model = sourceModel(); + if (!model) + return false; + if (role != Qt::EditRole) + return false; + // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't + // sort after renames + { + ((QFileSystemModel *)model)->setNameFilterDisables(true); + ((QFileSystemModel *)model)->setNameFilterDisables(false); + } + return model->setData(mapToSource(index), value.toString() + ".png", role); + } private: - void thumbnailImage(QString path) - { - auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); - connect(&(runnable->m_resultEmitter), SIGNAL(resultsReady(QString)), - SLOT(thumbnailReady(QString))); - connect(&(runnable->m_resultEmitter), SIGNAL(resultsFailed(QString)), - SLOT(thumbnailFailed(QString))); - ((QThreadPool &)m_thumbnailingPool).start(runnable); - } + void thumbnailImage(QString path) + { + auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); + connect(&(runnable->m_resultEmitter), SIGNAL(resultsReady(QString)), + SLOT(thumbnailReady(QString))); + connect(&(runnable->m_resultEmitter), SIGNAL(resultsFailed(QString)), + SLOT(thumbnailFailed(QString))); + ((QThreadPool &)m_thumbnailingPool).start(runnable); + } private slots: - void thumbnailReady(QString path) { emit layoutChanged(); } - void thumbnailFailed(QString path) { m_failed.insert(path); } - void fileChanged(QString filepath) - { - m_thumbnailCache->setStale(filepath); - thumbnailImage(filepath); - // reinsert the path... - watcher.removePath(filepath); - watcher.addPath(filepath); - } + void thumbnailReady(QString path) { emit layoutChanged(); } + void thumbnailFailed(QString path) { m_failed.insert(path); } + void fileChanged(QString filepath) + { + m_thumbnailCache->setStale(filepath); + thumbnailImage(filepath); + // reinsert the path... + watcher.removePath(filepath); + watcher.addPath(filepath); + } private: - SharedIconCachePtr m_thumbnailCache; - QThreadPool m_thumbnailingPool; - QSet m_failed; - QSet watched; - QFileSystemWatcher watcher; + SharedIconCachePtr m_thumbnailCache; + QThreadPool m_thumbnailingPool; + QSet m_failed; + QSet watched; + QFileSystemWatcher watcher; }; class CenteredEditingDelegate : public QStyledItemDelegate { public: - explicit CenteredEditingDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {} - virtual ~CenteredEditingDelegate() {} - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index) const - { - auto widget = QStyledItemDelegate::createEditor(parent, option, index); - auto foo = dynamic_cast(widget); - if (foo) - { - foo->setAlignment(Qt::AlignHCenter); - foo->setFrame(true); - foo->setMaximumWidth(192); - } - return widget; - } + explicit CenteredEditingDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {} + virtual ~CenteredEditingDelegate() {} + virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const + { + auto widget = QStyledItemDelegate::createEditor(parent, option, index); + auto foo = dynamic_cast(widget); + if (foo) + { + foo->setAlignment(Qt::AlignHCenter); + foo->setFrame(true); + foo->setMaximumWidth(192); + } + return widget; + } }; ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) - : QWidget(parent), ui(new Ui::ScreenshotsPage) + : QMainWindow(parent), ui(new Ui::ScreenshotsPage) { - m_model.reset(new QFileSystemModel()); - m_filterModel.reset(new FilterModel()); - m_filterModel->setSourceModel(m_model.get()); - m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); - m_model->setReadOnly(false); - m_model->setNameFilters({"*.png"}); - m_model->setNameFilterDisables(false); - m_folder = path; - m_valid = FS::ensureFolderPathExists(m_folder); - - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - ui->listView->setIconSize(QSize(128, 128)); - ui->listView->setGridSize(QSize(192, 160)); - ui->listView->setSpacing(9); - // ui->listView->setUniformItemSizes(true); - ui->listView->setLayoutMode(QListView::Batched); - ui->listView->setViewMode(QListView::IconMode); - ui->listView->setResizeMode(QListView::Adjust); - ui->listView->installEventFilter(this); - ui->listView->setEditTriggers(0); - ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); - connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex))); + m_model.reset(new QFileSystemModel()); + m_filterModel.reset(new FilterModel()); + m_filterModel->setSourceModel(m_model.get()); + m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); + m_model->setReadOnly(false); + m_model->setNameFilters({"*.png"}); + m_model->setNameFilterDisables(false); + m_folder = path; + m_valid = FS::ensureFolderPathExists(m_folder); + + ui->setupUi(this); + ui->toolBar->insertSpacer(ui->actionView_Folder); + + ui->listView->setIconSize(QSize(128, 128)); + ui->listView->setGridSize(QSize(192, 160)); + ui->listView->setSpacing(9); + // ui->listView->setUniformItemSizes(true); + ui->listView->setLayoutMode(QListView::Batched); + ui->listView->setViewMode(QListView::IconMode); + ui->listView->setResizeMode(QListView::Adjust); + ui->listView->installEventFilter(this); + ui->listView->setEditTriggers(0); + ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); + connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex))); } bool ScreenshotsPage::eventFilter(QObject *obj, QEvent *evt) { - if (obj != ui->listView) - return QWidget::eventFilter(obj, evt); - if (evt->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, evt); - } - QKeyEvent *keyEvent = static_cast(evt); - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_deleteBtn_clicked(); - return true; - case Qt::Key_F2: - on_renameBtn_clicked(); - return true; - default: - break; - } - return QWidget::eventFilter(obj, evt); + if (obj != ui->listView) + return QWidget::eventFilter(obj, evt); + if (evt->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, evt); + } + QKeyEvent *keyEvent = static_cast(evt); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_actionDelete_triggered(); + return true; + case Qt::Key_F2: + on_actionRename_triggered(); + return true; + default: + break; + } + return QWidget::eventFilter(obj, evt); } ScreenshotsPage::~ScreenshotsPage() { - delete ui; + delete ui; +} + +void ScreenshotsPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->listView->mapToGlobal(pos)); + delete menu; +} + +QMenu * ScreenshotsPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } void ScreenshotsPage::onItemActivated(QModelIndex index) { - if (!index.isValid()) - return; - auto info = m_model->fileInfo(index); - QString fileName = info.absoluteFilePath(); - DesktopServices::openFile(info.absoluteFilePath()); + if (!index.isValid()) + return; + auto info = m_model->fileInfo(index); + QString fileName = info.absoluteFilePath(); + DesktopServices::openFile(info.absoluteFilePath()); } -void ScreenshotsPage::on_viewFolderBtn_clicked() +void ScreenshotsPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_folder, true); + DesktopServices::openDirectory(m_folder, true); } -void ScreenshotsPage::on_uploadBtn_clicked() +void ScreenshotsPage::on_actionUpload_triggered() { - auto selection = ui->listView->selectionModel()->selectedRows(); - if (selection.isEmpty()) - return; - - QList uploaded; - auto job = NetJobPtr(new NetJob("Screenshot Upload")); - for (auto item : selection) - { - auto info = m_model->fileInfo(item); - auto screenshot = std::make_shared(info); - uploaded.push_back(screenshot); - job->addNetAction(ImgurUpload::make(screenshot)); - } - SequentialTask task; - auto albumTask = NetJobPtr(new NetJob("Imgur Album Creation")); - auto imgurAlbum = ImgurAlbumCreation::make(uploaded); - albumTask->addNetAction(imgurAlbum); - task.addTask(job.unwrap()); - task.addTask(albumTask.unwrap()); - m_uploadActive = true; - ProgressDialog prog(this); - if (prog.execWithTask(&task) != QDialog::Accepted) - { - CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), - tr("Unknown error"), QMessageBox::Warning)->exec(); - } - else - { - auto link = QString("https://imgur.com/a/%1").arg(imgurAlbum->id()); - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(link); - CustomMessageBox::selectable( - this, - tr("Upload finished"), - tr("The link to the uploaded album has been placed in your clipboard.") .arg(link), - QMessageBox::Information - )->exec(); - } - m_uploadActive = false; + auto selection = ui->listView->selectionModel()->selectedRows(); + if (selection.isEmpty()) + return; + + QList uploaded; + auto job = NetJobPtr(new NetJob("Screenshot Upload")); + if(selection.size() < 2) + { + auto item = selection.at(0); + auto info = m_model->fileInfo(item); + auto screenshot = std::make_shared(info); + job->addNetAction(ImgurUpload::make(screenshot)); + + m_uploadActive = true; + ProgressDialog dialog(this); + if(dialog.execWithTask(job.get()) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), + tr("Unknown error"), QMessageBox::Warning)->exec(); + } + else + { + auto link = screenshot->m_url; + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(link); + CustomMessageBox::selectable( + this, + tr("Upload finished"), + tr("The link to the uploaded screenshot has been placed in your clipboard.") + .arg(link), + QMessageBox::Information + )->exec(); + } + + m_uploadActive = false; + return; + } + + for (auto item : selection) + { + auto info = m_model->fileInfo(item); + auto screenshot = std::make_shared(info); + uploaded.push_back(screenshot); + job->addNetAction(ImgurUpload::make(screenshot)); + } + SequentialTask task; + auto albumTask = NetJobPtr(new NetJob("Imgur Album Creation")); + auto imgurAlbum = ImgurAlbumCreation::make(uploaded); + albumTask->addNetAction(imgurAlbum); + task.addTask(job.unwrap()); + task.addTask(albumTask.unwrap()); + m_uploadActive = true; + ProgressDialog prog(this); + if (prog.execWithTask(&task) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), + tr("Unknown error"), QMessageBox::Warning)->exec(); + } + else + { + auto link = QString("https://imgur.com/a/%1").arg(imgurAlbum->id()); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(link); + CustomMessageBox::selectable( + this, + tr("Upload finished"), + tr("The link to the uploaded album has been placed in your clipboard.") .arg(link), + QMessageBox::Information + )->exec(); + } + m_uploadActive = false; } -void ScreenshotsPage::on_deleteBtn_clicked() +void ScreenshotsPage::on_actionDelete_triggered() { - auto mbox = CustomMessageBox::selectable( - this, tr("Are you sure?"), tr("This will delete all selected screenshots."), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No); - std::unique_ptr box(mbox); - - if (box->exec() != QMessageBox::Yes) - return; - - auto selected = ui->listView->selectionModel()->selectedIndexes(); - for (auto item : selected) - { - m_model->remove(item); - } + auto mbox = CustomMessageBox::selectable( + this, tr("Are you sure?"), tr("This will delete all selected screenshots."), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No); + std::unique_ptr box(mbox); + + if (box->exec() != QMessageBox::Yes) + return; + + auto selected = ui->listView->selectionModel()->selectedIndexes(); + for (auto item : selected) + { + m_model->remove(item); + } } -void ScreenshotsPage::on_renameBtn_clicked() +void ScreenshotsPage::on_actionRename_triggered() { - auto selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.isEmpty()) - return; - ui->listView->edit(selection[0]); - // TODO: mass renaming + auto selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.isEmpty()) + return; + ui->listView->edit(selection[0]); + // TODO: mass renaming } void ScreenshotsPage::openedImpl() { - if(!m_valid) - { - m_valid = FS::ensureFolderPathExists(m_folder); - } - if (m_valid) - { - QString path = QDir(m_folder).absolutePath(); - auto idx = m_model->setRootPath(path); - if(idx.isValid()) - { - ui->listView->setModel(m_filterModel.get()); - ui->listView->setRootIndex(m_filterModel->mapFromSource(idx)); - } - else - { - ui->listView->setModel(nullptr); - } - } + if(!m_valid) + { + m_valid = FS::ensureFolderPathExists(m_folder); + } + if (m_valid) + { + QString path = QDir(m_folder).absolutePath(); + auto idx = m_model->setRootPath(path); + if(idx.isValid()) + { + ui->listView->setModel(m_filterModel.get()); + ui->listView->setRootIndex(m_filterModel->mapFromSource(idx)); + } + else + { + ui->listView->setModel(nullptr); + } + } } #include "ScreenshotsPage.moc" diff --git a/application/pages/instance/ScreenshotsPage.h b/application/pages/instance/ScreenshotsPage.h index e31fb8b4..9adf79af 100644 --- a/application/pages/instance/ScreenshotsPage.h +++ b/application/pages/instance/ScreenshotsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include +#include #include "pages/BasePage.h" #include @@ -31,54 +31,59 @@ struct ScreenShot; class ScreenshotList; class ImgurAlbumCreation; -class ScreenshotsPage : public QWidget, public BasePage +class ScreenshotsPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ScreenshotsPage(QString path, QWidget *parent = 0); - virtual ~ScreenshotsPage(); + explicit ScreenshotsPage(QString path, QWidget *parent = 0); + virtual ~ScreenshotsPage(); - virtual void openedImpl() override; + virtual void openedImpl() override; - enum - { - NothingDone = 0x42 - }; + enum + { + NothingDone = 0x42 + }; + + virtual bool eventFilter(QObject *, QEvent *) override; + virtual QString displayName() const override + { + return tr("Screenshots"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("screenshots"); + } + virtual QString id() const override + { + return "screenshots"; + } + virtual QString helpPage() const override + { + return "Screenshots-management"; + } + virtual bool apply() override + { + return !m_uploadActive; + } + +protected: + QMenu * createPopupMenu() override; - virtual bool eventFilter(QObject *, QEvent *) override; - virtual QString displayName() const override - { - return tr("Screenshots"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("screenshots"); - } - virtual QString id() const override - { - return "screenshots"; - } - virtual QString helpPage() const override - { - return "Screenshots-management"; - } - virtual bool apply() override - { - return !m_uploadActive; - } private slots: - void on_uploadBtn_clicked(); - void on_deleteBtn_clicked(); - void on_renameBtn_clicked(); - void on_viewFolderBtn_clicked(); - void onItemActivated(QModelIndex); + void on_actionUpload_triggered(); + void on_actionDelete_triggered(); + void on_actionRename_triggered(); + void on_actionView_Folder_triggered(); + void onItemActivated(QModelIndex); + void ShowContextMenu(const QPoint &pos); private: - Ui::ScreenshotsPage *ui; - std::shared_ptr m_model; - std::shared_ptr m_filterModel; - QString m_folder; - bool m_valid = false; - bool m_uploadActive = false; + Ui::ScreenshotsPage *ui; + std::shared_ptr m_model; + std::shared_ptr m_filterModel; + QString m_folder; + bool m_valid = false; + bool m_uploadActive = false; }; diff --git a/application/pages/instance/ScreenshotsPage.ui b/application/pages/instance/ScreenshotsPage.ui index d05c4384..42d4db64 100644 --- a/application/pages/instance/ScreenshotsPage.ui +++ b/application/pages/instance/ScreenshotsPage.ui @@ -1,106 +1,90 @@ ScreenshotsPage - + 0 0 - 723 - 532 + 800 + 600 - - - 0 + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + toolBar - - 0 + + Qt::ToolButtonTextOnly - - 0 + + RightToolBarArea + + + false + + + + + + + + + Upload - - 0 + + + + Delete - - - - 0 - - - - Tab 1 - - - - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - - - - - - - &Upload - - - - - - - &Delete - - - - - - - &Rename - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - &View Folder - - - - - - - - - - + + + + Rename + + + + + View Folder + + - - listView - uploadBtn - deleteBtn - renameBtn - viewFolderBtn - + + + WideBar + QToolBar +
widgets/WideBar.h
+
+
diff --git a/application/pages/instance/ServersPage.cpp b/application/pages/instance/ServersPage.cpp new file mode 100644 index 00000000..c33eef1f --- /dev/null +++ b/application/pages/instance/ServersPage.cpp @@ -0,0 +1,760 @@ +#include "ServersPage.h" +#include "ui_ServersPage.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. + +struct Server +{ + // Types + enum class AcceptsTextures : int + { + ASK = 0, + ALWAYS = 1, + NEVER = 2 + }; + + // Methods + Server() + { + m_name = QObject::tr("Minecraft Server"); + } + Server(const QString & name, const QString & address) + { + m_name = name; + m_address = address; + } + Server(nbt::tag_compound& server) + { + std::string addressStr(server["ip"]); + m_address = QString::fromUtf8(addressStr.c_str()); + + std::string nameStr(server["name"]); + m_name = QString::fromUtf8(nameStr.c_str()); + + if(server["icon"]) + { + std::string base64str(server["icon"]); + m_icon = QByteArray::fromBase64(base64str.c_str()); + } + + if(server.has_key("acceptTextures", nbt::tag_type::Byte)) + { + bool value = server["acceptTextures"].as().get(); + if(value) + { + m_acceptsTextures = AcceptsTextures::ALWAYS; + } + else + { + m_acceptsTextures = AcceptsTextures::NEVER; + } + } + } + + void serialize(nbt::tag_compound& server) + { + server.insert("name", m_name.trimmed().toUtf8().toStdString()); + server.insert("ip", m_address.trimmed().toUtf8().toStdString()); + if(m_icon.size()) + { + server.insert("icon", m_icon.toBase64().toStdString()); + } + if(m_acceptsTextures != AcceptsTextures::ASK) + { + server.insert("acceptTextures", nbt::tag_byte(m_acceptsTextures == AcceptsTextures::ALWAYS)); + } + } + + // Data - persistent and user changeable + QString m_name; + QString m_address; + AcceptsTextures m_acceptsTextures = AcceptsTextures::ASK; + + // Data - persistent and automatically updated + QByteArray m_icon; + + // Data - temporary + bool m_checked = false; + bool m_up = false; + QString m_motd; // https://mctools.org/motd-creator + int m_ping = 0; + int m_currentPlayers = 0; + int m_maxPlayers = 0; +}; + +static std::unique_ptr parseServersDat(const QString& filename) +{ + try + { + QByteArray input = FS::read(filename); + std::istringstream foo(std::string(input.constData(), input.size())); + auto pair = nbt::io::read_compound(foo); + + if(pair.first != "") + return nullptr; + + if(pair.second == nullptr) + return nullptr; + + return std::move(pair.second); + } + catch (...) + { + return nullptr; + } +} + +static bool serializeServerDat(const QString& filename, nbt::tag_compound * levelInfo) +{ + try + { + if(!FS::ensureFilePathExists(filename)) + { + return false; + } + std::ostringstream s; + nbt::io::write_tag("", *levelInfo, s); + QByteArray val(s.str().data(), (int) s.str().size() ); + FS::write(filename, val); + return true; + } + catch (...) + { + return false; + } +} + +class ServersModel: public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles + { + ServerPtrRole = Qt::UserRole, + }; + explicit ServersModel(const QString &path, QObject *parent = 0) + : QAbstractListModel(parent) + { + m_path = path; + m_watcher = new QFileSystemWatcher(this); + connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &ServersModel::fileChanged); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &ServersModel::dirChanged); + m_saveTimer.setSingleShot(true); + m_saveTimer.setInterval(5000); + connect(&m_saveTimer, &QTimer::timeout, this, &ServersModel::save_internal); + } + virtual ~ServersModel() {}; + + void observe() + { + if(m_observed) + { + return; + } + m_observed = true; + + if(!m_loaded) + { + load(); + } + + updateFSObserver(); + } + + void unobserve() + { + if(!m_observed) + { + return; + } + m_observed = false; + + updateFSObserver(); + } + + void lock() + { + if(m_locked) + { + return; + } + saveNow(); + + m_locked = true; + updateFSObserver(); + } + + void unlock() + { + if(!m_locked) + { + return; + } + m_locked = false; + + updateFSObserver(); + } + + int addEmptyRow(int position) + { + if(m_locked) + { + return -1; + } + if(position < 0 || position >= rowCount()) + { + position = rowCount(); + } + beginInsertRows(QModelIndex(), position, position); + m_servers.insert(position, Server()); + endInsertRows(); + scheduleSave(); + return position; + } + + bool removeRow(int row) + { + if(m_locked) + { + return false; + } + if(row < 0 || row >= rowCount()) + { + return false; + } + beginRemoveRows(QModelIndex(), row, row); + m_servers.removeAt(row); + endRemoveRows(); // does absolutely nothing, the selected server stays as the next line... + scheduleSave(); + return true; + } + + bool moveUp(int row) + { + if(m_locked) + { + return false; + } + if(row <= 0) + { + return false; + } + beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1); + m_servers.swap(row-1, row); + endMoveRows(); + scheduleSave(); + return true; + } + + bool moveDown(int row) + { + if(m_locked) + { + return false; + } + int count = rowCount(); + if(row + 1 >= count) + { + return false; + } + beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2); + m_servers.swap(row+1, row); + endMoveRows(); + scheduleSave(); + return true; + } + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if (section < 0 || section >= COLUMN_COUNT) + return QVariant(); + + if(role == Qt::DisplayRole) + { + switch(section) + { + case 0: + return tr("Name"); + case 1: + return tr("Address"); + case 2: + return tr("Latency"); + } + } + + return QAbstractListModel::headerData(section, orientation, role); + } + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + if(column < 0 || column >= COLUMN_COUNT) + return QVariant(); + + if (row < 0 || row >= m_servers.size()) + return QVariant(); + + switch(column) + { + case 0: + switch (role) + { + case Qt::DecorationRole: + { + auto & bytes = m_servers[row].m_icon; + if(bytes.size()) + { + QPixmap px; + if(px.loadFromData(bytes)) + return QIcon(px); + } + return MMC->getThemedIcon("unknown_server"); + } + case Qt::DisplayRole: + return m_servers[row].m_name; + case ServerPtrRole: + return QVariant::fromValue((void *)&m_servers[row]); + default: + return QVariant(); + } + case 1: + switch (role) + { + case Qt::DisplayRole: + return m_servers[row].m_address; + default: + return QVariant(); + } + case 2: + switch (role) + { + case Qt::DisplayRole: + return m_servers[row].m_ping; + default: + return QVariant(); + } + default: + return QVariant(); + } + } + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + return m_servers.size(); + } + int columnCount(const QModelIndex & parent) const override + { + return COLUMN_COUNT; + } + + Server * at(int index) + { + if(index < 0 || index >= rowCount()) + { + return nullptr; + } + return &m_servers[index]; + } + + void setName(int row, const QString & name) + { + if(m_locked) + { + return; + } + auto server = at(row); + if(!server || server->m_name == name) + { + return; + } + server->m_name = name; + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + scheduleSave(); + } + + void setAddress(int row, const QString & address) + { + if(m_locked) + { + return; + } + auto server = at(row); + if(!server || server->m_address == address) + { + return; + } + server->m_address = address; + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + scheduleSave(); + } + + void setAcceptsTextures(int row, Server::AcceptsTextures textures) + { + if(m_locked) + { + return; + } + auto server = at(row); + if(!server || server->m_acceptsTextures == textures) + { + return; + } + server->m_acceptsTextures = textures; + emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1)); + scheduleSave(); + } + + void load() + { + cancelSave(); + beginResetModel(); + QList servers; + auto serversDat = parseServersDat(serversPath()); + if(serversDat) + { + auto &serversList = serversDat->at("servers").as(); + for(auto iter = serversList.begin(); iter != serversList.end(); iter++) + { + auto & serverTag = (*iter).as(); + Server s(serverTag); + servers.append(s); + } + } + m_servers.swap(servers); + m_loaded = true; + endResetModel(); + } + + void saveNow() + { + if(saveIsScheduled()) + { + save_internal(); + } + } + + +public slots: + void dirChanged(const QString& path) + { + qDebug() << "Changed:" << path; + load(); + } + void fileChanged(const QString& path) + { + qDebug() << "Changed:" << path; + } + +private slots: + void save_internal() + { + cancelSave(); + QString path = serversPath(); + qDebug() << "Server list about to be saved to" << path; + + nbt::tag_compound out; + nbt::tag_list list; + for(auto & server: m_servers) + { + nbt::tag_compound serverNbt; + server.serialize(serverNbt); + list.push_back(std::move(serverNbt)); + } + out.insert("servers", nbt::value(std::move(list))); + + if(!serializeServerDat(path, &out)) + { + qDebug() << "Failed to save server list:" << path << "Will try again."; + scheduleSave(); + } + } + +private: + void scheduleSave() + { + if(!m_loaded) + { + qDebug() << "Server list should never save if it didn't successfully load, path:" << m_path; + return; + } + if(!m_dirty) + { + m_dirty = true; + qDebug() << "Server list save is scheduled for" << m_path; + } + m_saveTimer.start(); + } + + void cancelSave() + { + m_dirty = false; + m_saveTimer.stop(); + } + + bool saveIsScheduled() const + { + return m_dirty; + } + + void updateFSObserver() + { + bool observingFS = m_watcher->directories().contains(m_path); + if(m_observed && m_locked) + { + if(!observingFS) + { + qWarning() << "Will watch" << m_path; + if(!m_watcher->addPath(m_path)) + { + qWarning() << "Failed to start watching" << m_path; + } + } + } + else + { + if(observingFS) + { + qWarning() << "Will stop watching" << m_path; + if(!m_watcher->removePath(m_path)) + { + qWarning() << "Failed to stop watching" << m_path; + } + } + } + } + + QString serversPath() + { + QFileInfo foo(FS::PathCombine(m_path, "servers.dat")); + return foo.filePath(); + } + +private: + bool m_loaded = false; + bool m_locked = false; + bool m_observed = false; + bool m_dirty = false; + QString m_path; + QList m_servers; + QFileSystemWatcher *m_watcher = nullptr; + QTimer m_saveTimer; +}; + +ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent) + : QMainWindow(parent), ui(new Ui::ServersPage) +{ + ui->setupUi(this); + m_inst = inst; + m_model = new ServersModel(inst->gameRoot(), this); + ui->serversView->setIconSize(QSize(64,64)); + ui->serversView->setModel(m_model); + ui->serversView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->serversView, &QTreeView::customContextMenuRequested, this, &ServersPage::ShowContextMenu); + + auto head = ui->serversView->header(); + if(head->count()) + { + head->setSectionResizeMode(0, QHeaderView::Stretch); + for(int i = 1; i < head->count(); i++) + { + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + } + + auto selectionModel = ui->serversView->selectionModel(); + connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged); + connect(m_inst, &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed); + connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited); + connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited); + connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int))); + connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ServersPage::rowsRemoved); + + m_locked = m_inst->isRunning(); + if(m_locked) + { + m_model->lock(); + } + + updateState(); +} + +ServersPage::~ServersPage() +{ + m_model->saveNow(); +} + +void ServersPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->serversView->mapToGlobal(pos)); + delete menu; +} + +QMenu * ServersPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; +} + +void ServersPage::on_RunningState_changed(bool running) +{ + if(m_locked == running) + { + return; + } + m_locked = running; + if(m_locked) + { + m_model->lock(); + } + else + { + m_model->unlock(); + } + updateState(); +} + +void ServersPage::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + int nextServer = -1; + if (!current.isValid()) + { + nextServer = -1; + } + else + { + nextServer = current.row(); + } + currentServer = nextServer; + updateState(); +} + +// WARNING: this is here because currentChanged is not accurate when removing rows. the current item needs to be fixed up after removal. +void ServersPage::rowsRemoved(const QModelIndex& parent, int first, int last) +{ + if(currentServer < first) + { + // current was before the removal + return; + } + else if(currentServer >= first && currentServer <= last) + { + // current got removed... + return; + } + else + { + // current was past the removal + int count = last - first + 1; + currentServer -= count; + } +} + +void ServersPage::nameEdited(const QString& name) +{ + m_model->setName(currentServer, name); +} + +void ServersPage::addressEdited(const QString& address) +{ + m_model->setAddress(currentServer, address); +} + +void ServersPage::resourceIndexChanged(int index) +{ + auto acceptsTextures = Server::AcceptsTextures(index); + m_model->setAcceptsTextures(currentServer, acceptsTextures); +} + +void ServersPage::updateState() +{ + auto server = m_model->at(currentServer); + + bool serverEditEnabled = server && !m_locked; + ui->addressLine->setEnabled(serverEditEnabled); + ui->nameLine->setEnabled(serverEditEnabled); + ui->resourceComboBox->setEnabled(serverEditEnabled); + ui->actionMove_Down->setEnabled(serverEditEnabled); + ui->actionMove_Up->setEnabled(serverEditEnabled); + ui->actionRemove->setEnabled(serverEditEnabled); + + if(server) + { + ui->addressLine->setText(server->m_address); + ui->nameLine->setText(server->m_name); + ui->resourceComboBox->setCurrentIndex(int(server->m_acceptsTextures)); + } + else + { + ui->addressLine->setText(QString()); + ui->nameLine->setText(QString()); + ui->resourceComboBox->setCurrentIndex(0); + } + + ui->actionAdd->setDisabled(m_locked); +} + +void ServersPage::openedImpl() +{ + m_model->observe(); +} + +void ServersPage::closedImpl() +{ + m_model->unobserve(); +} + +void ServersPage::on_actionAdd_triggered() +{ + int position = m_model->addEmptyRow(currentServer + 1); + if(position < 0) + { + return; + } + // select the new row + ui->serversView->selectionModel()->setCurrentIndex( + m_model->index(position), + QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear | QItemSelectionModel::Rows + ); + currentServer = position; +} + +void ServersPage::on_actionRemove_triggered() +{ + m_model->removeRow(currentServer); +} + +void ServersPage::on_actionMove_Up_triggered() +{ + if(m_model->moveUp(currentServer)) + { + currentServer --; + } +} + +void ServersPage::on_actionMove_Down_triggered() +{ + if(m_model->moveDown(currentServer)) + { + currentServer ++; + } +} + +#include "ServersPage.moc" diff --git a/application/pages/instance/ServersPage.h b/application/pages/instance/ServersPage.h new file mode 100644 index 00000000..c81f47be --- /dev/null +++ b/application/pages/instance/ServersPage.h @@ -0,0 +1,93 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "pages/BasePage.h" +#include + +namespace Ui +{ +class ServersPage; +} + +struct Server; +class ServersModel; +class MinecraftInstance; + +class ServersPage : public QMainWindow, public BasePage +{ + Q_OBJECT + +public: + explicit ServersPage(MinecraftInstance *inst, QWidget *parent = 0); + virtual ~ServersPage(); + + void openedImpl() override; + void closedImpl() override; + + virtual QString displayName() const override + { + return tr("Servers"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("unknown_server"); + } + virtual QString id() const override + { + return "servers"; + } + virtual QString helpPage() const override + { + return "Servers-management"; + } + +protected: + QMenu * createPopupMenu() override; + +private: + void updateState(); + void scheduleSave(); + bool saveIsScheduled() const; + +private slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void rowsRemoved(const QModelIndex &parent, int first, int last); + + void on_actionAdd_triggered(); + void on_actionRemove_triggered(); + void on_actionMove_Up_triggered(); + void on_actionMove_Down_triggered(); + + void on_RunningState_changed(bool running); + + void nameEdited(const QString & name); + void addressEdited(const QString & address); + void resourceIndexChanged(int index);\ + + void ShowContextMenu(const QPoint &pos); + +private: // data + int currentServer = -1; + bool m_locked = true; + Ui::ServersPage *ui = nullptr; + ServersModel * m_model = nullptr; + MinecraftInstance * m_inst = nullptr; +}; + diff --git a/application/pages/instance/ServersPage.ui b/application/pages/instance/ServersPage.ui new file mode 100644 index 00000000..9c31abe3 --- /dev/null +++ b/application/pages/instance/ServersPage.ui @@ -0,0 +1,191 @@ + + + ServersPage + + + + 0 + 0 + 1318 + 879 + + + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + 64 + 64 + + + + false + + + false + + + + + + + 6 + + + 6 + + + + + &Name + + + nameLine + + + + + + + + + + Address + + + addressLine + + + + + + + + + + Reso&urces + + + resourceComboBox + + + + + + + + Ask to download + + + + + Always download + + + + + Never download + + + + + + + + + + + toolBar + + + Qt::LeftToolBarArea|Qt::RightToolBarArea + + + Qt::ToolButtonTextOnly + + + false + + + RightToolBarArea + + + false + + + + + + + + + Add + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + + WideBar + QToolBar +
widgets/WideBar.h
+
+
+ + serversView + nameLine + addressLine + resourceComboBox + + + +
diff --git a/application/pages/instance/TexturePackPage.h b/application/pages/instance/TexturePackPage.h index b03614f0..a792ba07 100644 --- a/application/pages/instance/TexturePackPage.h +++ b/application/pages/instance/TexturePackPage.h @@ -4,16 +4,17 @@ class TexturePackPage : public ModFolderPage { + Q_OBJECT public: - explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", - tr("Texture packs"), "Texture-packs", parent) - { - ui->configFolderBtn->setHidden(true); - } - virtual ~TexturePackPage() {} - virtual bool shouldDisplay() const override - { - return m_inst->traits().contains("texturepacks"); - } + explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) + : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", + tr("Texture packs"), "Texture-packs", parent) + { + ui->actionView_configs->setVisible(false); + } + virtual ~TexturePackPage() {} + virtual bool shouldDisplay() const override + { + return m_inst->traits().contains("texturepacks"); + } }; diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp index 00ae0a7e..2acc13c2 100644 --- a/application/pages/instance/VersionPage.cpp +++ b/application/pages/instance/VersionPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ #include "MultiMC.h" #include +#include #include #include +#include #include "VersionPage.h" #include "ui_VersionPage.h" @@ -25,7 +27,6 @@ #include "dialogs/CustomMessageBox.h" #include "dialogs/VersionSelectDialog.h" #include "dialogs/NewComponentDialog.h" -#include "dialogs/ModEditDialogCommon.h" #include "dialogs/ProgressDialog.h" #include @@ -38,532 +39,574 @@ #include "minecraft/ComponentList.h" #include "minecraft/auth/MojangAccountList.h" -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" #include "icons/IconList.h" #include "Exception.h" - -#include "MultiMC.h" +#include "Version.h" #include #include class IconProxy : public QIdentityProxyModel { - Q_OBJECT + Q_OBJECT public: - IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget) - { - connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone); - m_parentWidget = parentWidget; - } - - virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override - { - QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role); - int column = proxyIndex.column(); - if(column == 0 && role == Qt::DecorationRole && m_parentWidget) - { - if(!var.isNull()) - { - auto string = var.toString(); - if(string == "warning") - { - return MMC->getThemedIcon("status-yellow"); - } - else if(string == "error") - { - return MMC->getThemedIcon("status-bad"); - } - } - return MMC->getThemedIcon("status-good"); - } - return var; - } + IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget) + { + connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone); + m_parentWidget = parentWidget; + } + + virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override + { + QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role); + int column = proxyIndex.column(); + if(column == 0 && role == Qt::DecorationRole && m_parentWidget) + { + if(!var.isNull()) + { + auto string = var.toString(); + if(string == "warning") + { + return MMC->getThemedIcon("status-yellow"); + } + else if(string == "error") + { + return MMC->getThemedIcon("status-bad"); + } + } + return MMC->getThemedIcon("status-good"); + } + return var; + } private slots: - void widgetGone() - { - m_parentWidget = nullptr; - } + void widgetGone() + { + m_parentWidget = nullptr; + } private: - QWidget *m_parentWidget = nullptr; + QWidget *m_parentWidget = nullptr; }; QIcon VersionPage::icon() const { - return MMC->icons()->getIcon(m_inst->iconKey()); + return MMC->icons()->getIcon(m_inst->iconKey()); } bool VersionPage::shouldDisplay() const { - return !m_inst->isRunning(); + return true; +} + +QMenu * VersionPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) - : QWidget(parent), ui(new Ui::VersionPage), m_inst(inst) -{ - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - m_profile = m_inst->getComponentList(); - - reloadComponentList(); - - if (m_profile) - { - auto proxy = new IconProxy(ui->packageView); - proxy->setSourceModel(m_profile.get()); - ui->packageView->setModel(proxy); - ui->packageView->installEventFilter(this); - ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); - connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); - auto smodel = ui->packageView->selectionModel(); - connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); - updateVersionControls(); - // select first item. - preselect(0); - } - else - { - disableVersionControls(); - } - connect(m_inst, &MinecraftInstance::versionReloaded, this, - &VersionPage::updateVersionControls); + : QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst) +{ + ui->setupUi(this); + + ui->toolBar->insertSpacer(ui->actionReload); + + m_profile = m_inst->getComponentList(); + + reloadComponentList(); + + auto proxy = new IconProxy(ui->packageView); + proxy->setSourceModel(m_profile.get()); + ui->packageView->setModel(proxy); + ui->packageView->installEventFilter(this); + ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); + auto smodel = ui->packageView->selectionModel(); + connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); + + connect(m_profile.get(), &ComponentList::minecraftChanged, this, &VersionPage::updateVersionControls); + controlsEnabled = !m_inst->isRunning(); + updateVersionControls(); + preselect(0); + connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus); + connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::ShowContextMenu); } VersionPage::~VersionPage() { - delete ui; + delete ui; +} + +void VersionPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->packageView->mapToGlobal(pos)); + delete menu; } void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &previous) { - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - int row = current.row(); - auto patch = m_profile->getComponent(row); - auto severity = patch->getProblemSeverity(); - switch(severity) - { - case ProblemSeverity::Warning: - ui->frame->setModText(tr("%1 possibly has issues.").arg(patch->getName())); - break; - case ProblemSeverity::Error: - ui->frame->setModText(tr("%1 has issues!").arg(patch->getName())); - break; - default: - case ProblemSeverity::None: - ui->frame->clear(); - return; - } - - auto &problems = patch->getProblems(); - QString problemOut; - for (auto &problem: problems) - { - if(problem.m_severity == ProblemSeverity::Error) - { - problemOut += tr("Error: "); - } - else if(problem.m_severity == ProblemSeverity::Warning) - { - problemOut += tr("Warning: "); - } - problemOut += problem.m_description; - problemOut += "\n"; - } - ui->frame->setModDescription(problemOut); + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + auto patch = m_profile->getComponent(row); + auto severity = patch->getProblemSeverity(); + switch(severity) + { + case ProblemSeverity::Warning: + ui->frame->setModText(tr("%1 possibly has issues.").arg(patch->getName())); + break; + case ProblemSeverity::Error: + ui->frame->setModText(tr("%1 has issues!").arg(patch->getName())); + break; + default: + case ProblemSeverity::None: + ui->frame->clear(); + return; + } + + auto &problems = patch->getProblems(); + QString problemOut; + for (auto &problem: problems) + { + if(problem.m_severity == ProblemSeverity::Error) + { + problemOut += tr("Error: "); + } + else if(problem.m_severity == ProblemSeverity::Warning) + { + problemOut += tr("Warning: "); + } + problemOut += problem.m_description; + problemOut += "\n"; + } + ui->frame->setModDescription(problemOut); } +void VersionPage::updateRunningStatus(bool running) +{ + if(controlsEnabled == running) { + controlsEnabled = !running; + updateVersionControls(); + } +} void VersionPage::updateVersionControls() { - ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(true); - updateButtons(); + // FIXME: this is a dirty hack + auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft")); + bool newCraft = controlsEnabled && (minecraftVersion >= Version("1.14")); + bool oldCraft = controlsEnabled && (minecraftVersion <= Version("1.12.2")); + ui->actionInstall_Fabric->setEnabled(newCraft); + ui->actionInstall_Forge->setEnabled(oldCraft); + ui->actionInstall_LiteLoader->setEnabled(oldCraft); + ui->actionReload->setEnabled(true); + updateButtons(); } -void VersionPage::disableVersionControls() +void VersionPage::updateButtons(int row) { - ui->forgeBtn->setEnabled(false); - ui->liteloaderBtn->setEnabled(false); - ui->reloadBtn->setEnabled(false); - updateButtons(); + if(row == -1) + row = currentRow(); + auto patch = m_profile->getComponent(row); + ui->actionRemove->setEnabled(controlsEnabled && patch && patch->isRemovable()); + ui->actionMove_down->setEnabled(controlsEnabled && patch && patch->isMoveable()); + ui->actionMove_up->setEnabled(controlsEnabled && patch && patch->isMoveable()); + ui->actionChange_version->setEnabled(controlsEnabled && patch && patch->isVersionChangeable()); + ui->actionEdit->setEnabled(controlsEnabled && patch && patch->isCustom()); + ui->actionCustomize->setEnabled(controlsEnabled && patch && patch->isCustomizable()); + ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible()); + ui->actionDownload_All->setEnabled(controlsEnabled); + ui->actionAdd_Empty->setEnabled(controlsEnabled); + ui->actionReload->setEnabled(controlsEnabled); + ui->actionInstall_mods->setEnabled(controlsEnabled); + ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled); + ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled); } bool VersionPage::reloadComponentList() { - try - { - m_profile->reload(Net::Mode::Online); - return true; - } - catch (Exception &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - return false; - } - catch (...) - { - QMessageBox::critical( - this, tr("Error"), - tr("Couldn't load the instance profile.")); - return false; - } -} - -void VersionPage::on_reloadBtn_clicked() -{ - reloadComponentList(); - m_container->refreshContainer(); -} - -void VersionPage::on_removeBtn_clicked() -{ - if (ui->packageView->currentIndex().isValid()) - { - // FIXME: use actual model, not reloading. - if (!m_profile->remove(ui->packageView->currentIndex().row())) - { - QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); - } - } - updateButtons(); - reloadComponentList(); - m_container->refreshContainer(); -} - -void VersionPage::on_modBtn_clicked() -{ - if(m_container) - { - m_container->selectPage("mods"); - } -} - -void VersionPage::on_jarmodBtn_clicked() -{ - auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); - if(!list.empty()) - { - m_profile->installJarMods(list); - } - updateButtons(); -} - -void VersionPage::on_jarBtn_clicked() -{ - auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); - if(!jarPath.isEmpty()) - { - m_profile->installCustomJar(jarPath); - } - updateButtons(); -} - -void VersionPage::on_moveUpBtn_clicked() -{ - try - { - m_profile->move(currentRow(), ComponentList::MoveUp); - } - catch (Exception &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - } - updateButtons(); -} - -void VersionPage::on_moveDownBtn_clicked() -{ - try - { - m_profile->move(currentRow(), ComponentList::MoveDown); - } - catch (Exception &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - } - updateButtons(); -} - -void VersionPage::on_changeVersionBtn_clicked() -{ - auto versionRow = currentRow(); - if(versionRow == -1) - { - return; - } - auto patch = m_profile->getComponent(versionRow); - auto name = patch->getName(); - auto list = patch->getVersionList(); - if(!list) - { - return; - } - auto uid = list->uid(); - // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata... - if(uid == "net.minecraftforge") - { - on_forgeBtn_clicked(); - return; - } - else if (uid == "com.mumfrey.liteloader") - { - on_liteloaderBtn_clicked(); - return; - } - VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); - auto currentVersion = patch->getVersion(); - if(!currentVersion.isEmpty()) - { - vselect.setCurrentVersion(currentVersion); - } - if (!vselect.exec() || !vselect.selectedVersion()) - return; - - qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor(); - bool important = false; - if(uid == "net.minecraft") - { - important = true; - } - m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important); - m_profile->resolve(Net::Mode::Online); - m_container->refreshContainer(); -} - -void VersionPage::on_downloadBtn_clicked() -{ - if (!MMC->accounts()->anyAccountIsValid()) - { - CustomMessageBox::selectable( - this, tr("Error"), - tr("MultiMC cannot download Minecraft or update instances unless you have at least " - "one account added.\nPlease add your Mojang or Minecraft account."), - QMessageBox::Warning)->show(); - return; - } - - auto updateTask = m_inst->createUpdateTask(Net::Mode::Online); - if (!updateTask) - { - return; - } - ProgressDialog tDialog(this); - connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); - // FIXME: unused return value - tDialog.execWithTask(updateTask.get()); - updateButtons(); - m_container->refreshContainer(); -} - -void VersionPage::on_forgeBtn_clicked() -{ - auto vlist = ENV.metadataIndex()->get("net.minecraftforge"); - if(!vlist) - { - return; - } - VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this); - vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!")); - - auto currentVersion = m_profile->getComponentVersion("net.minecraftforge"); - if(!currentVersion.isEmpty()) - { - vselect.setCurrentVersion(currentVersion); - } - - if (vselect.exec() && vselect.selectedVersion()) - { - auto vsn = vselect.selectedVersion(); - m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor()); - m_profile->resolve(Net::Mode::Online); - // m_profile->installVersion(); - preselect(m_profile->rowCount(QModelIndex())-1); - m_container->refreshContainer(); - } -} - -void VersionPage::on_addEmptyBtn_clicked() -{ - NewComponentDialog compdialog(QString(), QString(), this); - QStringList blacklist; - for(int i = 0; i < m_profile->rowCount(); i++) - { - auto comp = m_profile->getComponent(i); - blacklist.push_back(comp->getID()); - } - compdialog.setBlacklist(blacklist); - if (compdialog.exec()) - { - qDebug() << "name:" << compdialog.name(); - qDebug() << "uid:" << compdialog.uid(); - m_profile->installEmpty(compdialog.uid(), compdialog.name()); - } -} - -void VersionPage::on_liteloaderBtn_clicked() -{ - auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader"); - if(!vlist) - { - return; - } - VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this); - vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!")); - - auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader"); - if(!currentVersion.isEmpty()) - { - vselect.setCurrentVersion(currentVersion); - } - - if (vselect.exec() && vselect.selectedVersion()) - { - auto vsn = vselect.selectedVersion(); - m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor()); - m_profile->resolve(Net::Mode::Online); - // m_profile->installVersion(vselect.selectedVersion()); - preselect(m_profile->rowCount(QModelIndex())-1); - m_container->refreshContainer(); - } + try + { + m_profile->reload(Net::Mode::Online); + return true; + } + catch (const Exception &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + return false; + } + catch (...) + { + QMessageBox::critical( + this, tr("Error"), + tr("Couldn't load the instance profile.")); + return false; + } } -void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +void VersionPage::on_actionReload_triggered() { - currentIdx = current.row(); - updateButtons(currentIdx); + reloadComponentList(); + m_container->refreshContainer(); } -void VersionPage::preselect(int row) +void VersionPage::on_actionRemove_triggered() { - if(row < 0) - { - row = 0; - } - if(row >= m_profile->rowCount(QModelIndex())) - { - row = m_profile->rowCount(QModelIndex()) - 1; - } - if(row < 0) - { - return; - } - auto model_index = m_profile->index(row); - ui->packageView->selectionModel()->select(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - updateButtons(row); + if (ui->packageView->currentIndex().isValid()) + { + // FIXME: use actual model, not reloading. + if (!m_profile->remove(ui->packageView->currentIndex().row())) + { + QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); + } + } + updateButtons(); + reloadComponentList(); + m_container->refreshContainer(); } -void VersionPage::updateButtons(int row) +void VersionPage::on_actionInstall_mods_triggered() +{ + if(m_container) + { + m_container->selectPage("mods"); + } +} + +void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() +{ + auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); + if(!list.empty()) + { + m_profile->installJarMods(list); + } + updateButtons(); +} + +void VersionPage::on_actionReplace_Minecraft_jar_triggered() +{ + auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); + if(!jarPath.isEmpty()) + { + m_profile->installCustomJar(jarPath); + } + updateButtons(); +} + +void VersionPage::on_actionMove_up_triggered() +{ + try + { + m_profile->move(currentRow(), ComponentList::MoveUp); + } + catch (const Exception &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } + updateButtons(); +} + +void VersionPage::on_actionMove_down_triggered() +{ + try + { + m_profile->move(currentRow(), ComponentList::MoveDown); + } + catch (const Exception &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } + updateButtons(); +} + +void VersionPage::on_actionChange_version_triggered() +{ + auto versionRow = currentRow(); + if(versionRow == -1) + { + return; + } + auto patch = m_profile->getComponent(versionRow); + auto name = patch->getName(); + auto list = patch->getVersionList(); + if(!list) + { + return; + } + auto uid = list->uid(); + // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata... + if(uid == "net.minecraftforge") + { + on_actionInstall_Forge_triggered(); + return; + } + else if (uid == "com.mumfrey.liteloader") + { + on_actionInstall_LiteLoader_triggered(); + return; + } + VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); + if (uid == "net.fabricmc.intermediary") + { + vselect.setEmptyString(tr("No Fabric Loader versions are currently available.")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!")); + vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); + } + auto currentVersion = patch->getVersion(); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + if (!vselect.exec() || !vselect.selectedVersion()) + return; + + qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor(); + bool important = false; + if(uid == "net.minecraft") + { + important = true; + } + m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important); + m_profile->resolve(Net::Mode::Online); + m_container->refreshContainer(); +} + +void VersionPage::on_actionDownload_triggered() +{ + if (!MMC->accounts()->anyAccountIsValid()) + { + CustomMessageBox::selectable( + this, tr("Error"), + tr("MultiMC cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning)->show(); + return; + } + + auto updateTask = m_inst->createUpdateTask(Net::Mode::Online); + if (!updateTask) + { + return; + } + ProgressDialog tDialog(this); + connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); + // FIXME: unused return value + tDialog.execWithTask(updateTask.get()); + updateButtons(); + m_container->refreshContainer(); +} + +void VersionPage::on_actionInstall_Forge_triggered() +{ + auto vlist = ENV.metadataIndex()->get("net.minecraftforge"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this); + vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!")); + + auto currentVersion = m_profile->getComponentVersion("net.minecraftforge"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + // m_profile->installVersion(); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + +void VersionPage::on_actionInstall_Fabric_triggered() +{ + auto vlist = ENV.metadataIndex()->get("net.fabricmc.fabric-loader"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this); + vselect.setEmptyString(tr("No Fabric Loader versions are currently available.")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!")); + + auto currentVersion = m_profile->getComponentVersion("net.fabricmc.fabric-loader"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("net.fabricmc.fabric-loader", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + +void VersionPage::on_actionAdd_Empty_triggered() +{ + NewComponentDialog compdialog(QString(), QString(), this); + QStringList blacklist; + for(int i = 0; i < m_profile->rowCount(); i++) + { + auto comp = m_profile->getComponent(i); + blacklist.push_back(comp->getID()); + } + compdialog.setBlacklist(blacklist); + if (compdialog.exec()) + { + qDebug() << "name:" << compdialog.name(); + qDebug() << "uid:" << compdialog.uid(); + m_profile->installEmpty(compdialog.uid(), compdialog.name()); + } +} + +void VersionPage::on_actionInstall_LiteLoader_triggered() { - if(row == -1) - row = currentRow(); - auto patch = m_profile->getComponent(row); - if (!patch) - { - ui->removeBtn->setDisabled(true); - ui->moveDownBtn->setDisabled(true); - ui->moveUpBtn->setDisabled(true); - ui->changeVersionBtn->setDisabled(true); - ui->editBtn->setDisabled(true); - ui->customizeBtn->setDisabled(true); - ui->revertBtn->setDisabled(true); - } - else - { - ui->removeBtn->setEnabled(patch->isRemovable()); - ui->moveDownBtn->setEnabled(patch->isMoveable()); - ui->moveUpBtn->setEnabled(patch->isMoveable()); - ui->changeVersionBtn->setEnabled(patch->isVersionChangeable()); - ui->editBtn->setEnabled(patch->isCustom()); - ui->customizeBtn->setEnabled(patch->isCustomizable()); - ui->revertBtn->setEnabled(patch->isRevertible()); - } + auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this); + vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!")); + + auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + // m_profile->installVersion(vselect.selectedVersion()); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + +void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + currentIdx = current.row(); + updateButtons(currentIdx); +} + +void VersionPage::preselect(int row) +{ + if(row < 0) + { + row = 0; + } + if(row >= m_profile->rowCount(QModelIndex())) + { + row = m_profile->rowCount(QModelIndex()) - 1; + } + if(row < 0) + { + return; + } + auto model_index = m_profile->index(row); + ui->packageView->selectionModel()->select(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + updateButtons(row); } void VersionPage::onGameUpdateError(QString error) { - CustomMessageBox::selectable(this, tr("Error updating instance"), error, - QMessageBox::Warning)->show(); + CustomMessageBox::selectable(this, tr("Error updating instance"), error, QMessageBox::Warning)->show(); } Component * VersionPage::current() { - auto row = currentRow(); - if(row < 0) - { - return nullptr; - } - return m_profile->getComponent(row); + auto row = currentRow(); + if(row < 0) + { + return nullptr; + } + return m_profile->getComponent(row); } int VersionPage::currentRow() { - if (ui->packageView->selectionModel()->selectedRows().isEmpty()) - { - return -1; - } - return ui->packageView->selectionModel()->selectedRows().first().row(); -} - -void VersionPage::on_customizeBtn_clicked() -{ - auto version = currentRow(); - if(version == -1) - { - return; - } - auto patch = m_profile->getComponent(version); - if(!patch->getVersionFile()) - { - // TODO: wait for the update task to finish here... - return; - } - if(!m_profile->customize(version)) - { - // TODO: some error box here - } - updateButtons(); - preselect(currentIdx); -} - -void VersionPage::on_editBtn_clicked() -{ - auto version = current(); - if(!version) - { - return; - } - auto filename = version->getFilename(); - if(!QFileInfo::exists(filename)) - { - qWarning() << "file" << filename << "can't be opened for editing, doesn't exist!"; - return; - } - MMC->openJsonEditor(filename); -} - -void VersionPage::on_revertBtn_clicked() -{ - auto version = currentRow(); - if(version == -1) - { - return; - } - if(!m_profile->revertToBase(version)) - { - // TODO: some error box here - } - updateButtons(); - preselect(currentIdx); - m_container->refreshContainer(); + if (ui->packageView->selectionModel()->selectedRows().isEmpty()) + { + return -1; + } + return ui->packageView->selectionModel()->selectedRows().first().row(); +} + +void VersionPage::on_actionCustomize_triggered() +{ + auto version = currentRow(); + if(version == -1) + { + return; + } + auto patch = m_profile->getComponent(version); + if(!patch->getVersionFile()) + { + // TODO: wait for the update task to finish here... + return; + } + if(!m_profile->customize(version)) + { + // TODO: some error box here + } + updateButtons(); + preselect(currentIdx); +} + +void VersionPage::on_actionEdit_triggered() +{ + auto version = current(); + if(!version) + { + return; + } + auto filename = version->getFilename(); + if(!QFileInfo::exists(filename)) + { + qWarning() << "file" << filename << "can't be opened for editing, doesn't exist!"; + return; + } + MMC->openJsonEditor(filename); +} + +void VersionPage::on_actionRevert_triggered() +{ + auto version = currentRow(); + if(version == -1) + { + return; + } + if(!m_profile->revertToBase(version)) + { + // TODO: some error box here + } + updateButtons(); + preselect(currentIdx); + m_container->refreshContainer(); } #include "VersionPage.moc" diff --git a/application/pages/instance/VersionPage.h b/application/pages/instance/VersionPage.h index 85304ea5..31344eb9 100644 --- a/application/pages/instance/VersionPage.h +++ b/application/pages/instance/VersionPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include +#include #include "minecraft/MinecraftInstance.h" #include "minecraft/ComponentList.h" @@ -26,70 +26,74 @@ namespace Ui class VersionPage; } -class VersionPage : public QWidget, public BasePage +class VersionPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0); - virtual ~VersionPage(); - virtual QString displayName() const override - { - return tr("Version"); - } - virtual QIcon icon() const override; - virtual QString id() const override - { - return "version"; - } - virtual QString helpPage() const override - { - return "Instance-Version"; - } - virtual bool shouldDisplay() const override; + explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0); + virtual ~VersionPage(); + virtual QString displayName() const override + { + return tr("Version"); + } + virtual QIcon icon() const override; + virtual QString id() const override + { + return "version"; + } + virtual QString helpPage() const override + { + return "Instance-Version"; + } + virtual bool shouldDisplay() const override; private slots: - void on_forgeBtn_clicked(); - void on_addEmptyBtn_clicked(); - void on_liteloaderBtn_clicked(); - void on_reloadBtn_clicked(); - void on_removeBtn_clicked(); - void on_moveUpBtn_clicked(); - void on_moveDownBtn_clicked(); - void on_jarmodBtn_clicked(); - void on_jarBtn_clicked(); - void on_revertBtn_clicked(); - void on_editBtn_clicked(); - void on_modBtn_clicked(); - void on_customizeBtn_clicked(); - void on_downloadBtn_clicked(); + void on_actionChange_version_triggered(); + void on_actionInstall_Forge_triggered(); + void on_actionInstall_Fabric_triggered(); + void on_actionAdd_Empty_triggered(); + void on_actionInstall_LiteLoader_triggered(); + void on_actionReload_triggered(); + void on_actionRemove_triggered(); + void on_actionMove_up_triggered(); + void on_actionMove_down_triggered(); + void on_actionAdd_to_Minecraft_jar_triggered(); + void on_actionReplace_Minecraft_jar_triggered(); + void on_actionRevert_triggered(); + void on_actionEdit_triggered(); + void on_actionInstall_mods_triggered(); + void on_actionCustomize_triggered(); + void on_actionDownload_triggered(); - void updateVersionControls(); - void disableVersionControls(); - void on_changeVersionBtn_clicked(); + void updateVersionControls(); private: - Component * current(); - int currentRow(); - void updateButtons(int row = -1); - void preselect(int row = 0); - int doUpdate(); + Component * current(); + int currentRow(); + void updateButtons(int row = -1); + void preselect(int row = 0); + int doUpdate(); protected: - /// FIXME: this shouldn't be necessary! - bool reloadComponentList(); + QMenu * createPopupMenu() override; + + /// FIXME: this shouldn't be necessary! + bool reloadComponentList(); private: - Ui::VersionPage *ui; - std::shared_ptr m_profile; - MinecraftInstance *m_inst; - int currentIdx = 0; + Ui::VersionPage *ui; + std::shared_ptr m_profile; + MinecraftInstance *m_inst; + int currentIdx = 0; + bool controlsEnabled = false; public slots: - void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); + void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); private slots: - void onGameUpdateError(QString error); - void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); - + void updateRunningStatus(bool running); + void onGameUpdateError(QString error); + void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); + void ShowContextMenu(const QPoint &pos); }; diff --git a/application/pages/instance/VersionPage.ui b/application/pages/instance/VersionPage.ui index d54dd840..e16f18bc 100644 --- a/application/pages/instance/VersionPage.ui +++ b/application/pages/instance/VersionPage.ui @@ -1,279 +1,231 @@ VersionPage - + 0 0 - 870 - 1008 + 961 + 1091 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - Tab 1 - - - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAlwaysOff - - - false - - - true - - - - - - - - - Selection - - - Qt::AlignCenter - - - - - - - Change version of the selected package. - - - Change version - - - - - - - Make the selected package apply sooner. - - - Move up - - - - - - - Make the selected package apply later. - - - Move down - - - - - - - Remove selected package from the instance. - - - Remove - - - - - - - - - - Edit - - - Qt::AlignCenter - - - - - - - Customize selected package. - - - Customize - - - - - - - Edit selected package. - - - Edit - - - - - - - Revert the selected package to default. - - - Revert - - - - - - - - - - Install - - - Qt::AlignCenter - - - - - - - Install the Minecraft Forge package. - - - Install Forge - - - - - - - Install the LiteLoader package. - - - Install LiteLoader - - - - - - - Install normal mods. - - - Install mods - - - - - - - - - - Advanced - - - Qt::AlignCenter - - - - - - - Add a mod into the Minecraft jar file. - - - Add to Minecraft.jar - - - - - - - Replace Minecraft.jar - - - - - - - Add Empty - - - - - - - Reload all packages. - - - Reload - - - - - - - Download the files needed to launch the instance now. - - - Download All - - - - - - - Qt::Vertical - - - - 111 - 13 - - - - - - - - - - - 0 - 0 - - - - - - - - - + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + false + + + false + + + true + + + + + + + + 0 + 0 + + + + + + + + + + + toolBar + + + Qt::LeftToolBarArea|Qt::RightToolBarArea + + + Qt::ToolButtonTextOnly + + + false + + + RightToolBarArea + + + false + + + + + + + + + + + + + + + + + + + + + + + + Change version + + + Change version of the selected package. + + + + + Move up + + + Make the selected package apply sooner. + + + + + Move down + + + Make the selected package apply later. + + + + + Remove + + + Remove selected package from the instance. + + + + + Customize + + + Customize selected package. + + + + + Edit + + + Edit selected package. + + + + + Revert + + + Revert the selected package to default. + + + + + Install Forge + + + Install the Minecraft Forge package. + + + + + Install Fabric + + + Install the Fabric Loader package. + + + + + Install LiteLoader + + + Install the LiteLoader package. + + + + + Install mods + + + Install normal mods. + + + + + Add to Minecraft.jar + + + Add a mod into the Minecraft jar file. + + + + + Replace Minecraft.jar + + + + + Add Empty + + + Add an empty custom package. + + + + + Reload + + + Reload all packages. + + + + + Download All + + + Download the files needed to launch the instance now. + + @@ -281,38 +233,18 @@ QTreeView
widgets/ModListView.h
- - LineSeparator - QWidget -
widgets/LineSeparator.h
- 1 -
MCModInfoFrame QFrame
widgets/MCModInfoFrame.h
1
+ + WideBar + QToolBar +
widgets/WideBar.h
+
- - packageView - changeVersionBtn - moveUpBtn - moveDownBtn - removeBtn - customizeBtn - editBtn - revertBtn - forgeBtn - liteloaderBtn - modBtn - jarmodBtn - jarBtn - addEmptyBtn - reloadBtn - downloadBtn - tabWidget -
diff --git a/application/pages/instance/WorldListPage.cpp b/application/pages/instance/WorldListPage.cpp index 539d26a0..8358a0f1 100644 --- a/application/pages/instance/WorldListPage.cpp +++ b/application/pages/instance/WorldListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ #include "ui_WorldListPage.h" #include "minecraft/WorldList.h" #include -#include "dialogs/ModEditDialogCommon.h" #include +#include #include #include #include @@ -31,300 +31,314 @@ #include #include -WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr worlds, QString id, - QString iconName, QString displayName, QString helpPage, - QWidget *parent) - : QWidget(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds), m_iconName(iconName), m_id(id), m_displayName(displayName), m_helpName(helpPage) +WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr worlds, QWidget *parent) + : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this); - proxy->setSortCaseSensitivity(Qt::CaseInsensitive); - proxy->setSourceModel(m_worlds.get()); - ui->worldTreeView->setSortingEnabled(true); - ui->worldTreeView->setModel(proxy); - ui->worldTreeView->installEventFilter(this); - - auto head = ui->worldTreeView->header(); - - head->setSectionResizeMode(0, QHeaderView::Stretch); - head->setSectionResizeMode(1, QHeaderView::ResizeToContents); - connect(ui->worldTreeView->selectionModel(), - SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, - SLOT(worldChanged(const QModelIndex &, const QModelIndex &))); - worldChanged(QModelIndex(), QModelIndex()); + ui->setupUi(this); + + ui->toolBar->insertSpacer(ui->actionRefresh); + + QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this); + proxy->setSortCaseSensitivity(Qt::CaseInsensitive); + proxy->setSourceModel(m_worlds.get()); + ui->worldTreeView->setSortingEnabled(true); + ui->worldTreeView->setModel(proxy); + ui->worldTreeView->installEventFilter(this); + ui->worldTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->worldTreeView, &QTreeView::customContextMenuRequested, this, &WorldListPage::ShowContextMenu); + + auto head = ui->worldTreeView->header(); + head->setSectionResizeMode(0, QHeaderView::Stretch); + head->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged); + worldChanged(QModelIndex(), QModelIndex()); } void WorldListPage::openedImpl() { - m_worlds->startWatching(); + m_worlds->startWatching(); } void WorldListPage::closedImpl() { - m_worlds->stopWatching(); + m_worlds->stopWatching(); } WorldListPage::~WorldListPage() { - m_worlds->stopWatching(); - delete ui; + m_worlds->stopWatching(); + delete ui; +} + +void WorldListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->worldTreeView->mapToGlobal(pos)); + delete menu; +} + +QMenu * WorldListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } bool WorldListPage::shouldDisplay() const { - return true; + return true; } bool WorldListPage::worldListFilter(QKeyEvent *keyEvent) { - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmWorldBtn_clicked(); - return true; - default: - break; - } - return QWidget::eventFilter(ui->worldTreeView, keyEvent); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_actionRemove_triggered(); + return true; + default: + break; + } + return QWidget::eventFilter(ui->worldTreeView, keyEvent); } bool WorldListPage::eventFilter(QObject *obj, QEvent *ev) { - if (ev->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast(ev); - if (obj == ui->worldTreeView) - return worldListFilter(keyEvent); - return QWidget::eventFilter(obj, ev); + if (ev->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast(ev); + if (obj == ui->worldTreeView) + return worldListFilter(keyEvent); + return QWidget::eventFilter(obj, ev); } -void WorldListPage::on_rmWorldBtn_clicked() +void WorldListPage::on_actionRemove_triggered() { - auto proxiedIndex = getSelectedWorld(); - - if(!proxiedIndex.isValid()) - return; - - auto result = QMessageBox::question(this, - tr("Are you sure?"), - tr("This will remove the selected world permenantly.\n" - "The world will be gone forever (A LONG TIME).\n" - "\n" - "Do you want to continue?")); - if(result != QMessageBox::Yes) - { - return; - } - m_worlds->stopWatching(); - m_worlds->deleteWorld(proxiedIndex.row()); - m_worlds->startWatching(); + auto proxiedIndex = getSelectedWorld(); + + if(!proxiedIndex.isValid()) + return; + + auto result = QMessageBox::question(this, + tr("Are you sure?"), + tr("This will remove the selected world permenantly.\n" + "The world will be gone forever (A LONG TIME).\n" + "\n" + "Do you want to continue?")); + if(result != QMessageBox::Yes) + { + return; + } + m_worlds->stopWatching(); + m_worlds->deleteWorld(proxiedIndex.row()); + m_worlds->startWatching(); } -void WorldListPage::on_viewFolderBtn_clicked() +void WorldListPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); + DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); } QModelIndex WorldListPage::getSelectedWorld() { - auto index = ui->worldTreeView->selectionModel()->currentIndex(); + auto index = ui->worldTreeView->selectionModel()->currentIndex(); - auto proxy = (QSortFilterProxyModel *) ui->worldTreeView->model(); - return proxy->mapToSource(index); + auto proxy = (QSortFilterProxyModel *) ui->worldTreeView->model(); + return proxy->mapToSource(index); } -void WorldListPage::on_copySeedBtn_clicked() +void WorldListPage::on_actionCopy_Seed_triggered() { - QModelIndex index = getSelectedWorld(); - - if (!index.isValid()) - { - return; - } - int64_t seed = m_worlds->data(index, WorldList::SeedRole).toLongLong(); - MMC->clipboard()->setText(QString::number(seed)); + QModelIndex index = getSelectedWorld(); + + if (!index.isValid()) + { + return; + } + int64_t seed = m_worlds->data(index, WorldList::SeedRole).toLongLong(); + MMC->clipboard()->setText(QString::number(seed)); } -void WorldListPage::on_mcEditBtn_clicked() +void WorldListPage::on_actionMCEdit_triggered() { - if(m_mceditStarting) - return; + if(m_mceditStarting) + return; - auto mcedit = MMC->mcedit(); + auto mcedit = MMC->mcedit(); - const QString mceditPath = mcedit->path(); + const QString mceditPath = mcedit->path(); - QModelIndex index = getSelectedWorld(); + QModelIndex index = getSelectedWorld(); - if (!index.isValid()) - { - return; - } + if (!index.isValid()) + { + return; + } - if(!worldSafetyNagQuestion()) - return; + if(!worldSafetyNagQuestion()) + return; - auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); + auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); - auto program = mcedit->getProgramPath(); - if(program.size()) - { + auto program = mcedit->getProgramPath(); + if(program.size()) + { #ifdef Q_OS_WIN32 - if(!QProcess::startDetached(program, {fullPath}, mceditPath)) - { - mceditError(); - } + if(!QProcess::startDetached(program, {fullPath}, mceditPath)) + { + mceditError(); + } #else - m_mceditProcess.reset(new LoggedProcess()); - m_mceditProcess->setDetachable(true); - connect(m_mceditProcess.get(), &LoggedProcess::stateChanged, this, &WorldListPage::mceditState); - m_mceditProcess->start(program, {fullPath}); - m_mceditProcess->setWorkingDirectory(mceditPath); - m_mceditStarting = true; + m_mceditProcess.reset(new LoggedProcess()); + m_mceditProcess->setDetachable(true); + connect(m_mceditProcess.get(), &LoggedProcess::stateChanged, this, &WorldListPage::mceditState); + m_mceditProcess->start(program, {fullPath}); + m_mceditProcess->setWorkingDirectory(mceditPath); + m_mceditStarting = true; #endif - } - else - { - QMessageBox::warning( - this->parentWidget(), - tr("No MCEdit found or set up!"), - tr("You do not have MCEdit set up or it was moved.\nYou can set it up in the global settings.") - ); - } + } + else + { + QMessageBox::warning( + this->parentWidget(), + tr("No MCEdit found or set up!"), + tr("You do not have MCEdit set up or it was moved.\nYou can set it up in the global settings.") + ); + } } void WorldListPage::mceditError() { - QMessageBox::warning( - this->parentWidget(), - tr("MCEdit failed to start!"), - tr("MCEdit failed to start.\nIt may be necessary to reinstall it.") - ); + QMessageBox::warning( + this->parentWidget(), + tr("MCEdit failed to start!"), + tr("MCEdit failed to start.\nIt may be necessary to reinstall it.") + ); } void WorldListPage::mceditState(LoggedProcess::State state) { - bool failed = false; - switch(state) - { - case LoggedProcess::NotRunning: - case LoggedProcess::Starting: - return; - case LoggedProcess::FailedToStart: - case LoggedProcess::Crashed: - case LoggedProcess::Aborted: - { - failed = true; - } - case LoggedProcess::Running: - case LoggedProcess::Finished: - { - m_mceditStarting = false; - break; - } - } - if(failed) - { - mceditError(); - } + bool failed = false; + switch(state) + { + case LoggedProcess::NotRunning: + case LoggedProcess::Starting: + return; + case LoggedProcess::FailedToStart: + case LoggedProcess::Crashed: + case LoggedProcess::Aborted: + { + failed = true; + } + case LoggedProcess::Running: + case LoggedProcess::Finished: + { + m_mceditStarting = false; + break; + } + } + if(failed) + { + mceditError(); + } } void WorldListPage::worldChanged(const QModelIndex ¤t, const QModelIndex &previous) { - QModelIndex index = getSelectedWorld(); - bool enable = index.isValid(); - ui->copySeedBtn->setEnabled(enable); - ui->mcEditBtn->setEnabled(enable); - ui->rmWorldBtn->setEnabled(enable); - ui->copyBtn->setEnabled(enable); - ui->renameBtn->setEnabled(enable); + QModelIndex index = getSelectedWorld(); + bool enable = index.isValid(); + ui->actionCopy_Seed->setEnabled(enable); + ui->actionMCEdit->setEnabled(enable); + ui->actionRemove->setEnabled(enable); + ui->actionCopy->setEnabled(enable); + ui->actionRename->setEnabled(enable); } -void WorldListPage::on_addBtn_clicked() +void WorldListPage::on_actionAdd_triggered() { - auto list = GuiUtil::BrowseForFiles( - m_helpName, - tr("Select a Minecraft world zip"), - tr("Minecraft World Zip File (*.zip)"), QString(), this->parentWidget()); - if (!list.empty()) - { - m_worlds->stopWatching(); - for (auto filename : list) - { - m_worlds->installWorld(QFileInfo(filename)); - } - m_worlds->startWatching(); - } + auto list = GuiUtil::BrowseForFiles( + displayName(), + tr("Select a Minecraft world zip"), + tr("Minecraft World Zip File (*.zip)"), QString(), this->parentWidget()); + if (!list.empty()) + { + m_worlds->stopWatching(); + for (auto filename : list) + { + m_worlds->installWorld(QFileInfo(filename)); + } + m_worlds->startWatching(); + } } bool WorldListPage::isWorldSafe(QModelIndex) { - return !m_inst->isRunning(); + return !m_inst->isRunning(); } bool WorldListPage::worldSafetyNagQuestion() { - if(!isWorldSafe(getSelectedWorld())) - { - auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); - if(result == QMessageBox::No) - { - return false; - } - } - return true; + if(!isWorldSafe(getSelectedWorld())) + { + auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); + if(result == QMessageBox::No) + { + return false; + } + } + return true; } -void WorldListPage::on_copyBtn_clicked() +void WorldListPage::on_actionCopy_triggered() { - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) - { - return; - } - - if(!worldSafetyNagQuestion()) - return; - - auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); - auto world = (World *) worldVariant.value(); - bool ok = false; - QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); - - if (ok && name.length() > 0) - { - world->install(m_worlds->dir().absolutePath(), name); - } + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) + { + return; + } + + if(!worldSafetyNagQuestion()) + return; + + auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); + auto world = (World *) worldVariant.value(); + bool ok = false; + QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); + + if (ok && name.length() > 0) + { + world->install(m_worlds->dir().absolutePath(), name); + } } -void WorldListPage::on_renameBtn_clicked() +void WorldListPage::on_actionRename_triggered() { - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) - { - return; - } + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) + { + return; + } - if(!worldSafetyNagQuestion()) - return; + if(!worldSafetyNagQuestion()) + return; - auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); - auto world = (World *) worldVariant.value(); + auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); + auto world = (World *) worldVariant.value(); - bool ok = false; - QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new world name."), QLineEdit::Normal, world->name(), &ok); + bool ok = false; + QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new world name."), QLineEdit::Normal, world->name(), &ok); - if (ok && name.length() > 0) - { - world->rename(name); - } + if (ok && name.length() > 0) + { + world->rename(name); + } } -void WorldListPage::on_refreshBtn_clicked() +void WorldListPage::on_actionRefresh_triggered() { - m_worlds->update(); + m_worlds->update(); } diff --git a/application/pages/instance/WorldListPage.h b/application/pages/instance/WorldListPage.h index 71b87bda..c39420da 100644 --- a/application/pages/instance/WorldListPage.h +++ b/application/pages/instance/WorldListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ #pragma once -#include +#include #include "minecraft/MinecraftInstance.h" #include "pages/BasePage.h" @@ -28,69 +28,70 @@ namespace Ui class WorldListPage; } -class WorldListPage : public QWidget, public BasePage +class WorldListPage : public QMainWindow, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit WorldListPage(BaseInstance *inst, std::shared_ptr worlds, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~WorldListPage(); + explicit WorldListPage( + BaseInstance *inst, + std::shared_ptr worlds, + QWidget *parent = 0 + ); + virtual ~WorldListPage(); - virtual QString displayName() const override - { - return m_displayName; - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon(m_iconName); - } - virtual QString id() const override - { - return m_id; - } - virtual QString helpPage() const override - { - return m_helpName; - } - virtual bool shouldDisplay() const override; + virtual QString displayName() const override + { + return tr("Worlds"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("worlds"); + } + virtual QString id() const override + { + return "worlds"; + } + virtual QString helpPage() const override + { + return "Worlds"; + } + virtual bool shouldDisplay() const override; - virtual void openedImpl() override; - virtual void closedImpl() override; + virtual void openedImpl() override; + virtual void closedImpl() override; protected: - bool eventFilter(QObject *obj, QEvent *ev) override; - bool worldListFilter(QKeyEvent *ev); + bool eventFilter(QObject *obj, QEvent *ev) override; + bool worldListFilter(QKeyEvent *ev); + QMenu * createPopupMenu() override; protected: - BaseInstance *m_inst; + BaseInstance *m_inst; private: - QModelIndex getSelectedWorld(); - bool isWorldSafe(QModelIndex index); - bool worldSafetyNagQuestion(); - void mceditError(); + QModelIndex getSelectedWorld(); + bool isWorldSafe(QModelIndex index); + bool worldSafetyNagQuestion(); + void mceditError(); private: - Ui::WorldListPage *ui; - std::shared_ptr m_worlds; - unique_qobject_ptr m_mceditProcess; - bool m_mceditStarting = false; - QString m_iconName; - QString m_id; - QString m_displayName; - QString m_helpName; + Ui::WorldListPage *ui; + std::shared_ptr m_worlds; + unique_qobject_ptr m_mceditProcess; + bool m_mceditStarting = false; private slots: - void on_copySeedBtn_clicked(); - void on_mcEditBtn_clicked(); - void on_rmWorldBtn_clicked(); - void on_addBtn_clicked(); - void on_copyBtn_clicked(); - void on_renameBtn_clicked(); - void on_refreshBtn_clicked(); - void on_viewFolderBtn_clicked(); - void worldChanged(const QModelIndex ¤t, const QModelIndex &previous); - void mceditState(LoggedProcess::State state); + void on_actionCopy_Seed_triggered(); + void on_actionMCEdit_triggered(); + void on_actionRemove_triggered(); + void on_actionAdd_triggered(); + void on_actionCopy_triggered(); + void on_actionRename_triggered(); + void on_actionRefresh_triggered(); + void on_actionView_Folder_triggered(); + void worldChanged(const QModelIndex ¤t, const QModelIndex &previous); + void mceditState(LoggedProcess::State state); + + void ShowContextMenu(const QPoint &pos); }; diff --git a/application/pages/instance/WorldListPage.ui b/application/pages/instance/WorldListPage.ui index 0018ddf3..b7a9e57a 100644 --- a/application/pages/instance/WorldListPage.ui +++ b/application/pages/instance/WorldListPage.ui @@ -1,168 +1,140 @@ WorldListPage - + 0 0 - 723 - 532 + 800 + 600 - - - 0 + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::DragDrop + + + true + + + true + + + true + + + false + + + + + + + + toolBar - - 0 + + Qt::LeftToolBarArea|Qt::RightToolBarArea - - 0 + + Qt::ToolButtonTextOnly - - 0 + + false - - - - 0 - - - - Tab 1 - - - - - - - - Add - - - - - - - - - - Rename - - - - - - - Copy - - - - - - - &Remove - - - - - - - MCEdit - - - - - - - - - - Copy Seed - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Refresh - - - - - - - &View Folder - - - - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::DragDrop - - - true - - - true - - - false - - - - - - - - + + RightToolBarArea + + + false + + + + + + + + + + + + + + + Add + + + + + Rename + + + + + Copy + + + + + Remove + + + + + MCEdit + + + + + Copy Seed + + + + + Refresh + + + + + View Folder + + - LineSeparator - QWidget -
widgets/LineSeparator.h
- 1 + WideBar + QToolBar +
widgets/WideBar.h
- - tabWidget - worldTreeView - addBtn - renameBtn - copyBtn - rmWorldBtn - mcEditBtn - copySeedBtn - refreshBtn - viewFolderBtn -
diff --git a/application/pages/modplatform/FTBPage.cpp b/application/pages/modplatform/FTBPage.cpp index f438fce7..dca86efd 100644 --- a/application/pages/modplatform/FTBPage.cpp +++ b/application/pages/modplatform/FTBPage.cpp @@ -1,222 +1,364 @@ #include "FTBPage.h" #include "ui_FTBPage.h" +#include + #include "MultiMC.h" -#include "FolderInstanceProvider.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/NewInstanceDialog.h" #include "modplatform/ftb/FtbPackFetchTask.h" #include "modplatform/ftb/FtbPackInstallTask.h" +#include "modplatform/ftb/FtbPrivatePackManager.h" #include "FtbListModel.h" FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), dialog(dialog), ui(new Ui::FTBPage) + : QWidget(parent), dialog(dialog), ui(new Ui::FTBPage) { - ftbFetchTask = new FtbPackFetchTask(); + ftbFetchTask.reset(new FtbPackFetchTask()); + ftbPrivatePacks.reset(new FtbPrivatePackManager()); + + ui->setupUi(this); + + { + publicFilterModel = new FtbFilterModel(this); + publicListModel = new FtbListModel(this); + publicFilterModel->setSourceModel(publicListModel); + + ui->publicPackList->setModel(publicFilterModel); + ui->publicPackList->setSortingEnabled(true); + ui->publicPackList->header()->hide(); + ui->publicPackList->setIndentation(0); + ui->publicPackList->setIconSize(QSize(42, 42)); - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + for(int i = 0; i < publicFilterModel->getAvailableSortings().size(); i++) + { + ui->sortByBox->addItem(publicFilterModel->getAvailableSortings().keys().at(i)); + } - { - publicFilterModel = new FtbFilterModel(this); - publicListModel = new FtbListModel(this); - publicFilterModel->setSourceModel(publicListModel); + ui->sortByBox->setCurrentText(publicFilterModel->translateCurrentSorting()); + } - ui->publicPackList->setModel(publicFilterModel); - ui->publicPackList->setSortingEnabled(true); - ui->publicPackList->header()->hide(); - ui->publicPackList->setIndentation(0); - ui->publicPackList->setIconSize(QSize(42, 42)); + { + thirdPartyFilterModel = new FtbFilterModel(this); + thirdPartyModel = new FtbListModel(this); + thirdPartyFilterModel->setSourceModel(thirdPartyModel); - for(int i = 0; i < publicFilterModel->getAvailableSortings().size(); i++) - { - ui->sortByBox->addItem(publicFilterModel->getAvailableSortings().keys().at(i)); - } + ui->thirdPartyPackList->setModel(thirdPartyFilterModel); + ui->thirdPartyPackList->setSortingEnabled(true); + ui->thirdPartyPackList->header()->hide(); + ui->thirdPartyPackList->setIndentation(0); + ui->thirdPartyPackList->setIconSize(QSize(42, 42)); - ui->sortByBox->setCurrentText(publicFilterModel->translateCurrentSorting()); - } + thirdPartyFilterModel->setSorting(publicFilterModel->getCurrentSorting()); + } - { - thirdPartyFilterModel = new FtbFilterModel(this); - thirdPartyModel = new FtbListModel(this); - thirdPartyFilterModel->setSourceModel(thirdPartyModel); + { + privateFilterModel = new FtbFilterModel(this); + privateListModel = new FtbListModel(this); + privateFilterModel->setSourceModel(privateListModel); - ui->thirdPartyPackList->setModel(thirdPartyFilterModel); - ui->thirdPartyPackList->setSortingEnabled(true); - ui->thirdPartyPackList->header()->hide(); - ui->thirdPartyPackList->setIndentation(0); - ui->thirdPartyPackList->setIconSize(QSize(42, 42)); + ui->privatePackList->setModel(privateFilterModel); + ui->privatePackList->setSortingEnabled(true); + ui->privatePackList->header()->hide(); + ui->privatePackList->setIndentation(0); + ui->privatePackList->setIconSize(QSize(42, 42)); - thirdPartyFilterModel->setSorting(publicFilterModel->getCurrentSorting()); - } + privateFilterModel->setSorting(publicFilterModel->getCurrentSorting()); + } - ui->packVersionSelection->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->packVersionSelection->view()->parentWidget()->setMaximumHeight(300); + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FTBPage::onSortingSelectionChanged); - connect(ui->packVersionSelection, &QComboBox::currentTextChanged, this, &FTBPage::onVersionSelectionItemChanged); + connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FTBPage::onSortingSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FTBPage::onVersionSelectionItemChanged); - connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPublicPackSelectionChanged); - connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onThirdPartyPackSelectionChanged); + connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPublicPackSelectionChanged); + connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onThirdPartyPackSelectionChanged); + connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPrivatePackSelectionChanged); - connect(ui->ftbTabWidget, &QTabWidget::currentChanged, this, &FTBPage::onTabChanged); + connect(ui->addPackBtn, &QPushButton::pressed, this, &FTBPage::onAddPackClicked); + connect(ui->removePackBtn, &QPushButton::pressed, this, &FTBPage::onRemovePackClicked); - ui->modpackInfo->setOpenExternalLinks(true); + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &FTBPage::onTabChanged); - ui->publicPackList->selectionModel()->reset(); - ui->thirdPartyPackList->selectionModel()->reset(); + // ui->modpackInfo->setOpenExternalLinks(true); + + ui->publicPackList->selectionModel()->reset(); + ui->thirdPartyPackList->selectionModel()->reset(); + ui->privatePackList->selectionModel()->reset(); + + onTabChanged(ui->tabWidget->currentIndex()); } FTBPage::~FTBPage() { - delete ui; - if(ftbFetchTask) { - ftbFetchTask->deleteLater(); - } + delete ui; } bool FTBPage::shouldDisplay() const { - return true; + return true; } void FTBPage::openedImpl() { - if(!initialized) - { - connect(ftbFetchTask, &FtbPackFetchTask::finished, this, &FTBPage::ftbPackDataDownloadSuccessfully); - connect(ftbFetchTask, &FtbPackFetchTask::failed, this, &FTBPage::ftbPackDataDownloadFailed); - ftbFetchTask->fetch(); - initialized = true; - } - suggestCurrent(); + if(!initialized) + { + connect(ftbFetchTask.get(), &FtbPackFetchTask::finished, this, &FTBPage::ftbPackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &FtbPackFetchTask::failed, this, &FTBPage::ftbPackDataDownloadFailed); + + connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFinished, this, &FTBPage::ftbPrivatePackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFailed, this, &FTBPage::ftbPrivatePackDataDownloadFailed); + + ftbFetchTask->fetch(); + ftbPrivatePacks->load(); + ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().toList()); + initialized = true; + } + suggestCurrent(); } void FTBPage::suggestCurrent() { - if(isOpened) - { - if(!selected.broken) - { - dialog->setSuggestedPack(selected.name, new FtbPackInstallTask(selected, selectedVersion)); - if(selected.type == FtbPackType::Public) { - publicListModel->getLogo(selected.logo, [this](QString logo){ - dialog->setSuggestedIconFromFile(logo, "ftb_" + selected.name); - }); - } else if (selected.type == FtbPackType::ThirdParty) { - thirdPartyModel->getLogo(selected.logo, [this](QString logo){ - dialog->setSuggestedIconFromFile(logo, "ftb_" + selected.name); - }); - } - } - else - { - dialog->setSuggestedPack(); - } - } + if(isOpened) + { + if(!selected.broken) + { + dialog->setSuggestedPack(selected.name, new FtbPackInstallTask(selected, selectedVersion)); + QString editedLogoName; + if(selected.logo.toLower().startsWith("ftb")) + { + editedLogoName = selected.logo; + } + else + { + editedLogoName = "ftb_" + selected.logo; + } + + editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png")); + + if(selected.type == FtbPackType::Public) + { + publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + else if (selected.type == FtbPackType::ThirdParty) + { + thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + else if (selected.type == FtbPackType::Private) + { + privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + } + else + { + dialog->setSuggestedPack(); + } + } } void FTBPage::ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks) { - publicListModel->fill(publicPacks); - thirdPartyModel->fill(thirdPartyPacks); + publicListModel->fill(publicPacks); + thirdPartyModel->fill(thirdPartyPacks); } void FTBPage::ftbPackDataDownloadFailed(QString reason) { - //TODO: Display the error + //TODO: Display the error +} + +void FTBPage::ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack) +{ + privateListModel->addPack(pack); +} + +void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) +{ + auto reply = QMessageBox::question( + this, + tr("FTB private packs"), + tr("Failed to download pack information for code %1.\nShould it be removed now?").arg(packCode) + ); + if(reply == QMessageBox::Yes) + { + ftbPrivatePacks->remove(packCode); + } } void FTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) { - if(!now.isValid()) - { - onPackSelectionChanged(); - return; - } - FtbModpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value(); - onPackSelectionChanged(&selectedPack); + if(!now.isValid()) + { + onPackSelectionChanged(); + return; + } + FtbModpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value(); + onPackSelectionChanged(&selectedPack); } void FTBPage::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev) { - if(!now.isValid()) - { - onPackSelectionChanged(); - return; - } - FtbModpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value(); - onPackSelectionChanged(&selectedPack); + if(!now.isValid()) + { + onPackSelectionChanged(); + return; + } + FtbModpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value(); + onPackSelectionChanged(&selectedPack); +} + +void FTBPage::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev) +{ + if(!now.isValid()) + { + onPackSelectionChanged(); + return; + } + FtbModpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value(); + onPackSelectionChanged(&selectedPack); } void FTBPage::onPackSelectionChanged(FtbModpack* pack) { - ui->packVersionSelection->clear(); - if(pack) - { - ui->modpackInfo->setHtml("Pack by " + pack->author + "" + "
Minecraft " + pack->mcVersion + "
" - "
" + pack->description + "
  • " + pack->mods.replace(";", "
  • ") + "
"); - bool currentAdded = false; - - for(int i = 0; i < pack->oldVersions.size(); i++) - { - if(pack->currentVersion == pack->oldVersions.at(i)) - { - currentAdded = true; - } - ui->packVersionSelection->addItem(pack->oldVersions.at(i)); - } - - if(!currentAdded) - { - ui->packVersionSelection->addItem(pack->currentVersion); - } - selected = *pack; - } - suggestCurrent(); + ui->versionSelectionBox->clear(); + if(pack) + { + currentModpackInfo->setHtml("Pack by " + pack->author + "" + + "
Minecraft " + pack->mcVersion + "
" + "
" + pack->description + "
  • " + pack->mods.replace(";", "
  • ") + + "
"); + bool currentAdded = false; + + for(int i = 0; i < pack->oldVersions.size(); i++) + { + if(pack->currentVersion == pack->oldVersions.at(i)) + { + currentAdded = true; + } + ui->versionSelectionBox->addItem(pack->oldVersions.at(i)); + } + + if(!currentAdded) + { + ui->versionSelectionBox->addItem(pack->currentVersion); + } + selected = *pack; + } + else + { + currentModpackInfo->setHtml(""); + ui->versionSelectionBox->clear(); + if(isOpened) + { + dialog->setSuggestedPack(); + } + return; + } + suggestCurrent(); } void FTBPage::onVersionSelectionItemChanged(QString data) { - if(data.isNull() || data.isEmpty()) - { - selectedVersion = ""; - return; - } + if(data.isNull() || data.isEmpty()) + { + selectedVersion = ""; + return; + } - selectedVersion = data; - suggestCurrent(); + selectedVersion = data; + suggestCurrent(); } void FTBPage::onSortingSelectionChanged(QString data) { - FtbFilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); - publicFilterModel->setSorting(toSet); - thirdPartyFilterModel->setSorting(toSet); + FtbFilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); + publicFilterModel->setSorting(toSet); + thirdPartyFilterModel->setSorting(toSet); + privateFilterModel->setSorting(toSet); } void FTBPage::onTabChanged(int tab) { - FtbFilterModel* currentModel = nullptr; - QTreeView* currentList = nullptr; - if (tab == 0) - { - currentModel = publicFilterModel; - currentList = ui->publicPackList; - } - else - { - currentModel = thirdPartyFilterModel; - currentList = ui->thirdPartyPackList; - } - QModelIndex idx = currentList->currentIndex(); - if(idx.isValid()) - { - auto pack = currentModel->data(idx, Qt::UserRole).value(); - onPackSelectionChanged(&pack); - } - else - { - onPackSelectionChanged(); - } + if(tab == 1) + { + currentModel = thirdPartyFilterModel; + currentList = ui->thirdPartyPackList; + currentModpackInfo = ui->thirdPartyPackDescription; + } + else if(tab == 2) + { + currentModel = privateFilterModel; + currentList = ui->privatePackList; + currentModpackInfo = ui->privatePackDescription; + } + else + { + currentModel = publicFilterModel; + currentList = ui->publicPackList; + currentModpackInfo = ui->publicPackDescription; + } + + currentList->selectionModel()->reset(); + QModelIndex idx = currentList->currentIndex(); + if(idx.isValid()) + { + auto pack = currentModel->data(idx, Qt::UserRole).value(); + onPackSelectionChanged(&pack); + } + else + { + onPackSelectionChanged(); + } +} + +void FTBPage::onAddPackClicked() +{ + bool ok; + QString text = QInputDialog::getText( + this, + tr("Add FTB pack"), + tr("Enter pack code:"), + QLineEdit::Normal, + QString(), + &ok + ); + if(ok && !text.isEmpty()) + { + ftbPrivatePacks->add(text); + ftbFetchTask->fetchPrivate({text}); + } +} + +void FTBPage::onRemovePackClicked() +{ + auto index = ui->privatePackList->currentIndex(); + if(!index.isValid()) + { + return; + } + auto row = index.row(); + FtbModpack pack = privateListModel->at(row); + auto answer = QMessageBox::question( + this, + tr("Remove pack"), + tr("Are you sure you want to remove pack %1?").arg(pack.name), + QMessageBox::Yes | QMessageBox::No + ); + if(answer != QMessageBox::Yes) + { + return; + } + + ftbPrivatePacks->remove(pack.packCode); + privateListModel->remove(row); + onPackSelectionChanged(); } diff --git a/application/pages/modplatform/FTBPage.h b/application/pages/modplatform/FTBPage.h index 00c5e9c5..215252ee 100644 --- a/application/pages/modplatform/FTBPage.h +++ b/application/pages/modplatform/FTBPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ #pragma once #include +#include +#include #include "pages/BasePage.h" #include #include "tasks/Task.h" #include "modplatform/ftb/PackHelpers.h" #include "modplatform/ftb/FtbPackFetchTask.h" +#include "QObjectPtr.h" namespace Ui { @@ -31,62 +34,81 @@ class FTBPage; class FtbListModel; class FtbFilterModel; class NewInstanceDialog; +class FtbPrivatePackListModel; +class FtbPrivatePackFilterModel; +class FtbPrivatePackManager; class FTBPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit FTBPage(NewInstanceDialog * dialog, QWidget *parent = 0); - virtual ~FTBPage(); - QString displayName() const override - { - return tr("FTB Legacy"); - } - QIcon icon() const override - { - return MMC->getThemedIcon("ftb_logo"); - } - QString id() const override - { - return "ftb"; - } - QString helpPage() const override - { - return "FTB-platform"; - } - bool shouldDisplay() const override; - void openedImpl() override; + explicit FTBPage(NewInstanceDialog * dialog, QWidget *parent = 0); + virtual ~FTBPage(); + QString displayName() const override + { + return tr("FTB Legacy"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("ftb_logo"); + } + QString id() const override + { + return "ftb"; + } + QString helpPage() const override + { + return "FTB-platform"; + } + bool shouldDisplay() const override; + void openedImpl() override; private: - void suggestCurrent(); - void onPackSelectionChanged(FtbModpack *pack = nullptr); + void suggestCurrent(); + void onPackSelectionChanged(FtbModpack *pack = nullptr); private slots: - void ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); - void ftbPackDataDownloadFailed(QString reason); + void ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); + void ftbPackDataDownloadFailed(QString reason); - void onSortingSelectionChanged(QString data); - void onVersionSelectionItemChanged(QString data); + void ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack); + void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); - void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); - void onThirdPartyPackSelectionChanged(QModelIndex first, QModelIndex second); + void onSortingSelectionChanged(QString data); + void onVersionSelectionItemChanged(QString data); - void onTabChanged(int tab); + void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); + void onThirdPartyPackSelectionChanged(QModelIndex first, QModelIndex second); + void onPrivatePackSelectionChanged(QModelIndex first, QModelIndex second); + + void onTabChanged(int tab); + + void onAddPackClicked(); + void onRemovePackClicked(); private: - bool initialized = false; - FtbModpack selected; - QString selectedVersion; + FtbFilterModel* currentModel = nullptr; + QTreeView* currentList = nullptr; + QTextBrowser* currentModpackInfo = nullptr; + + bool initialized = false; + FtbModpack selected; + QString selectedVersion; + + FtbListModel* publicListModel = nullptr; + FtbFilterModel* publicFilterModel = nullptr; + + FtbListModel *thirdPartyModel = nullptr; + FtbFilterModel *thirdPartyFilterModel = nullptr; - FtbListModel* publicListModel = nullptr; - FtbFilterModel* publicFilterModel = nullptr; + FtbListModel *privateListModel = nullptr; + FtbFilterModel *privateFilterModel = nullptr; - FtbListModel *thirdPartyModel = nullptr; - FtbFilterModel *thirdPartyFilterModel = nullptr; + unique_qobject_ptr ftbFetchTask; + std::unique_ptr ftbPrivatePacks; - FtbPackFetchTask *ftbFetchTask = nullptr; - NewInstanceDialog* dialog = nullptr; + NewInstanceDialog* dialog = nullptr; - Ui::FTBPage *ui = nullptr; + Ui::FTBPage *ui = nullptr; }; diff --git a/application/pages/modplatform/FTBPage.ui b/application/pages/modplatform/FTBPage.ui index cb085af2..e5ed78cb 100644 --- a/application/pages/modplatform/FTBPage.ui +++ b/application/pages/modplatform/FTBPage.ui @@ -11,18 +11,6 @@ - - 0 - - - 0 - - - 0 - - - 0 - @@ -30,86 +18,107 @@ - + Public - - - - - Qt::ScrollBarAsNeeded + + + + + + 250 + 16777215 + - - - - Version selected: + + + + + + + + 3rd Party + + + + + + + + + + 250 + 16777215 + - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Private + + + + + + + 250 + 16777215 + - - + + + + Add pack + + - - - - - - - 0 - 0 - - - - QTabWidget::Rounded - - - 0 + + + Remove selected pack - - - Public Packs - - - - - - Qt::ScrollBarAsNeeded - - - Qt::ScrollBarAlwaysOff - - - - - - - - 3rd Party Packs - - - - - - Qt::ScrollBarAsNeeded - - - Qt::ScrollBarAlwaysOff - - - - - + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 265 + 0 + + + + + + diff --git a/application/pages/modplatform/FtbListModel.cpp b/application/pages/modplatform/FtbListModel.cpp index c14907c6..51aec890 100644 --- a/application/pages/modplatform/FtbListModel.cpp +++ b/application/pages/modplatform/FtbListModel.cpp @@ -10,56 +10,58 @@ #include #include +#include "net/URLConstants.h" + FtbFilterModel::FtbFilterModel(QObject *parent) : QSortFilterProxyModel(parent) { - currentSorting = Sorting::ByGameVersion; - sortings.insert(tr("Sort by name"), Sorting::ByName); - sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion); + currentSorting = Sorting::ByGameVersion; + sortings.insert(tr("Sort by name"), Sorting::ByName); + sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion); } bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - FtbModpack leftPack = sourceModel()->data(left, Qt::UserRole).value(); - FtbModpack rightPack = sourceModel()->data(right, Qt::UserRole).value(); + FtbModpack leftPack = sourceModel()->data(left, Qt::UserRole).value(); + FtbModpack rightPack = sourceModel()->data(right, Qt::UserRole).value(); - if(currentSorting == Sorting::ByGameVersion) { - Version lv(leftPack.mcVersion); - Version rv(rightPack.mcVersion); - return lv < rv; + if(currentSorting == Sorting::ByGameVersion) { + Version lv(leftPack.mcVersion); + Version rv(rightPack.mcVersion); + return lv < rv; - } else if(currentSorting == Sorting::ByName) { - return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; - } + } else if(currentSorting == Sorting::ByName) { + return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; + } - //UHM, some inavlid value set?! - qWarning() << "Invalid sorting set!"; - return true; + //UHM, some inavlid value set?! + qWarning() << "Invalid sorting set!"; + return true; } bool FtbFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - return true; + return true; } const QMap FtbFilterModel::getAvailableSortings() { - return sortings; + return sortings; } QString FtbFilterModel::translateCurrentSorting() { - return sortings.key(currentSorting); + return sortings.key(currentSorting); } void FtbFilterModel::setSorting(Sorting s) { - currentSorting = s; - invalidate(); + currentSorting = s; + invalidate(); } FtbFilterModel::Sorting FtbFilterModel::getCurrentSorting() { - return currentSorting; + return currentSorting; } FtbListModel::FtbListModel(QObject *parent) : QAbstractListModel(parent) @@ -72,128 +74,183 @@ FtbListModel::~FtbListModel() QString FtbListModel::translatePackType(FtbPackType type) const { - if(type == FtbPackType::Public) { - return tr("Public Modpack"); - } else if(type == FtbPackType::ThirdParty) { - return tr("Third Party Modpack"); - } else if(type == FtbPackType::Private) { - return tr("Private Modpack"); - } else { - return tr("Unknown Type"); - } + switch(type) + { + case FtbPackType::Public: + return tr("Public Modpack"); + case FtbPackType::ThirdParty: + return tr("Third Party Modpack"); + case FtbPackType::Private: + return tr("Private Modpack"); + } + qWarning() << "Unknown FTB modpack type:" << int(type); + return QString(); } int FtbListModel::rowCount(const QModelIndex &parent) const { - return modpacks.size(); + return modpacks.size(); } int FtbListModel::columnCount(const QModelIndex &parent) const { - return 1; + return 1; } QVariant FtbListModel::data(const QModelIndex &index, int role) const { - int pos = index.row(); - if(pos >= modpacks.size() || pos < 0 || !index.isValid()) { - return QString("INVALID INDEX %1").arg(pos); - } - - FtbModpack pack = modpacks.at(pos); - if(role == Qt::DisplayRole) { - return pack.name + "\n" + translatePackType(pack.type); - } else if (role == Qt::ToolTipRole) { - if(pack.description.length() > 100) { - //some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); - return edit; - - } - return pack.description; - } else if(role == Qt::DecorationRole) { - if(m_logoMap.contains(pack.logo)) { - return (m_logoMap.value(pack.logo)); - } - QIcon icon = MMC->getThemedIcon("screenshot-placeholder"); - ((FtbListModel *)this)->requestLogo(pack.logo); - return icon; - } else if(role == Qt::TextColorRole) { - if(pack.broken) { - //FIXME: Hardcoded color - return QColor(255, 0, 50); - } else if(pack.bugged) { - //FIXME: Hardcoded color - //bugged pack, currently only indicates bugged xml - return QColor(244, 229, 66); - } - } else if(role == Qt::UserRole) { - QVariant v; - v.setValue(pack); - return v; - } - - return QVariant(); + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QString("INVALID INDEX %1").arg(pos); + } + + FtbModpack pack = modpacks.at(pos); + if(role == Qt::DisplayRole) + { + return pack.name + "\n" + translatePackType(pack.type); + } + else if (role == Qt::ToolTipRole) + { + if(pack.description.length() > 100) + { + //some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + + } + return pack.description; + } + else if(role == Qt::DecorationRole) + { + if(m_logoMap.contains(pack.logo)) + { + return (m_logoMap.value(pack.logo)); + } + QIcon icon = MMC->getThemedIcon("screenshot-placeholder"); + ((FtbListModel *)this)->requestLogo(pack.logo); + return icon; + } + else if(role == Qt::TextColorRole) + { + if(pack.broken) + { + //FIXME: Hardcoded color + return QColor(255, 0, 50); + } + else if(pack.bugged) + { + //FIXME: Hardcoded color + //bugged pack, currently only indicates bugged xml + return QColor(244, 229, 66); + } + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); } void FtbListModel::fill(FtbModpackList modpacks) { - beginResetModel(); - this->modpacks = modpacks; - endResetModel(); + beginResetModel(); + this->modpacks = modpacks; + endResetModel(); +} + +void FtbListModel::addPack(FtbModpack modpack) +{ + beginResetModel(); + this->modpacks.append(modpack); + endResetModel(); +} + +void FtbListModel::clear() +{ + beginResetModel(); + modpacks.clear(); + endResetModel(); } FtbModpack FtbListModel::at(int row) { - return modpacks.at(row); + return modpacks.at(row); +} + +void FtbListModel::remove(int row) +{ + if(row < 0 || row >= modpacks.size()) + { + qWarning() << "Attempt to remove FTB modpacks with invalid row" << row; + return; + } + beginRemoveRows(QModelIndex(), row, row); + modpacks.removeAt(row); + endRemoveRows(); } void FtbListModel::logoLoaded(QString logo, QIcon out) { - m_loadingLogos.removeAll(logo); - m_logoMap.insert(logo, out); - emit dataChanged(createIndex(0, 0), createIndex(1, 0)); + m_loadingLogos.removeAll(logo); + m_logoMap.insert(logo, out); + emit dataChanged(createIndex(0, 0), createIndex(1, 0)); } void FtbListModel::logoFailed(QString logo) { - m_failedLogos.append(logo); - m_loadingLogos.removeAll(logo); + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); } void FtbListModel::requestLogo(QString file) { - if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) { - return; - } - - MetaEntryPtr entry = ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); - NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file)); - job->addNetAction(Net::Download::makeCached(QUrl(QString("https://ftb.cursecdn.com/FTB2/static/%1").arg(file)), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::finished, this, [this, file, fullPath]{ - emit logoLoaded(file, QIcon(fullPath)); - if(waitingCallbacks.contains(file)) { - waitingCallbacks.value(file)(fullPath); - } - }); - - QObject::connect(job, &NetJob::failed, this, [this, file]{ - emit logoFailed(file); - }); - - job->start(); - - m_loadingLogos.append(file); + if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) + { + return; + } + + MetaEntryPtr entry = ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); + NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file)); + job->addNetAction(Net::Download::makeCached(QUrl(QString(URLConstants::FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::finished, this, [this, file, fullPath] + { + emit logoLoaded(file, QIcon(fullPath)); + if(waitingCallbacks.contains(file)) + { + waitingCallbacks.value(file)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, file] + { + emit logoFailed(file); + }); + + job->start(); + + m_loadingLogos.append(file); } void FtbListModel::getLogo(const QString &logo, LogoCallback callback) { - if(m_logoMap.contains(logo)) { - callback(ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); - } else { - requestLogo(logo); - } + if(m_logoMap.contains(logo)) + { + callback(ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + } + else + { + requestLogo(logo); + } +} + +Qt::ItemFlags FtbListModel::flags(const QModelIndex &index) const +{ + return QAbstractListModel::flags(index); } diff --git a/application/pages/modplatform/FtbListModel.h b/application/pages/modplatform/FtbListModel.h index d3a82b73..34749b24 100644 --- a/application/pages/modplatform/FtbListModel.h +++ b/application/pages/modplatform/FtbListModel.h @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -15,54 +16,59 @@ typedef std::function LogoCallback; class FtbFilterModel : public QSortFilterProxyModel { + Q_OBJECT public: - FtbFilterModel(QObject* parent = Q_NULLPTR); - enum Sorting { - ByName, - ByGameVersion - }; - const QMap getAvailableSortings(); - QString translateCurrentSorting(); - void setSorting(Sorting sorting); - Sorting getCurrentSorting(); + FtbFilterModel(QObject* parent = Q_NULLPTR); + enum Sorting { + ByName, + ByGameVersion + }; + const QMap getAvailableSortings(); + QString translateCurrentSorting(); + void setSorting(Sorting sorting); + Sorting getCurrentSorting(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: - QMap sortings; - Sorting currentSorting; + QMap sortings; + Sorting currentSorting; }; class FtbListModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT private: - FtbModpackList modpacks; - QStringList m_failedLogos; - QStringList m_loadingLogos; - FtbLogoMap m_logoMap; - QMap waitingCallbacks; + FtbModpackList modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + FtbLogoMap m_logoMap; + QMap waitingCallbacks; - void requestLogo(QString file); - QString translatePackType(FtbPackType type) const; + void requestLogo(QString file); + QString translatePackType(FtbPackType type) const; private slots: - void logoFailed(QString logo); - void logoLoaded(QString logo, QIcon out); + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); public: - FtbListModel(QObject *parent); - ~FtbListModel(); - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; + FtbListModel(QObject *parent); + ~FtbListModel(); + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; - void fill(FtbModpackList modpacks); + void fill(FtbModpackList modpacks); + void addPack(FtbModpack modpack); + void clear(); + void remove(int row); - FtbModpack at(int row); - void getLogo(const QString &logo, LogoCallback callback); + FtbModpack at(int row); + void getLogo(const QString &logo, LogoCallback callback); }; diff --git a/application/pages/modplatform/ImportPage.cpp b/application/pages/modplatform/ImportPage.cpp index 75a51245..af243511 100644 --- a/application/pages/modplatform/ImportPage.cpp +++ b/application/pages/modplatform/ImportPage.cpp @@ -2,11 +2,9 @@ #include "ui_ImportPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include "dialogs/NewInstanceDialog.h" #include +#include #include #include #include @@ -14,114 +12,119 @@ class UrlValidator : public QValidator { public: - using QValidator::QValidator; + using QValidator::QValidator; - State validate(QString &in, int &pos) const - { - const QUrl url(in); - if (url.isValid() && !url.isRelative() && !url.isEmpty()) - { - return Acceptable; - } - else if (QFile::exists(in)) - { - return Acceptable; - } - else - { - return Intermediate; - } - } + State validate(QString &in, int &pos) const + { + const QUrl url(in); + if (url.isValid() && !url.isRelative() && !url.isEmpty()) + { + return Acceptable; + } + else if (QFile::exists(in)) + { + return Acceptable; + } + else + { + return Intermediate; + } + } }; ImportPage::ImportPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::ImportPage), dialog(dialog) + : QWidget(parent), ui(new Ui::ImportPage), dialog(dialog) { - ui->setupUi(this); - ui->modpackEdit->setValidator(new UrlValidator(ui->modpackEdit)); - connect(ui->modpackEdit, &QLineEdit::textChanged, this, &ImportPage::updateState); + ui->setupUi(this); + ui->modpackEdit->setValidator(new UrlValidator(ui->modpackEdit)); + connect(ui->modpackEdit, &QLineEdit::textChanged, this, &ImportPage::updateState); } ImportPage::~ImportPage() { - delete ui; + delete ui; } bool ImportPage::shouldDisplay() const { - return true; + return true; } void ImportPage::openedImpl() { - updateState(); + updateState(); } void ImportPage::updateState() { - if(!isOpened) - { - return; - } - if(ui->modpackEdit->hasAcceptableInput()) - { - QString input = ui->modpackEdit->text(); - auto url = QUrl::fromUserInput(input); - if(url.isLocalFile()) - { - // FIXME: actually do some validation of what's inside here... this is fake AF - QFileInfo fi(input); - if(fi.exists() && fi.suffix() == "zip") - { - QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); - } - } - else - { - // hook, line and sinker. - QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); - } - } - else - { - dialog->setSuggestedPack(); - } + if(!isOpened) + { + return; + } + if(ui->modpackEdit->hasAcceptableInput()) + { + QString input = ui->modpackEdit->text(); + auto url = QUrl::fromUserInput(input); + if(url.isLocalFile()) + { + // FIXME: actually do some validation of what's inside here... this is fake AF + QFileInfo fi(input); + if(fi.exists() && fi.suffix() == "zip") + { + QFileInfo fi(url.fileName()); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + } + } + else + { + if(input.endsWith("?client=y")) { + input.chop(9); + input.append("/file"); + url = QUrl::fromUserInput(input); + } + // hook, line and sinker. + QFileInfo fi(url.fileName()); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + } + } + else + { + dialog->setSuggestedPack(); + } } void ImportPage::setUrl(const QString& url) { - ui->modpackEdit->setText(url); - updateState(); + ui->modpackEdit->setText(url); + updateState(); } void ImportPage::on_modpackBtn_clicked() { - const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)")); - if (url.isValid()) - { - if (url.isLocalFile()) - { - ui->modpackEdit->setText(url.toLocalFile()); - } - else - { - ui->modpackEdit->setText(url.toString()); - } - } + const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)")); + if (url.isValid()) + { + if (url.isLocalFile()) + { + ui->modpackEdit->setText(url.toLocalFile()); + } + else + { + ui->modpackEdit->setText(url.toString()); + } + } } QUrl ImportPage::modpackUrl() const { - const QUrl url(ui->modpackEdit->text()); - if (url.isValid() && !url.isRelative() && !url.host().isEmpty()) - { - return url; - } - else - { - return QUrl::fromLocalFile(ui->modpackEdit->text()); - } + const QUrl url(ui->modpackEdit->text()); + if (url.isValid() && !url.isRelative() && !url.host().isEmpty()) + { + return url; + } + else + { + return QUrl::fromLocalFile(ui->modpackEdit->text()); + } } diff --git a/application/pages/modplatform/ImportPage.h b/application/pages/modplatform/ImportPage.h index 8f62e6b1..3afb0045 100644 --- a/application/pages/modplatform/ImportPage.h +++ b/application/pages/modplatform/ImportPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,41 +30,41 @@ class NewInstanceDialog; class ImportPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit ImportPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~ImportPage(); - virtual QString displayName() const override - { - return tr("Import from zip"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("viewfolder"); - } - virtual QString id() const override - { - return "import"; - } - virtual QString helpPage() const override - { - return "Zip-import"; - } - virtual bool shouldDisplay() const override; + explicit ImportPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~ImportPage(); + virtual QString displayName() const override + { + return tr("Import from zip"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("viewfolder"); + } + virtual QString id() const override + { + return "import"; + } + virtual QString helpPage() const override + { + return "Zip-import"; + } + virtual bool shouldDisplay() const override; - void setUrl(const QString & url); - void openedImpl() override; + void setUrl(const QString & url); + void openedImpl() override; private slots: - void on_modpackBtn_clicked(); - void updateState(); + void on_modpackBtn_clicked(); + void updateState(); private: - QUrl modpackUrl() const; + QUrl modpackUrl() const; private: - Ui::ImportPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; + Ui::ImportPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; }; diff --git a/application/pages/modplatform/TechnicPage.cpp b/application/pages/modplatform/TechnicPage.cpp index c0f4faa5..2f95bec8 100644 --- a/application/pages/modplatform/TechnicPage.cpp +++ b/application/pages/modplatform/TechnicPage.cpp @@ -2,28 +2,25 @@ #include "ui_TechnicPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include "dialogs/NewInstanceDialog.h" TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) + : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) { - ui->setupUi(this); + ui->setupUi(this); } TechnicPage::~TechnicPage() { - delete ui; + delete ui; } bool TechnicPage::shouldDisplay() const { - return true; + return true; } void TechnicPage::openedImpl() { - dialog->setSuggestedPack(); + dialog->setSuggestedPack(); } diff --git a/application/pages/modplatform/TechnicPage.h b/application/pages/modplatform/TechnicPage.h index 5b0f16a6..cc82ddf3 100644 --- a/application/pages/modplatform/TechnicPage.h +++ b/application/pages/modplatform/TechnicPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,32 +30,32 @@ class NewInstanceDialog; class TechnicPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~TechnicPage(); - virtual QString displayName() const override - { - return tr("Technic"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("technic"); - } - virtual QString id() const override - { - return "technic"; - } - virtual QString helpPage() const override - { - return "Technic-platform"; - } - virtual bool shouldDisplay() const override; - - void openedImpl() override; + explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~TechnicPage(); + virtual QString displayName() const override + { + return tr("Technic"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("technic"); + } + virtual QString id() const override + { + return "technic"; + } + virtual QString helpPage() const override + { + return "Technic-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; private: - Ui::TechnicPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; + Ui::TechnicPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; }; diff --git a/application/pages/modplatform/TwitchPage.cpp b/application/pages/modplatform/TwitchPage.cpp index 42aa46be..ea0f9267 100644 --- a/application/pages/modplatform/TwitchPage.cpp +++ b/application/pages/modplatform/TwitchPage.cpp @@ -2,28 +2,59 @@ #include "ui_TwitchPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include "dialogs/NewInstanceDialog.h" +#include TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) + : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) { - ui->setupUi(this); + ui->setupUi(this); + connect(ui->checkButton, &QPushButton::clicked, this, &TwitchPage::triggerCheck); } TwitchPage::~TwitchPage() { - delete ui; + delete ui; } bool TwitchPage::shouldDisplay() const { - return false; + return true; } void TwitchPage::openedImpl() { - dialog->setSuggestedPack(); + dialog->setSuggestedPack(); +} + +void TwitchPage::triggerCheck(bool) +{ + if(m_modIdResolver) { + return; + } + auto task = new Flame::UrlResolvingTask(ui->lineEdit->text()); + connect(task, &Task::finished, this, &TwitchPage::checkDone); + m_modIdResolver.reset(task); + task->start(); +} + +void TwitchPage::setUrl(const QString& url) +{ + ui->lineEdit->setText(url); + triggerCheck(true); +} + +void TwitchPage::checkDone() +{ + auto result = m_modIdResolver->getResults(); + auto formatted = QString("Project %1, File %2").arg(result.projectId).arg(result.fileId); + if(result.resolved && result.type == Flame::File::Type::Modpack) { + ui->twitchLabel->setText(formatted); + QFileInfo fi(result.fileName); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(result.url)); + } else { + ui->twitchLabel->setPixmap(QPixmap(QString::fromUtf8(":/assets/deadglitch"))); + dialog->setSuggestedPack(); + } + m_modIdResolver.reset(); } diff --git a/application/pages/modplatform/TwitchPage.h b/application/pages/modplatform/TwitchPage.h index 8e072917..600913cd 100644 --- a/application/pages/modplatform/TwitchPage.h +++ b/application/pages/modplatform/TwitchPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include "pages/BasePage.h" #include #include "tasks/Task.h" +#include "modplatform/flame/UrlResolvingTask.h" namespace Ui { @@ -30,32 +31,39 @@ class NewInstanceDialog; class TwitchPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~TwitchPage(); - virtual QString displayName() const override - { - return tr("Twitch"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("twitch"); - } - virtual QString id() const override - { - return "twitch"; - } - virtual QString helpPage() const override - { - return "Twitch-platform"; - } - virtual bool shouldDisplay() const override; - - void openedImpl() override; + explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~TwitchPage(); + virtual QString displayName() const override + { + return tr("Twitch URL"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("twitch"); + } + virtual QString id() const override + { + return "twitch"; + } + virtual QString helpPage() const override + { + return "Twitch-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + void setUrl(const QString & url); + +private slots: + void triggerCheck(bool checked); + void checkDone(); private: - Ui::TwitchPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; + Ui::TwitchPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + shared_qobject_ptr m_modIdResolver; }; diff --git a/application/pages/modplatform/TwitchPage.ui b/application/pages/modplatform/TwitchPage.ui index 0930f541..0db2484d 100644 --- a/application/pages/modplatform/TwitchPage.ui +++ b/application/pages/modplatform/TwitchPage.ui @@ -10,9 +10,25 @@ 405 - - - + + + + + + + + Twitch URL: + + + + + + + + 0 + 0 + + 40 @@ -26,8 +42,19 @@ + + + + Check + + + + + lineEdit + checkButton + diff --git a/application/pages/modplatform/VanillaPage.cpp b/application/pages/modplatform/VanillaPage.cpp index c355fccb..17535f1e 100644 --- a/application/pages/modplatform/VanillaPage.cpp +++ b/application/pages/modplatform/VanillaPage.cpp @@ -2,9 +2,6 @@ #include "ui_VanillaPage.h" #include "MultiMC.h" -#include "FolderInstanceProvider.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ProgressDialog.h" #include #include @@ -15,81 +12,84 @@ #include VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) - : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) + : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion); - filterChanged(); - connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh); + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion); + filterChanged(); + connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh); } void VanillaPage::openedImpl() { - if(!initialized) - { - auto vlist = ENV.metadataIndex()->get("net.minecraft"); - ui->versionList->initialize(vlist.get()); - initialized = true; - } - else - { - suggestCurrent(); - } + if(!initialized) + { + auto vlist = ENV.metadataIndex()->get("net.minecraft"); + ui->versionList->initialize(vlist.get()); + initialized = true; + } + else + { + suggestCurrent(); + } } void VanillaPage::refresh() { - ui->versionList->loadList(); + ui->versionList->loadList(); } void VanillaPage::filterChanged() { - QStringList out; - if(ui->alphaFilter->isChecked()) - out << "(old_alpha)"; - if(ui->betaFilter->isChecked()) - out << "(old_beta)"; - if(ui->snapshotFilter->isChecked()) - out << "(snapshot)"; - if(ui->oldSnapshotFilter->isChecked()) - out << "(old_snapshot)"; - if(ui->releaseFilter->isChecked()) - out << "(release)"; - auto regexp = out.join('|'); - ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); + QStringList out; + if(ui->alphaFilter->isChecked()) + out << "(old_alpha)"; + if(ui->betaFilter->isChecked()) + out << "(old_beta)"; + if(ui->snapshotFilter->isChecked()) + out << "(snapshot)"; + if(ui->oldSnapshotFilter->isChecked()) + out << "(old_snapshot)"; + if(ui->releaseFilter->isChecked()) + out << "(release)"; + if(ui->experimentsFilter->isChecked()) + out << "(experiment)"; + auto regexp = out.join('|'); + ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); } VanillaPage::~VanillaPage() { - delete ui; + delete ui; } bool VanillaPage::shouldDisplay() const { - return true; + return true; } BaseVersionPtr VanillaPage::selectedVersion() const { - return m_selectedVersion; + return m_selectedVersion; } void VanillaPage::suggestCurrent() { - if(m_selectedVersion && isOpened) - { - dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); - } + if(m_selectedVersion && isOpened) + { + dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + } } void VanillaPage::setSelectedVersion(BaseVersionPtr version) { - m_selectedVersion = version; - suggestCurrent(); + m_selectedVersion = version; + suggestCurrent(); } diff --git a/application/pages/modplatform/VanillaPage.h b/application/pages/modplatform/VanillaPage.h index 91c65edf..cc77733c 100644 --- a/application/pages/modplatform/VanillaPage.h +++ b/application/pages/modplatform/VanillaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,46 +30,46 @@ class NewInstanceDialog; class VanillaPage : public QWidget, public BasePage { - Q_OBJECT + Q_OBJECT public: - explicit VanillaPage(NewInstanceDialog *dialog, QWidget *parent = 0); - virtual ~VanillaPage(); - virtual QString displayName() const override - { - return tr("Vanilla"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("minecraft"); - } - virtual QString id() const override - { - return "vanilla"; - } - virtual QString helpPage() const override - { - return "Vanilla-platform"; - } - virtual bool shouldDisplay() const override; - void openedImpl() override; + explicit VanillaPage(NewInstanceDialog *dialog, QWidget *parent = 0); + virtual ~VanillaPage(); + virtual QString displayName() const override + { + return tr("Vanilla"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("minecraft"); + } + virtual QString id() const override + { + return "vanilla"; + } + virtual QString helpPage() const override + { + return "Vanilla-platform"; + } + virtual bool shouldDisplay() const override; + void openedImpl() override; - BaseVersionPtr selectedVersion() const; + BaseVersionPtr selectedVersion() const; public slots: - void setSelectedVersion(BaseVersionPtr version); + void setSelectedVersion(BaseVersionPtr version); private slots: - void filterChanged(); + void filterChanged(); private: - void refresh(); - void suggestCurrent(); + void refresh(); + void suggestCurrent(); private: - bool initialized = false; - NewInstanceDialog *dialog = nullptr; - Ui::VanillaPage *ui = nullptr; - bool m_versionSetByUser = false; - BaseVersionPtr m_selectedVersion; + bool initialized = false; + NewInstanceDialog *dialog = nullptr; + Ui::VanillaPage *ui = nullptr; + bool m_versionSetByUser = false; + BaseVersionPtr m_selectedVersion; }; diff --git a/application/pages/modplatform/VanillaPage.ui b/application/pages/modplatform/VanillaPage.ui index ae9cab47..47effc86 100644 --- a/application/pages/modplatform/VanillaPage.ui +++ b/application/pages/modplatform/VanillaPage.ui @@ -98,6 +98,16 @@ + + + + Experiments + + + true + + + @@ -144,6 +154,16 @@ 1 + + tabWidget + releaseFilter + snapshotFilter + oldSnapshotFilter + betaFilter + alphaFilter + experimentsFilter + refreshBtn +
diff --git a/application/resources/OSX/OSX.qrc b/application/resources/OSX/OSX.qrc index a7d7be17..19fd4b6a 100644 --- a/application/resources/OSX/OSX.qrc +++ b/application/resources/OSX/OSX.qrc @@ -1,37 +1,38 @@ - - index.theme - scalable/about.svg - scalable/accounts.svg - scalable/bug.svg - scalable/centralmods.svg - scalable/checkupdate.svg - scalable/copy.svg - scalable/coremods.svg - scalable/externaltools.svg - scalable/help.svg + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/externaltools.svg + scalable/help.svg scalable/instance-settings.svg - scalable/jarmods.svg - scalable/java.svg - scalable/loadermods.svg - scalable/log.svg - scalable/minecraft.svg - scalable/multimc.svg - scalable/new.svg - scalable/news.svg - scalable/notes.svg - scalable/patreon.svg - scalable/proxy.svg - scalable/quickmods.svg - scalable/refresh.svg - scalable/resourcepacks.svg - scalable/screenshots.svg - scalable/settings.svg - scalable/status-bad.svg - scalable/status-good.svg - scalable/status-yellow.svg - scalable/viewfolder.svg - scalable/worlds.svg - + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/multimc.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/patreon.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + diff --git a/application/resources/OSX/scalable/language.svg b/application/resources/OSX/scalable/language.svg new file mode 100644 index 00000000..4f7d002a --- /dev/null +++ b/application/resources/OSX/scalable/language.svg @@ -0,0 +1,40 @@ + +image/svg+xml \ No newline at end of file diff --git a/application/resources/assets/assets.qrc b/application/resources/assets/assets.qrc index 8d213387..38638e7f 100644 --- a/application/resources/assets/assets.qrc +++ b/application/resources/assets/assets.qrc @@ -2,6 +2,6 @@ underconstruction.png - deadglitch.svg + deadglitch.svg diff --git a/application/resources/backgrounds/backgrounds.qrc b/application/resources/backgrounds/backgrounds.qrc index 55de139e..83505635 100644 --- a/application/resources/backgrounds/backgrounds.qrc +++ b/application/resources/backgrounds/backgrounds.qrc @@ -2,5 +2,6 @@ catbgrnd2.png + catmas.png diff --git a/application/resources/backgrounds/catbgrnd2.png b/application/resources/backgrounds/catbgrnd2.png index 2b949e0b..e9de7f27 100644 Binary files a/application/resources/backgrounds/catbgrnd2.png and b/application/resources/backgrounds/catbgrnd2.png differ diff --git a/application/resources/backgrounds/catmas.png b/application/resources/backgrounds/catmas.png new file mode 100644 index 00000000..8bdb1d5c Binary files /dev/null and b/application/resources/backgrounds/catmas.png differ diff --git a/application/resources/documents/documents.qrc b/application/resources/documents/documents.qrc index 77fc0cf4..007efcde 100644 --- a/application/resources/documents/documents.qrc +++ b/application/resources/documents/documents.qrc @@ -1,7 +1,7 @@ - - ../../../COPYING.md - + + ../../../COPYING.md + diff --git a/application/resources/flat/flat.qrc b/application/resources/flat/flat.qrc index aee2e30c..b6e2ee38 100644 --- a/application/resources/flat/flat.qrc +++ b/application/resources/flat/flat.qrc @@ -1,44 +1,45 @@ - - index.theme - scalable/about.svg - scalable/accounts.svg - scalable/bug.svg - scalable/cat.svg - scalable/centralmods.svg - scalable/checkupdate.svg - scalable/copy.svg - scalable/coremods.svg - scalable/discord.svg - scalable/externaltools.svg - scalable/help.svg - scalable/instance-settings.svg - scalable/jarmods.svg - scalable/java.svg - scalable/loadermods.svg - scalable/log.svg - scalable/minecraft.svg - scalable/multimc.svg - scalable/new.svg - scalable/news.svg - scalable/notes.svg - scalable/packages.svg - scalable/patreon.svg - scalable/proxy.svg - scalable/quickmods.svg - scalable/reddit-alien.svg - scalable/refresh.svg - scalable/resourcepacks.svg - scalable/screenshot-placeholder.svg - scalable/screenshots.svg - scalable/settings.svg - scalable/star.svg - scalable/status-bad.svg - scalable/status-good.svg - scalable/status-running.svg - scalable/status-yellow.svg - scalable/viewfolder.svg - scalable/worlds.svg - + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/cat.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/discord.svg + scalable/externaltools.svg + scalable/help.svg + scalable/instance-settings.svg + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/multimc.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/packages.svg + scalable/patreon.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/reddit-alien.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/screenshot-placeholder.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/star.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-running.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + diff --git a/application/resources/flat/scalable/language.svg b/application/resources/flat/scalable/language.svg new file mode 100644 index 00000000..f4d3f2f4 --- /dev/null +++ b/application/resources/flat/scalable/language.svg @@ -0,0 +1,103 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/resources/iOS/iOS.qrc b/application/resources/iOS/iOS.qrc index 0cb642f4..511e390b 100644 --- a/application/resources/iOS/iOS.qrc +++ b/application/resources/iOS/iOS.qrc @@ -1,37 +1,38 @@ - - index.theme - scalable/about.svg - scalable/accounts.svg - scalable/bug.svg - scalable/centralmods.svg - scalable/checkupdate.svg - scalable/copy.svg - scalable/coremods.svg - scalable/externaltools.svg - scalable/help.svg - scalable/instance-settings.svg - scalable/jarmods.svg - scalable/java.svg - scalable/loadermods.svg - scalable/log.svg - scalable/minecraft.svg - scalable/multimc.svg - scalable/new.svg - scalable/news.svg - scalable/notes.svg - scalable/patreon.svg - scalable/proxy.svg - scalable/quickmods.svg - scalable/refresh.svg - scalable/resourcepacks.svg - scalable/screenshots.svg - scalable/settings.svg - scalable/status-bad.svg - scalable/status-good.svg - scalable/status-yellow.svg - scalable/viewfolder.svg - scalable/worlds.svg - + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/externaltools.svg + scalable/help.svg + scalable/instance-settings.svg + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/multimc.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/patreon.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + diff --git a/application/resources/iOS/scalable/language.svg b/application/resources/iOS/scalable/language.svg new file mode 100644 index 00000000..fcc3436e --- /dev/null +++ b/application/resources/iOS/scalable/language.svg @@ -0,0 +1,32 @@ + +image/svg+xml + + + + + \ No newline at end of file diff --git a/application/resources/multimc/128x128/unknown_server.png b/application/resources/multimc/128x128/unknown_server.png new file mode 100644 index 00000000..ec98382d Binary files /dev/null and b/application/resources/multimc/128x128/unknown_server.png differ diff --git a/application/resources/multimc/index.theme b/application/resources/multimc/index.theme index 0fe7e7d7..6061b7f8 100644 --- a/application/resources/multimc/index.theme +++ b/application/resources/multimc/index.theme @@ -2,7 +2,7 @@ Name=multimc Comment=MultiMC Default Icons Inherits=default -Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable +Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable,scalable/instances [8x8] Size=8 @@ -33,6 +33,11 @@ Size=50 [64x64] Size=64 +[128x128] +Size=128 +MinSize=33 +MaxSize=128 + [128x128/instances] Size=128 MinSize=33 @@ -46,3 +51,8 @@ Size=48 Type=Scalable MinSize=16 MaxSize=256 + +[scalable/instances] +Size=128 +MinSize=16 +MaxSize=256 diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc index bea3a325..22e7af86 100644 --- a/application/resources/multimc/multimc.qrc +++ b/application/resources/multimc/multimc.qrc @@ -1,306 +1,314 @@ - index.theme - - scalable/logo.svg - - - scalable/multimc.svg - - - scalable/reddit-alien.svg - - - scalable/twitch.svg - - - scalable/technic.svg - - - scalable/proxy.svg - - - scalable/java.svg - - - 16x16/star.png - 24x24/star.png - 32x32/star.png - 48x48/star.png - 64x64/star.png - - - 16x16/worlds.png - 22x22/worlds.png - 32x32/worlds.png - 48x48/worlds.png - 64x64/worlds.png - - - 16x16/minecraft.png - 24x24/minecraft.png - 32x32/minecraft.png - 48x48/minecraft.png - 256x256/minecraft.png - - - 16x16/about.png - 22x22/about.png - 32x32/about.png - 48x48/about.png - 64x64/about.png - - - scalable/bug.svg - 16x16/bug.png - 22x22/bug.png - 32x32/bug.png - 48x48/bug.png - 64x64/bug.png - - - - 16x16/screenshots.png - 22x22/screenshots.png - 32x32/screenshots.png - 48x48/screenshots.png - 64x64/screenshots.png - scalable/screenshots.svg - - - scalable/custom-commands.svg - - - 16x16/patreon.png - 22x22/patreon.png - 24x24/patreon.png - 32x32/patreon.png - 48x48/patreon.png - 64x64/patreon.png - - - 16x16/cat.png - 22x22/cat.png - 24x24/cat.png - 32x32/cat.png - 48x48/cat.png - 64x64/cat.png - - - scalable/centralmods.svg - 16x16/centralmods.png - 22x22/centralmods.png - 32x32/centralmods.png - 48x48/centralmods.png - 64x64/centralmods.png - - - scalable/checkupdate.svg - 16x16/checkupdate.png - 22x22/checkupdate.png - 32x32/checkupdate.png - 48x48/checkupdate.png - 64x64/checkupdate.png - - - 16x16/copy.png - 22x22/copy.png - 32x32/copy.png - 48x48/copy.png - 64x64/copy.png - - - 16x16/help.png - 22x22/help.png - 32x32/help.png - 48x48/help.png - 64x64/help.png - - - 16x16/new.png + index.theme + + scalable/logo.svg + + + scalable/multimc.svg + + + scalable/reddit-alien.svg + + + scalable/twitch.svg + + + scalable/technic.svg + + + scalable/proxy.svg + + + scalable/language.svg + + + scalable/java.svg + + + 16x16/star.png + 24x24/star.png + 32x32/star.png + 48x48/star.png + 64x64/star.png + + + 16x16/worlds.png + 22x22/worlds.png + 32x32/worlds.png + 48x48/worlds.png + 64x64/worlds.png + + + 16x16/minecraft.png + 24x24/minecraft.png + 32x32/minecraft.png + 48x48/minecraft.png + 256x256/minecraft.png + + + 16x16/about.png + 22x22/about.png + 32x32/about.png + 48x48/about.png + 64x64/about.png + + + scalable/bug.svg + 16x16/bug.png + 22x22/bug.png + 32x32/bug.png + 48x48/bug.png + 64x64/bug.png + + + + 16x16/screenshots.png + 22x22/screenshots.png + 32x32/screenshots.png + 48x48/screenshots.png + 64x64/screenshots.png + scalable/screenshots.svg + + + scalable/custom-commands.svg + + + 16x16/patreon.png + 22x22/patreon.png + 24x24/patreon.png + 32x32/patreon.png + 48x48/patreon.png + 64x64/patreon.png + + + 16x16/cat.png + 22x22/cat.png + 24x24/cat.png + 32x32/cat.png + 48x48/cat.png + 64x64/cat.png + + + scalable/centralmods.svg + 16x16/centralmods.png + 22x22/centralmods.png + 32x32/centralmods.png + 48x48/centralmods.png + 64x64/centralmods.png + + + scalable/checkupdate.svg + 16x16/checkupdate.png + 22x22/checkupdate.png + 32x32/checkupdate.png + 48x48/checkupdate.png + 64x64/checkupdate.png + + + 16x16/copy.png + 22x22/copy.png + 32x32/copy.png + 48x48/copy.png + 64x64/copy.png + + + 16x16/help.png + 22x22/help.png + 32x32/help.png + 48x48/help.png + 64x64/help.png + + + 16x16/new.png 22x22/new.png 32x32/new.png - 48x48/new.png - 64x64/new.png - - - scalable/news.svg - 16x16/news.png - 22x22/news.png - 32x32/news.png - 48x48/news.png - 64x64/news.png - - - 16x16/status-bad.png - 24x24/status-bad.png - 22x22/status-bad.png - 32x32/status-bad.png - 48x48/status-bad.png - 64x64/status-bad.png - - - 16x16/status-good.png - 24x24/status-good.png - 22x22/status-good.png - 32x32/status-good.png - 48x48/status-good.png - 64x64/status-good.png - - - 16x16/status-yellow.png - 24x24/status-yellow.png - 22x22/status-yellow.png - 32x32/status-yellow.png - 48x48/status-yellow.png - 64x64/status-yellow.png - - - 16x16/status-running.png - 24x24/status-running.png - 22x22/status-running.png - 32x32/status-running.png - 48x48/status-running.png - 64x64/status-running.png - scalable/status-running.svg - - - 16x16/loadermods.png - 24x24/loadermods.png - 32x32/loadermods.png - 64x64/loadermods.png - - - 16x16/jarmods.png - 24x24/jarmods.png - 32x32/jarmods.png - 64x64/jarmods.png - - - 16x16/coremods.png - 24x24/coremods.png - 32x32/coremods.png - 64x64/coremods.png - - - 16x16/resourcepacks.png - 24x24/resourcepacks.png - 32x32/resourcepacks.png - 64x64/resourcepacks.png - - - 16x16/refresh.png - 22x22/refresh.png - 32x32/refresh.png - 48x48/refresh.png - 64x64/refresh.png - - - 16x16/settings.png + 48x48/new.png + 64x64/new.png + + + scalable/news.svg + 16x16/news.png + 22x22/news.png + 32x32/news.png + 48x48/news.png + 64x64/news.png + + + 16x16/status-bad.png + 24x24/status-bad.png + 22x22/status-bad.png + 32x32/status-bad.png + 48x48/status-bad.png + 64x64/status-bad.png + + + 16x16/status-good.png + 24x24/status-good.png + 22x22/status-good.png + 32x32/status-good.png + 48x48/status-good.png + 64x64/status-good.png + + + 16x16/status-yellow.png + 24x24/status-yellow.png + 22x22/status-yellow.png + 32x32/status-yellow.png + 48x48/status-yellow.png + 64x64/status-yellow.png + + + 16x16/status-running.png + 24x24/status-running.png + 22x22/status-running.png + 32x32/status-running.png + 48x48/status-running.png + 64x64/status-running.png + scalable/status-running.svg + + + 16x16/loadermods.png + 24x24/loadermods.png + 32x32/loadermods.png + 64x64/loadermods.png + + + 16x16/jarmods.png + 24x24/jarmods.png + 32x32/jarmods.png + 64x64/jarmods.png + + + 16x16/coremods.png + 24x24/coremods.png + 32x32/coremods.png + 64x64/coremods.png + + + 16x16/resourcepacks.png + 24x24/resourcepacks.png + 32x32/resourcepacks.png + 64x64/resourcepacks.png + + + 16x16/refresh.png + 22x22/refresh.png + 32x32/refresh.png + 48x48/refresh.png + 64x64/refresh.png + + + 16x16/settings.png 22x22/settings.png - 32x32/settings.png - 48x48/settings.png - 64x64/settings.png - - - 16x16/instance-settings.png - 22x22/instance-settings.png - 32x32/instance-settings.png - 48x48/instance-settings.png - 64x64/instance-settings.png - - - scalable/viewfolder.svg - 16x16/viewfolder.png + 32x32/settings.png + 48x48/settings.png + 64x64/settings.png + + + 16x16/instance-settings.png + 22x22/instance-settings.png + 32x32/instance-settings.png + 48x48/instance-settings.png + 64x64/instance-settings.png + + + scalable/viewfolder.svg + 16x16/viewfolder.png 22x22/viewfolder.png - 32x32/viewfolder.png - 48x48/viewfolder.png - 64x64/viewfolder.png + 32x32/viewfolder.png + 48x48/viewfolder.png + 64x64/viewfolder.png - - 8x8/noaccount.png - 16x16/noaccount.png - 24x24/noaccount.png - 32x32/noaccount.png - 48x48/noaccount.png + + 8x8/noaccount.png + 16x16/noaccount.png + 24x24/noaccount.png + 32x32/noaccount.png + 48x48/noaccount.png - - 8x8/noaccount.png - 16x16/noaccount.png - 24x24/noaccount.png - 32x32/noaccount.png - 48x48/noaccount.png + + 8x8/noaccount.png + 16x16/noaccount.png + 24x24/noaccount.png + 32x32/noaccount.png + 48x48/noaccount.png - - 16x16/log.png - 24x24/log.png - 32x32/log.png - 48x48/log.png - 64x64/log.png + + 16x16/log.png + 24x24/log.png + 32x32/log.png + 48x48/log.png + 64x64/log.png - - scalable/screenshot-placeholder.svg + + 128x128/unknown_server.png - - scalable/discord.svg + + scalable/screenshot-placeholder.svg - - 32x32/instances/chicken.png - 128x128/instances/chicken.png + + scalable/discord.svg - 32x32/instances/creeper.png - 128x128/instances/creeper.png + + 32x32/instances/chicken.png + 128x128/instances/chicken.png - 32x32/instances/enderpearl.png - 128x128/instances/enderpearl.png + 32x32/instances/creeper.png + 128x128/instances/creeper.png - 32x32/instances/ftb_glow.png - 128x128/instances/ftb_glow.png + 32x32/instances/enderpearl.png + 128x128/instances/enderpearl.png - 32x32/instances/ftb_logo.png - 128x128/instances/ftb_logo.png + 32x32/instances/ftb_glow.png + 128x128/instances/ftb_glow.png - 32x32/instances/flame.png - 128x128/instances/flame.png + 32x32/instances/ftb_logo.png + 128x128/instances/ftb_logo.png - 32x32/instances/gear.png - 128x128/instances/gear.png + 32x32/instances/flame.png + 128x128/instances/flame.png - 32x32/instances/herobrine.png - 128x128/instances/herobrine.png + 32x32/instances/gear.png + 128x128/instances/gear.png - 32x32/instances/infinity.png - 128x128/instances/infinity.png + 32x32/instances/herobrine.png + 128x128/instances/herobrine.png - 32x32/instances/magitech.png - 128x128/instances/magitech.png + 32x32/instances/infinity.png + 128x128/instances/infinity.png - 32x32/instances/meat.png - 128x128/instances/meat.png + 32x32/instances/magitech.png + 128x128/instances/magitech.png - 32x32/instances/netherstar.png - 128x128/instances/netherstar.png + 32x32/instances/meat.png + 128x128/instances/meat.png - 32x32/instances/skeleton.png - 128x128/instances/skeleton.png + 32x32/instances/netherstar.png + 128x128/instances/netherstar.png - 32x32/instances/squarecreeper.png - 128x128/instances/squarecreeper.png + 32x32/instances/skeleton.png + 128x128/instances/skeleton.png - 32x32/instances/steve.png - 128x128/instances/steve.png + 32x32/instances/squarecreeper.png + 128x128/instances/squarecreeper.png - 32x32/instances/brick.png - 32x32/instances/diamond.png - 32x32/instances/dirt.png - 32x32/instances/gold.png - 32x32/instances/grass.png - 32x32/instances/iron.png - 32x32/instances/planks.png - 32x32/instances/stone.png - 32x32/instances/tnt.png + 32x32/instances/steve.png + 128x128/instances/steve.png - 50x50/instances/enderman.png - + 32x32/instances/brick.png + 32x32/instances/diamond.png + 32x32/instances/dirt.png + 32x32/instances/gold.png + 32x32/instances/grass.png + 32x32/instances/iron.png + 32x32/instances/planks.png + 32x32/instances/stone.png + 32x32/instances/tnt.png + + 50x50/instances/enderman.png + + scalable/instances/fox.svg + diff --git a/application/resources/multimc/scalable/instances/fox.svg b/application/resources/multimc/scalable/instances/fox.svg new file mode 100644 index 00000000..fcf16b2f --- /dev/null +++ b/application/resources/multimc/scalable/instances/fox.svg @@ -0,0 +1,290 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/resources/multimc/scalable/language.svg b/application/resources/multimc/scalable/language.svg new file mode 100644 index 00000000..968e3538 --- /dev/null +++ b/application/resources/multimc/scalable/language.svg @@ -0,0 +1,109 @@ + +image/svg+xml \ No newline at end of file diff --git a/application/resources/pe_blue/pe_blue.qrc b/application/resources/pe_blue/pe_blue.qrc index 1706371a..98445d88 100644 --- a/application/resources/pe_blue/pe_blue.qrc +++ b/application/resources/pe_blue/pe_blue.qrc @@ -1,37 +1,38 @@ - - index.theme - scalable/about.svg - scalable/accounts.svg - scalable/bug.svg - scalable/centralmods.svg - scalable/checkupdate.svg - scalable/copy.svg - scalable/coremods.svg - scalable/externaltools.svg - scalable/help.svg - scalable/instance-settings.svg - scalable/jarmods.svg - scalable/java.svg - scalable/loadermods.svg - scalable/log.svg - scalable/minecraft.svg - scalable/multimc.svg - scalable/new.svg - scalable/news.svg - scalable/notes.svg - scalable/patreon.svg - scalable/proxy.svg - scalable/quickmods.svg - scalable/refresh.svg - scalable/resourcepacks.svg - scalable/screenshots.svg - scalable/settings.svg - scalable/status-bad.svg - scalable/status-good.svg - scalable/status-yellow.svg - scalable/viewfolder.svg - scalable/worlds.svg - + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/externaltools.svg + scalable/help.svg + scalable/instance-settings.svg + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/multimc.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/patreon.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + diff --git a/application/resources/pe_blue/scalable/language.svg b/application/resources/pe_blue/scalable/language.svg new file mode 100644 index 00000000..92868516 --- /dev/null +++ b/application/resources/pe_blue/scalable/language.svg @@ -0,0 +1,46 @@ + +image/svg+xml \ No newline at end of file diff --git a/application/resources/pe_colored/pe_colored.qrc b/application/resources/pe_colored/pe_colored.qrc index 588e3ac0..fbaaf9e4 100644 --- a/application/resources/pe_colored/pe_colored.qrc +++ b/application/resources/pe_colored/pe_colored.qrc @@ -1,37 +1,38 @@ - - index.theme - scalable/about.svg - scalable/accounts.svg - scalable/bug.svg - scalable/centralmods.svg - scalable/checkupdate.svg - scalable/copy.svg - scalable/coremods.svg - scalable/externaltools.svg - scalable/help.svg - scalable/instance-settings.svg - scalable/jarmods.svg - scalable/java.svg - scalable/loadermods.svg - scalable/log.svg - scalable/minecraft.svg - scalable/multimc.svg - scalable/new.svg - scalable/news.svg - scalable/notes.svg - scalable/patreon.svg - scalable/proxy.svg - scalable/quickmods.svg - scalable/refresh.svg - scalable/resourcepacks.svg - scalable/screenshots.svg - scalable/settings.svg - scalable/status-bad.svg - scalable/status-good.svg - scalable/status-yellow.svg - scalable/viewfolder.svg - scalable/worlds.svg - + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/externaltools.svg + scalable/help.svg + scalable/instance-settings.svg + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/multimc.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/patreon.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + diff --git a/application/resources/pe_colored/scalable/language.svg b/application/resources/pe_colored/scalable/language.svg new file mode 100644 index 00000000..80c1dcad --- /dev/null +++ b/application/resources/pe_colored/scalable/language.svg @@ -0,0 +1,44 @@ + +image/svg+xml + + + + + + + + \ No newline at end of file diff --git a/application/resources/pe_dark/pe_dark.qrc b/application/resources/pe_dark/pe_dark.qrc index 3543f590..a57b6a14 100644 --- a/application/resources/pe_dark/pe_dark.qrc +++ b/application/resources/pe_dark/pe_dark.qrc @@ -1,37 +1,38 @@ - - index.theme - scalable/about.svg - scalable/accounts.svg - scalable/bug.svg - scalable/centralmods.svg - scalable/checkupdate.svg - scalable/copy.svg - scalable/coremods.svg - scalable/externaltools.svg - scalable/help.svg - scalable/instance-settings.svg - scalable/jarmods.svg - scalable/java.svg - scalable/loadermods.svg - scalable/log.svg - scalable/minecraft.svg - scalable/multimc.svg - scalable/new.svg - scalable/news.svg - scalable/notes.svg - scalable/patreon.svg - scalable/proxy.svg - scalable/quickmods.svg - scalable/refresh.svg - scalable/resourcepacks.svg - scalable/screenshots.svg - scalable/settings.svg - scalable/status-bad.svg - scalable/status-good.svg - scalable/status-yellow.svg - scalable/viewfolder.svg - scalable/worlds.svg - + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/externaltools.svg + scalable/help.svg + scalable/instance-settings.svg + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/multimc.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/patreon.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + diff --git a/application/resources/pe_dark/scalable/language.svg b/application/resources/pe_dark/scalable/language.svg new file mode 100644 index 00000000..1a9b4c5c --- /dev/null +++ b/application/resources/pe_dark/scalable/language.svg @@ -0,0 +1,45 @@ + +image/svg+xml \ No newline at end of file diff --git a/application/resources/pe_light/pe_light.qrc b/application/resources/pe_light/pe_light.qrc index f83d6eba..6d77c835 100644 --- a/application/resources/pe_light/pe_light.qrc +++ b/application/resources/pe_light/pe_light.qrc @@ -1,38 +1,39 @@ - - index.theme - scalable/about.svg - scalable/accounts.svg - scalable/bug.svg - scalable/centralmods.svg - scalable/checkupdate.svg - scalable/copy.svg - scalable/coremods.svg - scalable/externaltools.svg - scalable/help.svg - scalable/instance-settings.svg - scalable/jarmods.svg - scalable/java.svg - scalable/loadermods.svg - scalable/log.svg - scalable/minecraft.svg - scalable/multimc.svg - scalable/new.svg - scalable/news.svg - scalable/notes.svg - scalable/patreon.svg - scalable/proxy.svg - scalable/quickmods.svg - scalable/refresh.svg - scalable/resourcepacks.svg - scalable/screenshots.svg - scalable/settings.svg - scalable/status-bad.svg - scalable/status-good.svg - scalable/status-yellow.svg - scalable/viewfolder.svg - scalable/worlds.svg - + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/externaltools.svg + scalable/help.svg + scalable/instance-settings.svg + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/multimc.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/patreon.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + diff --git a/application/resources/pe_light/scalable/language.svg b/application/resources/pe_light/scalable/language.svg new file mode 100644 index 00000000..57d5e3de --- /dev/null +++ b/application/resources/pe_light/scalable/language.svg @@ -0,0 +1,80 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/setupwizard/AnalyticsWizardPage.cpp b/application/setupwizard/AnalyticsWizardPage.cpp index 1109249b..6e063e99 100644 --- a/application/setupwizard/AnalyticsWizardPage.cpp +++ b/application/setupwizard/AnalyticsWizardPage.cpp @@ -8,22 +8,22 @@ #include AnalyticsWizardPage::AnalyticsWizardPage(QWidget *parent) - : BaseWizardPage(parent) + : BaseWizardPage(parent) { - setObjectName(QStringLiteral("analyticsPage")); - verticalLayout_3 = new QVBoxLayout(this); - verticalLayout_3->setObjectName(QStringLiteral("verticalLayout_3")); - textBrowser = new QTextBrowser(this); - textBrowser->setObjectName(QStringLiteral("textBrowser")); - textBrowser->setAcceptRichText(false); - textBrowser->setOpenExternalLinks(true); - verticalLayout_3->addWidget(textBrowser); + setObjectName(QStringLiteral("analyticsPage")); + verticalLayout_3 = new QVBoxLayout(this); + verticalLayout_3->setObjectName(QStringLiteral("verticalLayout_3")); + textBrowser = new QTextBrowser(this); + textBrowser->setObjectName(QStringLiteral("textBrowser")); + textBrowser->setAcceptRichText(false); + textBrowser->setOpenExternalLinks(true); + verticalLayout_3->addWidget(textBrowser); - checkBox = new QCheckBox(this); - checkBox->setObjectName(QStringLiteral("checkBox")); - checkBox->setChecked(true); - verticalLayout_3->addWidget(checkBox); - retranslate(); + checkBox = new QCheckBox(this); + checkBox->setObjectName(QStringLiteral("checkBox")); + checkBox->setChecked(true); + verticalLayout_3->addWidget(checkBox); + retranslate(); } AnalyticsWizardPage::~AnalyticsWizardPage() @@ -32,29 +32,29 @@ AnalyticsWizardPage::~AnalyticsWizardPage() bool AnalyticsWizardPage::validatePage() { - auto settings = MMC->settings(); - auto status = checkBox->isChecked(); - settings->set("Analytics", status); - return true; + auto settings = MMC->settings(); + auto status = checkBox->isChecked(); + settings->set("Analytics", status); + return true; } void AnalyticsWizardPage::retranslate() { - setTitle(tr("Analytics")); - setSubTitle(tr("We track some anonymous statistics about users.")); - textBrowser->setHtml(tr( - "" - "

MultiMC sends anonymous usage statistics on every start of the application. This helps us decide what platforms and issues to focus on.

" - "

The data is processed by Google Analytics, see their article on the " - "matter.

" - "

The following data is collected:

" - "
  • A random unique ID of the MultiMC installation.
    It is stored in the application settings (multimc.cfg).
  • " - "
  • Anonymized (partial) IP address.
  • " - "
  • MultiMC version.
  • " - "
  • Operating system name, version and architecture.
  • " - "
  • CPU architecture (kernel architecture on linux).
  • " - "
  • Size of system memory.
  • " - "
  • Java version, architecture and memory settings.
" - "

If we change the tracked information, you will see this page again.

")); - checkBox->setText(tr("Enable Analytics")); + setTitle(tr("Analytics")); + setSubTitle(tr("We track some anonymous statistics about users.")); + textBrowser->setHtml(tr( + "" + "

MultiMC sends anonymous usage statistics on every start of the application. This helps us decide what platforms and issues to focus on.

" + "

The data is processed by Google Analytics, see their article on the " + "matter.

" + "

The following data is collected:

" + "
  • A random unique ID of the MultiMC installation.
    It is stored in the application settings (multimc.cfg).
  • " + "
  • Anonymized (partial) IP address.
  • " + "
  • MultiMC version.
  • " + "
  • Operating system name, version and architecture.
  • " + "
  • CPU architecture (kernel architecture on linux).
  • " + "
  • Size of system memory.
  • " + "
  • Java version, architecture and memory settings.
" + "

If we change the tracked information, you will see this page again.

")); + checkBox->setText(tr("Enable Analytics")); } diff --git a/application/setupwizard/AnalyticsWizardPage.h b/application/setupwizard/AnalyticsWizardPage.h index 03de9cb0..c451db2c 100644 --- a/application/setupwizard/AnalyticsWizardPage.h +++ b/application/setupwizard/AnalyticsWizardPage.h @@ -8,18 +8,18 @@ class QCheckBox; class AnalyticsWizardPage : public BaseWizardPage { - Q_OBJECT; + Q_OBJECT public: - explicit AnalyticsWizardPage(QWidget *parent = Q_NULLPTR); - virtual ~AnalyticsWizardPage(); + explicit AnalyticsWizardPage(QWidget *parent = Q_NULLPTR); + virtual ~AnalyticsWizardPage(); - bool validatePage() override; + bool validatePage() override; protected: - void retranslate() override; + void retranslate() override; private: - QVBoxLayout *verticalLayout_3 = nullptr; - QTextBrowser *textBrowser = nullptr; - QCheckBox *checkBox = nullptr; + QVBoxLayout *verticalLayout_3 = nullptr; + QTextBrowser *textBrowser = nullptr; + QCheckBox *checkBox = nullptr; }; \ No newline at end of file diff --git a/application/setupwizard/BaseWizardPage.h b/application/setupwizard/BaseWizardPage.h index 9ad54e09..72dbecfd 100644 --- a/application/setupwizard/BaseWizardPage.h +++ b/application/setupwizard/BaseWizardPage.h @@ -6,28 +6,28 @@ class BaseWizardPage : public QWizardPage { public: - explicit BaseWizardPage(QWidget *parent = Q_NULLPTR) - : QWizardPage(parent) - { - } - virtual ~BaseWizardPage() {}; + explicit BaseWizardPage(QWidget *parent = Q_NULLPTR) + : QWizardPage(parent) + { + } + virtual ~BaseWizardPage() {}; - virtual bool wantsRefreshButton() - { - return false; - } - virtual void refresh() - { - } + virtual bool wantsRefreshButton() + { + return false; + } + virtual void refresh() + { + } protected: - virtual void retranslate() = 0; - void changeEvent(QEvent * event) override - { - if (event->type() == QEvent::LanguageChange) - { - retranslate(); - } - QWizardPage::changeEvent(event); - } + virtual void retranslate() = 0; + void changeEvent(QEvent * event) override + { + if (event->type() == QEvent::LanguageChange) + { + retranslate(); + } + QWizardPage::changeEvent(event); + } }; diff --git a/application/setupwizard/JavaWizardPage.cpp b/application/setupwizard/JavaWizardPage.cpp index cd2bca46..ad571c09 100644 --- a/application/setupwizard/JavaWizardPage.cpp +++ b/application/setupwizard/JavaWizardPage.cpp @@ -21,76 +21,76 @@ JavaWizardPage::JavaWizardPage(QWidget *parent) - :BaseWizardPage(parent) + :BaseWizardPage(parent) { - setupUi(); + setupUi(); } void JavaWizardPage::setupUi() { - setObjectName(QStringLiteral("javaPage")); - QVBoxLayout * layout = new QVBoxLayout(this); + setObjectName(QStringLiteral("javaPage")); + QVBoxLayout * layout = new QVBoxLayout(this); - m_java_widget = new JavaSettingsWidget(this); - layout->addWidget(m_java_widget); - setLayout(layout); + m_java_widget = new JavaSettingsWidget(this); + layout->addWidget(m_java_widget); + setLayout(layout); - retranslate(); + retranslate(); } void JavaWizardPage::refresh() { - m_java_widget->refresh(); + m_java_widget->refresh(); } void JavaWizardPage::initializePage() { - m_java_widget->initialize(); + m_java_widget->initialize(); } bool JavaWizardPage::wantsRefreshButton() { - return true; + return true; } bool JavaWizardPage::validatePage() { - auto settings = MMC->settings(); - auto result = m_java_widget->validate(); - switch(result) - { - default: - case JavaSettingsWidget::ValidationStatus::Bad: - { - return false; - } - case JavaSettingsWidget::ValidationStatus::AllOK: - { - settings->set("JavaPath", m_java_widget->javaPath()); - } - case JavaSettingsWidget::ValidationStatus::JavaBad: - { - // Memory - auto s = MMC->settings(); - s->set("MinMemAlloc", m_java_widget->minHeapSize()); - s->set("MaxMemAlloc", m_java_widget->maxHeapSize()); - if (m_java_widget->permGenEnabled()) - { - s->set("PermGen", m_java_widget->permGenSize()); - } - else - { - s->reset("PermGen"); - } - return true; - } - } + auto settings = MMC->settings(); + auto result = m_java_widget->validate(); + switch(result) + { + default: + case JavaSettingsWidget::ValidationStatus::Bad: + { + return false; + } + case JavaSettingsWidget::ValidationStatus::AllOK: + { + settings->set("JavaPath", m_java_widget->javaPath()); + } + case JavaSettingsWidget::ValidationStatus::JavaBad: + { + // Memory + auto s = MMC->settings(); + s->set("MinMemAlloc", m_java_widget->minHeapSize()); + s->set("MaxMemAlloc", m_java_widget->maxHeapSize()); + if (m_java_widget->permGenEnabled()) + { + s->set("PermGen", m_java_widget->permGenSize()); + } + else + { + s->reset("PermGen"); + } + return true; + } + } } void JavaWizardPage::retranslate() { - setTitle(tr("Java")); - setSubTitle(tr("You do not have a working Java set up yet or it went missing.\n" - "Please select one of the following or browse for a java executable.")); - m_java_widget->retranslate(); + setTitle(tr("Java")); + setSubTitle(tr("You do not have a working Java set up yet or it went missing.\n" + "Please select one of the following or browse for a java executable.")); + m_java_widget->retranslate(); } diff --git a/application/setupwizard/JavaWizardPage.h b/application/setupwizard/JavaWizardPage.h index 4ea31d24..0d749039 100644 --- a/application/setupwizard/JavaWizardPage.h +++ b/application/setupwizard/JavaWizardPage.h @@ -6,24 +6,24 @@ class JavaSettingsWidget; class JavaWizardPage : public BaseWizardPage { - Q_OBJECT; + Q_OBJECT public: - explicit JavaWizardPage(QWidget *parent = Q_NULLPTR); + explicit JavaWizardPage(QWidget *parent = Q_NULLPTR); - virtual ~JavaWizardPage() - { - }; + virtual ~JavaWizardPage() + { + }; - bool wantsRefreshButton() override; - void refresh() override; - void initializePage() override; - bool validatePage() override; + bool wantsRefreshButton() override; + void refresh() override; + void initializePage() override; + bool validatePage() override; protected: /* methods */ - void setupUi(); - void retranslate() override; + void setupUi(); + void retranslate() override; private: /* data */ - JavaSettingsWidget *m_java_widget = nullptr; + JavaSettingsWidget *m_java_widget = nullptr; }; diff --git a/application/setupwizard/LanguageWizardPage.cpp b/application/setupwizard/LanguageWizardPage.cpp index b884c91a..ca93c6f5 100644 --- a/application/setupwizard/LanguageWizardPage.cpp +++ b/application/setupwizard/LanguageWizardPage.cpp @@ -2,25 +2,19 @@ #include #include +#include "widgets/LanguageSelectionWidget.h" #include -#include LanguageWizardPage::LanguageWizardPage(QWidget *parent) - : BaseWizardPage(parent) + : BaseWizardPage(parent) { - setObjectName(QStringLiteral("languagePage")); - verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - languageView = new QListView(this); - languageView->setObjectName(QStringLiteral("languageView")); - verticalLayout->addWidget(languageView); - retranslate(); + setObjectName(QStringLiteral("languagePage")); + auto layout = new QVBoxLayout(this); + mainWidget = new LanguageSelectionWidget(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(mainWidget); - auto translations = MMC->translations(); - auto index = translations->selectedIndex(); - languageView->setModel(translations.get()); - languageView->setCurrentIndex(index); - connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageWizardPage::languageRowChanged); + retranslate(); } LanguageWizardPage::~LanguageWizardPage() @@ -29,38 +23,26 @@ LanguageWizardPage::~LanguageWizardPage() bool LanguageWizardPage::wantsRefreshButton() { - return true; + return true; } void LanguageWizardPage::refresh() { - auto translations = MMC->translations(); - translations->downloadIndex(); + auto translations = MMC->translations(); + translations->downloadIndex(); } bool LanguageWizardPage::validatePage() { - auto settings = MMC->settings(); - auto translations = MMC->translations(); - QString key = translations->data(languageView->currentIndex(), Qt::UserRole).toString(); - settings->set("Language", key); - return true; + auto settings = MMC->settings(); + QString key = mainWidget->getSelectedLanguageKey(); + settings->set("Language", key); + return true; } void LanguageWizardPage::retranslate() { - setTitle(tr("Language")); - setSubTitle(tr("Select the language to use in MultiMC")); -} - -void LanguageWizardPage::languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - if (current == previous) - { - return; - } - auto translations = MMC->translations(); - QString key = translations->data(current, Qt::UserRole).toString(); - translations->selectLanguage(key); - translations->updateLanguage(key); + setTitle(tr("Language")); + setSubTitle(tr("Select the language to use in MultiMC")); + mainWidget->retranslate(); } diff --git a/application/setupwizard/LanguageWizardPage.h b/application/setupwizard/LanguageWizardPage.h index 8b55d57f..45a0e5c0 100644 --- a/application/setupwizard/LanguageWizardPage.h +++ b/application/setupwizard/LanguageWizardPage.h @@ -2,30 +2,25 @@ #include "BaseWizardPage.h" -class QVBoxLayout; -class QListView; +class LanguageSelectionWidget; class LanguageWizardPage : public BaseWizardPage { - Q_OBJECT; + Q_OBJECT public: - explicit LanguageWizardPage(QWidget *parent = Q_NULLPTR); + explicit LanguageWizardPage(QWidget *parent = Q_NULLPTR); - virtual ~LanguageWizardPage(); + virtual ~LanguageWizardPage(); - bool wantsRefreshButton() override; + bool wantsRefreshButton() override; - void refresh() override; + void refresh() override; - bool validatePage() override; + bool validatePage() override; protected: - void retranslate() override; - -protected slots: - void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void retranslate() override; private: - QVBoxLayout *verticalLayout = nullptr; - QListView *languageView = nullptr; + LanguageSelectionWidget *mainWidget = nullptr; }; diff --git a/application/setupwizard/SetupWizard.cpp b/application/setupwizard/SetupWizard.cpp index c4b21713..5c2d60ac 100644 --- a/application/setupwizard/SetupWizard.cpp +++ b/application/setupwizard/SetupWizard.cpp @@ -12,74 +12,74 @@ SetupWizard::SetupWizard(QWidget *parent) : QWizard(parent) { - setObjectName(QStringLiteral("SetupWizard")); - resize(615, 659); - // make it ugly everywhere to avoid variability in theming - setWizardStyle(QWizard::ClassicStyle); - setOptions(QWizard::NoCancelButton | QWizard::IndependentPages | QWizard::HaveCustomButton1); + setObjectName(QStringLiteral("SetupWizard")); + resize(615, 659); + // make it ugly everywhere to avoid variability in theming + setWizardStyle(QWizard::ClassicStyle); + setOptions(QWizard::NoCancelButton | QWizard::IndependentPages | QWizard::HaveCustomButton1); - retranslate(); + retranslate(); - connect(this, &QWizard::currentIdChanged, this, &SetupWizard::pageChanged); + connect(this, &QWizard::currentIdChanged, this, &SetupWizard::pageChanged); } void SetupWizard::retranslate() { - setButtonText(QWizard::NextButton, tr("&Next >")); - setButtonText(QWizard::BackButton, tr("< &Back")); - setButtonText(QWizard::FinishButton, tr("&Finish")); - setButtonText(QWizard::CustomButton1, tr("&Refresh")); - setWindowTitle(tr("MultiMC Quick Setup")); + setButtonText(QWizard::NextButton, tr("&Next >")); + setButtonText(QWizard::BackButton, tr("< &Back")); + setButtonText(QWizard::FinishButton, tr("&Finish")); + setButtonText(QWizard::CustomButton1, tr("&Refresh")); + setWindowTitle(tr("MultiMC Quick Setup")); } BaseWizardPage * SetupWizard::getBasePage(int id) { - if(id == -1) - return nullptr; - auto pagePtr = page(id); - if(!pagePtr) - return nullptr; - return dynamic_cast(pagePtr); + if(id == -1) + return nullptr; + auto pagePtr = page(id); + if(!pagePtr) + return nullptr; + return dynamic_cast(pagePtr); } BaseWizardPage * SetupWizard::getCurrentBasePage() { - return getBasePage(currentId()); + return getBasePage(currentId()); } void SetupWizard::pageChanged(int id) { - auto basePagePtr = getBasePage(id); - if(!basePagePtr) - { - return; - } - if(basePagePtr->wantsRefreshButton()) - { - setButtonLayout({QWizard::CustomButton1, QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); - auto customButton = button(QWizard::CustomButton1); - connect(customButton, &QAbstractButton::pressed, [&](){ - auto basePagePtr = getCurrentBasePage(); - if(basePagePtr) - { - basePagePtr->refresh(); - } - }); - } - else - { - setButtonLayout({QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); - } + auto basePagePtr = getBasePage(id); + if(!basePagePtr) + { + return; + } + if(basePagePtr->wantsRefreshButton()) + { + setButtonLayout({QWizard::CustomButton1, QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); + auto customButton = button(QWizard::CustomButton1); + connect(customButton, &QAbstractButton::pressed, [&](){ + auto basePagePtr = getCurrentBasePage(); + if(basePagePtr) + { + basePagePtr->refresh(); + } + }); + } + else + { + setButtonLayout({QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); + } } void SetupWizard::changeEvent(QEvent *event) { - if (event->type() == QEvent::LanguageChange) - { - retranslate(); - } - QWizard::changeEvent(event); + if (event->type() == QEvent::LanguageChange) + { + retranslate(); + } + QWizard::changeEvent(event); } SetupWizard::~SetupWizard() diff --git a/application/setupwizard/SetupWizard.h b/application/setupwizard/SetupWizard.h index e3292997..08b0d805 100644 --- a/application/setupwizard/SetupWizard.h +++ b/application/setupwizard/SetupWizard.h @@ -1,4 +1,4 @@ -/* Copyright 2017-2018 MultiMC Contributors +/* Copyright 2017-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,20 +26,20 @@ class BaseWizardPage; class SetupWizard : public QWizard { - Q_OBJECT + Q_OBJECT public: /* con/destructors */ - explicit SetupWizard(QWidget *parent = 0); - virtual ~SetupWizard(); + explicit SetupWizard(QWidget *parent = 0); + virtual ~SetupWizard(); - void changeEvent(QEvent * event) override; - BaseWizardPage *getBasePage(int id); - BaseWizardPage *getCurrentBasePage(); + void changeEvent(QEvent * event) override; + BaseWizardPage *getBasePage(int id); + BaseWizardPage *getCurrentBasePage(); private slots: - void pageChanged(int id); + void pageChanged(int id); private: /* methods */ - void retranslate(); + void retranslate(); }; diff --git a/application/themes/BrightTheme.cpp b/application/themes/BrightTheme.cpp index 2763c982..b9188bdd 100644 --- a/application/themes/BrightTheme.cpp +++ b/application/themes/BrightTheme.cpp @@ -2,55 +2,55 @@ QString BrightTheme::id() { - return "bright"; + return "bright"; } QString BrightTheme::name() { - return QObject::tr("Bright"); + return QObject::tr("Bright"); } bool BrightTheme::hasColorScheme() { - return true; + return true; } QPalette BrightTheme::colorScheme() { - QPalette brightPalette; - brightPalette.setColor(QPalette::Window, QColor(239,240,241)); - brightPalette.setColor(QPalette::WindowText, QColor(49,54,59)); - brightPalette.setColor(QPalette::Base, QColor(252,252,252)); - brightPalette.setColor(QPalette::AlternateBase, QColor(239,240,241)); - brightPalette.setColor(QPalette::ToolTipBase, QColor(49,54,59)); - brightPalette.setColor(QPalette::ToolTipText, QColor(239,240,241)); - brightPalette.setColor(QPalette::Text, QColor(49,54,59)); - brightPalette.setColor(QPalette::Button, QColor(239,240,241)); - brightPalette.setColor(QPalette::ButtonText, QColor(49,54,59)); - brightPalette.setColor(QPalette::BrightText, Qt::red); - brightPalette.setColor(QPalette::Link, QColor(41, 128, 185)); - brightPalette.setColor(QPalette::Highlight, QColor(61, 174, 233)); - brightPalette.setColor(QPalette::HighlightedText, QColor(239,240,241)); - return fadeInactive(brightPalette, fadeAmount(), fadeColor()); + QPalette brightPalette; + brightPalette.setColor(QPalette::Window, QColor(239,240,241)); + brightPalette.setColor(QPalette::WindowText, QColor(49,54,59)); + brightPalette.setColor(QPalette::Base, QColor(252,252,252)); + brightPalette.setColor(QPalette::AlternateBase, QColor(239,240,241)); + brightPalette.setColor(QPalette::ToolTipBase, QColor(49,54,59)); + brightPalette.setColor(QPalette::ToolTipText, QColor(239,240,241)); + brightPalette.setColor(QPalette::Text, QColor(49,54,59)); + brightPalette.setColor(QPalette::Button, QColor(239,240,241)); + brightPalette.setColor(QPalette::ButtonText, QColor(49,54,59)); + brightPalette.setColor(QPalette::BrightText, Qt::red); + brightPalette.setColor(QPalette::Link, QColor(41, 128, 185)); + brightPalette.setColor(QPalette::Highlight, QColor(61, 174, 233)); + brightPalette.setColor(QPalette::HighlightedText, QColor(239,240,241)); + return fadeInactive(brightPalette, fadeAmount(), fadeColor()); } double BrightTheme::fadeAmount() { - return 0.5; + return 0.5; } QColor BrightTheme::fadeColor() { - return QColor(239,240,241); + return QColor(239,240,241); } bool BrightTheme::hasStyleSheet() { - return false; + return false; } QString BrightTheme::appStyleSheet() { - return QString(); + return QString(); } diff --git a/application/themes/BrightTheme.h b/application/themes/BrightTheme.h index 31b31b2d..c61f52d5 100644 --- a/application/themes/BrightTheme.h +++ b/application/themes/BrightTheme.h @@ -5,15 +5,15 @@ class BrightTheme: public FusionTheme { public: - virtual ~BrightTheme() {} + virtual ~BrightTheme() {} - QString id() override; - QString name() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; + QString id() override; + QString name() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; }; diff --git a/application/themes/CustomTheme.cpp b/application/themes/CustomTheme.cpp index a6c7bf8c..3e3e27de 100644 --- a/application/themes/CustomTheme.cpp +++ b/application/themes/CustomTheme.cpp @@ -8,237 +8,237 @@ const char * styleFile = "themeStyle.css"; static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAmount, QColor &fadeColor, QString &name, QString &widgets) { - QFileInfo pathInfo(path); - if(pathInfo.exists() && pathInfo.isFile()) - { - try - { - auto doc = Json::requireDocument(path, "Theme JSON file"); - const QJsonObject root = doc.object(); - name = Json::requireString(root, "name", "Theme name"); - widgets = Json::requireString(root, "widgets", "Qt widget theme"); - auto colorsRoot = Json::requireObject(root, "colors", "colors object"); - auto readColor = [&](QString colorName) -> QColor - { - auto colorValue = Json::ensureString(colorsRoot, colorName, QString()); - if(!colorValue.isEmpty()) - { - QColor color(colorValue); - if(!color.isValid()) - { - qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized."; - return QColor(); - } - return color; - } - return QColor(); - }; - auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) - { - auto color = readColor(colorName); - if(color.isValid()) - { - palette.setColor(role, color); - } - else - { - qDebug() << "Color value for" << colorName << "was not present."; - } - }; - - // palette - readAndSetColor(QPalette::Window, "Window"); - readAndSetColor(QPalette::WindowText, "WindowText"); - readAndSetColor(QPalette::Base, "Base"); - readAndSetColor(QPalette::AlternateBase, "AlternateBase"); - readAndSetColor(QPalette::ToolTipBase, "ToolTipBase"); - readAndSetColor(QPalette::ToolTipText, "ToolTipText"); - readAndSetColor(QPalette::Text, "Text"); - readAndSetColor(QPalette::Button, "Button"); - readAndSetColor(QPalette::ButtonText, "ButtonText"); - readAndSetColor(QPalette::BrightText, "BrightText"); - readAndSetColor(QPalette::Link, "Link"); - readAndSetColor(QPalette::Highlight, "Highlight"); - readAndSetColor(QPalette::HighlightedText, "HighlightedText"); - - //fade - fadeColor = readColor("fadeColor"); - fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount"); - - } - catch(Exception e) - { - qWarning() << "Couldn't load theme json: " << e.cause(); - return false; - } - } - else - { - qDebug() << "No theme json present."; - return false; - } - return true; + QFileInfo pathInfo(path); + if(pathInfo.exists() && pathInfo.isFile()) + { + try + { + auto doc = Json::requireDocument(path, "Theme JSON file"); + const QJsonObject root = doc.object(); + name = Json::requireString(root, "name", "Theme name"); + widgets = Json::requireString(root, "widgets", "Qt widget theme"); + auto colorsRoot = Json::requireObject(root, "colors", "colors object"); + auto readColor = [&](QString colorName) -> QColor + { + auto colorValue = Json::ensureString(colorsRoot, colorName, QString()); + if(!colorValue.isEmpty()) + { + QColor color(colorValue); + if(!color.isValid()) + { + qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized."; + return QColor(); + } + return color; + } + return QColor(); + }; + auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) + { + auto color = readColor(colorName); + if(color.isValid()) + { + palette.setColor(role, color); + } + else + { + qDebug() << "Color value for" << colorName << "was not present."; + } + }; + + // palette + readAndSetColor(QPalette::Window, "Window"); + readAndSetColor(QPalette::WindowText, "WindowText"); + readAndSetColor(QPalette::Base, "Base"); + readAndSetColor(QPalette::AlternateBase, "AlternateBase"); + readAndSetColor(QPalette::ToolTipBase, "ToolTipBase"); + readAndSetColor(QPalette::ToolTipText, "ToolTipText"); + readAndSetColor(QPalette::Text, "Text"); + readAndSetColor(QPalette::Button, "Button"); + readAndSetColor(QPalette::ButtonText, "ButtonText"); + readAndSetColor(QPalette::BrightText, "BrightText"); + readAndSetColor(QPalette::Link, "Link"); + readAndSetColor(QPalette::Highlight, "Highlight"); + readAndSetColor(QPalette::HighlightedText, "HighlightedText"); + + //fade + fadeColor = readColor("fadeColor"); + fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount"); + + } + catch (const Exception &e) + { + qWarning() << "Couldn't load theme json: " << e.cause(); + return false; + } + } + else + { + qDebug() << "No theme json present."; + return false; + } + return true; } static bool writeThemeJson(const QString &path, const QPalette &palette, double fadeAmount, QColor fadeColor, QString name, QString widgets) { - QJsonObject rootObj; - rootObj.insert("name", name); - rootObj.insert("widgets", widgets); - - QJsonObject colorsObj; - auto insertColor = [&](QPalette::ColorRole role, QString colorName) - { - colorsObj.insert(colorName, palette.color(role).name()); - }; - - // palette - insertColor(QPalette::Window, "Window"); - insertColor(QPalette::WindowText, "WindowText"); - insertColor(QPalette::Base, "Base"); - insertColor(QPalette::AlternateBase, "AlternateBase"); - insertColor(QPalette::ToolTipBase, "ToolTipBase"); - insertColor(QPalette::ToolTipText, "ToolTipText"); - insertColor(QPalette::Text, "Text"); - insertColor(QPalette::Button, "Button"); - insertColor(QPalette::ButtonText, "ButtonText"); - insertColor(QPalette::BrightText, "BrightText"); - insertColor(QPalette::Link, "Link"); - insertColor(QPalette::Highlight, "Highlight"); - insertColor(QPalette::HighlightedText, "HighlightedText"); - - // fade - colorsObj.insert("fadeColor", fadeColor.name()); - colorsObj.insert("fadeAmount", fadeAmount); - - rootObj.insert("colors", colorsObj); - try - { - Json::write(rootObj, path); - return true; - } - catch (Exception e) - { - qWarning() << "Failed to write theme json to" << path; - return false; - } + QJsonObject rootObj; + rootObj.insert("name", name); + rootObj.insert("widgets", widgets); + + QJsonObject colorsObj; + auto insertColor = [&](QPalette::ColorRole role, QString colorName) + { + colorsObj.insert(colorName, palette.color(role).name()); + }; + + // palette + insertColor(QPalette::Window, "Window"); + insertColor(QPalette::WindowText, "WindowText"); + insertColor(QPalette::Base, "Base"); + insertColor(QPalette::AlternateBase, "AlternateBase"); + insertColor(QPalette::ToolTipBase, "ToolTipBase"); + insertColor(QPalette::ToolTipText, "ToolTipText"); + insertColor(QPalette::Text, "Text"); + insertColor(QPalette::Button, "Button"); + insertColor(QPalette::ButtonText, "ButtonText"); + insertColor(QPalette::BrightText, "BrightText"); + insertColor(QPalette::Link, "Link"); + insertColor(QPalette::Highlight, "Highlight"); + insertColor(QPalette::HighlightedText, "HighlightedText"); + + // fade + colorsObj.insert("fadeColor", fadeColor.name()); + colorsObj.insert("fadeAmount", fadeAmount); + + rootObj.insert("colors", colorsObj); + try + { + Json::write(rootObj, path); + return true; + } + catch (const Exception &e) + { + qWarning() << "Failed to write theme json to" << path; + return false; + } } CustomTheme::CustomTheme(ITheme* baseTheme, QString folder) { - m_id = folder; - QString path = FS::PathCombine("themes", m_id); - QString pathResources = FS::PathCombine("themes", m_id, "resources"); - - qDebug() << "Loading theme" << m_id; - - if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) - { - qWarning() << "couldn't create folder for theme!"; - m_palette = baseTheme->colorScheme(); - m_styleSheet = baseTheme->appStyleSheet(); - return; - } - - auto themeFilePath = FS::PathCombine(path, themeFile); - - m_palette = baseTheme->colorScheme(); - if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets)) - { - m_name = "Custom"; - m_palette = baseTheme->colorScheme(); - m_fadeColor = baseTheme->fadeColor(); - m_fadeAmount = baseTheme->fadeAmount(); - m_widgets = baseTheme->qtTheme(); - - QFileInfo info(themeFilePath); - if(!info.exists()) - { - writeThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, "Custom", m_widgets); - } - } - else - { - m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); - } - - auto cssFilePath = FS::PathCombine(path, styleFile); - QFileInfo info (cssFilePath); - if(info.isFile()) - { - try - { - // TODO: validate css? - m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); - } - catch(Exception e) - { - qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; - m_styleSheet = baseTheme->appStyleSheet(); - } - } - else - { - qDebug() << "No theme css present."; - m_styleSheet = baseTheme->appStyleSheet(); - try - { - FS::write(cssFilePath, m_styleSheet.toUtf8()); - } - catch(Exception e) - { - qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; - } - } + m_id = folder; + QString path = FS::PathCombine("themes", m_id); + QString pathResources = FS::PathCombine("themes", m_id, "resources"); + + qDebug() << "Loading theme" << m_id; + + if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) + { + qWarning() << "couldn't create folder for theme!"; + m_palette = baseTheme->colorScheme(); + m_styleSheet = baseTheme->appStyleSheet(); + return; + } + + auto themeFilePath = FS::PathCombine(path, themeFile); + + m_palette = baseTheme->colorScheme(); + if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets)) + { + m_name = "Custom"; + m_palette = baseTheme->colorScheme(); + m_fadeColor = baseTheme->fadeColor(); + m_fadeAmount = baseTheme->fadeAmount(); + m_widgets = baseTheme->qtTheme(); + + QFileInfo info(themeFilePath); + if(!info.exists()) + { + writeThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, "Custom", m_widgets); + } + } + else + { + m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); + } + + auto cssFilePath = FS::PathCombine(path, styleFile); + QFileInfo info (cssFilePath); + if(info.isFile()) + { + try + { + // TODO: validate css? + m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); + } + catch (const Exception &e) + { + qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; + m_styleSheet = baseTheme->appStyleSheet(); + } + } + else + { + qDebug() << "No theme css present."; + m_styleSheet = baseTheme->appStyleSheet(); + try + { + FS::write(cssFilePath, m_styleSheet.toUtf8()); + } + catch (const Exception &e) + { + qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; + } + } } QStringList CustomTheme::searchPaths() { - return { FS::PathCombine("themes", m_id, "resources") }; + return { FS::PathCombine("themes", m_id, "resources") }; } QString CustomTheme::id() { - return m_id; + return m_id; } QString CustomTheme::name() { - return m_name; + return m_name; } bool CustomTheme::hasColorScheme() { - return true; + return true; } QPalette CustomTheme::colorScheme() { - return m_palette; + return m_palette; } bool CustomTheme::hasStyleSheet() { - return true; + return true; } QString CustomTheme::appStyleSheet() { - return m_styleSheet; + return m_styleSheet; } double CustomTheme::fadeAmount() { - return m_fadeAmount; + return m_fadeAmount; } QColor CustomTheme::fadeColor() { - return m_fadeColor; + return m_fadeColor; } QString CustomTheme::qtTheme() { - return m_widgets; + return m_widgets; } diff --git a/application/themes/CustomTheme.h b/application/themes/CustomTheme.h index 9023b13e..d216895d 100644 --- a/application/themes/CustomTheme.h +++ b/application/themes/CustomTheme.h @@ -5,27 +5,27 @@ class CustomTheme: public ITheme { public: - CustomTheme(ITheme * baseTheme, QString folder); - virtual ~CustomTheme() {} + CustomTheme(ITheme * baseTheme, QString folder); + virtual ~CustomTheme() {} - QString id() override; - QString name() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; - QString qtTheme() override; - QStringList searchPaths() override; + QString id() override; + QString name() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; + QString qtTheme() override; + QStringList searchPaths() override; private: /* data */ - QPalette m_palette; - QColor m_fadeColor; - double m_fadeAmount; - QString m_styleSheet; - QString m_name; - QString m_id; - QString m_widgets; + QPalette m_palette; + QColor m_fadeColor; + double m_fadeAmount; + QString m_styleSheet; + QString m_name; + QString m_id; + QString m_widgets; }; diff --git a/application/themes/DarkTheme.cpp b/application/themes/DarkTheme.cpp index cf4a81e1..31ecd559 100644 --- a/application/themes/DarkTheme.cpp +++ b/application/themes/DarkTheme.cpp @@ -2,54 +2,54 @@ QString DarkTheme::id() { - return "dark"; + return "dark"; } QString DarkTheme::name() { - return QObject::tr("Dark"); + return QObject::tr("Dark"); } bool DarkTheme::hasColorScheme() { - return true; + return true; } QPalette DarkTheme::colorScheme() { - QPalette darkPalette; - darkPalette.setColor(QPalette::Window, QColor(49,54,59)); - darkPalette.setColor(QPalette::WindowText, Qt::white); - darkPalette.setColor(QPalette::Base, QColor(35,38,41)); - darkPalette.setColor(QPalette::AlternateBase, QColor(49,54,59)); - darkPalette.setColor(QPalette::ToolTipBase, Qt::white); - darkPalette.setColor(QPalette::ToolTipText, Qt::white); - darkPalette.setColor(QPalette::Text, Qt::white); - darkPalette.setColor(QPalette::Button, QColor(49,54,59)); - darkPalette.setColor(QPalette::ButtonText, Qt::white); - darkPalette.setColor(QPalette::BrightText, Qt::red); - darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::HighlightedText, Qt::black); - return fadeInactive(darkPalette, fadeAmount(), fadeColor()); + QPalette darkPalette; + darkPalette.setColor(QPalette::Window, QColor(49,54,59)); + darkPalette.setColor(QPalette::WindowText, Qt::white); + darkPalette.setColor(QPalette::Base, QColor(35,38,41)); + darkPalette.setColor(QPalette::AlternateBase, QColor(49,54,59)); + darkPalette.setColor(QPalette::ToolTipBase, Qt::white); + darkPalette.setColor(QPalette::ToolTipText, Qt::white); + darkPalette.setColor(QPalette::Text, Qt::white); + darkPalette.setColor(QPalette::Button, QColor(49,54,59)); + darkPalette.setColor(QPalette::ButtonText, Qt::white); + darkPalette.setColor(QPalette::BrightText, Qt::red); + darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::HighlightedText, Qt::black); + return fadeInactive(darkPalette, fadeAmount(), fadeColor()); } double DarkTheme::fadeAmount() { - return 0.5; + return 0.5; } QColor DarkTheme::fadeColor() { - return QColor(49,54,59); + return QColor(49,54,59); } bool DarkTheme::hasStyleSheet() { - return true; + return true; } QString DarkTheme::appStyleSheet() { - return "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"; + return "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"; } diff --git a/application/themes/DarkTheme.h b/application/themes/DarkTheme.h index f3c18e22..9bd2f343 100644 --- a/application/themes/DarkTheme.h +++ b/application/themes/DarkTheme.h @@ -5,14 +5,14 @@ class DarkTheme: public FusionTheme { public: - virtual ~DarkTheme() {} + virtual ~DarkTheme() {} - QString id() override; - QString name() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; + QString id() override; + QString name() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; }; diff --git a/application/themes/FusionTheme.cpp b/application/themes/FusionTheme.cpp index aaf7b3d5..cf3286ba 100644 --- a/application/themes/FusionTheme.cpp +++ b/application/themes/FusionTheme.cpp @@ -2,5 +2,5 @@ QString FusionTheme::qtTheme() { - return "Fusion"; + return "Fusion"; } diff --git a/application/themes/FusionTheme.h b/application/themes/FusionTheme.h index e2d9014c..ee34245a 100644 --- a/application/themes/FusionTheme.h +++ b/application/themes/FusionTheme.h @@ -5,7 +5,7 @@ class FusionTheme: public ITheme { public: - virtual ~FusionTheme() {} + virtual ~FusionTheme() {} - QString qtTheme() override; + QString qtTheme() override; }; diff --git a/application/themes/ITheme.cpp b/application/themes/ITheme.cpp index b1cecf57..bfec87e7 100644 --- a/application/themes/ITheme.cpp +++ b/application/themes/ITheme.cpp @@ -6,42 +6,42 @@ void ITheme::apply(bool) { - QApplication::setStyle(QStyleFactory::create(qtTheme())); - if(hasColorScheme()) - { - QApplication::setPalette(colorScheme()); - } - if(hasStyleSheet()) - { - MMC->setStyleSheet(appStyleSheet()); - } - else - { - MMC->setStyleSheet(QString()); - } - QDir::setSearchPaths("theme", searchPaths()); + QApplication::setStyle(QStyleFactory::create(qtTheme())); + if(hasColorScheme()) + { + QApplication::setPalette(colorScheme()); + } + if(hasStyleSheet()) + { + MMC->setStyleSheet(appStyleSheet()); + } + else + { + MMC->setStyleSheet(QString()); + } + QDir::setSearchPaths("theme", searchPaths()); } QPalette ITheme::fadeInactive(QPalette in, qreal bias, QColor color) { - auto blend = [&in, bias, color](QPalette::ColorRole role) - { - QColor from = in.color(QPalette::Active, role); - QColor blended = Rainbow::mix(from, color, bias); - in.setColor(QPalette::Disabled, role, blended); - }; - blend(QPalette::Window); - blend(QPalette::WindowText); - blend(QPalette::Base); - blend(QPalette::AlternateBase); - blend(QPalette::ToolTipBase); - blend(QPalette::ToolTipText); - blend(QPalette::Text); - blend(QPalette::Button); - blend(QPalette::ButtonText); - blend(QPalette::BrightText); - blend(QPalette::Link); - blend(QPalette::Highlight); - blend(QPalette::HighlightedText); - return in; + auto blend = [&in, bias, color](QPalette::ColorRole role) + { + QColor from = in.color(QPalette::Active, role); + QColor blended = Rainbow::mix(from, color, bias); + in.setColor(QPalette::Disabled, role, blended); + }; + blend(QPalette::Window); + blend(QPalette::WindowText); + blend(QPalette::Base); + blend(QPalette::AlternateBase); + blend(QPalette::ToolTipBase); + blend(QPalette::ToolTipText); + blend(QPalette::Text); + blend(QPalette::Button); + blend(QPalette::ButtonText); + blend(QPalette::BrightText); + blend(QPalette::Link); + blend(QPalette::Highlight); + blend(QPalette::HighlightedText); + return in; } diff --git a/application/themes/ITheme.h b/application/themes/ITheme.h index b75001c2..c2347cf6 100644 --- a/application/themes/ITheme.h +++ b/application/themes/ITheme.h @@ -7,21 +7,21 @@ class QStyle; class ITheme { public: - virtual ~ITheme() {} - virtual void apply(bool initial); - virtual QString id() = 0; - virtual QString name() = 0; - virtual bool hasStyleSheet() = 0; - virtual QString appStyleSheet() = 0; - virtual QString qtTheme() = 0; - virtual bool hasColorScheme() = 0; - virtual QPalette colorScheme() = 0; - virtual QColor fadeColor() = 0; - virtual double fadeAmount() = 0; - virtual QStringList searchPaths() - { - return {}; - } + virtual ~ITheme() {} + virtual void apply(bool initial); + virtual QString id() = 0; + virtual QString name() = 0; + virtual bool hasStyleSheet() = 0; + virtual QString appStyleSheet() = 0; + virtual QString qtTheme() = 0; + virtual bool hasColorScheme() = 0; + virtual QPalette colorScheme() = 0; + virtual QColor fadeColor() = 0; + virtual double fadeAmount() = 0; + virtual QStringList searchPaths() + { + return {}; + } - static QPalette fadeInactive(QPalette in, qreal bias, QColor color); + static QPalette fadeInactive(QPalette in, qreal bias, QColor color); }; diff --git a/application/themes/SystemTheme.cpp b/application/themes/SystemTheme.cpp index a9ee853a..00b2300d 100644 --- a/application/themes/SystemTheme.cpp +++ b/application/themes/SystemTheme.cpp @@ -6,75 +6,75 @@ SystemTheme::SystemTheme() { - const auto & style = QApplication::style(); - systemPalette = style->standardPalette(); - QString lowerThemeName = style->objectName(); - qDebug() << systemTheme; - QStringList styles = QStyleFactory::keys(); - for(auto &st: styles) - { - if(st.toLower() == lowerThemeName) - { - systemTheme = st; - return; - } - } - // fall back to fusion if we can't find the current theme. - systemTheme = "Fusion"; - qDebug() << "System theme not found, defaulted to Fusion"; + const auto & style = QApplication::style(); + systemPalette = style->standardPalette(); + QString lowerThemeName = style->objectName(); + qDebug() << systemTheme; + QStringList styles = QStyleFactory::keys(); + for(auto &st: styles) + { + if(st.toLower() == lowerThemeName) + { + systemTheme = st; + return; + } + } + // fall back to fusion if we can't find the current theme. + systemTheme = "Fusion"; + qDebug() << "System theme not found, defaulted to Fusion"; } void SystemTheme::apply(bool initial) { - // if we are applying the system theme as the first theme, just don't touch anything. it's for the better... - if(initial) - { - return; - } - ITheme::apply(initial); + // if we are applying the system theme as the first theme, just don't touch anything. it's for the better... + if(initial) + { + return; + } + ITheme::apply(initial); } QString SystemTheme::id() { - return "system"; + return "system"; } QString SystemTheme::name() { - return QObject::tr("System"); + return QObject::tr("System"); } QString SystemTheme::qtTheme() { - return systemTheme; + return systemTheme; } QPalette SystemTheme::colorScheme() { - return systemPalette; + return systemPalette; } QString SystemTheme::appStyleSheet() { - return QString(); + return QString(); } double SystemTheme::fadeAmount() { - return 0.5; + return 0.5; } QColor SystemTheme::fadeColor() { - return QColor(128,128,128); + return QColor(128,128,128); } bool SystemTheme::hasStyleSheet() { - return false; + return false; } bool SystemTheme::hasColorScheme() { - return true; + return true; } diff --git a/application/themes/SystemTheme.h b/application/themes/SystemTheme.h index 18291b68..fe450600 100644 --- a/application/themes/SystemTheme.h +++ b/application/themes/SystemTheme.h @@ -5,20 +5,20 @@ class SystemTheme: public ITheme { public: - SystemTheme(); - virtual ~SystemTheme() {} - void apply(bool initial) override; + SystemTheme(); + virtual ~SystemTheme() {} + void apply(bool initial) override; - QString id() override; - QString name() override; - QString qtTheme() override; - bool hasStyleSheet() override; - QString appStyleSheet() override; - bool hasColorScheme() override; - QPalette colorScheme() override; - double fadeAmount() override; - QColor fadeColor() override; + QString id() override; + QString name() override; + QString qtTheme() override; + bool hasStyleSheet() override; + QString appStyleSheet() override; + bool hasColorScheme() override; + QPalette colorScheme() override; + double fadeAmount() override; + QColor fadeColor() override; private: - QPalette systemPalette; - QString systemTheme; + QPalette systemPalette; + QString systemTheme; }; diff --git a/application/widgets/Common.cpp b/application/widgets/Common.cpp index 9b730d6c..f72f3596 100644 --- a/application/widgets/Common.cpp +++ b/application/widgets/Common.cpp @@ -2,26 +2,26 @@ // Origin: Qt QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) + qreal &widthUsed) { - QStringList lines; - height = 0; - widthUsed = 0; - textLayout.beginLayout(); - QString str = textLayout.text(); - while (true) - { - QTextLine line = textLayout.createLine(); - if (!line.isValid()) - break; - if (line.textLength() == 0) - break; - line.setLineWidth(lineWidth); - line.setPosition(QPointF(0, height)); - height += line.height(); - lines.append(str.mid(line.textStart(), line.textLength())); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); - } - textLayout.endLayout(); - return lines; + QStringList lines; + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + lines.append(str.mid(line.textStart(), line.textLength())); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); + return lines; } diff --git a/application/widgets/Common.h b/application/widgets/Common.h index fc46e08f..b3fbe1a0 100644 --- a/application/widgets/Common.h +++ b/application/widgets/Common.h @@ -3,4 +3,4 @@ #include QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed); \ No newline at end of file + qreal &widthUsed); \ No newline at end of file diff --git a/application/widgets/CustomCommands.cpp b/application/widgets/CustomCommands.cpp index 9f11e344..9e7673fd 100644 --- a/application/widgets/CustomCommands.cpp +++ b/application/widgets/CustomCommands.cpp @@ -6,43 +6,43 @@ CustomCommands::~CustomCommands() } CustomCommands::CustomCommands(QWidget* parent): - QWidget(parent), - ui(new Ui::CustomCommands) + QWidget(parent), + ui(new Ui::CustomCommands) { - ui->setupUi(this); + ui->setupUi(this); } void CustomCommands::initialize(bool checkable, bool checked, const QString& prelaunch, const QString& wrapper, const QString& postexit) { - ui->customCommandsGroupBox->setCheckable(checkable); - if(checkable) - { - ui->customCommandsGroupBox->setChecked(checked); - } - ui->preLaunchCmdTextBox->setText(prelaunch); - ui->wrapperCmdTextBox->setText(wrapper); - ui->postExitCmdTextBox->setText(postexit); + ui->customCommandsGroupBox->setCheckable(checkable); + if(checkable) + { + ui->customCommandsGroupBox->setChecked(checked); + } + ui->preLaunchCmdTextBox->setText(prelaunch); + ui->wrapperCmdTextBox->setText(wrapper); + ui->postExitCmdTextBox->setText(postexit); } bool CustomCommands::checked() const { - if(!ui->customCommandsGroupBox->isCheckable()) - return true; - return ui->customCommandsGroupBox->isChecked(); + if(!ui->customCommandsGroupBox->isCheckable()) + return true; + return ui->customCommandsGroupBox->isChecked(); } QString CustomCommands::prelaunchCommand() const { - return ui->preLaunchCmdTextBox->text(); + return ui->preLaunchCmdTextBox->text(); } QString CustomCommands::wrapperCommand() const { - return ui->wrapperCmdTextBox->text(); + return ui->wrapperCmdTextBox->text(); } QString CustomCommands::postexitCommand() const { - return ui->postExitCmdTextBox->text(); + return ui->postExitCmdTextBox->text(); } diff --git a/application/widgets/CustomCommands.h b/application/widgets/CustomCommands.h index 2bc7cb1a..451b2cc0 100644 --- a/application/widgets/CustomCommands.h +++ b/application/widgets/CustomCommands.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2018 MultiMC Contributors +/* Copyright 2018-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,20 +24,20 @@ class CustomCommands; class CustomCommands : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit CustomCommands(QWidget *parent = 0); - ~CustomCommands(); - void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit); + explicit CustomCommands(QWidget *parent = 0); + ~CustomCommands(); + void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit); - bool checked() const; - QString prelaunchCommand() const; - QString wrapperCommand() const; - QString postexitCommand() const; + bool checked() const; + QString prelaunchCommand() const; + QString wrapperCommand() const; + QString postexitCommand() const; private: - Ui::CustomCommands *ui; + Ui::CustomCommands *ui; }; diff --git a/application/widgets/CustomCommands.ui b/application/widgets/CustomCommands.ui index d3bc86b8..c302fe39 100644 --- a/application/widgets/CustomCommands.ui +++ b/application/widgets/CustomCommands.ui @@ -14,6 +14,18 @@ Form + + 0 + + + 0 + + + 0 + + + 0 + diff --git a/application/widgets/FocusLineEdit.cpp b/application/widgets/FocusLineEdit.cpp index 139126c8..b272100c 100644 --- a/application/widgets/FocusLineEdit.cpp +++ b/application/widgets/FocusLineEdit.cpp @@ -3,23 +3,23 @@ FocusLineEdit::FocusLineEdit(QWidget *parent) : QLineEdit(parent) { - _selectOnMousePress = false; + _selectOnMousePress = false; } void FocusLineEdit::focusInEvent(QFocusEvent *e) { - QLineEdit::focusInEvent(e); - selectAll(); - _selectOnMousePress = true; + QLineEdit::focusInEvent(e); + selectAll(); + _selectOnMousePress = true; } void FocusLineEdit::mousePressEvent(QMouseEvent *me) { - QLineEdit::mousePressEvent(me); - if (_selectOnMousePress) - { - selectAll(); - _selectOnMousePress = false; - } - qDebug() << selectedText(); + QLineEdit::mousePressEvent(me); + if (_selectOnMousePress) + { + selectAll(); + _selectOnMousePress = false; + } + qDebug() << selectedText(); } diff --git a/application/widgets/FocusLineEdit.h b/application/widgets/FocusLineEdit.h index 6d1c78a8..71b4f140 100644 --- a/application/widgets/FocusLineEdit.h +++ b/application/widgets/FocusLineEdit.h @@ -2,16 +2,16 @@ class FocusLineEdit : public QLineEdit { - Q_OBJECT + Q_OBJECT public: - FocusLineEdit(QWidget *parent); - virtual ~FocusLineEdit() - { - } + FocusLineEdit(QWidget *parent); + virtual ~FocusLineEdit() + { + } protected: - void focusInEvent(QFocusEvent *e); - void mousePressEvent(QMouseEvent *me); + void focusInEvent(QFocusEvent *e); + void mousePressEvent(QMouseEvent *me); - bool _selectOnMousePress; + bool _selectOnMousePress; }; diff --git a/application/widgets/IconLabel.cpp b/application/widgets/IconLabel.cpp index 86c8a431..bf1c2358 100644 --- a/application/widgets/IconLabel.cpp +++ b/application/widgets/IconLabel.cpp @@ -7,37 +7,37 @@ #include IconLabel::IconLabel(QWidget *parent, QIcon icon, QSize size) - : QWidget(parent), m_size(size), m_icon(icon) + : QWidget(parent), m_size(size), m_icon(icon) { - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); } QSize IconLabel::sizeHint() const { - return m_size; + return m_size; } void IconLabel::setIcon(QIcon icon) { - m_icon = icon; - update(); + m_icon = icon; + update(); } void IconLabel::paintEvent(QPaintEvent *) { - QPainter p(this); - QRect rect = contentsRect(); - int width = rect.width(); - int height = rect.height(); - if(width < height) - { - rect.setHeight(width); - rect.translate(0, (height - width) / 2); - } - else if (width > height) - { - rect.setWidth(height); - rect.translate((width - height) / 2, 0); - } - m_icon.paint(&p, rect); + QPainter p(this); + QRect rect = contentsRect(); + int width = rect.width(); + int height = rect.height(); + if(width < height) + { + rect.setHeight(width); + rect.translate(0, (height - width) / 2); + } + else if (width > height) + { + rect.setWidth(height); + rect.translate((width - height) / 2, 0); + } + m_icon.paint(&p, rect); } diff --git a/application/widgets/IconLabel.h b/application/widgets/IconLabel.h index a2f1eef3..6d212c4c 100644 --- a/application/widgets/IconLabel.h +++ b/application/widgets/IconLabel.h @@ -9,18 +9,18 @@ class QStyleOption; */ class IconLabel : public QWidget { - Q_OBJECT + Q_OBJECT public: - /// Create a line separator. orientation is the orientation of the line. - explicit IconLabel(QWidget *parent, QIcon icon, QSize size); + /// Create a line separator. orientation is the orientation of the line. + explicit IconLabel(QWidget *parent, QIcon icon, QSize size); - virtual QSize sizeHint() const; - virtual void paintEvent(QPaintEvent *); + virtual QSize sizeHint() const; + virtual void paintEvent(QPaintEvent *); - void setIcon(QIcon icon); + void setIcon(QIcon icon); private: - QSize m_size; - QIcon m_icon; + QSize m_size; + QIcon m_icon; }; diff --git a/application/widgets/JavaSettingsWidget.cpp b/application/widgets/JavaSettingsWidget.cpp index 1e9d5546..a11dd1aa 100644 --- a/application/widgets/JavaSettingsWidget.cpp +++ b/application/widgets/JavaSettingsWidget.cpp @@ -19,410 +19,410 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) { - m_availableMemory = Sys::getSystemRam() / Sys::megabyte; - - goodIcon = MMC->getThemedIcon("status-good"); - yellowIcon = MMC->getThemedIcon("status-yellow"); - badIcon = MMC->getThemedIcon("status-bad"); - setupUi(); - - connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected); - connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); - connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); - connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); + m_availableMemory = Sys::getSystemRam() / Sys::megabyte; + + goodIcon = MMC->getThemedIcon("status-good"); + yellowIcon = MMC->getThemedIcon("status-yellow"); + badIcon = MMC->getThemedIcon("status-bad"); + setupUi(); + + connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); + connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); + connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); + connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected); + connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); + connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); + connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); } void JavaSettingsWidget::setupUi() { - setObjectName(QStringLiteral("javaSettingsWidget")); - m_verticalLayout = new QVBoxLayout(this); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - - m_versionWidget = new VersionSelectWidget(this); - m_verticalLayout->addWidget(m_versionWidget); - - m_horizontalLayout = new QHBoxLayout(); - m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - m_javaPathTextBox = new QLineEdit(this); - m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox")); - - m_horizontalLayout->addWidget(m_javaPathTextBox); - - m_javaBrowseBtn = new QPushButton(this); - m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn")); - - m_horizontalLayout->addWidget(m_javaBrowseBtn); - - m_javaStatusBtn = new QToolButton(this); - m_javaStatusBtn->setIcon(yellowIcon); - m_horizontalLayout->addWidget(m_javaStatusBtn); - - m_verticalLayout->addLayout(m_horizontalLayout); - - m_memoryGroupBox = new QGroupBox(this); - m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox")); - m_gridLayout_2 = new QGridLayout(m_memoryGroupBox); - m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2")); - - m_labelMinMem = new QLabel(m_memoryGroupBox); - m_labelMinMem->setObjectName(QStringLiteral("labelMinMem")); - m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1); - - m_minMemSpinBox = new QSpinBox(m_memoryGroupBox); - m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox")); - m_minMemSpinBox->setSuffix(QStringLiteral(" MB")); - m_minMemSpinBox->setMinimum(128); - m_minMemSpinBox->setMaximum(m_availableMemory); - m_minMemSpinBox->setSingleStep(128); - m_labelMinMem->setBuddy(m_minMemSpinBox); - m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1); - - m_labelMaxMem = new QLabel(m_memoryGroupBox); - m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem")); - m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1); - - m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox); - m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox")); - m_maxMemSpinBox->setSuffix(QStringLiteral(" MB")); - m_maxMemSpinBox->setMinimum(128); - m_maxMemSpinBox->setMaximum(m_availableMemory); - m_maxMemSpinBox->setSingleStep(128); - m_labelMaxMem->setBuddy(m_maxMemSpinBox); - m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1); - - m_labelPermGen = new QLabel(m_memoryGroupBox); - m_labelPermGen->setObjectName(QStringLiteral("labelPermGen")); - m_labelPermGen->setText(QStringLiteral("PermGen:")); - m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1); - m_labelPermGen->setVisible(false); - - m_permGenSpinBox = new QSpinBox(m_memoryGroupBox); - m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox")); - m_permGenSpinBox->setSuffix(QStringLiteral(" MB")); - m_permGenSpinBox->setMinimum(64); - m_permGenSpinBox->setMaximum(m_availableMemory); - m_permGenSpinBox->setSingleStep(8); - m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1); - m_permGenSpinBox->setVisible(false); - - m_verticalLayout->addWidget(m_memoryGroupBox); - - retranslate(); + setObjectName(QStringLiteral("javaSettingsWidget")); + m_verticalLayout = new QVBoxLayout(this); + m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + + m_versionWidget = new VersionSelectWidget(this); + m_verticalLayout->addWidget(m_versionWidget); + + m_horizontalLayout = new QHBoxLayout(); + m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + m_javaPathTextBox = new QLineEdit(this); + m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox")); + + m_horizontalLayout->addWidget(m_javaPathTextBox); + + m_javaBrowseBtn = new QPushButton(this); + m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn")); + + m_horizontalLayout->addWidget(m_javaBrowseBtn); + + m_javaStatusBtn = new QToolButton(this); + m_javaStatusBtn->setIcon(yellowIcon); + m_horizontalLayout->addWidget(m_javaStatusBtn); + + m_verticalLayout->addLayout(m_horizontalLayout); + + m_memoryGroupBox = new QGroupBox(this); + m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox")); + m_gridLayout_2 = new QGridLayout(m_memoryGroupBox); + m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2")); + + m_labelMinMem = new QLabel(m_memoryGroupBox); + m_labelMinMem->setObjectName(QStringLiteral("labelMinMem")); + m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1); + + m_minMemSpinBox = new QSpinBox(m_memoryGroupBox); + m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox")); + m_minMemSpinBox->setSuffix(QStringLiteral(" MB")); + m_minMemSpinBox->setMinimum(128); + m_minMemSpinBox->setMaximum(m_availableMemory); + m_minMemSpinBox->setSingleStep(128); + m_labelMinMem->setBuddy(m_minMemSpinBox); + m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1); + + m_labelMaxMem = new QLabel(m_memoryGroupBox); + m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem")); + m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1); + + m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox); + m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox")); + m_maxMemSpinBox->setSuffix(QStringLiteral(" MB")); + m_maxMemSpinBox->setMinimum(128); + m_maxMemSpinBox->setMaximum(m_availableMemory); + m_maxMemSpinBox->setSingleStep(128); + m_labelMaxMem->setBuddy(m_maxMemSpinBox); + m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1); + + m_labelPermGen = new QLabel(m_memoryGroupBox); + m_labelPermGen->setObjectName(QStringLiteral("labelPermGen")); + m_labelPermGen->setText(QStringLiteral("PermGen:")); + m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1); + m_labelPermGen->setVisible(false); + + m_permGenSpinBox = new QSpinBox(m_memoryGroupBox); + m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox")); + m_permGenSpinBox->setSuffix(QStringLiteral(" MB")); + m_permGenSpinBox->setMinimum(64); + m_permGenSpinBox->setMaximum(m_availableMemory); + m_permGenSpinBox->setSingleStep(8); + m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1); + m_permGenSpinBox->setVisible(false); + + m_verticalLayout->addWidget(m_memoryGroupBox); + + retranslate(); } void JavaSettingsWidget::initialize() { - m_versionWidget->initialize(MMC->javalist().get()); - m_versionWidget->setResizeOn(2); - auto s = MMC->settings(); - // Memory - observedMinMemory = s->get("MinMemAlloc").toInt(); - observedMaxMemory = s->get("MaxMemAlloc").toInt(); - observedPermGenMemory = s->get("PermGen").toInt(); - m_minMemSpinBox->setValue(observedMinMemory); - m_maxMemSpinBox->setValue(observedMaxMemory); - m_permGenSpinBox->setValue(observedPermGenMemory); + m_versionWidget->initialize(MMC->javalist().get()); + m_versionWidget->setResizeOn(2); + auto s = MMC->settings(); + // Memory + observedMinMemory = s->get("MinMemAlloc").toInt(); + observedMaxMemory = s->get("MaxMemAlloc").toInt(); + observedPermGenMemory = s->get("PermGen").toInt(); + m_minMemSpinBox->setValue(observedMinMemory); + m_maxMemSpinBox->setValue(observedMaxMemory); + m_permGenSpinBox->setValue(observedPermGenMemory); } void JavaSettingsWidget::refresh() { - m_versionWidget->loadList(); + m_versionWidget->loadList(); } JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate() { - switch(javaStatus) - { - default: - case JavaStatus::NotSet: - case JavaStatus::DoesNotExist: - case JavaStatus::DoesNotStart: - case JavaStatus::ReturnedInvalidData: - { - int button = CustomMessageBox::selectable( - this, - tr("No Java version selected"), - tr("You didn't select a Java version or selected something that doesn't work.\n" - "MultiMC will not be able to start Minecraft.\n" - "Do you wish to proceed without any Java?" - "\n\n" - "You can change the Java version in the settings later.\n" - ), - QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::NoButton - )->exec(); - if(button == QMessageBox::No) - { - return ValidationStatus::Bad; - } - return ValidationStatus::JavaBad; - } - break; - case JavaStatus::Pending: - { - return ValidationStatus::Bad; - } - case JavaStatus::Good: - { - return ValidationStatus::AllOK; - } - } + switch(javaStatus) + { + default: + case JavaStatus::NotSet: + case JavaStatus::DoesNotExist: + case JavaStatus::DoesNotStart: + case JavaStatus::ReturnedInvalidData: + { + int button = CustomMessageBox::selectable( + this, + tr("No Java version selected"), + tr("You didn't select a Java version or selected something that doesn't work.\n" + "MultiMC will not be able to start Minecraft.\n" + "Do you wish to proceed without any Java?" + "\n\n" + "You can change the Java version in the settings later.\n" + ), + QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::NoButton + )->exec(); + if(button == QMessageBox::No) + { + return ValidationStatus::Bad; + } + return ValidationStatus::JavaBad; + } + break; + case JavaStatus::Pending: + { + return ValidationStatus::Bad; + } + case JavaStatus::Good: + { + return ValidationStatus::AllOK; + } + } } QString JavaSettingsWidget::javaPath() const { - return m_javaPathTextBox->text(); + return m_javaPathTextBox->text(); } int JavaSettingsWidget::maxHeapSize() const { - return m_maxMemSpinBox->value(); + return m_maxMemSpinBox->value(); } int JavaSettingsWidget::minHeapSize() const { - return m_minMemSpinBox->value(); + return m_minMemSpinBox->value(); } bool JavaSettingsWidget::permGenEnabled() const { - return m_permGenSpinBox->isVisible(); + return m_permGenSpinBox->isVisible(); } int JavaSettingsWidget::permGenSize() const { - return m_permGenSpinBox->value(); + return m_permGenSpinBox->value(); } void JavaSettingsWidget::memoryValueChanged(int) { - bool actuallyChanged = false; - int min = m_minMemSpinBox->value(); - int max = m_maxMemSpinBox->value(); - int permgen = m_permGenSpinBox->value(); - QObject *obj = sender(); - if (obj == m_minMemSpinBox && min != observedMinMemory) - { - observedMinMemory = min; - actuallyChanged = true; - if (min > max) - { - observedMaxMemory = min; - m_maxMemSpinBox->setValue(min); - } - } - else if (obj == m_maxMemSpinBox && max != observedMaxMemory) - { - observedMaxMemory = max; - actuallyChanged = true; - if (min > max) - { - observedMinMemory = max; - m_minMemSpinBox->setValue(max); - } - } - else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) - { - observedPermGenMemory = permgen; - actuallyChanged = true; - } - if(actuallyChanged) - { - checkJavaPathOnEdit(m_javaPathTextBox->text()); - } + bool actuallyChanged = false; + int min = m_minMemSpinBox->value(); + int max = m_maxMemSpinBox->value(); + int permgen = m_permGenSpinBox->value(); + QObject *obj = sender(); + if (obj == m_minMemSpinBox && min != observedMinMemory) + { + observedMinMemory = min; + actuallyChanged = true; + if (min > max) + { + observedMaxMemory = min; + m_maxMemSpinBox->setValue(min); + } + } + else if (obj == m_maxMemSpinBox && max != observedMaxMemory) + { + observedMaxMemory = max; + actuallyChanged = true; + if (min > max) + { + observedMinMemory = max; + m_minMemSpinBox->setValue(max); + } + } + else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) + { + observedPermGenMemory = permgen; + actuallyChanged = true; + } + if(actuallyChanged) + { + checkJavaPathOnEdit(m_javaPathTextBox->text()); + } } void JavaSettingsWidget::javaVersionSelected(BaseVersionPtr version) { - auto java = std::dynamic_pointer_cast(version); - if(!java) - { - return; - } - auto visible = java->id.requiresPermGen(); - m_labelPermGen->setVisible(visible); - m_permGenSpinBox->setVisible(visible); - m_javaPathTextBox->setText(java->path); - checkJavaPath(java->path); + auto java = std::dynamic_pointer_cast(version); + if(!java) + { + return; + } + auto visible = java->id.requiresPermGen(); + m_labelPermGen->setVisible(visible); + m_permGenSpinBox->setVisible(visible); + m_javaPathTextBox->setText(java->path); + checkJavaPath(java->path); } void JavaSettingsWidget::on_javaBrowseBtn_clicked() { - QString filter; + QString filter; #if defined Q_OS_WIN32 - filter = "Java (javaw.exe)"; + filter = "Java (javaw.exe)"; #else - filter = "Java (java)"; + filter = "Java (java)"; #endif - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter); - if(raw_path.isEmpty()) - { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - m_javaPathTextBox->setText(cooked_path); - checkJavaPath(cooked_path); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter); + if(raw_path.isEmpty()) + { + return; + } + QString cooked_path = FS::NormalizePath(raw_path); + m_javaPathTextBox->setText(cooked_path); + checkJavaPath(cooked_path); } void JavaSettingsWidget::on_javaStatusBtn_clicked() { - QString text; - bool failed = false; - switch(javaStatus) - { - case JavaStatus::NotSet: - checkJavaPath(m_javaPathTextBox->text()); - return; - case JavaStatus::DoesNotExist: - text += QObject::tr("The specified file either doesn't exist or is not a proper executable."); - failed = true; - break; - case JavaStatus::DoesNotStart: - { - text += QObject::tr("The specified java binary didn't start properly.
"); - auto htmlError = m_result.errorLog; - if(!htmlError.isEmpty()) - { - htmlError.replace('\n', "
"); - text += QString("%1").arg(htmlError); - } - failed = true; - break; - } - case JavaStatus::ReturnedInvalidData: - { - text += QObject::tr("The specified java binary returned unexpected results:
"); - auto htmlOut = m_result.outLog; - if(!htmlOut.isEmpty()) - { - htmlOut.replace('\n', "
"); - text += QString("%1").arg(htmlOut); - } - failed = true; - break; - } - case JavaStatus::Good: - text += QObject::tr("Java test succeeded!
Platform reported: %1
Java version " - "reported: %2
").arg(m_result.realPlatform, m_result.javaVersion.toString()); - break; - case JavaStatus::Pending: - // TODO: abort here? - return; - } - CustomMessageBox::selectable( - this, - failed ? QObject::tr("Java test success") : QObject::tr("Java test failure"), - text, - failed ? QMessageBox::Critical : QMessageBox::Information - )->show(); + QString text; + bool failed = false; + switch(javaStatus) + { + case JavaStatus::NotSet: + checkJavaPath(m_javaPathTextBox->text()); + return; + case JavaStatus::DoesNotExist: + text += QObject::tr("The specified file either doesn't exist or is not a proper executable."); + failed = true; + break; + case JavaStatus::DoesNotStart: + { + text += QObject::tr("The specified java binary didn't start properly.
"); + auto htmlError = m_result.errorLog; + if(!htmlError.isEmpty()) + { + htmlError.replace('\n', "
"); + text += QString("%1").arg(htmlError); + } + failed = true; + break; + } + case JavaStatus::ReturnedInvalidData: + { + text += QObject::tr("The specified java binary returned unexpected results:
"); + auto htmlOut = m_result.outLog; + if(!htmlOut.isEmpty()) + { + htmlOut.replace('\n', "
"); + text += QString("%1").arg(htmlOut); + } + failed = true; + break; + } + case JavaStatus::Good: + text += QObject::tr("Java test succeeded!
Platform reported: %1
Java version " + "reported: %2
").arg(m_result.realPlatform, m_result.javaVersion.toString()); + break; + case JavaStatus::Pending: + // TODO: abort here? + return; + } + CustomMessageBox::selectable( + this, + failed ? QObject::tr("Java test success") : QObject::tr("Java test failure"), + text, + failed ? QMessageBox::Critical : QMessageBox::Information + )->show(); } void JavaSettingsWidget::setJavaStatus(JavaSettingsWidget::JavaStatus status) { - javaStatus = status; - switch(javaStatus) - { - case JavaStatus::Good: - m_javaStatusBtn->setIcon(goodIcon); - break; - case JavaStatus::NotSet: - case JavaStatus::Pending: - m_javaStatusBtn->setIcon(yellowIcon); - break; - default: - m_javaStatusBtn->setIcon(badIcon); - break; - } + javaStatus = status; + switch(javaStatus) + { + case JavaStatus::Good: + m_javaStatusBtn->setIcon(goodIcon); + break; + case JavaStatus::NotSet: + case JavaStatus::Pending: + m_javaStatusBtn->setIcon(yellowIcon); + break; + default: + m_javaStatusBtn->setIcon(badIcon); + break; + } } void JavaSettingsWidget::javaPathEdited(const QString& path) { - checkJavaPathOnEdit(path); + checkJavaPathOnEdit(path); } void JavaSettingsWidget::checkJavaPathOnEdit(const QString& path) { - auto realPath = FS::ResolveExecutable(path); - QFileInfo pathInfo(realPath); - if (pathInfo.baseName().toLower().contains("java")) - { - checkJavaPath(path); - } - else - { - if(!m_checker) - { - setJavaStatus(JavaStatus::NotSet); - } - } + auto realPath = FS::ResolveExecutable(path); + QFileInfo pathInfo(realPath); + if (pathInfo.baseName().toLower().contains("java")) + { + checkJavaPath(path); + } + else + { + if(!m_checker) + { + setJavaStatus(JavaStatus::NotSet); + } + } } void JavaSettingsWidget::checkJavaPath(const QString &path) { - if(m_checker) - { - queuedCheck = path; - return; - } - auto realPath = FS::ResolveExecutable(path); - if(realPath.isNull()) - { - setJavaStatus(JavaStatus::DoesNotExist); - return; - } - setJavaStatus(JavaStatus::Pending); - m_checker.reset(new JavaChecker()); - m_checker->m_path = path; - m_checker->m_minMem = m_minMemSpinBox->value(); - m_checker->m_maxMem = m_maxMemSpinBox->value(); - if(m_permGenSpinBox->isVisible()) - { - m_checker->m_permGen = m_permGenSpinBox->value(); - } - connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); - m_checker->performCheck(); + if(m_checker) + { + queuedCheck = path; + return; + } + auto realPath = FS::ResolveExecutable(path); + if(realPath.isNull()) + { + setJavaStatus(JavaStatus::DoesNotExist); + return; + } + setJavaStatus(JavaStatus::Pending); + m_checker.reset(new JavaChecker()); + m_checker->m_path = path; + m_checker->m_minMem = m_minMemSpinBox->value(); + m_checker->m_maxMem = m_maxMemSpinBox->value(); + if(m_permGenSpinBox->isVisible()) + { + m_checker->m_permGen = m_permGenSpinBox->value(); + } + connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); + m_checker->performCheck(); } void JavaSettingsWidget::checkFinished(JavaCheckResult result) { - m_result = result; - switch(result.validity) - { - case JavaCheckResult::Validity::Valid: - { - setJavaStatus(JavaStatus::Good); - break; - } - case JavaCheckResult::Validity::ReturnedInvalidData: - { - setJavaStatus(JavaStatus::ReturnedInvalidData); - break; - } - case JavaCheckResult::Validity::Errored: - { - setJavaStatus(JavaStatus::DoesNotStart); - break; - } - } - m_checker.reset(); - if(!queuedCheck.isNull()) - { - checkJavaPath(queuedCheck); - queuedCheck.clear(); - } + m_result = result; + switch(result.validity) + { + case JavaCheckResult::Validity::Valid: + { + setJavaStatus(JavaStatus::Good); + break; + } + case JavaCheckResult::Validity::ReturnedInvalidData: + { + setJavaStatus(JavaStatus::ReturnedInvalidData); + break; + } + case JavaCheckResult::Validity::Errored: + { + setJavaStatus(JavaStatus::DoesNotStart); + break; + } + } + m_checker.reset(); + if(!queuedCheck.isNull()) + { + checkJavaPath(queuedCheck); + queuedCheck.clear(); + } } void JavaSettingsWidget::retranslate() { - m_memoryGroupBox->setTitle(tr("Memory")); - m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use.")); - m_labelMinMem->setText(tr("Minimum memory allocation:")); - m_labelMaxMem->setText(tr("Maximum memory allocation:")); - m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); - m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); - m_javaBrowseBtn->setText(tr("Browse")); + m_memoryGroupBox->setTitle(tr("Memory")); + m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use.")); + m_labelMinMem->setText(tr("Minimum memory allocation:")); + m_labelMaxMem->setText(tr("Maximum memory allocation:")); + m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); + m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); + m_javaBrowseBtn->setText(tr("Browse")); } diff --git a/application/widgets/JavaSettingsWidget.h b/application/widgets/JavaSettingsWidget.h index 3a94f851..0d280daf 100644 --- a/application/widgets/JavaSettingsWidget.h +++ b/application/widgets/JavaSettingsWidget.h @@ -22,81 +22,81 @@ class QToolButton; */ class JavaSettingsWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit JavaSettingsWidget(QWidget *parent); - virtual ~JavaSettingsWidget() {}; + explicit JavaSettingsWidget(QWidget *parent); + virtual ~JavaSettingsWidget() {}; - enum class JavaStatus - { - NotSet, - Pending, - Good, - DoesNotExist, - DoesNotStart, - ReturnedInvalidData - } javaStatus = JavaStatus::NotSet; + enum class JavaStatus + { + NotSet, + Pending, + Good, + DoesNotExist, + DoesNotStart, + ReturnedInvalidData + } javaStatus = JavaStatus::NotSet; - enum class ValidationStatus - { - Bad, - JavaBad, - AllOK - }; + enum class ValidationStatus + { + Bad, + JavaBad, + AllOK + }; - void refresh(); - void initialize(); - ValidationStatus validate(); - void retranslate(); + void refresh(); + void initialize(); + ValidationStatus validate(); + void retranslate(); - bool permGenEnabled() const; - int permGenSize() const; - int minHeapSize() const; - int maxHeapSize() const; - QString javaPath() const; + bool permGenEnabled() const; + int permGenSize() const; + int minHeapSize() const; + int maxHeapSize() const; + QString javaPath() const; protected slots: - void memoryValueChanged(int); - void javaPathEdited(const QString &path); - void javaVersionSelected(BaseVersionPtr version); - void on_javaBrowseBtn_clicked(); - void on_javaStatusBtn_clicked(); - void checkFinished(JavaCheckResult result); + void memoryValueChanged(int); + void javaPathEdited(const QString &path); + void javaVersionSelected(BaseVersionPtr version); + void on_javaBrowseBtn_clicked(); + void on_javaStatusBtn_clicked(); + void checkFinished(JavaCheckResult result); protected: /* methods */ - void checkJavaPathOnEdit(const QString &path); - void checkJavaPath(const QString &path); - void setJavaStatus(JavaStatus status); - void setupUi(); + void checkJavaPathOnEdit(const QString &path); + void checkJavaPath(const QString &path); + void setJavaStatus(JavaStatus status); + void setupUi(); private: /* data */ - VersionSelectWidget *m_versionWidget = nullptr; - QVBoxLayout *m_verticalLayout = nullptr; + VersionSelectWidget *m_versionWidget = nullptr; + QVBoxLayout *m_verticalLayout = nullptr; - QLineEdit * m_javaPathTextBox = nullptr; - QPushButton * m_javaBrowseBtn = nullptr; - QToolButton * m_javaStatusBtn = nullptr; - QHBoxLayout *m_horizontalLayout = nullptr; + QLineEdit * m_javaPathTextBox = nullptr; + QPushButton * m_javaBrowseBtn = nullptr; + QToolButton * m_javaStatusBtn = nullptr; + QHBoxLayout *m_horizontalLayout = nullptr; - QGroupBox *m_memoryGroupBox = nullptr; - QGridLayout *m_gridLayout_2 = nullptr; - QSpinBox *m_maxMemSpinBox = nullptr; - QLabel *m_labelMinMem = nullptr; - QLabel *m_labelMaxMem = nullptr; - QSpinBox *m_minMemSpinBox = nullptr; - QLabel *m_labelPermGen = nullptr; - QSpinBox *m_permGenSpinBox = nullptr; - QIcon goodIcon; - QIcon yellowIcon; - QIcon badIcon; + QGroupBox *m_memoryGroupBox = nullptr; + QGridLayout *m_gridLayout_2 = nullptr; + QSpinBox *m_maxMemSpinBox = nullptr; + QLabel *m_labelMinMem = nullptr; + QLabel *m_labelMaxMem = nullptr; + QSpinBox *m_minMemSpinBox = nullptr; + QLabel *m_labelPermGen = nullptr; + QSpinBox *m_permGenSpinBox = nullptr; + QIcon goodIcon; + QIcon yellowIcon; + QIcon badIcon; - int observedMinMemory = 0; - int observedMaxMemory = 0; - int observedPermGenMemory = 0; - QString queuedCheck; - uint64_t m_availableMemory = 0ull; - shared_qobject_ptr m_checker; - JavaCheckResult m_result; + int observedMinMemory = 0; + int observedMaxMemory = 0; + int observedPermGenMemory = 0; + QString queuedCheck; + uint64_t m_availableMemory = 0ull; + shared_qobject_ptr m_checker; + JavaCheckResult m_result; }; diff --git a/application/widgets/LabeledToolButton.cpp b/application/widgets/LabeledToolButton.cpp index 744d2e00..ab273b65 100644 --- a/application/widgets/LabeledToolButton.cpp +++ b/application/widgets/LabeledToolButton.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,32 +28,32 @@ */ LabeledToolButton::LabeledToolButton(QWidget * parent) - : QToolButton(parent) - , m_label(new QLabel(this)) + : QToolButton(parent) + , m_label(new QLabel(this)) { - //QToolButton::setText(" "); - m_label->setWordWrap(true); - m_label->setMouseTracking(false); - m_label->setAlignment(Qt::AlignCenter); - m_label->setTextInteractionFlags(Qt::NoTextInteraction); - // somehow, this makes word wrap work in the QLabel. yay. - //m_label->setMinimumWidth(100); + //QToolButton::setText(" "); + m_label->setWordWrap(true); + m_label->setMouseTracking(false); + m_label->setAlignment(Qt::AlignCenter); + m_label->setTextInteractionFlags(Qt::NoTextInteraction); + // somehow, this makes word wrap work in the QLabel. yay. + //m_label->setMinimumWidth(100); } QString LabeledToolButton::text() const { - return m_label->text(); + return m_label->text(); } void LabeledToolButton::setText(const QString & text) { - m_label->setText(text); + m_label->setText(text); } void LabeledToolButton::setIcon(QIcon icon) { - m_icon = icon; - resetIcon(); + m_icon = icon; + resetIcon(); } @@ -62,54 +62,54 @@ void LabeledToolButton::setIcon(QIcon icon) */ QSize LabeledToolButton::sizeHint() const { - /* - Q_D(const QToolButton); - if (d->sizeHint.isValid()) - return d->sizeHint; - */ - ensurePolished(); + /* + Q_D(const QToolButton); + if (d->sizeHint.isValid()) + return d->sizeHint; + */ + ensurePolished(); - int w = 0, h = 0; - QStyleOptionToolButton opt; - initStyleOption(&opt); - QSize sz =m_label->sizeHint(); - w = sz.width(); - h = sz.height(); + int w = 0, h = 0; + QStyleOptionToolButton opt; + initStyleOption(&opt); + QSize sz =m_label->sizeHint(); + w = sz.width(); + h = sz.height(); - opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height - if (popupMode() == MenuButtonPopup) - w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); - - QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); - QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut()); - return sizeHint; + opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height + if (popupMode() == MenuButtonPopup) + w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); + + QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); + QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut()); + return sizeHint; } void LabeledToolButton::resizeEvent(QResizeEvent * event) { - m_label->setGeometry(QRect(4, 4, width()-8, height()-8)); - if(!m_icon.isNull()) - { - resetIcon(); - } - QWidget::resizeEvent(event); + m_label->setGeometry(QRect(4, 4, width()-8, height()-8)); + if(!m_icon.isNull()) + { + resetIcon(); + } + QWidget::resizeEvent(event); } void LabeledToolButton::resetIcon() { - auto iconSz = m_icon.actualSize(QSize(160, 80)); - float w = iconSz.width(); - float h = iconSz.height(); - float ar = w/h; - // FIXME: hardcoded max size of 160x80 - int newW = 80 * ar; - if(newW > 160) - newW = 160; - QSize newSz (newW, 80); - auto pixmap = m_icon.pixmap(newSz); - m_label->setPixmap(pixmap); - m_label->setMinimumHeight(80); - m_label->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); + auto iconSz = m_icon.actualSize(QSize(160, 80)); + float w = iconSz.width(); + float h = iconSz.height(); + float ar = w/h; + // FIXME: hardcoded max size of 160x80 + int newW = 80 * ar; + if(newW > 160) + newW = 160; + QSize newSz (newW, 80); + auto pixmap = m_icon.pixmap(newSz); + m_label->setPixmap(pixmap); + m_label->setMinimumHeight(80); + m_label->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); } diff --git a/application/widgets/LabeledToolButton.h b/application/widgets/LabeledToolButton.h index 151a5c2c..136ebd23 100644 --- a/application/widgets/LabeledToolButton.h +++ b/application/widgets/LabeledToolButton.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,19 +22,19 @@ class QLabel; class LabeledToolButton : public QToolButton { - Q_OBJECT + Q_OBJECT - QLabel * m_label; - QIcon m_icon; + QLabel * m_label; + QIcon m_icon; public: - LabeledToolButton(QWidget * parent = 0); + LabeledToolButton(QWidget * parent = 0); - QString text() const; - void setText(const QString & text); - void setIcon(QIcon icon); - virtual QSize sizeHint() const; + QString text() const; + void setText(const QString & text); + void setIcon(QIcon icon); + virtual QSize sizeHint() const; protected: - void resizeEvent(QResizeEvent * event); - void resetIcon(); + void resizeEvent(QResizeEvent * event); + void resetIcon(); }; diff --git a/application/widgets/LanguageSelectionWidget.cpp b/application/widgets/LanguageSelectionWidget.cpp new file mode 100644 index 00000000..80e26b39 --- /dev/null +++ b/application/widgets/LanguageSelectionWidget.cpp @@ -0,0 +1,68 @@ +#include "LanguageSelectionWidget.h" + +#include +#include +#include +#include +#include "MultiMC.h" +#include "translations/TranslationsModel.h" + +LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : + QWidget(parent) +{ + verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + languageView = new QTreeView(this); + languageView->setObjectName(QStringLiteral("languageView")); + languageView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + languageView->setAlternatingRowColors(true); + languageView->setRootIsDecorated(false); + languageView->setItemsExpandable(false); + languageView->setWordWrap(true); + languageView->header()->setCascadingSectionResizes(true); + languageView->header()->setStretchLastSection(false); + verticalLayout->addWidget(languageView); + helpUsLabel = new QLabel(this); + helpUsLabel->setObjectName(QStringLiteral("helpUsLabel")); + helpUsLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + helpUsLabel->setOpenExternalLinks(true); + helpUsLabel->setWordWrap(true); + verticalLayout->addWidget(helpUsLabel); + + auto translations = MMC->translations(); + auto index = translations->selectedIndex(); + languageView->setModel(translations.get()); + languageView->setCurrentIndex(index); + languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch); + connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged); + verticalLayout->setContentsMargins(0,0,0,0); +} + +QString LanguageSelectionWidget::getSelectedLanguageKey() const +{ + auto translations = MMC->translations(); + return translations->data(languageView->currentIndex(), Qt::UserRole).toString(); +} + +void LanguageSelectionWidget::retranslate() +{ + QString text = + tr("Don't see your language or the quality is poor?") + + "
" + + QString("%1").arg(tr("Help us with translations!")); + helpUsLabel->setText(text); + +} + +void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous) +{ + if (current == previous) + { + return; + } + auto translations = MMC->translations(); + QString key = translations->data(current, Qt::UserRole).toString(); + translations->selectLanguage(key); + translations->updateLanguage(key); +} diff --git a/application/widgets/LanguageSelectionWidget.h b/application/widgets/LanguageSelectionWidget.h new file mode 100644 index 00000000..03e29bd8 --- /dev/null +++ b/application/widgets/LanguageSelectionWidget.h @@ -0,0 +1,41 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class QVBoxLayout; +class QTreeView; +class QLabel; + +class LanguageSelectionWidget: public QWidget +{ + Q_OBJECT +public: + explicit LanguageSelectionWidget(QWidget *parent = 0); + virtual ~LanguageSelectionWidget() { }; + + QString getSelectedLanguageKey() const; + void retranslate(); + +protected slots: + void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + QVBoxLayout *verticalLayout = nullptr; + QTreeView *languageView = nullptr; + QLabel *helpUsLabel = nullptr; +}; diff --git a/application/widgets/LineSeparator.cpp b/application/widgets/LineSeparator.cpp index f4ee173d..d03e6762 100644 --- a/application/widgets/LineSeparator.cpp +++ b/application/widgets/LineSeparator.cpp @@ -7,31 +7,31 @@ void LineSeparator::initStyleOption(QStyleOption *option) const { - option->initFrom(this); - // in a horizontal layout, the line is vertical (and vice versa) - if (m_orientation == Qt::Vertical) - option->state |= QStyle::State_Horizontal; + option->initFrom(this); + // in a horizontal layout, the line is vertical (and vice versa) + if (m_orientation == Qt::Vertical) + option->state |= QStyle::State_Horizontal; } LineSeparator::LineSeparator(QWidget *parent, Qt::Orientation orientation) - : QWidget(parent), m_orientation(orientation) + : QWidget(parent), m_orientation(orientation) { - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); } QSize LineSeparator::sizeHint() const { - QStyleOption opt; - initStyleOption(&opt); - const int extent = - style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget()); - return QSize(extent, extent); + QStyleOption opt; + initStyleOption(&opt); + const int extent = + style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget()); + return QSize(extent, extent); } void LineSeparator::paintEvent(QPaintEvent *) { - QPainter p(this); - QStyleOption opt; - initStyleOption(&opt); - style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget()); + QPainter p(this); + QStyleOption opt; + initStyleOption(&opt); + style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget()); } diff --git a/application/widgets/LineSeparator.h b/application/widgets/LineSeparator.h index 9546e747..22927b68 100644 --- a/application/widgets/LineSeparator.h +++ b/application/widgets/LineSeparator.h @@ -5,14 +5,14 @@ class QStyleOption; class LineSeparator : public QWidget { - Q_OBJECT + Q_OBJECT public: - /// Create a line separator. orientation is the orientation of the line. - explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Horizontal); - QSize sizeHint() const; - void paintEvent(QPaintEvent *); - void initStyleOption(QStyleOption *option) const; + /// Create a line separator. orientation is the orientation of the line. + explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Horizontal); + QSize sizeHint() const; + void paintEvent(QPaintEvent *); + void initStyleOption(QStyleOption *option) const; private: - Qt::Orientation m_orientation = Qt::Horizontal; + Qt::Orientation m_orientation = Qt::Horizontal; }; diff --git a/application/widgets/LogView.cpp b/application/widgets/LogView.cpp index 8e0346fa..26a2a527 100644 --- a/application/widgets/LogView.cpp +++ b/application/widgets/LogView.cpp @@ -4,141 +4,141 @@ LogView::LogView(QWidget* parent) : QPlainTextEdit(parent) { - setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - m_defaultFormat = new QTextCharFormat(currentCharFormat()); + setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + m_defaultFormat = new QTextCharFormat(currentCharFormat()); } LogView::~LogView() { - delete m_defaultFormat; + delete m_defaultFormat; } void LogView::setWordWrap(bool wrapping) { - if(wrapping) - { - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setLineWrapMode(QPlainTextEdit::WidgetWidth); - } - else - { - setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - setLineWrapMode(QPlainTextEdit::NoWrap); - } + if(wrapping) + { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setLineWrapMode(QPlainTextEdit::WidgetWidth); + } + else + { + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setLineWrapMode(QPlainTextEdit::NoWrap); + } } void LogView::setModel(QAbstractItemModel* model) { - if(m_model) - { - disconnect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); - disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); - disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); - disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); - } - m_model = model; - if(m_model) - { - connect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); - connect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); - connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); - connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); - connect(m_model, &QAbstractItemModel::destroyed, this, &LogView::modelDestroyed); - } - repopulate(); + if(m_model) + { + disconnect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); + disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); + disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); + disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); + } + m_model = model; + if(m_model) + { + connect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate); + connect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted); + connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted); + connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved); + connect(m_model, &QAbstractItemModel::destroyed, this, &LogView::modelDestroyed); + } + repopulate(); } QAbstractItemModel * LogView::model() const { - return m_model; + return m_model; } void LogView::modelDestroyed(QObject* model) { - if(m_model == model) - { - setModel(nullptr); - } + if(m_model == model) + { + setModel(nullptr); + } } void LogView::repopulate() { - auto doc = document(); - doc->clear(); - if(!m_model) - { - return; - } - rowsInserted(QModelIndex(), 0, m_model->rowCount() - 1); + auto doc = document(); + doc->clear(); + if(!m_model) + { + return; + } + rowsInserted(QModelIndex(), 0, m_model->rowCount() - 1); } void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int last) { - Q_UNUSED(parent) - Q_UNUSED(first) - Q_UNUSED(last) - QScrollBar *bar = verticalScrollBar(); - int max_bar = bar->maximum(); - int val_bar = bar->value(); - if (m_scroll) - { - m_scroll = (max_bar - val_bar) <= 1; - } - else - { - m_scroll = val_bar == max_bar; - } + Q_UNUSED(parent) + Q_UNUSED(first) + Q_UNUSED(last) + QScrollBar *bar = verticalScrollBar(); + int max_bar = bar->maximum(); + int val_bar = bar->value(); + if (m_scroll) + { + m_scroll = (max_bar - val_bar) <= 1; + } + else + { + m_scroll = val_bar == max_bar; + } } void LogView::rowsInserted(const QModelIndex& parent, int first, int last) { - for(int i = first; i <= last; i++) - { - auto idx = m_model->index(i, 0, parent); - auto text = m_model->data(idx, Qt::DisplayRole).toString(); - QTextCharFormat format(*m_defaultFormat); - auto font = m_model->data(idx, Qt::FontRole); - if(font.isValid()) - { - format.setFont(font.value()); - } - auto fg = m_model->data(idx, Qt::TextColorRole); - if(fg.isValid()) - { - format.setForeground(fg.value()); - } - auto bg = m_model->data(idx, Qt::BackgroundRole); - if(bg.isValid()) - { - format.setBackground(bg.value()); - } - auto workCursor = textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(text, format); - workCursor.insertBlock(); - } - if(m_scroll && !m_scrolling) - { - m_scrolling = true; - QMetaObject::invokeMethod( this, "scrollToBottom", Qt::QueuedConnection); - } + for(int i = first; i <= last; i++) + { + auto idx = m_model->index(i, 0, parent); + auto text = m_model->data(idx, Qt::DisplayRole).toString(); + QTextCharFormat format(*m_defaultFormat); + auto font = m_model->data(idx, Qt::FontRole); + if(font.isValid()) + { + format.setFont(font.value()); + } + auto fg = m_model->data(idx, Qt::TextColorRole); + if(fg.isValid()) + { + format.setForeground(fg.value()); + } + auto bg = m_model->data(idx, Qt::BackgroundRole); + if(bg.isValid()) + { + format.setBackground(bg.value()); + } + auto workCursor = textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertText(text, format); + workCursor.insertBlock(); + } + if(m_scroll && !m_scrolling) + { + m_scrolling = true; + QMetaObject::invokeMethod( this, "scrollToBottom", Qt::QueuedConnection); + } } void LogView::rowsRemoved(const QModelIndex& parent, int first, int last) { - // TODO: some day... maybe - Q_UNUSED(parent) - Q_UNUSED(first) - Q_UNUSED(last) + // TODO: some day... maybe + Q_UNUSED(parent) + Q_UNUSED(first) + Q_UNUSED(last) } void LogView::scrollToBottom() { - m_scrolling = false; - verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum()); + m_scrolling = false; + verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum()); } void LogView::findNext(const QString& what, bool reverse) { - find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); + find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); } diff --git a/application/widgets/LogView.h b/application/widgets/LogView.h index bb6747cd..3143360a 100644 --- a/application/widgets/LogView.h +++ b/application/widgets/LogView.h @@ -6,31 +6,31 @@ class QAbstractItemModel; class LogView: public QPlainTextEdit { - Q_OBJECT + Q_OBJECT public: - explicit LogView(QWidget *parent = nullptr); - virtual ~LogView(); + explicit LogView(QWidget *parent = nullptr); + virtual ~LogView(); - virtual void setModel(QAbstractItemModel *model); - QAbstractItemModel *model() const; + virtual void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; public slots: - void setWordWrap(bool wrapping); - void findNext(const QString & what, bool reverse); - void scrollToBottom(); + void setWordWrap(bool wrapping); + void findNext(const QString & what, bool reverse); + void scrollToBottom(); protected slots: - void repopulate(); - // note: this supports only appending - void rowsInserted(const QModelIndex &parent, int first, int last); - void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); - // note: this supports only removing from front - void rowsRemoved(const QModelIndex &parent, int first, int last); - void modelDestroyed(QObject * model); + void repopulate(); + // note: this supports only appending + void rowsInserted(const QModelIndex &parent, int first, int last); + void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); + // note: this supports only removing from front + void rowsRemoved(const QModelIndex &parent, int first, int last); + void modelDestroyed(QObject * model); protected: - QAbstractItemModel *m_model = nullptr; - QTextCharFormat *m_defaultFormat = nullptr; - bool m_scroll = false; - bool m_scrolling = false; + QAbstractItemModel *m_model = nullptr; + QTextCharFormat *m_defaultFormat = nullptr; + bool m_scroll = false; + bool m_scrolling = false; }; diff --git a/application/widgets/MCModInfoFrame.cpp b/application/widgets/MCModInfoFrame.cpp index 629c17e7..577b32a7 100644 --- a/application/widgets/MCModInfoFrame.cpp +++ b/application/widgets/MCModInfoFrame.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,145 +22,145 @@ void MCModInfoFrame::updateWithMod(Mod &m) { - if (m.type() == m.MOD_FOLDER) - { - clear(); - return; - } + if (m.type() == m.MOD_FOLDER) + { + clear(); + return; + } - QString text = ""; - QString name = ""; - if (m.name().isEmpty()) - name = m.mmc_id(); - else - name = m.name(); + QString text = ""; + QString name = ""; + if (m.name().isEmpty()) + name = m.mmc_id(); + else + name = m.name(); - if (m.homeurl().isEmpty()) - text = name; - else - text = "" + name + ""; - if (!m.authors().isEmpty()) - text += " by " + m.authors(); + if (m.homeurl().isEmpty()) + text = name; + else + text = "" + name + ""; + if (!m.authors().isEmpty()) + text += " by " + m.authors().join(", "); - setModText(text); + setModText(text); - if (m.description().isEmpty()) - { - setModDescription(QString()); - } - else - { - setModDescription(m.description()); - } + if (m.description().isEmpty()) + { + setModDescription(QString()); + } + else + { + setModDescription(m.description()); + } } void MCModInfoFrame::clear() { - setModText(QString()); - setModDescription(QString()); + setModText(QString()); + setModDescription(QString()); } MCModInfoFrame::MCModInfoFrame(QWidget *parent) : - QFrame(parent), - ui(new Ui::MCModInfoFrame) + QFrame(parent), + ui(new Ui::MCModInfoFrame) { - ui->setupUi(this); - ui->label_ModDescription->setHidden(true); - ui->label_ModText->setHidden(true); - updateHiddenState(); + ui->setupUi(this); + ui->label_ModDescription->setHidden(true); + ui->label_ModText->setHidden(true); + updateHiddenState(); } MCModInfoFrame::~MCModInfoFrame() { - delete ui; + delete ui; } void MCModInfoFrame::updateHiddenState() { - if(ui->label_ModDescription->isHidden() && ui->label_ModText->isHidden()) - { - setHidden(true); - } - else - { - setHidden(false); - } + if(ui->label_ModDescription->isHidden() && ui->label_ModText->isHidden()) + { + setHidden(true); + } + else + { + setHidden(false); + } } void MCModInfoFrame::setModText(QString text) { - if(text.isEmpty()) - { - ui->label_ModText->setHidden(true); - } - else - { - ui->label_ModText->setText(text); - ui->label_ModText->setHidden(false); - } - updateHiddenState(); + if(text.isEmpty()) + { + ui->label_ModText->setHidden(true); + } + else + { + ui->label_ModText->setText(text); + ui->label_ModText->setHidden(false); + } + updateHiddenState(); } void MCModInfoFrame::setModDescription(QString text) { - if(text.isEmpty()) - { - ui->label_ModDescription->setHidden(true); - updateHiddenState(); - return; - } - else - { - ui->label_ModDescription->setHidden(false); - updateHiddenState(); - } - ui->label_ModDescription->setToolTip(""); - QString intermediatetext = text.trimmed(); - bool prev(false); - QChar rem('\n'); - QString finaltext; - finaltext.reserve(intermediatetext.size()); - foreach(const QChar& c, intermediatetext) - { - if(c == rem && prev){ - continue; - } - prev = c == rem; - finaltext += c; - } - QString labeltext; - labeltext.reserve(300); - if(finaltext.length() > 290) - { - ui->label_ModDescription->setOpenExternalLinks(false); - ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); - desc = text; - labeltext.append("" + finaltext.left(287) + "..."); - QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); - } - else - { - ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText); - labeltext.append(finaltext); - } - ui->label_ModDescription->setText(labeltext); + if(text.isEmpty()) + { + ui->label_ModDescription->setHidden(true); + updateHiddenState(); + return; + } + else + { + ui->label_ModDescription->setHidden(false); + updateHiddenState(); + } + ui->label_ModDescription->setToolTip(""); + QString intermediatetext = text.trimmed(); + bool prev(false); + QChar rem('\n'); + QString finaltext; + finaltext.reserve(intermediatetext.size()); + foreach(const QChar& c, intermediatetext) + { + if(c == rem && prev){ + continue; + } + prev = c == rem; + finaltext += c; + } + QString labeltext; + labeltext.reserve(300); + if(finaltext.length() > 290) + { + ui->label_ModDescription->setOpenExternalLinks(false); + ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); + desc = text; + labeltext.append("" + finaltext.left(287) + "..."); + QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); + } + else + { + ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText); + labeltext.append(finaltext); + } + ui->label_ModDescription->setText(labeltext); } void MCModInfoFrame::modDescEllipsisHandler(const QString &link) { - if(!currentBox) - { - currentBox = CustomMessageBox::selectable(this, QString(), desc); - connect(currentBox, &QMessageBox::finished, this, &MCModInfoFrame::boxClosed); - currentBox->show(); - } - else - { - currentBox->setText(desc); - } + if(!currentBox) + { + currentBox = CustomMessageBox::selectable(this, QString(), desc); + connect(currentBox, &QMessageBox::finished, this, &MCModInfoFrame::boxClosed); + currentBox->show(); + } + else + { + currentBox->setText(desc); + } } void MCModInfoFrame::boxClosed(int result) { - currentBox = nullptr; + currentBox = nullptr; } diff --git a/application/widgets/MCModInfoFrame.h b/application/widgets/MCModInfoFrame.h index 6bcd345c..51aa4489 100644 --- a/application/widgets/MCModInfoFrame.h +++ b/application/widgets/MCModInfoFrame.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #pragma once #include -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" namespace Ui { @@ -25,28 +25,28 @@ class MCModInfoFrame; class MCModInfoFrame : public QFrame { - Q_OBJECT + Q_OBJECT public: - explicit MCModInfoFrame(QWidget *parent = 0); - ~MCModInfoFrame(); + explicit MCModInfoFrame(QWidget *parent = 0); + ~MCModInfoFrame(); - void setModText(QString text); - void setModDescription(QString text); + void setModText(QString text); + void setModDescription(QString text); - void updateWithMod(Mod &m); - void clear(); + void updateWithMod(Mod &m); + void clear(); public slots: - void modDescEllipsisHandler(const QString& link ); - void boxClosed(int result); + void modDescEllipsisHandler(const QString& link ); + void boxClosed(int result); private: - void updateHiddenState(); + void updateHiddenState(); private: - Ui::MCModInfoFrame *ui; - QString desc; - class QMessageBox * currentBox = nullptr; + Ui::MCModInfoFrame *ui; + QString desc; + class QMessageBox * currentBox = nullptr; }; diff --git a/application/widgets/ModListView.cpp b/application/widgets/ModListView.cpp index 96e8d91b..99972a40 100644 --- a/application/widgets/ModListView.cpp +++ b/application/widgets/ModListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,46 +21,46 @@ #include ModListView::ModListView ( QWidget* parent ) - :QTreeView ( parent ) + :QTreeView ( parent ) { - setAllColumnsShowFocus ( true ); - setExpandsOnDoubleClick ( false ); - setRootIsDecorated ( false ); - setSortingEnabled ( true ); - setAlternatingRowColors ( true ); - setSelectionMode ( QAbstractItemView::ExtendedSelection ); - setHeaderHidden ( false ); - setSelectionBehavior(QAbstractItemView::SelectRows); - setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn ); - setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded ); - setDropIndicatorShown(true); - setDragEnabled(true); - setDragDropMode(QAbstractItemView::DropOnly); - viewport()->setAcceptDrops(true); + setAllColumnsShowFocus ( true ); + setExpandsOnDoubleClick ( false ); + setRootIsDecorated ( false ); + setSortingEnabled ( true ); + setAlternatingRowColors ( true ); + setSelectionMode ( QAbstractItemView::ExtendedSelection ); + setHeaderHidden ( false ); + setSelectionBehavior(QAbstractItemView::SelectRows); + setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn ); + setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded ); + setDropIndicatorShown(true); + setDragEnabled(true); + setDragDropMode(QAbstractItemView::DropOnly); + viewport()->setAcceptDrops(true); } void ModListView::setModel ( QAbstractItemModel* model ) { - QTreeView::setModel ( model ); - auto head = header(); - head->setStretchLastSection(false); - // HACK: this is true for the checkbox column of mod lists - auto string = model->headerData(0,head->orientation()).toString(); - if(head->count() < 1) - { - return; - } - if(!string.size()) - { - head->setSectionResizeMode(0, QHeaderView::ResizeToContents); - head->setSectionResizeMode(1, QHeaderView::Stretch); - for(int i = 2; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - else - { - head->setSectionResizeMode(0, QHeaderView::Stretch); - for(int i = 1; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } + QTreeView::setModel ( model ); + auto head = header(); + head->setStretchLastSection(false); + // HACK: this is true for the checkbox column of mod lists + auto string = model->headerData(0,head->orientation()).toString(); + if(head->count() < 1) + { + return; + } + if(!string.size()) + { + head->setSectionResizeMode(0, QHeaderView::ResizeToContents); + head->setSectionResizeMode(1, QHeaderView::Stretch); + for(int i = 2; i < head->count(); i++) + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + else + { + head->setSectionResizeMode(0, QHeaderView::Stretch); + for(int i = 1; i < head->count(); i++) + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } } diff --git a/application/widgets/ModListView.h b/application/widgets/ModListView.h index baca23f4..5a07e868 100644 --- a/application/widgets/ModListView.h +++ b/application/widgets/ModListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,10 @@ #pragma once #include -class Mod; - class ModListView: public QTreeView { - Q_OBJECT + Q_OBJECT public: - explicit ModListView ( QWidget* parent = 0 ); - virtual void setModel ( QAbstractItemModel* model ); + explicit ModListView ( QWidget* parent = 0 ); + virtual void setModel ( QAbstractItemModel* model ); }; diff --git a/application/widgets/PageContainer.cpp b/application/widgets/PageContainer.cpp index 98de57e8..376e119b 100644 --- a/application/widgets/PageContainer.cpp +++ b/application/widgets/PageContainer.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,196 +36,204 @@ class PageEntryFilterModel : public QSortFilterProxyModel { public: - explicit PageEntryFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) - { - } + explicit PageEntryFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) + { + } protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const - { - const QString pattern = filterRegExp().pattern(); - const auto model = static_cast(sourceModel()); - const auto page = model->pages().at(sourceRow); - if (!page->shouldDisplay()) - return false; - // Regular contents check, then check page-filter. - return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); - } + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const + { + const QString pattern = filterRegExp().pattern(); + const auto model = static_cast(sourceModel()); + const auto page = model->pages().at(sourceRow); + if (!page->shouldDisplay()) + return false; + // Regular contents check, then check page-filter. + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); + } }; PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId, - QWidget *parent) - : QWidget(parent) + QWidget *parent) + : QWidget(parent) { - createUI(); - m_model = new PageModel(this); - m_proxyModel = new PageEntryFilterModel(this); - int counter = 0; - auto pages = pageProvider->getPages(); - for (auto page : pages) - { - page->stackIndex = m_pageStack->addWidget(dynamic_cast(page)); - page->listIndex = counter; - page->setParentContainer(this); - counter++; - } - m_model->setPages(pages); - - m_proxyModel->setSourceModel(m_model); - m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - - m_pageList->setIconSize(QSize(pageIconSize, pageIconSize)); - m_pageList->setSelectionMode(QAbstractItemView::SingleSelection); - m_pageList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - m_pageList->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); - m_pageList->setModel(m_proxyModel); - connect(m_pageList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), - this, SLOT(currentChanged(QModelIndex))); - m_pageStack->setStackingMode(QStackedLayout::StackOne); - m_pageList->setFocus(); - selectPage(defaultId); + createUI(); + m_model = new PageModel(this); + m_proxyModel = new PageEntryFilterModel(this); + int counter = 0; + auto pages = pageProvider->getPages(); + for (auto page : pages) + { + page->stackIndex = m_pageStack->addWidget(dynamic_cast(page)); + page->listIndex = counter; + page->setParentContainer(this); + counter++; + } + m_model->setPages(pages); + + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + m_pageList->setIconSize(QSize(pageIconSize, pageIconSize)); + m_pageList->setSelectionMode(QAbstractItemView::SingleSelection); + m_pageList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_pageList->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + m_pageList->setModel(m_proxyModel); + connect(m_pageList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), + this, SLOT(currentChanged(QModelIndex))); + m_pageStack->setStackingMode(QStackedLayout::StackOne); + m_pageList->setFocus(); + selectPage(defaultId); } bool PageContainer::selectPage(QString pageId) { - // now find what we want to have selected... - auto page = m_model->findPageEntryById(pageId); - QModelIndex index; - if (page) - { - index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); - } - if(!index.isValid()) - { - index = m_proxyModel->index(0, 0); - } - if (index.isValid()) - { - m_pageList->setCurrentIndex(index); - return true; - } - return false; + // now find what we want to have selected... + auto page = m_model->findPageEntryById(pageId); + QModelIndex index; + if (page) + { + index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); + } + if(!index.isValid()) + { + index = m_proxyModel->index(0, 0); + } + if (index.isValid()) + { + m_pageList->setCurrentIndex(index); + return true; + } + return false; } void PageContainer::refreshContainer() { - m_proxyModel->invalidate(); - if(!m_currentPage->shouldDisplay()) - { - auto index = m_proxyModel->index(0, 0); - if(index.isValid()) - { - m_pageList->setCurrentIndex(index); - } - else - { - // FIXME: unhandled corner case: what to do when there's no page to select? - } - } + m_proxyModel->invalidate(); + if(!m_currentPage->shouldDisplay()) + { + auto index = m_proxyModel->index(0, 0); + if(index.isValid()) + { + m_pageList->setCurrentIndex(index); + } + else + { + // FIXME: unhandled corner case: what to do when there's no page to select? + } + } } void PageContainer::createUI() { - m_pageStack = new QStackedLayout; - m_pageList = new PageView; - m_header = new QLabel(); - m_iconHeader = new IconLabel(this, QIcon(), QSize(24, 24)); - - QFont headerLabelFont = m_header->font(); - headerLabelFont.setBold(true); - const int pointSize = headerLabelFont.pointSize(); - if (pointSize > 0) - headerLabelFont.setPointSize(pointSize + 2); - m_header->setFont(headerLabelFont); - - QHBoxLayout *headerHLayout = new QHBoxLayout; - const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); - headerHLayout->addSpacerItem( - new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); - headerHLayout->addWidget(m_header); - headerHLayout->addSpacerItem( - new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); - headerHLayout->addWidget(m_iconHeader); - const int rightMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutRightMargin); - headerHLayout->addSpacerItem( - new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); - - m_pageStack->setMargin(0); - m_pageStack->addWidget(new QWidget(this)); - - m_layout = new QGridLayout; - m_layout->addLayout(headerHLayout, 0, 1, 1, 1); - m_layout->addWidget(m_pageList, 0, 0, 2, 1); - m_layout->addLayout(m_pageStack, 1, 1, 1, 1); - m_layout->setColumnStretch(1, 4); - setLayout(m_layout); + m_pageStack = new QStackedLayout; + m_pageList = new PageView; + m_header = new QLabel(); + m_iconHeader = new IconLabel(this, QIcon(), QSize(24, 24)); + + QFont headerLabelFont = m_header->font(); + headerLabelFont.setBold(true); + const int pointSize = headerLabelFont.pointSize(); + if (pointSize > 0) + headerLabelFont.setPointSize(pointSize + 2); + m_header->setFont(headerLabelFont); + + QHBoxLayout *headerHLayout = new QHBoxLayout; + const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); + headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_header); + headerHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_iconHeader); + const int rightMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutRightMargin); + headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->setContentsMargins(0, 6, 0, 0); + + m_pageStack->setMargin(0); + m_pageStack->addWidget(new QWidget(this)); + + m_layout = new QGridLayout; + m_layout->addLayout(headerHLayout, 0, 1, 1, 1); + m_layout->addWidget(m_pageList, 0, 0, 2, 1); + m_layout->addLayout(m_pageStack, 1, 1, 1, 1); + m_layout->setColumnStretch(1, 4); + m_layout->setContentsMargins(0,0,0,6); + setLayout(m_layout); } void PageContainer::addButtons(QWidget *buttons) { - m_layout->addWidget(buttons, 2, 0, 1, 2); + m_layout->addWidget(buttons, 2, 0, 1, 2); } void PageContainer::addButtons(QLayout *buttons) { - m_layout->addLayout(buttons, 2, 0, 1, 2); + m_layout->addLayout(buttons, 2, 0, 1, 2); } void PageContainer::showPage(int row) { - if (m_currentPage) - { - m_currentPage->closed(); - } - if (row != -1) - { - m_currentPage = m_model->pages().at(row); - } - else - { - m_currentPage = nullptr; - } - if (m_currentPage) - { - m_pageStack->setCurrentIndex(m_currentPage->stackIndex); - m_header->setText(m_currentPage->displayName()); - m_iconHeader->setIcon(m_currentPage->icon()); - m_currentPage->opened(); - } - else - { - m_pageStack->setCurrentIndex(0); - m_header->setText(QString()); - m_iconHeader->setIcon(MMC->getThemedIcon("bug")); - } + if (m_currentPage) + { + m_currentPage->closed(); + } + if (row != -1) + { + m_currentPage = m_model->pages().at(row); + } + else + { + m_currentPage = nullptr; + } + if (m_currentPage) + { + m_pageStack->setCurrentIndex(m_currentPage->stackIndex); + m_header->setText(m_currentPage->displayName()); + m_iconHeader->setIcon(m_currentPage->icon()); + m_currentPage->opened(); + } + else + { + m_pageStack->setCurrentIndex(0); + m_header->setText(QString()); + m_iconHeader->setIcon(MMC->getThemedIcon("bug")); + } } void PageContainer::help() { - if (m_currentPage) - { - QString pageId = m_currentPage->helpPage(); - if (pageId.isEmpty()) - return; - DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/" + pageId)); - } + if (m_currentPage) + { + QString pageId = m_currentPage->helpPage(); + if (pageId.isEmpty()) + return; + DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/" + pageId)); + } } void PageContainer::currentChanged(const QModelIndex ¤t) { - showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1); + showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1); } bool PageContainer::prepareToClose() { - for (auto page : m_model->pages()) - { - if (!page->apply()) - return false; - } - if (m_currentPage) - { - m_currentPage->closed(); - } - return true; + if(!saveAll()) + { + return false; + } + if (m_currentPage) + { + m_currentPage->closed(); + } + return true; +} + +bool PageContainer::saveAll() +{ + for (auto page : m_model->pages()) + { + if (!page->apply()) + return false; + } + return true; } diff --git a/application/widgets/PageContainer.h b/application/widgets/PageContainer.h index ea9f8ce1..925f4ba3 100644 --- a/application/widgets/PageContainer.h +++ b/application/widgets/PageContainer.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,56 +33,57 @@ class QGridLayout; class PageContainer : public QWidget, public BasePageContainer { - Q_OBJECT + Q_OBJECT public: - explicit PageContainer(BasePageProvider *pageProvider, QString defaultId = QString(), - QWidget *parent = 0); - virtual ~PageContainer() {} + explicit PageContainer(BasePageProvider *pageProvider, QString defaultId = QString(), + QWidget *parent = 0); + virtual ~PageContainer() {} - void addButtons(QWidget * buttons); - void addButtons(QLayout * buttons); - /* - * Save any unsaved state and prepare to be closed. - * @return true if everything can be saved, false if there is something that requires attention - */ - bool prepareToClose(); + void addButtons(QWidget * buttons); + void addButtons(QLayout * buttons); + /* + * Save any unsaved state and prepare to be closed. + * @return true if everything can be saved, false if there is something that requires attention + */ + bool prepareToClose(); + bool saveAll(); - /* request close - used by individual pages */ - bool requestClose() override - { - if(m_container) - { - return m_container->requestClose(); - } - return false; - } + /* request close - used by individual pages */ + bool requestClose() override + { + if(m_container) + { + return m_container->requestClose(); + } + return false; + } - virtual bool selectPage(QString pageId) override; + virtual bool selectPage(QString pageId) override; - void refreshContainer() override; - virtual void setParentContainer(BasePageContainer * container) - { - m_container = container; - }; + void refreshContainer() override; + virtual void setParentContainer(BasePageContainer * container) + { + m_container = container; + }; private: - void createUI(); + void createUI(); public slots: - void help(); + void help(); private slots: - void currentChanged(const QModelIndex ¤t); - void showPage(int row); + void currentChanged(const QModelIndex ¤t); + void showPage(int row); private: - BasePageContainer * m_container = nullptr; - BasePage * m_currentPage = 0; - QSortFilterProxyModel *m_proxyModel; - PageModel *m_model; - QStackedLayout *m_pageStack; - QListView *m_pageList; - QLabel *m_header; - IconLabel *m_iconHeader; - QGridLayout *m_layout; + BasePageContainer * m_container = nullptr; + BasePage * m_currentPage = 0; + QSortFilterProxyModel *m_proxyModel; + PageModel *m_model; + QStackedLayout *m_pageStack; + QListView *m_pageList; + QLabel *m_header; + IconLabel *m_iconHeader; + QGridLayout *m_layout; }; diff --git a/application/widgets/PageContainer_p.h b/application/widgets/PageContainer_p.h index ed8171f1..4a5a9239 100644 --- a/application/widgets/PageContainer_p.h +++ b/application/widgets/PageContainer_p.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,98 +26,98 @@ const int pageIconSize = 24; class PageViewDelegate : public QStyledItemDelegate { public: - PageViewDelegate(QObject *parent) : QStyledItemDelegate(parent) - { - } - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const - { - QSize size = QStyledItemDelegate::sizeHint(option, index); - size.setHeight(qMax(size.height(), 32)); - return size; - } + PageViewDelegate(QObject *parent) : QStyledItemDelegate(parent) + { + } + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize size = QStyledItemDelegate::sizeHint(option, index); + size.setHeight(qMax(size.height(), 32)); + return size; + } }; class PageModel : public QAbstractListModel { public: - PageModel(QObject *parent = 0) : QAbstractListModel(parent) - { - QPixmap empty(pageIconSize, pageIconSize); - empty.fill(Qt::transparent); - m_emptyIcon = QIcon(empty); - } - virtual ~PageModel() {} + PageModel(QObject *parent = 0) : QAbstractListModel(parent) + { + QPixmap empty(pageIconSize, pageIconSize); + empty.fill(Qt::transparent); + m_emptyIcon = QIcon(empty); + } + virtual ~PageModel() {} - int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return parent.isValid() ? 0 : m_pages.size(); - } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const - { - switch (role) - { - case Qt::DisplayRole: - return m_pages.at(index.row())->displayName(); - case Qt::DecorationRole: - { - QIcon icon = m_pages.at(index.row())->icon(); - if (icon.isNull()) - icon = m_emptyIcon; - // HACK: fixes icon stretching on windows. TODO: report Qt bug for this - return QIcon(icon.pixmap(QSize(48,48))); - } - } - return QVariant(); - } + int rowCount(const QModelIndex &parent = QModelIndex()) const + { + return parent.isValid() ? 0 : m_pages.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + switch (role) + { + case Qt::DisplayRole: + return m_pages.at(index.row())->displayName(); + case Qt::DecorationRole: + { + QIcon icon = m_pages.at(index.row())->icon(); + if (icon.isNull()) + icon = m_emptyIcon; + // HACK: fixes icon stretching on windows. TODO: report Qt bug for this + return QIcon(icon.pixmap(QSize(48,48))); + } + } + return QVariant(); + } - void setPages(const QList &pages) - { - beginResetModel(); - m_pages = pages; - endResetModel(); - } - const QList &pages() const - { - return m_pages; - } + void setPages(const QList &pages) + { + beginResetModel(); + m_pages = pages; + endResetModel(); + } + const QList &pages() const + { + return m_pages; + } - BasePage * findPageEntryById(QString id) - { - for(auto page: m_pages) - { - if (page->id() == id) - return page; - } - return nullptr; - } + BasePage * findPageEntryById(QString id) + { + for(auto page: m_pages) + { + if (page->id() == id) + return page; + } + return nullptr; + } - QList m_pages; - QIcon m_emptyIcon; + QList m_pages; + QIcon m_emptyIcon; }; class PageView : public QListView { public: - PageView(QWidget *parent = 0) : QListView(parent) - { - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); - setItemDelegate(new PageViewDelegate(this)); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - } + PageView(QWidget *parent = 0) : QListView(parent) + { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + setItemDelegate(new PageViewDelegate(this)); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } - virtual QSize sizeHint() const - { - int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; - if (verticalScrollBar()->isVisible()) - width += verticalScrollBar()->width(); - return QSize(width, 100); - } + virtual QSize sizeHint() const + { + int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; + if (verticalScrollBar()->isVisible()) + width += verticalScrollBar()->width(); + return QSize(width, 100); + } - virtual bool eventFilter(QObject *obj, QEvent *event) - { - if (obj == verticalScrollBar() && - (event->type() == QEvent::Show || event->type() == QEvent::Hide)) - updateGeometry(); - return QListView::eventFilter(obj, event); - } + virtual bool eventFilter(QObject *obj, QEvent *event) + { + if (obj == verticalScrollBar() && + (event->type() == QEvent::Show || event->type() == QEvent::Hide)) + updateGeometry(); + return QListView::eventFilter(obj, event); + } }; diff --git a/application/widgets/ProgressWidget.cpp b/application/widgets/ProgressWidget.cpp index fab099a9..911e555d 100644 --- a/application/widgets/ProgressWidget.cpp +++ b/application/widgets/ProgressWidget.cpp @@ -9,65 +9,65 @@ #include "tasks/Task.h" ProgressWidget::ProgressWidget(QWidget *parent) - : QWidget(parent) + : QWidget(parent) { - m_label = new QLabel(this); - m_label->setWordWrap(true); - m_bar = new QProgressBar(this); - m_bar->setMinimum(0); - m_bar->setMaximum(100); - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(m_label); - layout->addWidget(m_bar); - layout->addStretch(); - setLayout(layout); + m_label = new QLabel(this); + m_label->setWordWrap(true); + m_bar = new QProgressBar(this); + m_bar->setMinimum(0); + m_bar->setMaximum(100); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_label); + layout->addWidget(m_bar); + layout->addStretch(); + setLayout(layout); } void ProgressWidget::start(std::shared_ptr task) { - if (m_task) - { - disconnect(m_task.get(), 0, this, 0); - } - m_task = task; - connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish); - connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus); - connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress); - connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed); - if (!m_task->isRunning()) - { - QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection); - } + if (m_task) + { + disconnect(m_task.get(), 0, this, 0); + } + m_task = task; + connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish); + connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus); + connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress); + connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed); + if (!m_task->isRunning()) + { + QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection); + } } bool ProgressWidget::exec(std::shared_ptr task) { - QEventLoop loop; - connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); - start(task); - if (task->isRunning()) - { - loop.exec(); - } - return task->wasSuccessful(); + QEventLoop loop; + connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); + start(task); + if (task->isRunning()) + { + loop.exec(); + } + return task->wasSuccessful(); } void ProgressWidget::handleTaskFinish() { - if (!m_task->wasSuccessful()) - { - m_label->setText(m_task->failReason()); - } + if (!m_task->wasSuccessful()) + { + m_label->setText(m_task->failReason()); + } } void ProgressWidget::handleTaskStatus(const QString &status) { - m_label->setText(status); + m_label->setText(status); } void ProgressWidget::handleTaskProgress(qint64 current, qint64 total) { - m_bar->setMaximum(total); - m_bar->setValue(current); + m_bar->setMaximum(total); + m_bar->setValue(current); } void ProgressWidget::taskDestroyed() { - m_task = nullptr; + m_task = nullptr; } diff --git a/application/widgets/ProgressWidget.h b/application/widgets/ProgressWidget.h index 08d8a157..fa67748a 100644 --- a/application/widgets/ProgressWidget.h +++ b/application/widgets/ProgressWidget.h @@ -11,22 +11,22 @@ class QLabel; class ProgressWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit ProgressWidget(QWidget *parent = nullptr); + explicit ProgressWidget(QWidget *parent = nullptr); public slots: - void start(std::shared_ptr task); - bool exec(std::shared_ptr task); + void start(std::shared_ptr task); + bool exec(std::shared_ptr task); private slots: - void handleTaskFinish(); - void handleTaskStatus(const QString &status); - void handleTaskProgress(qint64 current, qint64 total); - void taskDestroyed(); + void handleTaskFinish(); + void handleTaskStatus(const QString &status); + void handleTaskProgress(qint64 current, qint64 total); + void taskDestroyed(); private: - QLabel *m_label; - QProgressBar *m_bar; - std::shared_ptr m_task; + QLabel *m_label; + QProgressBar *m_bar; + std::shared_ptr m_task; }; diff --git a/application/widgets/ServerStatus.cpp b/application/widgets/ServerStatus.cpp index f1963b68..a7016c0c 100644 --- a/application/widgets/ServerStatus.cpp +++ b/application/widgets/ServerStatus.cpp @@ -15,80 +15,80 @@ class ClickableLabel : public QLabel { - Q_OBJECT + Q_OBJECT public: - ClickableLabel(QWidget *parent) : QLabel(parent) - { - setCursor(Qt::PointingHandCursor); - } + ClickableLabel(QWidget *parent) : QLabel(parent) + { + setCursor(Qt::PointingHandCursor); + } - ~ClickableLabel(){}; + ~ClickableLabel(){}; signals: - void clicked(); + void clicked(); protected: - void mousePressEvent(QMouseEvent *event) - { - emit clicked(); - } + void mousePressEvent(QMouseEvent *event) + { + emit clicked(); + } }; class ClickableIconLabel : public IconLabel { - Q_OBJECT + Q_OBJECT public: - ClickableIconLabel(QWidget *parent, QIcon icon, QSize size) : IconLabel(parent, icon, size) - { - setCursor(Qt::PointingHandCursor); - } + ClickableIconLabel(QWidget *parent, QIcon icon, QSize size) : IconLabel(parent, icon, size) + { + setCursor(Qt::PointingHandCursor); + } - ~ClickableIconLabel(){}; + ~ClickableIconLabel(){}; signals: - void clicked(); + void clicked(); protected: - void mousePressEvent(QMouseEvent *event) - { - emit clicked(); - } + void mousePressEvent(QMouseEvent *event) + { + emit clicked(); + } }; ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { - layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - goodIcon = MMC->getThemedIcon("status-good"); - yellowIcon = MMC->getThemedIcon("status-yellow"); - badIcon = MMC->getThemedIcon("status-bad"); - - addStatus("authserver.mojang.com", tr("Auth")); - addLine(); - addStatus("sessionserver.mojang.com", tr("Session")); - addLine(); - addStatus("textures.minecraft.net", tr("Skins")); - addLine(); - addStatus("api.mojang.com", tr("API")); - - m_statusRefresh = new QToolButton(this); - m_statusRefresh->setCheckable(true); - m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_statusRefresh->setIcon(MMC->getThemedIcon("refresh")); - layout->addWidget(m_statusRefresh); - - setLayout(layout); - - // Start status checker - m_statusChecker.reset(new StatusChecker()); - { - auto reloader = m_statusChecker.get(); - connect(reloader, &StatusChecker::statusChanged, this, &ServerStatus::StatusChanged); - connect(reloader, &StatusChecker::statusLoading, this, &ServerStatus::StatusReloading); - connect(m_statusRefresh, &QAbstractButton::clicked, this, &ServerStatus::reloadStatus); - m_statusChecker->startTimer(60000); - reloadStatus(); - } + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + goodIcon = MMC->getThemedIcon("status-good"); + yellowIcon = MMC->getThemedIcon("status-yellow"); + badIcon = MMC->getThemedIcon("status-bad"); + + addStatus("authserver.mojang.com", tr("Auth")); + addLine(); + addStatus("sessionserver.mojang.com", tr("Session")); + addLine(); + addStatus("textures.minecraft.net", tr("Skins")); + addLine(); + addStatus("api.mojang.com", tr("API")); + + m_statusRefresh = new QToolButton(this); + m_statusRefresh->setCheckable(true); + m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly); + m_statusRefresh->setIcon(MMC->getThemedIcon("refresh")); + layout->addWidget(m_statusRefresh); + + setLayout(layout); + + // Start status checker + m_statusChecker.reset(new StatusChecker()); + { + auto reloader = m_statusChecker.get(); + connect(reloader, &StatusChecker::statusChanged, this, &ServerStatus::StatusChanged); + connect(reloader, &StatusChecker::statusLoading, this, &ServerStatus::StatusReloading); + connect(m_statusRefresh, &QAbstractButton::clicked, this, &ServerStatus::reloadStatus); + m_statusChecker->startTimer(60000); + reloadStatus(); + } } ServerStatus::~ServerStatus() @@ -97,83 +97,83 @@ ServerStatus::~ServerStatus() void ServerStatus::reloadStatus() { - m_statusChecker->reloadStatus(); + m_statusChecker->reloadStatus(); } void ServerStatus::addLine() { - layout->addWidget(new LineSeparator(this, Qt::Vertical)); + layout->addWidget(new LineSeparator(this, Qt::Vertical)); } void ServerStatus::addStatus(QString key, QString name) { - { - auto label = new ClickableIconLabel(this, badIcon, QSize(16, 16)); - label->setToolTip(key); - serverLabels[key] = label; - layout->addWidget(label); - connect(label,SIGNAL(clicked()),SLOT(clicked())); - } - { - auto label = new ClickableLabel(this); - label->setText(name); - label->setToolTip(key); - layout->addWidget(label); - connect(label,SIGNAL(clicked()),SLOT(clicked())); - } + { + auto label = new ClickableIconLabel(this, badIcon, QSize(16, 16)); + label->setToolTip(key); + serverLabels[key] = label; + layout->addWidget(label); + connect(label,SIGNAL(clicked()),SLOT(clicked())); + } + { + auto label = new ClickableLabel(this); + label->setText(name); + label->setToolTip(key); + layout->addWidget(label); + connect(label,SIGNAL(clicked()),SLOT(clicked())); + } } void ServerStatus::clicked() { - DesktopServices::openUrl(QUrl("https://help.mojang.com/")); + DesktopServices::openUrl(QUrl("https://help.mojang.com/")); } void ServerStatus::setStatus(QString key, int value) { - if (!serverLabels.contains(key)) - return; - IconLabel *label = serverLabels[key]; - switch(value) - { - case 0: - label->setIcon(goodIcon); - break; - case 1: - label->setIcon(yellowIcon); - break; - default: - case 2: - label->setIcon(badIcon); - break; - } + if (!serverLabels.contains(key)) + return; + IconLabel *label = serverLabels[key]; + switch(value) + { + case 0: + label->setIcon(goodIcon); + break; + case 1: + label->setIcon(yellowIcon); + break; + default: + case 2: + label->setIcon(badIcon); + break; + } } void ServerStatus::StatusChanged(const QMap statusEntries) { - auto convertStatus = [&](QString status)->int - { - if (status == "green") - return 0; - else if (status == "yellow") - return 1; - else if (status == "red") - return 2; - return 2; - } - ; - auto iter = statusEntries.begin(); - while (iter != statusEntries.end()) - { - QString key = iter.key(); - auto value = convertStatus(iter.value()); - setStatus(key, value); - iter++; - } + auto convertStatus = [&](QString status)->int + { + if (status == "green") + return 0; + else if (status == "yellow") + return 1; + else if (status == "red") + return 2; + return 2; + } + ; + auto iter = statusEntries.begin(); + while (iter != statusEntries.end()) + { + QString key = iter.key(); + auto value = convertStatus(iter.value()); + setStatus(key, value); + iter++; + } } void ServerStatus::StatusReloading(bool is_reloading) { - m_statusRefresh->setChecked(is_reloading); + m_statusRefresh->setChecked(is_reloading); } #include "ServerStatus.moc" diff --git a/application/widgets/ServerStatus.h b/application/widgets/ServerStatus.h index 9beb8e4f..e1e70dfb 100644 --- a/application/widgets/ServerStatus.h +++ b/application/widgets/ServerStatus.h @@ -12,29 +12,29 @@ class StatusChecker; class ServerStatus: public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit ServerStatus(QWidget *parent = nullptr, Qt::WindowFlags f = 0); - virtual ~ServerStatus(); + explicit ServerStatus(QWidget *parent = nullptr, Qt::WindowFlags f = 0); + virtual ~ServerStatus(); public slots: - void reloadStatus(); - void StatusChanged(const QMap statuses); - void StatusReloading(bool is_reloading); + void reloadStatus(); + void StatusChanged(const QMap statuses); + void StatusReloading(bool is_reloading); private slots: - void clicked(); + void clicked(); private: /* methods */ - void addLine(); - void addStatus(QString key, QString name); - void setStatus(QString key, int value); + void addLine(); + void addStatus(QString key, QString name); + void setStatus(QString key, int value); private: /* data */ - QHBoxLayout * layout = nullptr; - QToolButton *m_statusRefresh = nullptr; - QMap serverLabels; - QIcon goodIcon; - QIcon yellowIcon; - QIcon badIcon; - std::shared_ptr m_statusChecker; + QHBoxLayout * layout = nullptr; + QToolButton *m_statusRefresh = nullptr; + QMap serverLabels; + QIcon goodIcon; + QIcon yellowIcon; + QIcon badIcon; + std::shared_ptr m_statusChecker; }; diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp index 8c80ecf3..09df75b7 100644 --- a/application/widgets/VersionListView.cpp +++ b/application/widgets/VersionListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,155 +22,140 @@ #include "Common.h" VersionListView::VersionListView(QWidget *parent) - :QTreeView ( parent ) + :QTreeView ( parent ) { - m_emptyString = tr("No versions are currently available."); + m_emptyString = tr("No versions are currently available."); } void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end) { - if(!m_itemCount) - viewport()->update(); - m_itemCount += end-start+1; - QTreeView::rowsInserted(parent, start, end); + m_itemCount += end-start+1; + updateEmptyViewPort(); + QTreeView::rowsInserted(parent, start, end); } void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - m_itemCount -= end-start+1; - if(!m_itemCount) - viewport()->update(); - QTreeView::rowsInserted(parent, start, end); + m_itemCount -= end-start+1; + updateEmptyViewPort(); + QTreeView::rowsInserted(parent, start, end); } void VersionListView::setModel(QAbstractItemModel *model) { - m_itemCount = model->rowCount(); - if(!m_itemCount) - viewport()->update(); - QTreeView::setModel(model); + m_itemCount = model->rowCount(); + updateEmptyViewPort(); + QTreeView::setModel(model); } void VersionListView::reset() { - if(model()) - { - m_itemCount = model()->rowCount(); - } - viewport()->update(); - QTreeView::reset(); + if(model()) + { + m_itemCount = model()->rowCount(); + } + else { + m_itemCount = 0; + } + updateEmptyViewPort(); + QTreeView::reset(); } void VersionListView::setEmptyString(QString emptyString) { - m_emptyString = emptyString; - updateEmptyViewPort(); + m_emptyString = emptyString; + updateEmptyViewPort(); } void VersionListView::setEmptyErrorString(QString emptyErrorString) { - m_emptyErrorString = emptyErrorString; - updateEmptyViewPort(); + m_emptyErrorString = emptyErrorString; + updateEmptyViewPort(); } void VersionListView::setEmptyMode(VersionListView::EmptyMode mode) { - m_emptyMode = mode; - updateEmptyViewPort(); + m_emptyMode = mode; + updateEmptyViewPort(); } void VersionListView::updateEmptyViewPort() { - if(!m_itemCount) - { - viewport()->update(); - } + setAccessibleDescription(currentEmptyString()); + + if(!m_itemCount) + { + viewport()->update(); + } } void VersionListView::paintEvent(QPaintEvent *event) { - if(m_itemCount) - { - QTreeView::paintEvent(event); - } - else - { - paintInfoLabel(event); - } + if(m_itemCount) + { + QTreeView::paintEvent(event); + } + else + { + paintInfoLabel(event); + } } -void VersionListView::paintInfoLabel(QPaintEvent *event) +QString VersionListView::currentEmptyString() const { - QString emptyString; - switch(m_emptyMode) - { - case VersionListView::Empty: - return; - case VersionListView::String: - emptyString = m_emptyString; - break; - case VersionListView::ErrorString: - emptyString = m_emptyErrorString; - break; - } + if(m_itemCount) { + return QString(); + } + switch(m_emptyMode) + { + default: + case VersionListView::Empty: + return QString(); + case VersionListView::String: + return m_emptyString; + case VersionListView::ErrorString: + return m_emptyErrorString; + } +} + + +void VersionListView::paintInfoLabel(QPaintEvent *event) const +{ + QString emptyString = currentEmptyString(); + //calculate the rect for the overlay QPainter painter(viewport()); painter.setRenderHint(QPainter::Antialiasing, true); - QFont font("sans", 20); + QFont font("sans", 20); font.setBold(true); - - QRect bounds = viewport()->geometry(); - bounds.moveTop(0); - QTextLayout layout(emptyString, font); - qreal height = 0.0; - qreal widthUsed = 0.0; - QStringList lines = viewItemTextLayout(layout, bounds.width() - 20, height, widthUsed); - QRect rect (0,0, widthUsed, height); - rect.setWidth(rect.width()+20); - rect.setHeight(rect.height()+20); - rect.moveCenter(bounds.center()); + + QRect bounds = viewport()->geometry(); + bounds.moveTop(0); + auto innerBounds = bounds; + innerBounds.adjust(10, 10, -10, -10); + + QColor background = QApplication::palette().color(QPalette::Foreground); + QColor foreground = QApplication::palette().color(QPalette::Base); + foreground.setAlpha(190); + painter.setFont(font); + auto fontMetrics = painter.fontMetrics(); + auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); + textRect.moveCenter(bounds.center()); + + auto wrapRect = textRect; + wrapRect.adjust(-10, -10, 10, 10); + //check if we are allowed to draw in our area - if (!event->rect().intersects(rect)) { + if (!event->rect().intersects(wrapRect)) { return; } - //draw the letter of the topmost item semitransparent in the middle - QColor background = QApplication::palette().color(QPalette::Foreground); - QColor foreground = QApplication::palette().color(QPalette::Base); - /* - background.setAlpha(128 - scrollFade); - foreground.setAlpha(128 - scrollFade); - */ + painter.setBrush(QBrush(background)); painter.setPen(foreground); - painter.drawRoundedRect(rect, 5.0, 5.0); - foreground.setAlpha(190); + painter.drawRoundedRect(wrapRect, 5.0, 5.0); + painter.setPen(foreground); painter.setFont(font); - painter.drawText(rect, Qt::AlignCenter, lines.join("\n")); - -} - -/* -void ModListView::setModel ( QAbstractItemModel* model ) -{ - QTreeView::setModel ( model ); - auto head = header(); - head->setStretchLastSection(false); - // HACK: this is true for the checkbox column of mod lists - auto string = model->headerData(0,head->orientation()).toString(); - if(!string.size()) - { - head->setSectionResizeMode(0, QHeaderView::ResizeToContents); - head->setSectionResizeMode(1, QHeaderView::Stretch); - for(int i = 2; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - else - { - head->setSectionResizeMode(0, QHeaderView::Stretch); - for(int i = 1; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } + painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); } -*/ diff --git a/application/widgets/VersionListView.h b/application/widgets/VersionListView.h index b7a881e9..37f7b27e 100644 --- a/application/widgets/VersionListView.h +++ b/application/widgets/VersionListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,42 +16,41 @@ #pragma once #include -class Mod; - class VersionListView : public QTreeView { - Q_OBJECT + Q_OBJECT public: - explicit VersionListView(QWidget *parent = 0); - virtual void paintEvent(QPaintEvent *event) override; - virtual void setModel(QAbstractItemModel* model) override; + explicit VersionListView(QWidget *parent = 0); + virtual void paintEvent(QPaintEvent *event) override; + virtual void setModel(QAbstractItemModel* model) override; - enum EmptyMode - { - Empty, - String, - ErrorString - }; + enum EmptyMode + { + Empty, + String, + ErrorString + }; - void setEmptyString(QString emptyString); - void setEmptyErrorString(QString emptyErrorString); - void setEmptyMode(EmptyMode mode); + void setEmptyString(QString emptyString); + void setEmptyErrorString(QString emptyErrorString); + void setEmptyMode(EmptyMode mode); public slots: - virtual void reset() override; + virtual void reset() override; protected slots: - virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override; - virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; private: /* methods */ - void paintInfoLabel(QPaintEvent *event); - void updateEmptyViewPort(); + void paintInfoLabel(QPaintEvent *event) const; + void updateEmptyViewPort(); + QString currentEmptyString() const; private: /* variables */ - int m_itemCount = 0; - QString m_emptyString; - QString m_emptyErrorString; - EmptyMode m_emptyMode = Empty; + int m_itemCount = 0; + QString m_emptyString; + QString m_emptyErrorString; + EmptyMode m_emptyMode = Empty; }; diff --git a/application/widgets/VersionSelectWidget.cpp b/application/widgets/VersionSelectWidget.cpp index ce1141b6..9925a6b4 100644 --- a/application/widgets/VersionSelectWidget.cpp +++ b/application/widgets/VersionSelectWidget.cpp @@ -7,50 +7,51 @@ #include VersionSelectWidget::VersionSelectWidget(QWidget* parent) - : QWidget(parent) + : QWidget(parent) { - setObjectName(QStringLiteral("VersionSelectWidget")); - verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - verticalLayout->setContentsMargins(0, 0, 0, 0); + setObjectName(QStringLiteral("VersionSelectWidget")); + verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + verticalLayout->setContentsMargins(0, 0, 0, 0); - m_proxyModel = new VersionProxyModel(this); + m_proxyModel = new VersionProxyModel(this); - listView = new VersionListView(this); - listView->setObjectName(QStringLiteral("listView")); - listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - listView->setAlternatingRowColors(true); - listView->setRootIsDecorated(false); - listView->setItemsExpandable(false); - listView->setWordWrap(true); - listView->header()->setCascadingSectionResizes(true); - listView->header()->setStretchLastSection(false); - listView->setModel(m_proxyModel); - verticalLayout->addWidget(listView); + listView = new VersionListView(this); + listView->setObjectName(QStringLiteral("listView")); + listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + listView->setAlternatingRowColors(true); + listView->setRootIsDecorated(false); + listView->setItemsExpandable(false); + listView->setWordWrap(true); + listView->header()->setCascadingSectionResizes(true); + listView->header()->setStretchLastSection(false); + listView->setModel(m_proxyModel); + verticalLayout->addWidget(listView); - sneakyProgressBar = new QProgressBar(this); - sneakyProgressBar->setObjectName(QStringLiteral("sneakyProgressBar")); - sneakyProgressBar->setFormat(QStringLiteral("%p%")); - verticalLayout->addWidget(sneakyProgressBar); - sneakyProgressBar->setHidden(true); - connect(listView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &VersionSelectWidget::currentRowChanged); + sneakyProgressBar = new QProgressBar(this); + sneakyProgressBar->setObjectName(QStringLiteral("sneakyProgressBar")); + sneakyProgressBar->setFormat(QStringLiteral("%p%")); + verticalLayout->addWidget(sneakyProgressBar); + sneakyProgressBar->setHidden(true); + connect(listView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &VersionSelectWidget::currentRowChanged); - QMetaObject::connectSlotsByName(this); + QMetaObject::connectSlotsByName(this); } void VersionSelectWidget::setCurrentVersion(const QString& version) { - m_currentVersion = version; + m_currentVersion = version; + m_proxyModel->setCurrentVersion(version); } void VersionSelectWidget::setEmptyString(QString emptyString) { - listView->setEmptyString(emptyString); + listView->setEmptyString(emptyString); } void VersionSelectWidget::setEmptyErrorString(QString emptyErrorString) { - listView->setEmptyErrorString(emptyErrorString); + listView->setEmptyErrorString(emptyErrorString); } VersionSelectWidget::~VersionSelectWidget() @@ -59,143 +60,143 @@ VersionSelectWidget::~VersionSelectWidget() void VersionSelectWidget::setResizeOn(int column) { - listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents); - resizeOnColumn = column; - listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); + listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents); + resizeOnColumn = column; + listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); } void VersionSelectWidget::initialize(BaseVersionList *vlist) { - m_vlist = vlist; - m_proxyModel->setSourceModel(vlist); - listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); - - if (!m_vlist->isLoaded()) - { - loadList(); - } - else - { - if (m_proxyModel->rowCount() == 0) - { - listView->setEmptyMode(VersionListView::String); - } - preselect(); - } + m_vlist = vlist; + m_proxyModel->setSourceModel(vlist); + listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); + + if (!m_vlist->isLoaded()) + { + loadList(); + } + else + { + if (m_proxyModel->rowCount() == 0) + { + listView->setEmptyMode(VersionListView::String); + } + preselect(); + } } void VersionSelectWidget::closeEvent(QCloseEvent * event) { - QWidget::closeEvent(event); + QWidget::closeEvent(event); } void VersionSelectWidget::loadList() { - auto newTask = m_vlist->getLoadTask(); - if (!newTask) - { - return; - } - loadTask = newTask.get(); - connect(loadTask, &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded); - connect(loadTask, &Task::failed, this, &VersionSelectWidget::onTaskFailed); - connect(loadTask, &Task::progress, this, &VersionSelectWidget::changeProgress); - if(!loadTask->isRunning()) - { - loadTask->start(); - } - sneakyProgressBar->setHidden(false); + auto newTask = m_vlist->getLoadTask(); + if (!newTask) + { + return; + } + loadTask = newTask.get(); + connect(loadTask, &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded); + connect(loadTask, &Task::failed, this, &VersionSelectWidget::onTaskFailed); + connect(loadTask, &Task::progress, this, &VersionSelectWidget::changeProgress); + if(!loadTask->isRunning()) + { + loadTask->start(); + } + sneakyProgressBar->setHidden(false); } void VersionSelectWidget::onTaskSucceeded() { - if (m_proxyModel->rowCount() == 0) - { - listView->setEmptyMode(VersionListView::String); - } - sneakyProgressBar->setHidden(true); - preselect(); - loadTask = nullptr; + if (m_proxyModel->rowCount() == 0) + { + listView->setEmptyMode(VersionListView::String); + } + sneakyProgressBar->setHidden(true); + preselect(); + loadTask = nullptr; } void VersionSelectWidget::onTaskFailed(const QString& reason) { - CustomMessageBox::selectable(this, tr("Error"), tr("List update failed:\n%1").arg(reason), QMessageBox::Warning)->show(); - onTaskSucceeded(); + CustomMessageBox::selectable(this, tr("Error"), tr("List update failed:\n%1").arg(reason), QMessageBox::Warning)->show(); + onTaskSucceeded(); } void VersionSelectWidget::changeProgress(qint64 current, qint64 total) { - sneakyProgressBar->setMaximum(total); - sneakyProgressBar->setValue(current); + sneakyProgressBar->setMaximum(total); + sneakyProgressBar->setValue(current); } void VersionSelectWidget::currentRowChanged(const QModelIndex& current, const QModelIndex&) { - auto variant = m_proxyModel->data(current, BaseVersionList::VersionPointerRole); - emit selectedVersionChanged(variant.value()); + auto variant = m_proxyModel->data(current, BaseVersionList::VersionPointerRole); + emit selectedVersionChanged(variant.value()); } void VersionSelectWidget::preselect() { - if(preselectedAlready) - return; - selectCurrent(); - if(preselectedAlready) - return; - selectRecommended(); + if(preselectedAlready) + return; + selectCurrent(); + if(preselectedAlready) + return; + selectRecommended(); } void VersionSelectWidget::selectCurrent() { - if(m_currentVersion.isEmpty()) - { - return; - } - auto idx = m_proxyModel->getVersion(m_currentVersion); - if(idx.isValid()) - { - preselectedAlready = true; - listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); - } + if(m_currentVersion.isEmpty()) + { + return; + } + auto idx = m_proxyModel->getVersion(m_currentVersion); + if(idx.isValid()) + { + preselectedAlready = true; + listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); + } } void VersionSelectWidget::selectRecommended() { - auto idx = m_proxyModel->getRecommended(); - if(idx.isValid()) - { - preselectedAlready = true; - listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); - } + auto idx = m_proxyModel->getRecommended(); + if(idx.isValid()) + { + preselectedAlready = true; + listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + listView->scrollTo(idx, QAbstractItemView::PositionAtCenter); + } } bool VersionSelectWidget::hasVersions() const { - return m_proxyModel->rowCount(QModelIndex()) != 0; + return m_proxyModel->rowCount(QModelIndex()) != 0; } BaseVersionPtr VersionSelectWidget::selectedVersion() const { - auto currentIndex = listView->selectionModel()->currentIndex(); - auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole); - return variant.value(); + auto currentIndex = listView->selectionModel()->currentIndex(); + auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole); + return variant.value(); } void VersionSelectWidget::setExactFilter(BaseVersionList::ModelRoles role, QString filter) { - m_proxyModel->setFilter(role, new ExactFilter(filter)); + m_proxyModel->setFilter(role, new ExactFilter(filter)); } void VersionSelectWidget::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter) { - m_proxyModel->setFilter(role, new ContainsFilter(filter)); + m_proxyModel->setFilter(role, new ContainsFilter(filter)); } void VersionSelectWidget::setFilter(BaseVersionList::ModelRoles role, Filter *filter) { - m_proxyModel->setFilter(role, filter); + m_proxyModel->setFilter(role, filter); } diff --git a/application/widgets/VersionSelectWidget.h b/application/widgets/VersionSelectWidget.h index c134887f..701f568e 100644 --- a/application/widgets/VersionSelectWidget.h +++ b/application/widgets/VersionSelectWidget.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,55 +27,55 @@ class Filter; class VersionSelectWidget: public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit VersionSelectWidget(QWidget *parent = 0); - ~VersionSelectWidget(); + explicit VersionSelectWidget(QWidget *parent = 0); + ~VersionSelectWidget(); - //! loads the list if needed. - void initialize(BaseVersionList *vlist); + //! loads the list if needed. + void initialize(BaseVersionList *vlist); - //! Starts a task that loads the list. - void loadList(); + //! Starts a task that loads the list. + void loadList(); - bool hasVersions() const; - BaseVersionPtr selectedVersion() const; - void selectRecommended(); - void selectCurrent(); + bool hasVersions() const; + BaseVersionPtr selectedVersion() const; + void selectRecommended(); + void selectCurrent(); - void setCurrentVersion(const QString & version); - void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); - void setExactFilter(BaseVersionList::ModelRoles role, QString filter); - void setFilter(BaseVersionList::ModelRoles role, Filter *filter); - void setEmptyString(QString emptyString); - void setEmptyErrorString(QString emptyErrorString); - void setResizeOn(int column); + void setCurrentVersion(const QString & version); + void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); + void setExactFilter(BaseVersionList::ModelRoles role, QString filter); + void setFilter(BaseVersionList::ModelRoles role, Filter *filter); + void setEmptyString(QString emptyString); + void setEmptyErrorString(QString emptyErrorString); + void setResizeOn(int column); signals: - void selectedVersionChanged(BaseVersionPtr version); + void selectedVersionChanged(BaseVersionPtr version); protected: - virtual void closeEvent ( QCloseEvent* ); + virtual void closeEvent ( QCloseEvent* ); private slots: - void onTaskSucceeded(); - void onTaskFailed(const QString &reason); - void changeProgress(qint64 current, qint64 total); - void currentRowChanged(const QModelIndex ¤t, const QModelIndex &); + void onTaskSucceeded(); + void onTaskFailed(const QString &reason); + void changeProgress(qint64 current, qint64 total); + void currentRowChanged(const QModelIndex ¤t, const QModelIndex &); private: - void preselect(); + void preselect(); private: - QString m_currentVersion; - BaseVersionList *m_vlist = nullptr; - VersionProxyModel *m_proxyModel = nullptr; - int resizeOnColumn = 0; - Task * loadTask; - bool preselectedAlready = false; + QString m_currentVersion; + BaseVersionList *m_vlist = nullptr; + VersionProxyModel *m_proxyModel = nullptr; + int resizeOnColumn = 0; + Task * loadTask; + bool preselectedAlready = false; private: - QVBoxLayout *verticalLayout = nullptr; - VersionListView *listView = nullptr; - QProgressBar *sneakyProgressBar = nullptr; + QVBoxLayout *verticalLayout = nullptr; + VersionListView *listView = nullptr; + QProgressBar *sneakyProgressBar = nullptr; }; diff --git a/application/widgets/WideBar.cpp b/application/widgets/WideBar.cpp new file mode 100644 index 00000000..cbd6c617 --- /dev/null +++ b/application/widgets/WideBar.cpp @@ -0,0 +1,116 @@ +#include "WideBar.h" +#include +#include + +class ActionButton : public QToolButton +{ + Q_OBJECT +public: + ActionButton(QAction * action, QWidget * parent = 0) : QToolButton(parent), m_action(action) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + connect(action, &QAction::changed, this, &ActionButton::actionChanged); + connect(this, &ActionButton::clicked, action, &QAction::trigger); + actionChanged(); + }; +private slots: + void actionChanged() { + setEnabled(m_action->isEnabled()); + setChecked(m_action->isChecked()); + setCheckable(m_action->isCheckable()); + setText(m_action->text()); + setIcon(m_action->icon()); + setToolTip(m_action->toolTip()); + setHidden(!m_action->isVisible()); + setFocusPolicy(Qt::NoFocus); + } +private: + QAction * m_action; +}; + + +WideBar::WideBar(const QString& title, QWidget* parent) : QToolBar(title, parent) +{ + setFloatable(false); + setMovable(false); +} + +WideBar::WideBar(QWidget* parent) : QToolBar(parent) +{ + setFloatable(false); + setMovable(false); +} + +struct WideBar::BarEntry { + enum Type { + None, + Action, + Separator, + Spacer + } type = None; + QAction *qAction = nullptr; + QAction *wideAction = nullptr; +}; + + +WideBar::~WideBar() +{ + for(auto *iter: m_entries) { + delete iter; + } +} + +void WideBar::addAction(QAction* action) +{ + auto entry = new BarEntry(); + entry->qAction = addWidget(new ActionButton(action, this)); + entry->wideAction = action; + entry->type = BarEntry::Action; + m_entries.push_back(entry); +} + +void WideBar::addSeparator() +{ + auto entry = new BarEntry(); + entry->qAction = QToolBar::addSeparator(); + entry->type = BarEntry::Separator; + m_entries.push_back(entry); +} + +void WideBar::insertSpacer(QAction* action) +{ + auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) { + return entry->wideAction == action; + }); + if(iter == m_entries.end()) { + return; + } + QWidget* spacer = new QWidget(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + auto entry = new BarEntry(); + entry->qAction = insertWidget((*iter)->qAction, spacer); + entry->type = BarEntry::Spacer; + m_entries.insert(iter, entry); +} + +QMenu * WideBar::createContextMenu(QWidget *parent, const QString & title) +{ + QMenu *contextMenu = new QMenu(title, parent); + for(auto & item: m_entries) { + switch(item->type) { + default: + case BarEntry::None: + break; + case BarEntry::Separator: + case BarEntry::Spacer: + contextMenu->addSeparator(); + break; + case BarEntry::Action: + contextMenu->addAction(item->wideAction); + break; + } + } + return contextMenu; +} + +#include "WideBar.moc" diff --git a/application/widgets/WideBar.h b/application/widgets/WideBar.h new file mode 100644 index 00000000..d1b8cbe7 --- /dev/null +++ b/application/widgets/WideBar.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +class QMenu; + +class WideBar : public QToolBar +{ + Q_OBJECT + +public: + explicit WideBar(const QString &title, QWidget * parent = nullptr); + explicit WideBar(QWidget * parent = nullptr); + virtual ~WideBar(); + + void addAction(QAction *action); + void addSeparator(); + void insertSpacer(QAction *action); + QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString()); + +private: + struct BarEntry; + QList m_entries; +}; diff --git a/changelog.md b/changelog.md index 7dbe4ce5..544c7abf 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,250 @@ -# MultiMC 0.6.2 +# MultiMC 0.6.7 -## New or changed features +The previous release introduced some extra buttons that made the instance window way too big for some displays. This release is aimed at fixing that, along with other UI and performance improvements. + +There are some accessibility fixes thrown in too. + +### New or changed features + +- Mod lists are now asynchronous and heavily threaded. + + Basically, both faster and more responsive. + + The changes necessary for this also pave the way for having other sources of mod metadata, and for adding more mod-related features support in general. + +- Mod list printed in log has been improved. + + It now also shows disabled mods, and has prefix and suffix that shows if the mod is enabled, and if it is a folder. + +- You can now enable and disable mods with the keyboard. + + Toggle with enter. + +- Enabling and disabling mods no longer makes the list forget what was selected. + +- GH-358: Switched all the dialog pages from using buttons in layouts to toolbars. + + Toolbar buttons are smaller, and the toolbars can overflow buttons into an overflow space. This allows requiring a lot less space for the windows. + + All of the relevant pages now also have context menus to offset the issues toolbars create when using screen readers. + +- Main window instance list is now compatible with screen readers. + + If you have poor or no eyesight, this makes MultiMC usable. + +- More instance pages are now visible when the instance is running. + + Mods, version and the like should now be visible, but most of the controls are disabled until the game closes. + +- GH-2550, GH-2722, GH-2762: Mod list sorting is much improved. + + You can now sort mods by enabled status. + + Sorting by version actually looks at the versions as versions, not words. + + Sorting by name ignores 'The' prefixes in mod names. For example, 'The Betweenlands' will be sorted as 'Betweenlands'. + + Sorting cascades from 'Enabled' to 'Name' and then 'Version'. This means that if you sort 'Enabled', the enabled and disabled mods are still sorted + by name and mods with the same name will be also sorted by version. + +# Previous releases + +## MultiMC 0.6.6 + +This release is mostly the smaller things that have accumulated over time, along with a big change in linux packaging. + +No 1.13+ Forge news yet. That's going to be a major overhaul of many of the internals of MultiMC. + +### **IMPORTANT** + +On linux, MultiMC no longer bundles the Qt libraries. This fixes many issues, but it might not run after the update unless you have the required libraries installed. + +Make sure you have the following packages before you update: + +- Arch: `qt5-base` +- Debian/Ubuntu: `qt5-default` +- CentOS/RHEL/Fedora: `qt5-qtbase-gui` +- Suse: `libqt5-qtbase` + +MultiMC on linux is built with Qt 5.4 and older versions of Qt will not work. + +This should be a massive improvement to system integration on linux and resolves GH-1784, GH-2605, GH-1979, GH-2271, GH-1992, GH-1816 and their many duplicates. + +#### New or changed features + +- GH-2487: No is now the default button when deleting instances. + +- It is now possible to launch with profilers in offline mode. + +- Massively improved support for icon formats when importing and exporting instances. + + All of the formats MultiMC supports are now supported in exported instances too, instead of just PNG. + +- Added the pocket fox icon. + + We still have the big one under the staircase. It's cute. Just hide your chickens. + +- Global settings can be opened from instance settings where appropriate. + + Many people use the instance overrides where using the global settings would be more appropriate. Hopefully this makes it clearer that the instance settings are overrides for the global settings. + +- Added direct Fabric loader support. + + Much overdue. It's good. Fabric mod metadata is also supported in the mod pages. + +- MultiMC now recognizes the new `experimental` Minecraft versions. + + Go mess with the combat experiment. It's interesting. + +- Added Twitch URL as an option to the Add Instance dialog. + + You can now drag the purple download buttons from CurseForge into MultiMC and get a modpack out of it. Much easier! + +#### Bugfixes + +- Translation folder is now created sooner, making first launch translation fetch work again. + +- GH-2716: MultiMC will no longer try to censor values shorter than 4 characters in logs. + + It was actually leaking information and destroying the logs instead of helping. + +- GH-2551: Trim server name and IP before saving them. + +- GH-2591: Fix multiple potential memory leaks and crashes related to destroying objects with Qt memory lifecycle model. + +- `run.sh` on linux now passes all arguments to MultiMC. + +- Adding a disabled mod duplicate now replaces the existing mod. + +- GH-2592: Newly created instances are now selected again. This was a very old regression. + +- GH-689: MultiMC no longer creates an imgur album for single screenshot uploads. + +- GH-1813: `#` is now saved properly when used in instance notes. + +- GH-2515: Deleting an instance externally while the delete dialog is open no longer leads to some other instance being deleted when you click OK. + +- GH-2499: Proxy settings are applied immediately and no longer need an application restart. + +- GH-1701: When downloading updates, the text now reflects the number of downloaded files better. + +- Icon scaling issues on macOS should now be fixed. + +## MultiMC 0.6.5 + +Finalizing the translation workflow improvements and adding fixes for sounds missing in old game versions. + +#### New or changed features + +- UI for the language settings has been unified across the application + +- GH-2209: Sounds in old (pre-1.6) versions should now work again + + The launcher now downloads the correct assets and reconstructs the `resources` folder inside instances. This mirrors the same fix implemented in vanilla. + + Also, a minor issue with the reconstruction being done twice per launch has been fixed. + + +## MultiMC 0.6.4 + +Update for a better translation workflow, and new FTB API location. + +### New or changed features + +- FTB API location has changed + + MultiMC now uses the new location and should keep working. + +- Translations have been overhauled, again + + It is now possible to put the translation source `.po` files into the `translations` folder and see changes in MultiMC immediately. + + The new translation workflow is like this: + * Get a `.po` file from here the [translations repository](https://github.com/MultiMC/MultiMC5-translate). + * Alternatively, get the `template.pot` and start a new translation based on it. + * Put it in the `translations` folder. + * Edit it with [POEdit](https://poedit.net/). + * See the changes in real time. + * When done, post the changed files on discord, or github. + + When using a `.po` file, MultiMC logs which strings are missing from the translation on the currently displayed UI screen(s), and which one are marked as fuzzy. This should make it easy to determine what's important. + +## MultiMC 0.6.3 + +This is a release mostly aimed at getting all the small changes and fixes out of the door. + +### Potentially breaking changes + +- Local libraries are only loaded from inside the instances now. + + Before, MultiMC allowed loading local libraries from the main `libraries` folder. + This in turn allowed existence of instances which could not be transported from one installation of MultiMC to another. + + GH-2475: A bug that allowed the launch to continue with missing local libraries has also been fixed. + + Effectively, you will get errors from launching such instances. You can fix the errors by copying the libraries to the locations indicated in the error log. + +### New or changed features + +- FTB import now has support for third party modpack codes. + + Better late than never? + +- Instance creation can now be interrupted / aborted. + +- GH-2053: You can now inspect and change the `servers.dat` file from MultiMC. + +- MultiMC now uses the https protocol for many more network requests. + +- GH-2352: There is now a button to open the `.minecraft` folder inside the selected instance. + +- GH-2232: MultiMC can now use `.gif` icons (not animated). + +- GH-2101: Instance renaming is now done inline, in the actual instance list. + +- GH-2452: When deleting a group, MultiMC asks for confirmation. + +- GH-1552: PermGen is no longer shown when it's not appropriate (java 8 and up). + +- GH-2144: When changing versions of a component like Forge, the current version is marked with `(installed)`. + +- GH-2374: World list has been improved: + + - Alternating line background colors have been added. + - The world game type is now shown in a column. + +- GH-2384: When installing a mod, existing mod with the same file name will be replaced. + +- The background cat sometimes wears a silly hat. + +### Bugfixes + +- GH-2252: Fixed odd drag and drop behaviour on Windows + + Drag and drop of URLs from a browser locked up the browser. This needs further fixes on macOS. + +- Instance naming fixes: + + - GH-2355: Whitespace prefix or suffix is no longer allowed. + - GH-2238: Newlines in instance names are no longer allowed either. + +- GH-2412: MultiMC no longer leaves behind zombie processes after launch on linux. + +- GH-2382: Version filter for the forge/liteloader version lists was not matching the whole version name. + +- GH-2488: More issues with broken relative URL redirection in Qt have been fixed. + +- Some memory leaks of downloaded data have been fixed. + +- MultiMC now handles instance groups and instance group saving better. + + Long deleted groups no longer persist in the group list. + +- GH-2467: Broken (and nonsensical) sorting indicators have been removed from the versions page header. + +## MultiMC 0.6.2 + +### New or changed features - MultiMC now has FTB integration: @@ -34,7 +278,7 @@ - MultiMC now probes the system for the name of the linux distribution as part of analytics. This will be used to focus future packaging efforts. - Secret cheat code has been added... What does it do? -## Bugfixes +### Bugfixes - VisualVM integration now works when VisualVM is bundled inside the MultiMC folder (uses a relative path). - When reinstalling a component, or changing a component version, the custom version is now removed first. @@ -47,8 +291,6 @@ - GH-2154: MultiMC now ignores the `hidden` flag of instance folders and they should show up correctly. - When migrating Legacy instances, custom `minecraft.jar` will be preserved. -# Previous releases - ## MultiMC 0.6.1 ### New or changed features @@ -977,4 +1219,4 @@ Long time coming, this release brought a lot of incremental improvements and fix - Added additional information to the about dialog. ## MultiMC 0.0 -- Initial release. \ No newline at end of file +- Initial release. diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake index 85eae156..39c2707b 100644 --- a/cmake/GetGitRevisionDescription.cmake +++ b/cmake/GetGitRevisionDescription.cmake @@ -31,7 +31,7 @@ # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) - return() + return() endif() set(__get_git_revision_description YES) @@ -40,91 +40,91 @@ set(__get_git_revision_description YES) get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) function(get_git_head_revision _refspecvar _hashvar) - set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - set(GIT_DIR "${GIT_PARENT_DIR}/.git") - while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories - set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") - get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) - if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) - # We have reached the root directory, we are not in git - set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) - set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) - return() - endif() - set(GIT_DIR "${GIT_PARENT_DIR}/.git") - endwhile() - # check if this is a submodule - if(NOT IS_DIRECTORY ${GIT_DIR}) - file(READ ${GIT_DIR} submodule) - string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) - get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) - get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) - endif() - set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") - if(NOT EXISTS "${GIT_DATA}") - file(MAKE_DIRECTORY "${GIT_DATA}") - endif() + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() - if(NOT EXISTS "${GIT_DIR}/HEAD") - return() - endif() - set(HEAD_FILE "${GIT_DATA}/HEAD") - configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) - configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" - "${GIT_DATA}/grabRef.cmake" - @ONLY) - include("${GIT_DATA}/grabRef.cmake") + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" + @ONLY) + include("${GIT_DATA}/grabRef.cmake") - set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) - set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_describe _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - get_git_head_revision(refspec hash) - if(NOT GIT_FOUND) - set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) - return() - endif() - if(NOT hash) - set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) - return() - endif() + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() - # TODO sanitize - #if((${ARGN}" MATCHES "&&") OR - # (ARGN MATCHES "||") OR - # (ARGN MATCHES "\\;")) - # message("Please report the following error to the project!") - # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") - #endif() + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() - #message(STATUS "Arguments to execute_process: ${ARGN}") + #message(STATUS "Arguments to execute_process: ${ARGN}") - execute_process(COMMAND - "${GIT_EXECUTABLE}" - describe - ${hash} - ${ARGN} - WORKING_DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE - res - OUTPUT_VARIABLE - out - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - set(out "${out}-${res}-NOTFOUND") - endif() + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + ${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() - set(${_var} "${out}" PARENT_SCOPE) + set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) - git_describe(out --exact-match ${ARGN}) - set(${_var} "${out}" PARENT_SCOPE) + git_describe(out --exact-match ${ARGN}) + set(${_var} "${out}" PARENT_SCOPE) endfunction() diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in index 6d8b708e..04db9a8e 100644 --- a/cmake/GetGitRevisionDescription.cmake.in +++ b/cmake/GetGitRevisionDescription.cmake.in @@ -19,23 +19,23 @@ file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") - # named branch - string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") - if(EXISTS "@GIT_DIR@/${HEAD_REF}") - configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - else() - configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) - file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) - if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") - set(HEAD_HASH "${CMAKE_MATCH_1}") - endif() - endif() + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + else() + configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) + file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) + if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") + set(HEAD_HASH "${CMAKE_MATCH_1}") + endif() + endif() else() - # detached HEAD - configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) - file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) - string(STRIP "${HEAD_HASH}" HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() diff --git a/cmake/GitFunctions.cmake b/cmake/GitFunctions.cmake index 898e7b01..a055b5de 100644 --- a/cmake/GitFunctions.cmake +++ b/cmake/GitFunctions.cmake @@ -9,29 +9,29 @@ include(CMakeParseArguments) if(GIT_FOUND) function(git_run) - set(oneValueArgs OUTPUT_VAR DEFAULT) - set(multiValueArgs COMMAND) - cmake_parse_arguments(GIT_RUN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(oneValueArgs OUTPUT_VAR DEFAULT) + set(multiValueArgs COMMAND) + cmake_parse_arguments(GIT_RUN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - execute_process(COMMAND ${GIT_EXECUTABLE} ${GIT_RUN_COMMAND} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE GIT_RESULTVAR - OUTPUT_VARIABLE GIT_OUTVAR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) + execute_process(COMMAND ${GIT_EXECUTABLE} ${GIT_RUN_COMMAND} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_RESULTVAR + OUTPUT_VARIABLE GIT_OUTVAR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) - if(GIT_RESULTVAR EQUAL 0) - set(${GIT_RUN_OUTPUT_VAR} "${GIT_OUTVAR}" PARENT_SCOPE) - else() - set(${GIT_RUN_OUTPUT_VAR} ${GIT_RUN_DEFAULT}) - message(STATUS "Failed to run Git: ${GIT_OUTVAR}") - endif() + if(GIT_RESULTVAR EQUAL 0) + set(${GIT_RUN_OUTPUT_VAR} "${GIT_OUTVAR}" PARENT_SCOPE) + else() + set(${GIT_RUN_OUTPUT_VAR} ${GIT_RUN_DEFAULT}) + message(STATUS "Failed to run Git: ${GIT_OUTVAR}") + endif() endfunction() else() function(git_run) - set(oneValueArgs OUTPUT_VAR DEFAULT) - set(multiValueArgs COMMAND) - cmake_parse_arguments(GIT_RUN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - set(${GIT_RUN_OUTPUT_VAR} ${GIT_RUN_DEFAULT}) + set(oneValueArgs OUTPUT_VAR DEFAULT) + set(multiValueArgs COMMAND) + cmake_parse_arguments(GIT_RUN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(${GIT_RUN_OUTPUT_VAR} ${GIT_RUN_DEFAULT}) endfunction(git_run) endif() diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index 809fab00..050123ee 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -2,39 +2,39 @@ - NSPrincipalClass - NSApplication - NSHighResolutionCapable - True - CFBundleDevelopmentRegion - English - CFBundleExecutable - ${MACOSX_BUNDLE_EXECUTABLE_NAME} - CFBundleGetInfoString - ${MACOSX_BUNDLE_INFO_STRING} - CFBundleIconFile - ${MACOSX_BUNDLE_ICON_FILE} - CFBundleIdentifier - ${MACOSX_BUNDLE_GUI_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLongVersionString - ${MACOSX_BUNDLE_LONG_VERSION_STRING} - CFBundleName - ${MACOSX_BUNDLE_BUNDLE_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - ${MACOSX_BUNDLE_SHORT_VERSION_STRING} - CFBundleSignature - ???? - CFBundleVersion - ${MACOSX_BUNDLE_BUNDLE_VERSION} - CSResourcesFileMapped - - LSRequiresCarbon - - NSHumanReadableCopyright - ${MACOSX_BUNDLE_COPYRIGHT} + NSPrincipalClass + NSApplication + NSHighResolutionCapable + True + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} diff --git a/cmake/UnitTest.cmake b/cmake/UnitTest.cmake index e48ff74d..9f2bc269 100644 --- a/cmake/UnitTest.cmake +++ b/cmake/UnitTest.cmake @@ -5,45 +5,44 @@ set(TEST_RESOURCE_PATH ${CMAKE_CURRENT_LIST_DIR}) message(${TEST_RESOURCE_PATH}) function(add_unit_test name) - set(options "") - set(oneValueArgs DATA) - set(multiValueArgs SOURCES LIBS QT) - - cmake_parse_arguments(OPT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) - - if(WIN32) - add_executable(${name}_test ${OPT_SOURCES} ${TEST_RESOURCE_PATH}/UnitTest/test.rc) - else() - add_executable(${name}_test ${OPT_SOURCES}) - endif() - - if(NOT "${OPT_DATA}" STREQUAL "") - set(TEST_DATA_PATH "${CMAKE_CURRENT_BINARY_DIR}/data") - set(TEST_DATA_PATH_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${OPT_DATA}") - message("From ${TEST_DATA_PATH_SRC} to ${TEST_DATA_PATH}") - string(REGEX REPLACE "[/\\:]" "_" DATA_TARGET_NAME "${TEST_DATA_PATH_SRC}") - if(UNIX) - # on unix we get the third / from the filename - set(TEST_DATA_URL "file://${TEST_DATA_PATH}") - else() - # we don't on windows, so we have to add it ourselves - set(TEST_DATA_URL "file:///${TEST_DATA_PATH}") - endif() - if(NOT TARGET "${DATA_TARGET_NAME}") - add_custom_target(${DATA_TARGET_NAME}) - add_dependencies(${name}_test ${DATA_TARGET_NAME}) - add_custom_command( - TARGET ${DATA_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} "-DTEST_DATA_URL=${TEST_DATA_URL}" -DSOURCE=${TEST_DATA_PATH_SRC} -DDESTINATION=${TEST_DATA_PATH} -P ${TEST_RESOURCE_PATH}/UnitTest/generate_test_data.cmake - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) - endif() - endif() - - target_link_libraries(${name}_test ${OPT_LIBS}) - qt5_use_modules(${name}_test Test ${OPT_QT}) - - target_include_directories(${name}_test PRIVATE "${TEST_RESOURCE_PATH}/UnitTest/") - - add_test(NAME ${name} COMMAND ${name}_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set(options "") + set(oneValueArgs DATA) + set(multiValueArgs SOURCES LIBS) + + cmake_parse_arguments(OPT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + + if(WIN32) + add_executable(${name}_test ${OPT_SOURCES} ${TEST_RESOURCE_PATH}/UnitTest/test.rc) + else() + add_executable(${name}_test ${OPT_SOURCES}) + endif() + + if(NOT "${OPT_DATA}" STREQUAL "") + set(TEST_DATA_PATH "${CMAKE_CURRENT_BINARY_DIR}/data") + set(TEST_DATA_PATH_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${OPT_DATA}") + message("From ${TEST_DATA_PATH_SRC} to ${TEST_DATA_PATH}") + string(REGEX REPLACE "[/\\:]" "_" DATA_TARGET_NAME "${TEST_DATA_PATH_SRC}") + if(UNIX) + # on unix we get the third / from the filename + set(TEST_DATA_URL "file://${TEST_DATA_PATH}") + else() + # we don't on windows, so we have to add it ourselves + set(TEST_DATA_URL "file:///${TEST_DATA_PATH}") + endif() + if(NOT TARGET "${DATA_TARGET_NAME}") + add_custom_target(${DATA_TARGET_NAME}) + add_dependencies(${name}_test ${DATA_TARGET_NAME}) + add_custom_command( + TARGET ${DATA_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} "-DTEST_DATA_URL=${TEST_DATA_URL}" -DSOURCE=${TEST_DATA_PATH_SRC} -DDESTINATION=${TEST_DATA_PATH} -P ${TEST_RESOURCE_PATH}/UnitTest/generate_test_data.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + endif() + endif() + + target_link_libraries(${name}_test Qt5::Test ${OPT_LIBS}) + + target_include_directories(${name}_test PRIVATE "${TEST_RESOURCE_PATH}/UnitTest/") + + add_test(NAME ${name} COMMAND ${name}_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endfunction() diff --git a/cmake/UnitTest/TestUtil.h b/cmake/UnitTest/TestUtil.h index 84f18a2e..a478bdde 100644 --- a/cmake/UnitTest/TestUtil.h +++ b/cmake/UnitTest/TestUtil.h @@ -11,16 +11,16 @@ class TestsInternal { public: - static QByteArray readFile(const QString &fileName) - { - QFile f(fileName); - f.open(QFile::ReadOnly); - return f.readAll(); - } - static QString readFileUtf8(const QString &fileName) - { - return QString::fromUtf8(readFile(fileName)); - } + static QByteArray readFile(const QString &fileName) + { + QFile f(fileName); + f.open(QFile::ReadOnly); + return f.readAll(); + } + static QString readFileUtf8(const QString &fileName) + { + return QString::fromUtf8(readFile(fileName)); + } }; #define MULTIMC_GET_TEST_FILE(file) TestsInternal::readFile(QFINDTESTDATA(file)) diff --git a/cmake/UnitTest/generate_test_data.cmake b/cmake/UnitTest/generate_test_data.cmake index d9250fab..d0bd4ab1 100644 --- a/cmake/UnitTest/generate_test_data.cmake +++ b/cmake/UnitTest/generate_test_data.cmake @@ -2,22 +2,22 @@ # variables. Create destination directory if it does not exist. function(configure_files srcDir destDir) - make_directory(${destDir}) + make_directory(${destDir}) - file(GLOB templateFiles RELATIVE ${srcDir} ${srcDir}/*) - foreach(templateFile ${templateFiles}) - set(srcTemplatePath ${srcDir}/${templateFile}) - if(NOT IS_DIRECTORY ${srcTemplatePath}) - configure_file( - ${srcTemplatePath} - ${destDir}/${templateFile} - @ONLY - NEWLINE_STYLE LF - ) - else() - configure_files("${srcTemplatePath}" "${destDir}/${templateFile}") - endif() - endforeach() + file(GLOB templateFiles RELATIVE ${srcDir} ${srcDir}/*) + foreach(templateFile ${templateFiles}) + set(srcTemplatePath ${srcDir}/${templateFile}) + if(NOT IS_DIRECTORY ${srcTemplatePath}) + configure_file( + ${srcTemplatePath} + ${destDir}/${templateFile} + @ONLY + NEWLINE_STYLE LF + ) + else() + configure_files("${srcTemplatePath}" "${destDir}/${templateFile}") + endif() + endforeach() endfunction() configure_files(${SOURCE} ${DESTINATION}) \ No newline at end of file diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index 99e3fe4d..f476da38 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -12,18 +12,18 @@ include/LocalPeer.h ) if(UNIX) - list(APPEND SINGLE_SOURCES - src/LockedFile_unix.cpp - ) + list(APPEND SINGLE_SOURCES + src/LockedFile_unix.cpp + ) endif() if(WIN32) - list(APPEND SINGLE_SOURCES - src/LockedFile_win.cpp - ) + list(APPEND SINGLE_SOURCES + src/LockedFile_win.cpp + ) endif() add_library(LocalPeer STATIC ${SINGLE_SOURCES}) target_include_directories(LocalPeer PUBLIC include) -qt5_use_modules(LocalPeer Core Network) +target_link_libraries(LocalPeer Qt5::Core Qt5::Network) diff --git a/libraries/LocalPeer/include/LocalPeer.h b/libraries/LocalPeer/include/LocalPeer.h index 7558f18e..a24e4775 100644 --- a/libraries/LocalPeer/include/LocalPeer.h +++ b/libraries/LocalPeer/include/LocalPeer.h @@ -50,51 +50,51 @@ class LockedFile; class ApplicationId { public: /* methods */ - // traditional app = installed system wide and used in a multi-user environment - static ApplicationId fromTraditionalApp(); - // ID based on a path with all the application data (no two instances with the same data path should run) - static ApplicationId fromPathAndVersion(const QString & dataPath, const QString & version); - // custom ID - static ApplicationId fromCustomId(const QString & id); - // custom ID, based on a raw string previously acquired from 'toString' - static ApplicationId fromRawString(const QString & id); + // traditional app = installed system wide and used in a multi-user environment + static ApplicationId fromTraditionalApp(); + // ID based on a path with all the application data (no two instances with the same data path should run) + static ApplicationId fromPathAndVersion(const QString & dataPath, const QString & version); + // custom ID + static ApplicationId fromCustomId(const QString & id); + // custom ID, based on a raw string previously acquired from 'toString' + static ApplicationId fromRawString(const QString & id); - QString toString() - { - return m_id; - } + QString toString() + { + return m_id; + } private: /* methods */ - ApplicationId(const QString & value) - { - m_id = value; - } + ApplicationId(const QString & value) + { + m_id = value; + } private: /* data */ - QString m_id; + QString m_id; }; class LocalPeer : public QObject { - Q_OBJECT + Q_OBJECT public: - LocalPeer(QObject *parent, const ApplicationId &appId); - ~LocalPeer(); - bool isClient(); - bool sendMessage(const QString &message, int timeout); - ApplicationId applicationId() const; + LocalPeer(QObject *parent, const ApplicationId &appId); + ~LocalPeer(); + bool isClient(); + bool sendMessage(const QString &message, int timeout); + ApplicationId applicationId() const; Q_SIGNALS: - void messageReceived(const QString &message); + void messageReceived(const QString &message); protected Q_SLOTS: - void receiveConnection(); + void receiveConnection(); protected: - ApplicationId id; - QString socketName; - std::unique_ptr server; - std::unique_ptr lockFile; + ApplicationId id; + QString socketName; + std::unique_ptr server; + std::unique_ptr lockFile; }; diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index f71c62d8..129f3abc 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -67,60 +67,60 @@ static const char* ack = "ack"; ApplicationId ApplicationId::fromTraditionalApp() { - QString protoId = QCoreApplication::applicationFilePath(); + QString protoId = QCoreApplication::applicationFilePath(); #if defined(Q_OS_WIN) - protoId = protoId.toLower(); + protoId = protoId.toLower(); #endif - auto prefix = protoId.section(QLatin1Char('/'), -1); - prefix.remove(QRegExp("[^a-zA-Z]")); - prefix.truncate(6); - QByteArray idc = protoId.toUtf8(); - quint16 idNum = qChecksum(idc.constData(), idc.size()); - auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); + auto prefix = protoId.section(QLatin1Char('/'), -1); + prefix.remove(QRegExp("[^a-zA-Z]")); + prefix.truncate(6); + QByteArray idc = protoId.toUtf8(); + quint16 idNum = qChecksum(idc.constData(), idc.size()); + auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); #if defined(Q_OS_WIN) - if (!pProcessIdToSessionId) - { - QLibrary lib("kernel32"); - pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); - } - if (pProcessIdToSessionId) - { - DWORD sessionId = 0; - pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); - socketName += QLatin1Char('-') + QString::number(sessionId, 16); - } + if (!pProcessIdToSessionId) + { + QLibrary lib("kernel32"); + pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); + } + if (pProcessIdToSessionId) + { + DWORD sessionId = 0; + pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); + socketName += QLatin1Char('-') + QString::number(sessionId, 16); + } #else - socketName += QLatin1Char('-') + QString::number(::getuid(), 16); + socketName += QLatin1Char('-') + QString::number(::getuid(), 16); #endif - return ApplicationId(socketName); + return ApplicationId(socketName); } ApplicationId ApplicationId::fromPathAndVersion(const QString& dataPath, const QString& version) { - QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1); - QString result = dataPath + QLatin1Char('-') + version; - shasum.addData(result.toUtf8()); - return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex())); + QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1); + QString result = dataPath + QLatin1Char('-') + version; + shasum.addData(result.toUtf8()); + return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex())); } ApplicationId ApplicationId::fromCustomId(const QString& id) { - return ApplicationId(QLatin1String("qtsingleapp-") + id); + return ApplicationId(QLatin1String("qtsingleapp-") + id); } ApplicationId ApplicationId::fromRawString(const QString& id) { - return ApplicationId(id); + return ApplicationId(id); } LocalPeer::LocalPeer(QObject * parent, const ApplicationId &appId) - : QObject(parent), id(appId) + : QObject(parent), id(appId) { - socketName = id.toString(); - server.reset(new QLocalServer()); - QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); - lockFile.reset(new LockedFile(lockName)); - lockFile->open(QIODevice::ReadWrite); + socketName = id.toString(); + server.reset(new QLocalServer()); + QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); + lockFile.reset(new LockedFile(lockName)); + lockFile->open(QIODevice::ReadWrite); } LocalPeer::~LocalPeer() @@ -129,113 +129,113 @@ LocalPeer::~LocalPeer() ApplicationId LocalPeer::applicationId() const { - return id; + return id; } bool LocalPeer::isClient() { - if (lockFile->isLocked()) - return false; + if (lockFile->isLocked()) + return false; - if (!lockFile->lock(LockedFile::WriteLock, false)) - return true; + if (!lockFile->lock(LockedFile::WriteLock, false)) + return true; - bool res = server->listen(socketName); + bool res = server->listen(socketName); #if defined(Q_OS_UNIX) - // ### Workaround - if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); - res = server->listen(socketName); - } + // ### Workaround + if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { + QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); + res = server->listen(socketName); + } #endif - if (!res) - qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); - QObject::connect(server.get(), SIGNAL(newConnection()), SLOT(receiveConnection())); - return false; + if (!res) + qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); + QObject::connect(server.get(), SIGNAL(newConnection()), SLOT(receiveConnection())); + return false; } bool LocalPeer::sendMessage(const QString &message, int timeout) { - if (!isClient()) - return false; - - QLocalSocket socket; - bool connOk = false; - for(int i = 0; i < 2; i++) { - // Try twice, in case the other instance is just starting up - socket.connectToServer(socketName); - connOk = socket.waitForConnected(timeout/2); - if (connOk || i) - { - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - } - if (!connOk) - { - return false; - } - - QByteArray uMsg(message.toUtf8()); - QDataStream ds(&socket); - - ds.writeBytes(uMsg.constData(), uMsg.size()); - if(!socket.waitForBytesWritten(timeout)) - { - return false; - } - - // wait for 'ack' - if(!socket.waitForReadyRead(timeout)) - { - return false; - } - - // make sure we got 'ack' - if(!(socket.read(qstrlen(ack)) == ack)) - { - return false; - } - return true; + if (!isClient()) + return false; + + QLocalSocket socket; + bool connOk = false; + for(int i = 0; i < 2; i++) { + // Try twice, in case the other instance is just starting up + socket.connectToServer(socketName); + connOk = socket.waitForConnected(timeout/2); + if (connOk || i) + { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + if (!connOk) + { + return false; + } + + QByteArray uMsg(message.toUtf8()); + QDataStream ds(&socket); + + ds.writeBytes(uMsg.constData(), uMsg.size()); + if(!socket.waitForBytesWritten(timeout)) + { + return false; + } + + // wait for 'ack' + if(!socket.waitForReadyRead(timeout)) + { + return false; + } + + // make sure we got 'ack' + if(!(socket.read(qstrlen(ack)) == ack)) + { + return false; + } + return true; } void LocalPeer::receiveConnection() { - QLocalSocket* socket = server->nextPendingConnection(); - if (!socket) - { - return; - } - - while (socket->bytesAvailable() < (int)sizeof(quint32)) - { - socket->waitForReadyRead(); - } - QDataStream ds(socket); - QByteArray uMsg; - quint32 remaining; - ds >> remaining; - uMsg.resize(remaining); - int got = 0; - char* uMsgBuf = uMsg.data(); - do - { - got = ds.readRawData(uMsgBuf, remaining); - remaining -= got; - uMsgBuf += got; - } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); - if (got < 0) - { - qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); - delete socket; - return; - } - QString message(QString::fromUtf8(uMsg)); - socket->write(ack, qstrlen(ack)); - socket->waitForBytesWritten(1000); - socket->waitForDisconnected(1000); // make sure client reads ack - delete socket; - emit messageReceived(message); //### (might take a long time to return) + QLocalSocket* socket = server->nextPendingConnection(); + if (!socket) + { + return; + } + + while (socket->bytesAvailable() < (int)sizeof(quint32)) + { + socket->waitForReadyRead(); + } + QDataStream ds(socket); + QByteArray uMsg; + quint32 remaining; + ds >> remaining; + uMsg.resize(remaining); + int got = 0; + char* uMsgBuf = uMsg.data(); + do + { + got = ds.readRawData(uMsgBuf, remaining); + remaining -= got; + uMsgBuf += got; + } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); + if (got < 0) + { + qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); + delete socket; + return; + } + QString message(QString::fromUtf8(uMsg)); + socket->write(ack, qstrlen(ack)); + socket->waitForBytesWritten(1000); + socket->waitForDisconnected(1000); // make sure client reads ack + delete socket; + emit messageReceived(message); //### (might take a long time to return) } diff --git a/libraries/LocalPeer/src/LockedFile.cpp b/libraries/LocalPeer/src/LockedFile.cpp index a4951bfe..73294a16 100644 --- a/libraries/LocalPeer/src/LockedFile.cpp +++ b/libraries/LocalPeer/src/LockedFile.cpp @@ -41,70 +41,70 @@ #include "LockedFile.h" /*! - \class QtLockedFile - - \brief The QtLockedFile class extends QFile with advisory locking - functions. - - A file may be locked in read or write mode. Multiple instances of - \e QtLockedFile, created in multiple processes running on the same - machine, may have a file locked in read mode. Exactly one instance - may have it locked in write mode. A read and a write lock cannot - exist simultaneously on the same file. - - The file locks are advisory. This means that nothing prevents - another process from manipulating a locked file using QFile or - file system functions offered by the OS. Serialization is only - guaranteed if all processes that access the file use - QLockedFile. Also, while holding a lock on a file, a process - must not open the same file again (through any API), or locks - can be unexpectedly lost. - - The lock provided by an instance of \e QtLockedFile is released - whenever the program terminates. This is true even when the - program crashes and no destructors are called. + \class QtLockedFile + + \brief The QtLockedFile class extends QFile with advisory locking + functions. + + A file may be locked in read or write mode. Multiple instances of + \e QtLockedFile, created in multiple processes running on the same + machine, may have a file locked in read mode. Exactly one instance + may have it locked in write mode. A read and a write lock cannot + exist simultaneously on the same file. + + The file locks are advisory. This means that nothing prevents + another process from manipulating a locked file using QFile or + file system functions offered by the OS. Serialization is only + guaranteed if all processes that access the file use + QLockedFile. Also, while holding a lock on a file, a process + must not open the same file again (through any API), or locks + can be unexpectedly lost. + + The lock provided by an instance of \e QtLockedFile is released + whenever the program terminates. This is true even when the + program crashes and no destructors are called. */ /*! \enum QtLockedFile::LockMode - This enum describes the available lock modes. + This enum describes the available lock modes. - \value ReadLock A read lock. - \value WriteLock A write lock. - \value NoLock Neither a read lock nor a write lock. + \value ReadLock A read lock. + \value WriteLock A write lock. + \value NoLock Neither a read lock nor a write lock. */ /*! - Constructs an unlocked \e QtLockedFile object. This constructor - behaves in the same way as \e QFile::QFile(). + Constructs an unlocked \e QtLockedFile object. This constructor + behaves in the same way as \e QFile::QFile(). - \sa QFile::QFile() + \sa QFile::QFile() */ LockedFile::LockedFile() - : QFile() + : QFile() { #ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; + wmutex = 0; + rmutex = 0; #endif - m_lock_mode = NoLock; + m_lock_mode = NoLock; } /*! - Constructs an unlocked QtLockedFile object with file \a name. This - constructor behaves in the same way as \e QFile::QFile(const - QString&). + Constructs an unlocked QtLockedFile object with file \a name. This + constructor behaves in the same way as \e QFile::QFile(const + QString&). - \sa QFile::QFile() + \sa QFile::QFile() */ LockedFile::LockedFile(const QString &name) - : QFile(name) + : QFile(name) { #ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; + wmutex = 0; + rmutex = 0; #endif - m_lock_mode = NoLock; + m_lock_mode = NoLock; } /*! @@ -122,72 +122,72 @@ Returns true if successful; otherwise false. */ bool LockedFile::open(OpenMode mode) { - if (mode & QIODevice::Truncate) { - qWarning("QtLockedFile::open(): Truncate mode not allowed."); - return false; - } - return QFile::open(mode); + if (mode & QIODevice::Truncate) { + qWarning("QtLockedFile::open(): Truncate mode not allowed."); + return false; + } + return QFile::open(mode); } /*! - Returns \e true if this object has a in read or write lock; - otherwise returns \e false. + Returns \e true if this object has a in read or write lock; + otherwise returns \e false. - \sa lockMode() + \sa lockMode() */ bool LockedFile::isLocked() const { - return m_lock_mode != NoLock; + return m_lock_mode != NoLock; } /*! - Returns the type of lock currently held by this object, or \e - QtLockedFile::NoLock. + Returns the type of lock currently held by this object, or \e + QtLockedFile::NoLock. - \sa isLocked() + \sa isLocked() */ LockedFile::LockMode LockedFile::lockMode() const { - return m_lock_mode; + return m_lock_mode; } /*! - \fn bool QtLockedFile::lock(LockMode mode, bool block = true) + \fn bool QtLockedFile::lock(LockMode mode, bool block = true) - Obtains a lock of type \a mode. The file must be opened before it - can be locked. + Obtains a lock of type \a mode. The file must be opened before it + can be locked. - If \a block is true, this function will block until the lock is - aquired. If \a block is false, this function returns \e false - immediately if the lock cannot be aquired. + If \a block is true, this function will block until the lock is + aquired. If \a block is false, this function returns \e false + immediately if the lock cannot be aquired. - If this object already has a lock of type \a mode, this function - returns \e true immediately. If this object has a lock of a - different type than \a mode, the lock is first released and then a - new lock is obtained. + If this object already has a lock of type \a mode, this function + returns \e true immediately. If this object has a lock of a + different type than \a mode, the lock is first released and then a + new lock is obtained. - This function returns \e true if, after it executes, the file is - locked by this object, and \e false otherwise. + This function returns \e true if, after it executes, the file is + locked by this object, and \e false otherwise. - \sa unlock(), isLocked(), lockMode() + \sa unlock(), isLocked(), lockMode() */ /*! - \fn bool QtLockedFile::unlock() + \fn bool QtLockedFile::unlock() - Releases a lock. + Releases a lock. - If the object has no lock, this function returns immediately. + If the object has no lock, this function returns immediately. - This function returns \e true if, after it executes, the file is - not locked by this object, and \e false otherwise. + This function returns \e true if, after it executes, the file is + not locked by this object, and \e false otherwise. - \sa lock(), isLocked(), lockMode() + \sa lock(), isLocked(), lockMode() */ /*! - \fn QtLockedFile::~QtLockedFile() + \fn QtLockedFile::~QtLockedFile() - Destroys the \e QtLockedFile object. If any locks were held, they - are released. + Destroys the \e QtLockedFile object. If any locks were held, they + are released. */ diff --git a/libraries/LocalPeer/src/LockedFile.h b/libraries/LocalPeer/src/LockedFile.h index 8c178250..2f29ee20 100644 --- a/libraries/LocalPeer/src/LockedFile.h +++ b/libraries/LocalPeer/src/LockedFile.h @@ -48,30 +48,30 @@ class LockedFile : public QFile { public: - enum LockMode { NoLock = 0, ReadLock, WriteLock }; + enum LockMode { NoLock = 0, ReadLock, WriteLock }; - LockedFile(); - LockedFile(const QString &name); - ~LockedFile(); + LockedFile(); + LockedFile(const QString &name); + ~LockedFile(); - bool open(OpenMode mode); + bool open(OpenMode mode); - bool lock(LockMode mode, bool block = true); - bool unlock(); - bool isLocked() const; - LockMode lockMode() const; + bool lock(LockMode mode, bool block = true); + bool unlock(); + bool isLocked() const; + LockMode lockMode() const; - private: + private: #ifdef Q_OS_WIN - Qt::HANDLE wmutex; - Qt::HANDLE rmutex; - QVector rmutexes; - QString mutexname; + Qt::HANDLE wmutex; + Qt::HANDLE rmutex; + QVector rmutexes; + QString mutexname; - Qt::HANDLE getMutexHandle(int idx, bool doCreate); - bool waitMutex(Qt::HANDLE mutex, bool doBlock); + Qt::HANDLE getMutexHandle(int idx, bool doCreate); + bool waitMutex(Qt::HANDLE mutex, bool doBlock); #endif - LockMode m_lock_mode; + LockMode m_lock_mode; }; diff --git a/libraries/LocalPeer/src/LockedFile_unix.cpp b/libraries/LocalPeer/src/LockedFile_unix.cpp index 4b68916c..6becc89e 100644 --- a/libraries/LocalPeer/src/LockedFile_unix.cpp +++ b/libraries/LocalPeer/src/LockedFile_unix.cpp @@ -47,68 +47,68 @@ bool LockedFile::lock(LockMode mode, bool block) { - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } - if (mode == NoLock) - return unlock(); + if (mode == NoLock) + return unlock(); - if (mode == m_lock_mode) - return true; + if (mode == m_lock_mode) + return true; - if (m_lock_mode != NoLock) - unlock(); + if (m_lock_mode != NoLock) + unlock(); - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; - int cmd = block ? F_SETLKW : F_SETLK; - int ret = fcntl(handle(), cmd, &fl); + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; + int cmd = block ? F_SETLKW : F_SETLK; + int ret = fcntl(handle(), cmd, &fl); - if (ret == -1) { - if (errno != EINTR && errno != EAGAIN) - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } + if (ret == -1) { + if (errno != EINTR && errno != EAGAIN) + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } - m_lock_mode = mode; - return true; + m_lock_mode = mode; + return true; } bool LockedFile::unlock() { - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = F_UNLCK; - int ret = fcntl(handle(), F_SETLKW, &fl); - - if (ret == -1) { - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - m_lock_mode = NoLock; - return true; + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + int ret = fcntl(handle(), F_SETLKW, &fl); + + if (ret == -1) { + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + m_lock_mode = NoLock; + return true; } LockedFile::~LockedFile() { - if (isOpen()) - unlock(); + if (isOpen()) + unlock(); } diff --git a/libraries/LocalPeer/src/LockedFile_win.cpp b/libraries/LocalPeer/src/LockedFile_win.cpp index f93ef27f..93d2c73b 100644 --- a/libraries/LocalPeer/src/LockedFile_win.cpp +++ b/libraries/LocalPeer/src/LockedFile_win.cpp @@ -48,158 +48,158 @@ Qt::HANDLE LockedFile::getMutexHandle(int idx, bool doCreate) { - if (mutexname.isEmpty()) { - QFileInfo fi(*this); - mutexname = QString::fromLatin1(MUTEX_PREFIX) - + fi.absoluteFilePath().toLower(); - } - QString mname(mutexname); - if (idx >= 0) - mname += QString::number(idx); - - Qt::HANDLE mutex; - if (doCreate) { - mutex = CreateMutexW(NULL, FALSE, (LPCWSTR)mname.utf16()); - if (!mutex) { - qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); - return 0; - } - } - else { - OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (LPCWSTR)mname.utf16()); - if (!mutex) { - if (GetLastError() != ERROR_FILE_NOT_FOUND) - qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); - return 0; - } - } - return mutex; + if (mutexname.isEmpty()) { + QFileInfo fi(*this); + mutexname = QString::fromLatin1(MUTEX_PREFIX) + + fi.absoluteFilePath().toLower(); + } + QString mname(mutexname); + if (idx >= 0) + mname += QString::number(idx); + + Qt::HANDLE mutex; + if (doCreate) { + mutex = CreateMutexW(NULL, FALSE, (LPCWSTR)mname.utf16()); + if (!mutex) { + qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); + return 0; + } + } + else { + mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (LPCWSTR)mname.utf16()); + if (!mutex) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) + qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); + return 0; + } + } + return mutex; } bool LockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) { - Q_ASSERT(mutex); - DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); - switch (res) { - case WAIT_OBJECT_0: - case WAIT_ABANDONED: - return true; - break; - case WAIT_TIMEOUT: - break; - default: - qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); - } - return false; + Q_ASSERT(mutex); + DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); + switch (res) { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + return true; + break; + case WAIT_TIMEOUT: + break; + default: + qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); + } + return false; } bool LockedFile::lock(LockMode mode, bool block) { - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - if (!wmutex && !(wmutex = getMutexHandle(-1, true))) - return false; - - if (!waitMutex(wmutex, block)) - return false; - - if (mode == ReadLock) { - int idx = 0; - for (; idx < MAX_READERS; idx++) { - rmutex = getMutexHandle(idx, false); - if (!rmutex || waitMutex(rmutex, false)) - break; - CloseHandle(rmutex); - } - bool ok = true; - if (idx >= MAX_READERS) { - qWarning("QtLockedFile::lock(): too many readers"); - rmutex = 0; - ok = false; - } - else if (!rmutex) { - rmutex = getMutexHandle(idx, true); - if (!rmutex || !waitMutex(rmutex, false)) - ok = false; - } - if (!ok && rmutex) { - CloseHandle(rmutex); - rmutex = 0; - } - ReleaseMutex(wmutex); - if (!ok) - return false; - } - else { - Q_ASSERT(rmutexes.isEmpty()); - for (int i = 0; i < MAX_READERS; i++) { - Qt::HANDLE mutex = getMutexHandle(i, false); - if (mutex) - rmutexes.append(mutex); - } - if (rmutexes.size()) { - DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), - TRUE, block ? INFINITE : 0); - if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { - if (res != WAIT_TIMEOUT) - qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); - m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky - unlock(); - return false; - } - } - } - - m_lock_mode = mode; - return true; + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + if (!wmutex && !(wmutex = getMutexHandle(-1, true))) + return false; + + if (!waitMutex(wmutex, block)) + return false; + + if (mode == ReadLock) { + int idx = 0; + for (; idx < MAX_READERS; idx++) { + rmutex = getMutexHandle(idx, false); + if (!rmutex || waitMutex(rmutex, false)) + break; + CloseHandle(rmutex); + } + bool ok = true; + if (idx >= MAX_READERS) { + qWarning("QtLockedFile::lock(): too many readers"); + rmutex = 0; + ok = false; + } + else if (!rmutex) { + rmutex = getMutexHandle(idx, true); + if (!rmutex || !waitMutex(rmutex, false)) + ok = false; + } + if (!ok && rmutex) { + CloseHandle(rmutex); + rmutex = 0; + } + ReleaseMutex(wmutex); + if (!ok) + return false; + } + else { + Q_ASSERT(rmutexes.isEmpty()); + for (int i = 0; i < MAX_READERS; i++) { + Qt::HANDLE mutex = getMutexHandle(i, false); + if (mutex) + rmutexes.append(mutex); + } + if (rmutexes.size()) { + DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), + TRUE, block ? INFINITE : 0); + if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { + if (res != WAIT_TIMEOUT) + qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); + m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky + unlock(); + return false; + } + } + } + + m_lock_mode = mode; + return true; } bool LockedFile::unlock() { - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - if (m_lock_mode == ReadLock) { - ReleaseMutex(rmutex); - CloseHandle(rmutex); - rmutex = 0; - } - else { - foreach(Qt::HANDLE mutex, rmutexes) { - ReleaseMutex(mutex); - CloseHandle(mutex); - } - rmutexes.clear(); - ReleaseMutex(wmutex); - } - - m_lock_mode = LockedFile::NoLock; - return true; + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + if (m_lock_mode == ReadLock) { + ReleaseMutex(rmutex); + CloseHandle(rmutex); + rmutex = 0; + } + else { + foreach(Qt::HANDLE mutex, rmutexes) { + ReleaseMutex(mutex); + CloseHandle(mutex); + } + rmutexes.clear(); + ReleaseMutex(wmutex); + } + + m_lock_mode = LockedFile::NoLock; + return true; } LockedFile::~LockedFile() { - if (isOpen()) - unlock(); - if (wmutex) - CloseHandle(wmutex); + if (isOpen()) + unlock(); + if (wmutex) + CloseHandle(wmutex); } diff --git a/libraries/classparser/CMakeLists.txt b/libraries/classparser/CMakeLists.txt index db266f53..3fe7591d 100644 --- a/libraries/classparser/CMakeLists.txt +++ b/libraries/classparser/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_AUTOMOC ON) include(TestBigEndian) test_big_endian(BIGENDIAN) if(${BIGENDIAN}) - add_definitions(-DMULTIMC_BIG_ENDIAN) + add_definitions(-DMULTIMC_BIG_ENDIAN) endif(${BIGENDIAN}) # Find Qt @@ -38,5 +38,4 @@ add_definitions(-DCLASSPARSER_LIBRARY) add_library(MultiMC_classparser STATIC ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS}) target_include_directories(MultiMC_classparser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(MultiMC_classparser MultiMC_quazip) -qt5_use_modules(MultiMC_classparser Core) +target_link_libraries(MultiMC_classparser MultiMC_quazip Qt5::Core) diff --git a/libraries/classparser/include/classparser.h b/libraries/classparser/include/classparser.h index 23a65589..6c619e47 100644 --- a/libraries/classparser/include/classparser.h +++ b/libraries/classparser/include/classparser.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * diff --git a/libraries/classparser/include/classparser_config.h b/libraries/classparser/include/classparser_config.h index db8f40a3..ee053de6 100644 --- a/libraries/classparser/include/classparser_config.h +++ b/libraries/classparser/include/classparser_config.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/libraries/classparser/src/annotations.cpp b/libraries/classparser/src/annotations.cpp index d1a7c046..18a9e880 100644 --- a/libraries/classparser/src/annotations.cpp +++ b/libraries/classparser/src/annotations.cpp @@ -6,80 +6,80 @@ namespace java { std::string annotation::toString() { - std::ostringstream ss; - ss << "Annotation type : " << type_index << " - " << pool[type_index].str_data << std::endl; - ss << "Contains " << name_val_pairs.size() << " pairs:" << std::endl; - for (unsigned i = 0; i < name_val_pairs.size(); i++) - { - std::pair &val = name_val_pairs[i]; - auto name_idx = val.first; - ss << pool[name_idx].str_data << "(" << name_idx << ")" - << " = " << val.second->toString() << std::endl; - } - return ss.str(); + std::ostringstream ss; + ss << "Annotation type : " << type_index << " - " << pool[type_index].str_data << std::endl; + ss << "Contains " << name_val_pairs.size() << " pairs:" << std::endl; + for (unsigned i = 0; i < name_val_pairs.size(); i++) + { + std::pair &val = name_val_pairs[i]; + auto name_idx = val.first; + ss << pool[name_idx].str_data << "(" << name_idx << ")" + << " = " << val.second->toString() << std::endl; + } + return ss.str(); } annotation *annotation::read(util::membuffer &input, constant_pool &pool) { - uint16_t type_index = 0; - input.read_be(type_index); - annotation *ann = new annotation(type_index, pool); + uint16_t type_index = 0; + input.read_be(type_index); + annotation *ann = new annotation(type_index, pool); - uint16_t num_pairs = 0; - input.read_be(num_pairs); - while (num_pairs) - { - uint16_t name_idx = 0; - // read name index - input.read_be(name_idx); - auto elem = element_value::readElementValue(input, pool); - // read value - ann->add_pair(name_idx, elem); - num_pairs--; - } - return ann; + uint16_t num_pairs = 0; + input.read_be(num_pairs); + while (num_pairs) + { + uint16_t name_idx = 0; + // read name index + input.read_be(name_idx); + auto elem = element_value::readElementValue(input, pool); + // read value + ann->add_pair(name_idx, elem); + num_pairs--; + } + return ann; } element_value *element_value::readElementValue(util::membuffer &input, - java::constant_pool &pool) + java::constant_pool &pool) { - element_value_type type = INVALID; - input.read(type); - uint16_t index = 0; - uint16_t index2 = 0; - std::vector vals; - switch (type) - { - case PRIMITIVE_BYTE: - case PRIMITIVE_CHAR: - case PRIMITIVE_DOUBLE: - case PRIMITIVE_FLOAT: - case PRIMITIVE_INT: - case PRIMITIVE_LONG: - case PRIMITIVE_SHORT: - case PRIMITIVE_BOOLEAN: - case STRING: - input.read_be(index); - return new element_value_simple(type, index, pool); - case ENUM_CONSTANT: - input.read_be(index); - input.read_be(index2); - return new element_value_enum(type, index, index2, pool); - case CLASS: // Class - input.read_be(index); - return new element_value_class(type, index, pool); - case ANNOTATION: // Annotation - // FIXME: runtime visibility info needs to be passed from parent - return new element_value_annotation(ANNOTATION, annotation::read(input, pool), pool); - case ARRAY: // Array - input.read_be(index); - for (int i = 0; i < index; i++) - { - vals.push_back(element_value::readElementValue(input, pool)); - } - return new element_value_array(ARRAY, vals, pool); - default: - throw new java::classfile_exception(); - } + element_value_type type = INVALID; + input.read(type); + uint16_t index = 0; + uint16_t index2 = 0; + std::vector vals; + switch (type) + { + case PRIMITIVE_BYTE: + case PRIMITIVE_CHAR: + case PRIMITIVE_DOUBLE: + case PRIMITIVE_FLOAT: + case PRIMITIVE_INT: + case PRIMITIVE_LONG: + case PRIMITIVE_SHORT: + case PRIMITIVE_BOOLEAN: + case STRING: + input.read_be(index); + return new element_value_simple(type, index, pool); + case ENUM_CONSTANT: + input.read_be(index); + input.read_be(index2); + return new element_value_enum(type, index, index2, pool); + case CLASS: // Class + input.read_be(index); + return new element_value_class(type, index, pool); + case ANNOTATION: // Annotation + // FIXME: runtime visibility info needs to be passed from parent + return new element_value_annotation(ANNOTATION, annotation::read(input, pool), pool); + case ARRAY: // Array + input.read_be(index); + for (int i = 0; i < index; i++) + { + vals.push_back(element_value::readElementValue(input, pool)); + } + return new element_value_array(ARRAY, vals, pool); + default: + throw new java::classfile_exception(); + } } } \ No newline at end of file diff --git a/libraries/classparser/src/annotations.h b/libraries/classparser/src/annotations.h index dd603af3..15bf05a4 100644 --- a/libraries/classparser/src/annotations.h +++ b/libraries/classparser/src/annotations.h @@ -7,21 +7,21 @@ namespace java { enum element_value_type : uint8_t { - INVALID = 0, - STRING = 's', - ENUM_CONSTANT = 'e', - CLASS = 'c', - ANNOTATION = '@', - ARRAY = '[', // one array dimension - PRIMITIVE_INT = 'I', // integer - PRIMITIVE_BYTE = 'B', // signed byte - PRIMITIVE_CHAR = 'C', // Unicode character code point in the Basic Multilingual Plane, - // encoded with UTF-16 - PRIMITIVE_DOUBLE = 'D', // double-precision floating-point value - PRIMITIVE_FLOAT = 'F', // single-precision floating-point value - PRIMITIVE_LONG = 'J', // long integer - PRIMITIVE_SHORT = 'S', // signed short - PRIMITIVE_BOOLEAN = 'Z' // true or false + INVALID = 0, + STRING = 's', + ENUM_CONSTANT = 'e', + CLASS = 'c', + ANNOTATION = '@', + ARRAY = '[', // one array dimension + PRIMITIVE_INT = 'I', // integer + PRIMITIVE_BYTE = 'B', // signed byte + PRIMITIVE_CHAR = 'C', // Unicode character code point in the Basic Multilingual Plane, + // encoded with UTF-16 + PRIMITIVE_DOUBLE = 'D', // double-precision floating-point value + PRIMITIVE_FLOAT = 'F', // single-precision floating-point value + PRIMITIVE_LONG = 'J', // long integer + PRIMITIVE_SHORT = 'S', // signed short + PRIMITIVE_BOOLEAN = 'Z' // true or false }; /** * The element_value structure is a discriminated union representing the value of an @@ -37,21 +37,21 @@ enum element_value_type : uint8_t class element_value { protected: - element_value_type type; - constant_pool &pool; + element_value_type type; + constant_pool &pool; public: - element_value(element_value_type type, constant_pool &pool) : type(type), pool(pool) {}; - virtual ~element_value() {} + element_value(element_value_type type, constant_pool &pool) : type(type), pool(pool) {}; + virtual ~element_value() {} - element_value_type getElementValueType() - { - return type; - } + element_value_type getElementValueType() + { + return type; + } - virtual std::string toString() = 0; + virtual std::string toString() = 0; - static element_value *readElementValue(util::membuffer &input, constant_pool &pool); + static element_value *readElementValue(util::membuffer &input, constant_pool &pool); }; /** @@ -62,58 +62,58 @@ public: class annotation { public: - typedef std::vector> value_list; + typedef std::vector> value_list; protected: - /** - * The value of the type_index item must be a valid index into the constant_pool table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing a field descriptor representing the annotation type corresponding - * to the annotation represented by this annotation structure. - */ - uint16_t type_index; - /** - * map between element_name_index and value. - * - * The value of the element_name_index item must be a valid index into the constant_pool - *table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - *representing - * a valid field descriptor (§4.3.2) that denotes the name of the annotation type element - *represented - * by this element_value_pairs entry. - */ - value_list name_val_pairs; - /** - * Reference to the parent constant pool - */ - constant_pool &pool; + /** + * The value of the type_index item must be a valid index into the constant_pool table. + * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure + * representing a field descriptor representing the annotation type corresponding + * to the annotation represented by this annotation structure. + */ + uint16_t type_index; + /** + * map between element_name_index and value. + * + * The value of the element_name_index item must be a valid index into the constant_pool + *table. + * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure + *representing + * a valid field descriptor (§4.3.2) that denotes the name of the annotation type element + *represented + * by this element_value_pairs entry. + */ + value_list name_val_pairs; + /** + * Reference to the parent constant pool + */ + constant_pool &pool; public: - annotation(uint16_t type_index, constant_pool &pool) - : type_index(type_index), pool(pool) {}; - ~annotation() - { - for (unsigned i = 0; i < name_val_pairs.size(); i++) - { - delete name_val_pairs[i].second; - } - } - void add_pair(uint16_t key, element_value *value) - { - name_val_pairs.push_back(std::make_pair(key, value)); - } - ; - value_list::const_iterator begin() - { - return name_val_pairs.cbegin(); - } - value_list::const_iterator end() - { - return name_val_pairs.cend(); - } - std::string toString(); - static annotation *read(util::membuffer &input, constant_pool &pool); + annotation(uint16_t type_index, constant_pool &pool) + : type_index(type_index), pool(pool) {}; + ~annotation() + { + for (unsigned i = 0; i < name_val_pairs.size(); i++) + { + delete name_val_pairs[i].second; + } + } + void add_pair(uint16_t key, element_value *value) + { + name_val_pairs.push_back(std::make_pair(key, value)); + } + ; + value_list::const_iterator begin() + { + return name_val_pairs.cbegin(); + } + value_list::const_iterator end() + { + return name_val_pairs.cend(); + } + std::string toString(); + static annotation *read(util::membuffer &input, constant_pool &pool); }; typedef std::vector annotation_table; @@ -121,158 +121,158 @@ typedef std::vector annotation_table; class element_value_simple : public element_value { protected: - /// index of the constant in the constant pool - uint16_t index; + /// index of the constant in the constant pool + uint16_t index; public: - element_value_simple(element_value_type type, uint16_t index, constant_pool &pool) - : element_value(type, pool), index(index) { - // TODO: verify consistency - }; - uint16_t getIndex() - { - return index; - } - virtual std::string toString() - { - return pool[index].toString(); - } - ; + element_value_simple(element_value_type type, uint16_t index, constant_pool &pool) + : element_value(type, pool), index(index) { + // TODO: verify consistency + }; + uint16_t getIndex() + { + return index; + } + virtual std::string toString() + { + return pool[index].toString(); + } + ; }; /// The enum_const_value item is used if the tag item is 'e'. class element_value_enum : public element_value { protected: - /** - * The value of the type_name_index item must be a valid index into the constant_pool table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing a valid field descriptor (§4.3.2) that denotes the internal form of the - * binary - * name (§4.2.1) of the type of the enum constant represented by this element_value - * structure. - */ - uint16_t typeIndex; - /** - * The value of the const_name_index item must be a valid index into the constant_pool - * table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing the simple name of the enum constant represented by this element_value - * structure. - */ - uint16_t valueIndex; + /** + * The value of the type_name_index item must be a valid index into the constant_pool table. + * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure + * representing a valid field descriptor (§4.3.2) that denotes the internal form of the + * binary + * name (§4.2.1) of the type of the enum constant represented by this element_value + * structure. + */ + uint16_t typeIndex; + /** + * The value of the const_name_index item must be a valid index into the constant_pool + * table. + * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure + * representing the simple name of the enum constant represented by this element_value + * structure. + */ + uint16_t valueIndex; public: - element_value_enum(element_value_type type, uint16_t typeIndex, uint16_t valueIndex, - constant_pool &pool) - : element_value(type, pool), typeIndex(typeIndex), valueIndex(valueIndex) - { - // TODO: verify consistency - } - uint16_t getValueIndex() - { - return valueIndex; - } - uint16_t getTypeIndex() - { - return typeIndex; - } - virtual std::string toString() - { - return "enum value"; - } - ; + element_value_enum(element_value_type type, uint16_t typeIndex, uint16_t valueIndex, + constant_pool &pool) + : element_value(type, pool), typeIndex(typeIndex), valueIndex(valueIndex) + { + // TODO: verify consistency + } + uint16_t getValueIndex() + { + return valueIndex; + } + uint16_t getTypeIndex() + { + return typeIndex; + } + virtual std::string toString() + { + return "enum value"; + } + ; }; class element_value_class : public element_value { protected: - /** - * The class_info_index item must be a valid index into the constant_pool table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing the return descriptor (§4.3.3) of the type that is reified by the class - * represented by this element_value structure. - * - * For example, 'V' for Void.class, 'Ljava/lang/Object;' for Object, etc. - * - * Or in plain english, you can store type information in annotations. Yay. - */ - uint16_t classIndex; + /** + * The class_info_index item must be a valid index into the constant_pool table. + * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure + * representing the return descriptor (§4.3.3) of the type that is reified by the class + * represented by this element_value structure. + * + * For example, 'V' for Void.class, 'Ljava/lang/Object;' for Object, etc. + * + * Or in plain english, you can store type information in annotations. Yay. + */ + uint16_t classIndex; public: - element_value_class(element_value_type type, uint16_t classIndex, constant_pool &pool) - : element_value(type, pool), classIndex(classIndex) - { - // TODO: verify consistency - } - uint16_t getIndex() - { - return classIndex; - } - virtual std::string toString() - { - return "class"; - } - ; + element_value_class(element_value_type type, uint16_t classIndex, constant_pool &pool) + : element_value(type, pool), classIndex(classIndex) + { + // TODO: verify consistency + } + uint16_t getIndex() + { + return classIndex; + } + virtual std::string toString() + { + return "class"; + } + ; }; /// nested annotations... yay class element_value_annotation : public element_value { private: - annotation *nestedAnnotation; + annotation *nestedAnnotation; public: - element_value_annotation(element_value_type type, annotation *nestedAnnotation, - constant_pool &pool) - : element_value(type, pool), nestedAnnotation(nestedAnnotation) {}; - ~element_value_annotation() - { - if (nestedAnnotation) - { - delete nestedAnnotation; - nestedAnnotation = nullptr; - } - } - virtual std::string toString() - { - return "nested annotation"; - } - ; + element_value_annotation(element_value_type type, annotation *nestedAnnotation, + constant_pool &pool) + : element_value(type, pool), nestedAnnotation(nestedAnnotation) {}; + ~element_value_annotation() + { + if (nestedAnnotation) + { + delete nestedAnnotation; + nestedAnnotation = nullptr; + } + } + virtual std::string toString() + { + return "nested annotation"; + } + ; }; /// and arrays! class element_value_array : public element_value { public: - typedef std::vector elem_vec; + typedef std::vector elem_vec; protected: - elem_vec values; + elem_vec values; public: - element_value_array(element_value_type type, std::vector &values, - constant_pool &pool) - : element_value(type, pool), values(values) {}; - ~element_value_array() - { - for (unsigned i = 0; i < values.size(); i++) - { - delete values[i]; - } - } - ; - elem_vec::const_iterator begin() - { - return values.cbegin(); - } - elem_vec::const_iterator end() - { - return values.cend(); - } - virtual std::string toString() - { - return "array"; - } - ; + element_value_array(element_value_type type, std::vector &values, + constant_pool &pool) + : element_value(type, pool), values(values) {}; + ~element_value_array() + { + for (unsigned i = 0; i < values.size(); i++) + { + delete values[i]; + } + } + ; + elem_vec::const_iterator begin() + { + return values.cbegin(); + } + elem_vec::const_iterator end() + { + return values.cend(); + } + virtual std::string toString() + { + return "array"; + } + ; }; } \ No newline at end of file diff --git a/libraries/classparser/src/classfile.h b/libraries/classparser/src/classfile.h index a5e7ee50..1616a828 100644 --- a/libraries/classparser/src/classfile.h +++ b/libraries/classparser/src/classfile.h @@ -11,146 +11,146 @@ namespace java class classfile : public util::membuffer { public: - classfile(char *data, std::size_t size) : membuffer(data, size) - { - valid = false; - is_synthetic = false; - read_be(magic); - if (magic != 0xCAFEBABE) - throw new classfile_exception(); - read_be(minor_version); - read_be(major_version); - constants.load(*this); - read_be(access_flags); - read_be(this_class); - read_be(super_class); + classfile(char *data, std::size_t size) : membuffer(data, size) + { + valid = false; + is_synthetic = false; + read_be(magic); + if (magic != 0xCAFEBABE) + throw new classfile_exception(); + read_be(minor_version); + read_be(major_version); + constants.load(*this); + read_be(access_flags); + read_be(this_class); + read_be(super_class); - // Interfaces - uint16_t iface_count = 0; - read_be(iface_count); - while (iface_count) - { - uint16_t iface; - read_be(iface); - interfaces.push_back(iface); - iface_count--; - } + // Interfaces + uint16_t iface_count = 0; + read_be(iface_count); + while (iface_count) + { + uint16_t iface; + read_be(iface); + interfaces.push_back(iface); + iface_count--; + } - // Fields - // read fields (and attributes from inside fields) (and possible inner classes. yay for - // recursion!) - // for now though, we will ignore all attributes - /* - * field_info - * { - * u2 access_flags; - * u2 name_index; - * u2 descriptor_index; - * u2 attributes_count; - * attribute_info attributes[attributes_count]; - * } - */ - uint16_t field_count = 0; - read_be(field_count); - while (field_count) - { - // skip field stuff - skip(6); - // and skip field attributes - uint16_t attr_count = 0; - read_be(attr_count); - while (attr_count) - { - skip(2); - uint32_t attr_length = 0; - read_be(attr_length); - skip(attr_length); - attr_count--; - } - field_count--; - } + // Fields + // read fields (and attributes from inside fields) (and possible inner classes. yay for + // recursion!) + // for now though, we will ignore all attributes + /* + * field_info + * { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + */ + uint16_t field_count = 0; + read_be(field_count); + while (field_count) + { + // skip field stuff + skip(6); + // and skip field attributes + uint16_t attr_count = 0; + read_be(attr_count); + while (attr_count) + { + skip(2); + uint32_t attr_length = 0; + read_be(attr_length); + skip(attr_length); + attr_count--; + } + field_count--; + } - // class methods - /* - * method_info - * { - * u2 access_flags; - * u2 name_index; - * u2 descriptor_index; - * u2 attributes_count; - * attribute_info attributes[attributes_count]; - * } - */ - uint16_t method_count = 0; - read_be(method_count); - while (method_count) - { - skip(6); - // and skip method attributes - uint16_t attr_count = 0; - read_be(attr_count); - while (attr_count) - { - skip(2); - uint32_t attr_length = 0; - read_be(attr_length); - skip(attr_length); - attr_count--; - } - method_count--; - } + // class methods + /* + * method_info + * { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + */ + uint16_t method_count = 0; + read_be(method_count); + while (method_count) + { + skip(6); + // and skip method attributes + uint16_t attr_count = 0; + read_be(attr_count); + while (attr_count) + { + skip(2); + uint32_t attr_length = 0; + read_be(attr_length); + skip(attr_length); + attr_count--; + } + method_count--; + } - // class attributes - // there are many kinds of attributes. this is just the generic wrapper structure. - // type is decided by attribute name. extensions to the standard are *possible* - // class annotations are one kind of a attribute (one per class) - /* - * attribute_info - * { - * u2 attribute_name_index; - * u4 attribute_length; - * u1 info[attribute_length]; - * } - */ - uint16_t class_attr_count = 0; - read_be(class_attr_count); - while (class_attr_count) - { - uint16_t name_idx = 0; - read_be(name_idx); - uint32_t attr_length = 0; - read_be(attr_length); + // class attributes + // there are many kinds of attributes. this is just the generic wrapper structure. + // type is decided by attribute name. extensions to the standard are *possible* + // class annotations are one kind of a attribute (one per class) + /* + * attribute_info + * { + * u2 attribute_name_index; + * u4 attribute_length; + * u1 info[attribute_length]; + * } + */ + uint16_t class_attr_count = 0; + read_be(class_attr_count); + while (class_attr_count) + { + uint16_t name_idx = 0; + read_be(name_idx); + uint32_t attr_length = 0; + read_be(attr_length); - auto name = constants[name_idx]; - if (name.str_data == "RuntimeVisibleAnnotations") - { - uint16_t num_annotations = 0; - read_be(num_annotations); - while (num_annotations) - { - visible_class_annotations.push_back(annotation::read(*this, constants)); - num_annotations--; - } - } - else - skip(attr_length); - class_attr_count--; - } - valid = true; - } - ; - bool valid; - bool is_synthetic; - uint32_t magic; - uint16_t minor_version; - uint16_t major_version; - constant_pool constants; - uint16_t access_flags; - uint16_t this_class; - uint16_t super_class; - // interfaces this class implements ? must be. investigate. - std::vector interfaces; - // FIXME: doesn't free up memory on delete - java::annotation_table visible_class_annotations; + auto name = constants[name_idx]; + if (name.str_data == "RuntimeVisibleAnnotations") + { + uint16_t num_annotations = 0; + read_be(num_annotations); + while (num_annotations) + { + visible_class_annotations.push_back(annotation::read(*this, constants)); + num_annotations--; + } + } + else + skip(attr_length); + class_attr_count--; + } + valid = true; + } + ; + bool valid; + bool is_synthetic; + uint32_t magic; + uint16_t minor_version; + uint16_t major_version; + constant_pool constants; + uint16_t access_flags; + uint16_t this_class; + uint16_t super_class; + // interfaces this class implements ? must be. investigate. + std::vector interfaces; + // FIXME: doesn't free up memory on delete + java::annotation_table visible_class_annotations; }; } \ No newline at end of file diff --git a/libraries/classparser/src/classparser.cpp b/libraries/classparser/src/classparser.cpp index 8837781f..e0b6728d 100644 --- a/libraries/classparser/src/classparser.cpp +++ b/libraries/classparser/src/classparser.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -26,60 +26,58 @@ namespace classparser QString GetMinecraftJarVersion(QString jarName) { - QString version; + QString version; - // check if minecraft.jar exists - QFile jar(jarName); - if (!jar.exists()) - return version; + // check if minecraft.jar exists + QFile jar(jarName); + if (!jar.exists()) + return version; - // open minecraft.jar - QuaZip zip(&jar); - if (!zip.open(QuaZip::mdUnzip)) - return version; + // open minecraft.jar + QuaZip zip(&jar); + if (!zip.open(QuaZip::mdUnzip)) + return version; - // open Minecraft.class - zip.setCurrentFile("net/minecraft/client/Minecraft.class", QuaZip::csSensitive); - QuaZipFile Minecraft(&zip); - if (!Minecraft.open(QuaZipFile::ReadOnly)) - return version; + // open Minecraft.class + zip.setCurrentFile("net/minecraft/client/Minecraft.class", QuaZip::csSensitive); + QuaZipFile Minecraft(&zip); + if (!Minecraft.open(QuaZipFile::ReadOnly)) + return version; - // read Minecraft.class - qint64 size = Minecraft.size(); - char *classfile = new char[size]; - Minecraft.read(classfile, size); + // read Minecraft.class + qint64 size = Minecraft.size(); + char *classfile = new char[size]; + Minecraft.read(classfile, size); - // parse Minecraft.class - try - { - char *temp = classfile; - java::classfile MinecraftClass(temp, size); - java::constant_pool constants = MinecraftClass.constants; - for (java::constant_pool::container_type::const_iterator iter = constants.begin(); - iter != constants.end(); iter++) - { - const java::constant &constant = *iter; - if (constant.type != java::constant::j_string_data) - continue; - const std::string &str = constant.str_data; - qDebug() << QString::fromStdString(str); - if (str.compare(0, 20, "Minecraft Minecraft ") == 0) - { - version = str.substr(20).data(); - break; - } - } - } - catch (java::classfile_exception &) - { - } + // parse Minecraft.class + try + { + char *temp = classfile; + java::classfile MinecraftClass(temp, size); + java::constant_pool constants = MinecraftClass.constants; + for (java::constant_pool::container_type::const_iterator iter = constants.begin(); + iter != constants.end(); iter++) + { + const java::constant &constant = *iter; + if (constant.type != java::constant_type_t::j_string_data) + continue; + const std::string &str = constant.str_data; + qDebug() << QString::fromStdString(str); + if (str.compare(0, 20, "Minecraft Minecraft ") == 0) + { + version = str.substr(20).data(); + break; + } + } + } + catch (const java::classfile_exception &) { } - // clean up - delete[] classfile; - Minecraft.close(); - zip.close(); - jar.close(); + // clean up + delete[] classfile; + Minecraft.close(); + zip.close(); + jar.close(); - return version; + return version; } } diff --git a/libraries/classparser/src/constants.h b/libraries/classparser/src/constants.h index 9c74ab20..3b6c3b7a 100644 --- a/libraries/classparser/src/constants.h +++ b/libraries/classparser/src/constants.h @@ -4,152 +4,161 @@ namespace java { +enum class constant_type_t : uint8_t +{ + j_hole = 0, // HACK: this is a hole in the array, because java is crazy + j_string_data = 1, + j_int = 3, + j_float = 4, + j_long = 5, + j_double = 6, + j_class = 7, + j_string = 8, + j_fieldref = 9, + j_methodref = 10, + j_interface_methodref = 11, + j_nameandtype = 12 + // FIXME: missing some constant types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 +}; + +struct ref_type_t +{ + /** + * Class reference: + * an index within the constant pool to a UTF-8 string containing + * the fully qualified class name (in internal format) + * Used for j_class, j_fieldref, j_methodref and j_interface_methodref + */ + uint16_t class_idx; + // used for j_fieldref, j_methodref and j_interface_methodref + uint16_t name_and_type_idx; +}; + +struct name_and_type_t +{ + uint16_t name_index; + uint16_t descriptor_index; +}; + class constant { public: - enum type_t : uint8_t - { - j_hole = 0, // HACK: this is a hole in the array, because java is crazy - j_string_data = 1, - j_int = 3, - j_float = 4, - j_long = 5, - j_double = 6, - j_class = 7, - j_string = 8, - j_fieldref = 9, - j_methodref = 10, - j_interface_methodref = 11, - j_nameandtype = 12 - // FIXME: missing some constant types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 - } type; + constant_type_t type = constant_type_t::j_hole; - constant(util::membuffer &buf) - { - buf.read(type); + constant(util::membuffer &buf) + { + buf.read(type); - // load data depending on type - switch (type) - { - case j_float: - case j_int: - buf.read_be(int_data); // same as float data really - break; - case j_double: - case j_long: - buf.read_be(long_data); // same as double - break; - case j_class: - buf.read_be(ref_type.class_idx); - break; - case j_fieldref: - case j_methodref: - case j_interface_methodref: - buf.read_be(ref_type.class_idx); - buf.read_be(ref_type.name_and_type_idx); - break; - case j_string: - buf.read_be(index); - break; - case j_string_data: - // HACK HACK: for now, we call these UTF-8 and do no further processing. - // Later, we should do some decoding. It's really modified UTF-8 - // * U+0000 is represented as 0xC0,0x80 invalid character - // * any single zero byte ends the string - // * characters above U+10000 are encoded like in CESU-8 - buf.read_jstr(str_data); - break; - case j_nameandtype: - buf.read_be(name_and_type.name_index); - buf.read_be(name_and_type.descriptor_index); - break; - default: - // invalid constant type! - throw new classfile_exception(); - } - } + // load data depending on type + switch (type) + { + case constant_type_t::j_float: + buf.read_be(data.int_data); + break; + case constant_type_t::j_int: + buf.read_be(data.int_data); // same as float data really + break; + case constant_type_t::j_double: + buf.read_be(data.long_data); + break; + case constant_type_t::j_long: + buf.read_be(data.long_data); // same as double + break; + case constant_type_t::j_class: + buf.read_be(data.ref_type.class_idx); + break; + case constant_type_t::j_fieldref: + case constant_type_t::j_methodref: + case constant_type_t::j_interface_methodref: + buf.read_be(data.ref_type.class_idx); + buf.read_be(data.ref_type.name_and_type_idx); + break; + case constant_type_t::j_string: + buf.read_be(data.index); + break; + case constant_type_t::j_string_data: + // HACK HACK: for now, we call these UTF-8 and do no further processing. + // Later, we should do some decoding. It's really modified UTF-8 + // * U+0000 is represented as 0xC0,0x80 invalid character + // * any single zero byte ends the string + // * characters above U+10000 are encoded like in CESU-8 + buf.read_jstr(str_data); + break; + case constant_type_t::j_nameandtype: + buf.read_be(data.name_and_type.name_index); + buf.read_be(data.name_and_type.descriptor_index); + break; + default: + // invalid constant type! + throw new classfile_exception(); + } + } + constant(int) + { + } - constant(int) - { - type = j_hole; - } + std::string toString() + { + std::ostringstream ss; + switch (type) + { + case constant_type_t::j_hole: + ss << "Fake legacy entry"; + break; + case constant_type_t::j_float: + ss << "Float: " << data.float_data; + break; + case constant_type_t::j_double: + ss << "Double: " << data.double_data; + break; + case constant_type_t::j_int: + ss << "Int: " << data.int_data; + break; + case constant_type_t::j_long: + ss << "Long: " << data.long_data; + break; + case constant_type_t::j_string_data: + ss << "StrData: " << str_data; + break; + case constant_type_t::j_string: + ss << "Str: " << data.index; + break; + case constant_type_t::j_fieldref: + ss << "FieldRef: " << data.ref_type.class_idx << " " << data.ref_type.name_and_type_idx; + break; + case constant_type_t::j_methodref: + ss << "MethodRef: " << data.ref_type.class_idx << " " << data.ref_type.name_and_type_idx; + break; + case constant_type_t::j_interface_methodref: + ss << "IfMethodRef: " << data.ref_type.class_idx << " " << data.ref_type.name_and_type_idx; + break; + case constant_type_t::j_class: + ss << "Class: " << data.ref_type.class_idx; + break; + case constant_type_t::j_nameandtype: + ss << "NameAndType: " << data.name_and_type.name_index << " " + << data.name_and_type.descriptor_index; + break; + default: + ss << "Invalid entry (" << int(type) << ")"; + break; + } + return ss.str(); + } - std::string toString() - { - std::ostringstream ss; - switch (type) - { - case j_hole: - ss << "Fake legacy entry"; - break; - case j_float: - ss << "Float: " << float_data; - break; - case j_double: - ss << "Double: " << double_data; - break; - case j_int: - ss << "Int: " << int_data; - break; - case j_long: - ss << "Long: " << long_data; - break; - case j_string_data: - ss << "StrData: " << str_data; - break; - case j_string: - ss << "Str: " << index; - break; - case j_fieldref: - ss << "FieldRef: " << ref_type.class_idx << " " << ref_type.name_and_type_idx; - break; - case j_methodref: - ss << "MethodRef: " << ref_type.class_idx << " " << ref_type.name_and_type_idx; - break; - case j_interface_methodref: - ss << "IfMethodRef: " << ref_type.class_idx << " " << ref_type.name_and_type_idx; - break; - case j_class: - ss << "Class: " << ref_type.class_idx; - break; - case j_nameandtype: - ss << "NameAndType: " << name_and_type.name_index << " " - << name_and_type.descriptor_index; - break; - default: - ss << "Invalid entry (" << int(type) << ")"; - break; - } - return ss.str(); - } + std::string str_data; /** String data in 'modified utf-8'.*/ - std::string str_data; /** String data in 'modified utf-8'.*/ - // store everything here. - union - { - int32_t int_data; - int64_t long_data; - float float_data; - double double_data; - uint16_t index; - struct - { - /** - * Class reference: - * an index within the constant pool to a UTF-8 string containing - * the fully qualified class name (in internal format) - * Used for j_class, j_fieldref, j_methodref and j_interface_methodref - */ - uint16_t class_idx; - // used for j_fieldref, j_methodref and j_interface_methodref - uint16_t name_and_type_idx; - } ref_type; - struct - { - uint16_t name_index; - uint16_t descriptor_index; - } name_and_type; - }; + // store everything here. + union + { + int32_t int_data; + int64_t long_data; + float float_data; + double double_data; + uint16_t index; + ref_type_t ref_type; + name_and_type_t name_and_type; + } data = {0}; }; /** @@ -159,64 +168,64 @@ public: class constant_pool { public: - /** - * Create a pool of constants - */ - constant_pool() - { - } - /** - * Load a java constant pool - */ - void load(util::membuffer &buf) - { - // FIXME: @SANITY this should check for the end of buffer. - uint16_t length = 0; - buf.read_be(length); - length--; - const constant *last_constant = nullptr; - while (length) - { - const constant &cnst = constant(buf); - constants.push_back(cnst); - last_constant = &constants[constants.size() - 1]; - if (last_constant->type == constant::j_double || - last_constant->type == constant::j_long) - { - // push in a fake constant to preserve indexing - constants.push_back(constant(0)); - length -= 2; - } - else - { - length--; - } - } - } - typedef std::vector container_type; - /** - * Access constants based on jar file index numbers (index of the first element is 1) - */ - java::constant &operator[](std::size_t constant_index) - { - if (constant_index == 0 || constant_index > constants.size()) - { - throw new classfile_exception(); - } - return constants[constant_index - 1]; - } - ; - container_type::const_iterator begin() const - { - return constants.begin(); - } - ; - container_type::const_iterator end() const - { - return constants.end(); - } + /** + * Create a pool of constants + */ + constant_pool() + { + } + /** + * Load a java constant pool + */ + void load(util::membuffer &buf) + { + // FIXME: @SANITY this should check for the end of buffer. + uint16_t length = 0; + buf.read_be(length); + length--; + const constant *last_constant = nullptr; + while (length) + { + const constant &cnst = constant(buf); + constants.push_back(cnst); + last_constant = &constants[constants.size() - 1]; + if (last_constant->type == constant_type_t::j_double || + last_constant->type == constant_type_t::j_long) + { + // push in a fake constant to preserve indexing + constants.push_back(constant(0)); + length -= 2; + } + else + { + length--; + } + } + } + typedef std::vector container_type; + /** + * Access constants based on jar file index numbers (index of the first element is 1) + */ + java::constant &operator[](std::size_t constant_index) + { + if (constant_index == 0 || constant_index > constants.size()) + { + throw new classfile_exception(); + } + return constants[constant_index - 1]; + } + ; + container_type::const_iterator begin() const + { + return constants.begin(); + } + ; + container_type::const_iterator end() const + { + return constants.end(); + } private: - container_type constants; + container_type constants; }; } diff --git a/libraries/classparser/src/javaendian.h b/libraries/classparser/src/javaendian.h index d488b382..076bff5e 100644 --- a/libraries/classparser/src/javaendian.h +++ b/libraries/classparser/src/javaendian.h @@ -9,68 +9,68 @@ namespace util #ifdef MULTIMC_BIG_ENDIAN inline uint64_t bigswap(uint64_t x) { - return x; + return x; } ; inline uint32_t bigswap(uint32_t x) { - return x; + return x; } ; inline uint16_t bigswap(uint16_t x) { - return x; + return x; } ; inline int64_t bigswap(int64_t x) { - return x; + return x; } ; inline int32_t bigswap(int32_t x) { - return x; + return x; } ; inline int16_t bigswap(int16_t x) { - return x; + return x; } ; #else inline uint64_t bigswap(uint64_t x) { - return (x >> 56) | ((x << 40) & 0x00FF000000000000) | ((x << 24) & 0x0000FF0000000000) | - ((x << 8) & 0x000000FF00000000) | ((x >> 8) & 0x00000000FF000000) | - ((x >> 24) & 0x0000000000FF0000) | ((x >> 40) & 0x000000000000FF00) | (x << 56); + return (x >> 56) | ((x << 40) & 0x00FF000000000000) | ((x << 24) & 0x0000FF0000000000) | + ((x << 8) & 0x000000FF00000000) | ((x >> 8) & 0x00000000FF000000) | + ((x >> 24) & 0x0000000000FF0000) | ((x >> 40) & 0x000000000000FF00) | (x << 56); } -; + inline uint32_t bigswap(uint32_t x) { - return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24); + return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24); } -; + inline uint16_t bigswap(uint16_t x) { - return (x >> 8) | (x << 8); + return (x >> 8) | (x << 8); } -; + inline int64_t bigswap(int64_t x) { - return (x >> 56) | ((x << 40) & 0x00FF000000000000) | ((x << 24) & 0x0000FF0000000000) | - ((x << 8) & 0x000000FF00000000) | ((x >> 8) & 0x00000000FF000000) | - ((x >> 24) & 0x0000000000FF0000) | ((x >> 40) & 0x000000000000FF00) | (x << 56); + return (x >> 56) | ((x << 40) & 0x00FF000000000000) | ((x << 24) & 0x0000FF0000000000) | + ((x << 8) & 0x000000FF00000000) | ((x >> 8) & 0x00000000FF000000) | + ((x >> 24) & 0x0000000000FF0000) | ((x >> 40) & 0x000000000000FF00) | (x << 56); } -; + inline int32_t bigswap(int32_t x) { - return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24); + return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24); } -; + inline int16_t bigswap(int16_t x) { - return (x >> 8) | (x << 8); + return (x >> 8) | (x << 8); } -; + #endif } diff --git a/libraries/classparser/src/membuffer.h b/libraries/classparser/src/membuffer.h index ab83412a..f81c9705 100644 --- a/libraries/classparser/src/membuffer.h +++ b/libraries/classparser/src/membuffer.h @@ -10,54 +10,54 @@ namespace util class membuffer { public: - membuffer(char *buffer, std::size_t size) - { - current = start = buffer; - end = start + size; - } - ~membuffer() - { - // maybe? possibly? left out to avoid confusion. for now. - // delete start; - } - /** - * Read some value. That's all ;) - */ - template void read(T &val) - { - val = *(T *)current; - current += sizeof(T); - } - /** - * Read a big-endian number - * valid for 2-byte, 4-byte and 8-byte variables - */ - template void read_be(T &val) - { - val = util::bigswap(*(T *)current); - current += sizeof(T); - } - /** - * Read a string in the format: - * 2B length (big endian, unsigned) - * length bytes data - */ - void read_jstr(std::string &str) - { - uint16_t length = 0; - read_be(length); - str.append(current, length); - current += length; - } - /** - * Skip N bytes - */ - void skip(std::size_t N) - { - current += N; - } + membuffer(char *buffer, std::size_t size) + { + current = start = buffer; + end = start + size; + } + ~membuffer() + { + // maybe? possibly? left out to avoid confusion. for now. + // delete start; + } + /** + * Read some value. That's all ;) + */ + template void read(T &val) + { + val = *(T *)current; + current += sizeof(T); + } + /** + * Read a big-endian number + * valid for 2-byte, 4-byte and 8-byte variables + */ + template void read_be(T &val) + { + val = util::bigswap(*(T *)current); + current += sizeof(T); + } + /** + * Read a string in the format: + * 2B length (big endian, unsigned) + * length bytes data + */ + void read_jstr(std::string &str) + { + uint16_t length = 0; + read_be(length); + str.append(current, length); + current += length; + } + /** + * Skip N bytes + */ + void skip(std::size_t N) + { + current += N; + } private: - char *start, *end, *current; + char *start, *end, *current; }; } diff --git a/libraries/hoedown/include/hoedown/autolink.h b/libraries/hoedown/include/hoedown/autolink.h index 528885c9..953e7807 100644 --- a/libraries/hoedown/include/hoedown/autolink.h +++ b/libraries/hoedown/include/hoedown/autolink.h @@ -15,7 +15,7 @@ extern "C" { *************/ typedef enum hoedown_autolink_flags { - HOEDOWN_AUTOLINK_SHORT_DOMAINS = (1 << 0) + HOEDOWN_AUTOLINK_SHORT_DOMAINS = (1 << 0) } hoedown_autolink_flags; @@ -28,15 +28,15 @@ int hoedown_autolink_is_safe(const uint8_t *data, size_t size); /* hoedown_autolink__www: search for the next www link in data */ size_t hoedown_autolink__www(size_t *rewind_p, hoedown_buffer *link, - uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags); + uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags); /* hoedown_autolink__email: search for the next email in data */ size_t hoedown_autolink__email(size_t *rewind_p, hoedown_buffer *link, - uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags); + uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags); /* hoedown_autolink__url: search for the next URL in data */ size_t hoedown_autolink__url(size_t *rewind_p, hoedown_buffer *link, - uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags); + uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags); #ifdef __cplusplus diff --git a/libraries/hoedown/include/hoedown/buffer.h b/libraries/hoedown/include/hoedown/buffer.h index d7703f8d..062d86ce 100644 --- a/libraries/hoedown/include/hoedown/buffer.h +++ b/libraries/hoedown/include/hoedown/buffer.h @@ -28,14 +28,14 @@ typedef void *(*hoedown_realloc_callback)(void *, size_t); typedef void (*hoedown_free_callback)(void *); struct hoedown_buffer { - uint8_t *data; /* actual character data */ - size_t size; /* size of the string */ - size_t asize; /* allocated size (0 = volatile buffer) */ - size_t unit; /* reallocation unit size (0 = read-only buffer) */ - - hoedown_realloc_callback data_realloc; - hoedown_free_callback data_free; - hoedown_free_callback buffer_free; + uint8_t *data; /* actual character data */ + size_t size; /* size of the string */ + size_t asize; /* allocated size (0 = volatile buffer) */ + size_t unit; /* reallocation unit size (0 = read-only buffer) */ + + hoedown_realloc_callback data_realloc; + hoedown_free_callback data_free; + hoedown_free_callback buffer_free; }; typedef struct hoedown_buffer hoedown_buffer; @@ -52,11 +52,11 @@ void *hoedown_realloc(void *ptr, size_t size) __attribute__ ((malloc)); /* hoedown_buffer_init: initialize a buffer with custom allocators */ void hoedown_buffer_init( - hoedown_buffer *buffer, - size_t unit, - hoedown_realloc_callback data_realloc, - hoedown_free_callback data_free, - hoedown_free_callback buffer_free + hoedown_buffer *buffer, + size_t unit, + hoedown_realloc_callback data_realloc, + hoedown_free_callback data_free, + hoedown_free_callback buffer_free ); /* hoedown_buffer_uninit: uninitialize an existing buffer */ @@ -116,15 +116,15 @@ void hoedown_buffer_free(hoedown_buffer *buf); /* HOEDOWN_BUFPUTSL: optimized hoedown_buffer_puts of a string literal */ #define HOEDOWN_BUFPUTSL(output, literal) \ - hoedown_buffer_put(output, (const uint8_t *)literal, sizeof(literal) - 1) + hoedown_buffer_put(output, (const uint8_t *)literal, sizeof(literal) - 1) /* HOEDOWN_BUFSETSL: optimized hoedown_buffer_sets of a string literal */ #define HOEDOWN_BUFSETSL(output, literal) \ - hoedown_buffer_set(output, (const uint8_t *)literal, sizeof(literal) - 1) + hoedown_buffer_set(output, (const uint8_t *)literal, sizeof(literal) - 1) /* HOEDOWN_BUFEQSL: optimized hoedown_buffer_eqs of a string literal */ #define HOEDOWN_BUFEQSL(output, literal) \ - hoedown_buffer_eq(output, (const uint8_t *)literal, sizeof(literal) - 1) + hoedown_buffer_eq(output, (const uint8_t *)literal, sizeof(literal) - 1) #ifdef __cplusplus diff --git a/libraries/hoedown/include/hoedown/document.h b/libraries/hoedown/include/hoedown/document.h index a8178fec..210c565e 100644 --- a/libraries/hoedown/include/hoedown/document.h +++ b/libraries/hoedown/include/hoedown/document.h @@ -16,68 +16,68 @@ extern "C" { *************/ typedef enum hoedown_extensions { - /* block-level extensions */ - HOEDOWN_EXT_TABLES = (1 << 0), - HOEDOWN_EXT_FENCED_CODE = (1 << 1), - HOEDOWN_EXT_FOOTNOTES = (1 << 2), - - /* span-level extensions */ - HOEDOWN_EXT_AUTOLINK = (1 << 3), - HOEDOWN_EXT_STRIKETHROUGH = (1 << 4), - HOEDOWN_EXT_UNDERLINE = (1 << 5), - HOEDOWN_EXT_HIGHLIGHT = (1 << 6), - HOEDOWN_EXT_QUOTE = (1 << 7), - HOEDOWN_EXT_SUPERSCRIPT = (1 << 8), - HOEDOWN_EXT_MATH = (1 << 9), - - /* other flags */ - HOEDOWN_EXT_NO_INTRA_EMPHASIS = (1 << 11), - HOEDOWN_EXT_SPACE_HEADERS = (1 << 12), - HOEDOWN_EXT_MATH_EXPLICIT = (1 << 13), - - /* negative flags */ - HOEDOWN_EXT_DISABLE_INDENTED_CODE = (1 << 14) + /* block-level extensions */ + HOEDOWN_EXT_TABLES = (1 << 0), + HOEDOWN_EXT_FENCED_CODE = (1 << 1), + HOEDOWN_EXT_FOOTNOTES = (1 << 2), + + /* span-level extensions */ + HOEDOWN_EXT_AUTOLINK = (1 << 3), + HOEDOWN_EXT_STRIKETHROUGH = (1 << 4), + HOEDOWN_EXT_UNDERLINE = (1 << 5), + HOEDOWN_EXT_HIGHLIGHT = (1 << 6), + HOEDOWN_EXT_QUOTE = (1 << 7), + HOEDOWN_EXT_SUPERSCRIPT = (1 << 8), + HOEDOWN_EXT_MATH = (1 << 9), + + /* other flags */ + HOEDOWN_EXT_NO_INTRA_EMPHASIS = (1 << 11), + HOEDOWN_EXT_SPACE_HEADERS = (1 << 12), + HOEDOWN_EXT_MATH_EXPLICIT = (1 << 13), + + /* negative flags */ + HOEDOWN_EXT_DISABLE_INDENTED_CODE = (1 << 14) } hoedown_extensions; #define HOEDOWN_EXT_BLOCK (\ - HOEDOWN_EXT_TABLES |\ - HOEDOWN_EXT_FENCED_CODE |\ - HOEDOWN_EXT_FOOTNOTES ) + HOEDOWN_EXT_TABLES |\ + HOEDOWN_EXT_FENCED_CODE |\ + HOEDOWN_EXT_FOOTNOTES ) #define HOEDOWN_EXT_SPAN (\ - HOEDOWN_EXT_AUTOLINK |\ - HOEDOWN_EXT_STRIKETHROUGH |\ - HOEDOWN_EXT_UNDERLINE |\ - HOEDOWN_EXT_HIGHLIGHT |\ - HOEDOWN_EXT_QUOTE |\ - HOEDOWN_EXT_SUPERSCRIPT |\ - HOEDOWN_EXT_MATH ) + HOEDOWN_EXT_AUTOLINK |\ + HOEDOWN_EXT_STRIKETHROUGH |\ + HOEDOWN_EXT_UNDERLINE |\ + HOEDOWN_EXT_HIGHLIGHT |\ + HOEDOWN_EXT_QUOTE |\ + HOEDOWN_EXT_SUPERSCRIPT |\ + HOEDOWN_EXT_MATH ) #define HOEDOWN_EXT_FLAGS (\ - HOEDOWN_EXT_NO_INTRA_EMPHASIS |\ - HOEDOWN_EXT_SPACE_HEADERS |\ - HOEDOWN_EXT_MATH_EXPLICIT ) + HOEDOWN_EXT_NO_INTRA_EMPHASIS |\ + HOEDOWN_EXT_SPACE_HEADERS |\ + HOEDOWN_EXT_MATH_EXPLICIT ) #define HOEDOWN_EXT_NEGATIVE (\ - HOEDOWN_EXT_DISABLE_INDENTED_CODE ) + HOEDOWN_EXT_DISABLE_INDENTED_CODE ) typedef enum hoedown_list_flags { - HOEDOWN_LIST_ORDERED = (1 << 0), - HOEDOWN_LI_BLOCK = (1 << 1) /*
  • containing block data */ + HOEDOWN_LIST_ORDERED = (1 << 0), + HOEDOWN_LI_BLOCK = (1 << 1) /*
  • containing block data */ } hoedown_list_flags; typedef enum hoedown_table_flags { - HOEDOWN_TABLE_ALIGN_LEFT = 1, - HOEDOWN_TABLE_ALIGN_RIGHT = 2, - HOEDOWN_TABLE_ALIGN_CENTER = 3, - HOEDOWN_TABLE_ALIGNMASK = 3, - HOEDOWN_TABLE_HEADER = 4 + HOEDOWN_TABLE_ALIGN_LEFT = 1, + HOEDOWN_TABLE_ALIGN_RIGHT = 2, + HOEDOWN_TABLE_ALIGN_CENTER = 3, + HOEDOWN_TABLE_ALIGNMASK = 3, + HOEDOWN_TABLE_HEADER = 4 } hoedown_table_flags; typedef enum hoedown_autolink_type { - HOEDOWN_AUTOLINK_NONE, /* used internally when it is not an autolink*/ - HOEDOWN_AUTOLINK_NORMAL, /* normal http/http/ftp/mailto/etc link */ - HOEDOWN_AUTOLINK_EMAIL /* e-mail link without explit mailto: */ + HOEDOWN_AUTOLINK_NONE, /* used internally when it is not an autolink*/ + HOEDOWN_AUTOLINK_NORMAL, /* normal http/http/ftp/mailto/etc link */ + HOEDOWN_AUTOLINK_EMAIL /* e-mail link without explit mailto: */ } hoedown_autolink_type; @@ -89,57 +89,57 @@ struct hoedown_document; typedef struct hoedown_document hoedown_document; struct hoedown_renderer_data { - void *opaque; + void *opaque; }; typedef struct hoedown_renderer_data hoedown_renderer_data; /* hoedown_renderer - functions for rendering parsed data */ struct hoedown_renderer { - /* state object */ - void *opaque; - - /* block level callbacks - NULL skips the block */ - void (*blockcode)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data); - void (*blockquote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - void (*header)(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data); - void (*hrule)(hoedown_buffer *ob, const hoedown_renderer_data *data); - void (*list)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data); - void (*listitem)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data); - void (*paragraph)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - void (*table)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - void (*table_header)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - void (*table_body)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - void (*table_row)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - void (*table_cell)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data); - void (*footnotes)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - void (*footnote_def)(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data); - void (*blockhtml)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); - - /* span level callbacks - NULL or return 0 prints the span verbatim */ - int (*autolink)(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data); - int (*codespan)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); - int (*double_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*underline)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*highlight)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*quote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*image)(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data); - int (*linebreak)(hoedown_buffer *ob, const hoedown_renderer_data *data); - int (*link)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data); - int (*triple_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*strikethrough)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*superscript)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); - int (*footnote_ref)(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data); - int (*math)(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data); - int (*raw_html)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); - - /* low level callbacks - NULL copies input directly into the output */ - void (*entity)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); - void (*normal_text)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); - - /* miscellaneous callbacks */ - void (*doc_header)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data); - void (*doc_footer)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data); + /* state object */ + void *opaque; + + /* block level callbacks - NULL skips the block */ + void (*blockcode)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data); + void (*blockquote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + void (*header)(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data); + void (*hrule)(hoedown_buffer *ob, const hoedown_renderer_data *data); + void (*list)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data); + void (*listitem)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data); + void (*paragraph)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + void (*table)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + void (*table_header)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + void (*table_body)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + void (*table_row)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + void (*table_cell)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data); + void (*footnotes)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + void (*footnote_def)(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data); + void (*blockhtml)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); + + /* span level callbacks - NULL or return 0 prints the span verbatim */ + int (*autolink)(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data); + int (*codespan)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); + int (*double_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*underline)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*highlight)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*quote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*image)(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data); + int (*linebreak)(hoedown_buffer *ob, const hoedown_renderer_data *data); + int (*link)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data); + int (*triple_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*strikethrough)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*superscript)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data); + int (*footnote_ref)(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data); + int (*math)(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data); + int (*raw_html)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); + + /* low level callbacks - NULL copies input directly into the output */ + void (*entity)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); + void (*normal_text)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data); + + /* miscellaneous callbacks */ + void (*doc_header)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data); + void (*doc_footer)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data); }; typedef struct hoedown_renderer hoedown_renderer; @@ -150,9 +150,9 @@ typedef struct hoedown_renderer hoedown_renderer; /* hoedown_document_new: allocate a new document processor instance */ hoedown_document *hoedown_document_new( - const hoedown_renderer *renderer, - hoedown_extensions extensions, - size_t max_nesting + const hoedown_renderer *renderer, + hoedown_extensions extensions, + size_t max_nesting ) __attribute__ ((malloc)); /* hoedown_document_render: render regular Markdown using the document processor */ diff --git a/libraries/hoedown/include/hoedown/html.h b/libraries/hoedown/include/hoedown/html.h index e46e7fd6..7c68809a 100644 --- a/libraries/hoedown/include/hoedown/html.h +++ b/libraries/hoedown/include/hoedown/html.h @@ -16,16 +16,16 @@ extern "C" { *************/ typedef enum hoedown_html_flags { - HOEDOWN_HTML_SKIP_HTML = (1 << 0), - HOEDOWN_HTML_ESCAPE = (1 << 1), - HOEDOWN_HTML_HARD_WRAP = (1 << 2), - HOEDOWN_HTML_USE_XHTML = (1 << 3) + HOEDOWN_HTML_SKIP_HTML = (1 << 0), + HOEDOWN_HTML_ESCAPE = (1 << 1), + HOEDOWN_HTML_HARD_WRAP = (1 << 2), + HOEDOWN_HTML_USE_XHTML = (1 << 3) } hoedown_html_flags; typedef enum hoedown_html_tag { - HOEDOWN_HTML_TAG_NONE = 0, - HOEDOWN_HTML_TAG_OPEN, - HOEDOWN_HTML_TAG_CLOSE + HOEDOWN_HTML_TAG_NONE = 0, + HOEDOWN_HTML_TAG_OPEN, + HOEDOWN_HTML_TAG_CLOSE } hoedown_html_tag; @@ -34,19 +34,19 @@ typedef enum hoedown_html_tag { *********/ struct hoedown_html_renderer_state { - void *opaque; + void *opaque; - struct { - int header_count; - int current_level; - int level_offset; - int nesting_level; - } toc_data; + struct { + int header_count; + int current_level; + int level_offset; + int nesting_level; + } toc_data; - hoedown_html_flags flags; + hoedown_html_flags flags; - /* extra callbacks */ - void (*link_attributes)(hoedown_buffer *ob, const hoedown_buffer *url, const hoedown_renderer_data *data); + /* extra callbacks */ + void (*link_attributes)(hoedown_buffer *ob, const hoedown_buffer *url, const hoedown_renderer_data *data); }; typedef struct hoedown_html_renderer_state hoedown_html_renderer_state; @@ -64,13 +64,13 @@ hoedown_html_tag hoedown_html_is_tag(const uint8_t *data, size_t size, const cha /* hoedown_html_renderer_new: allocates a regular HTML renderer */ hoedown_renderer *hoedown_html_renderer_new( - hoedown_html_flags render_flags, - int nesting_level + hoedown_html_flags render_flags, + int nesting_level ) __attribute__ ((malloc)); /* hoedown_html_toc_renderer_new: like hoedown_html_renderer_new, but the returned renderer produces the Table of Contents */ hoedown_renderer *hoedown_html_toc_renderer_new( - int nesting_level + int nesting_level ) __attribute__ ((malloc)); /* hoedown_html_renderer_free: deallocate an HTML renderer */ diff --git a/libraries/hoedown/include/hoedown/stack.h b/libraries/hoedown/include/hoedown/stack.h index bf9b439b..d1855f4f 100644 --- a/libraries/hoedown/include/hoedown/stack.h +++ b/libraries/hoedown/include/hoedown/stack.h @@ -15,9 +15,9 @@ extern "C" { *********/ struct hoedown_stack { - void **item; - size_t size; - size_t asize; + void **item; + size_t size; + size_t asize; }; typedef struct hoedown_stack hoedown_stack; diff --git a/libraries/hoedown/src/autolink.c b/libraries/hoedown/src/autolink.c index 9bc7fad5..3063b1a0 100644 --- a/libraries/hoedown/src/autolink.c +++ b/libraries/hoedown/src/autolink.c @@ -8,274 +8,274 @@ #ifndef _MSC_VER #include #else -#define strncasecmp _strnicmp +#define strncasecmp _strnicmp #endif int hoedown_autolink_is_safe(const uint8_t *data, size_t size) { - static const size_t valid_uris_count = 6; - static const char *valid_uris[] = { - "http://", "https://", "/", "#", "ftp://", "mailto:" - }; - static const size_t valid_uris_size[] = { 7, 8, 1, 1, 6, 7 }; - size_t i; - - for (i = 0; i < valid_uris_count; ++i) { - size_t len = valid_uris_size[i]; - - if (size > len && - strncasecmp((char *)data, valid_uris[i], len) == 0 && - isalnum(data[len])) - return 1; - } - - return 0; + static const size_t valid_uris_count = 6; + static const char *valid_uris[] = { + "http://", "https://", "/", "#", "ftp://", "mailto:" + }; + static const size_t valid_uris_size[] = { 7, 8, 1, 1, 6, 7 }; + size_t i; + + for (i = 0; i < valid_uris_count; ++i) { + size_t len = valid_uris_size[i]; + + if (size > len && + strncasecmp((char *)data, valid_uris[i], len) == 0 && + isalnum(data[len])) + return 1; + } + + return 0; } static size_t autolink_delim(uint8_t *data, size_t link_end, size_t max_rewind, size_t size) { - uint8_t cclose, copen = 0; - size_t i; - - for (i = 0; i < link_end; ++i) - if (data[i] == '<') { - link_end = i; - break; - } - - while (link_end > 0) { - if (strchr("?!.,:", data[link_end - 1]) != NULL) - link_end--; - - else if (data[link_end - 1] == ';') { - size_t new_end = link_end - 2; - - while (new_end > 0 && isalpha(data[new_end])) - new_end--; - - if (new_end < link_end - 2 && data[new_end] == '&') - link_end = new_end; - else - link_end--; - } - else break; - } - - if (link_end == 0) - return 0; - - cclose = data[link_end - 1]; - - switch (cclose) { - case '"': copen = '"'; break; - case '\'': copen = '\''; break; - case ')': copen = '('; break; - case ']': copen = '['; break; - case '}': copen = '{'; break; - } - - if (copen != 0) { - size_t closing = 0; - size_t opening = 0; - size_t i = 0; - - /* Try to close the final punctuation sign in this same line; - * if we managed to close it outside of the URL, that means that it's - * not part of the URL. If it closes inside the URL, that means it - * is part of the URL. - * - * Examples: - * - * foo http://www.pokemon.com/Pikachu_(Electric) bar - * => http://www.pokemon.com/Pikachu_(Electric) - * - * foo (http://www.pokemon.com/Pikachu_(Electric)) bar - * => http://www.pokemon.com/Pikachu_(Electric) - * - * foo http://www.pokemon.com/Pikachu_(Electric)) bar - * => http://www.pokemon.com/Pikachu_(Electric)) - * - * (foo http://www.pokemon.com/Pikachu_(Electric)) bar - * => foo http://www.pokemon.com/Pikachu_(Electric) - */ - - while (i < link_end) { - if (data[i] == copen) - opening++; - else if (data[i] == cclose) - closing++; - - i++; - } - - if (closing != opening) - link_end--; - } - - return link_end; + uint8_t cclose, copen = 0; + size_t i; + + for (i = 0; i < link_end; ++i) + if (data[i] == '<') { + link_end = i; + break; + } + + while (link_end > 0) { + if (strchr("?!.,:", data[link_end - 1]) != NULL) + link_end--; + + else if (data[link_end - 1] == ';') { + size_t new_end = link_end - 2; + + while (new_end > 0 && isalpha(data[new_end])) + new_end--; + + if (new_end < link_end - 2 && data[new_end] == '&') + link_end = new_end; + else + link_end--; + } + else break; + } + + if (link_end == 0) + return 0; + + cclose = data[link_end - 1]; + + switch (cclose) { + case '"': copen = '"'; break; + case '\'': copen = '\''; break; + case ')': copen = '('; break; + case ']': copen = '['; break; + case '}': copen = '{'; break; + } + + if (copen != 0) { + size_t closing = 0; + size_t opening = 0; + size_t i = 0; + + /* Try to close the final punctuation sign in this same line; + * if we managed to close it outside of the URL, that means that it's + * not part of the URL. If it closes inside the URL, that means it + * is part of the URL. + * + * Examples: + * + * foo http://www.pokemon.com/Pikachu_(Electric) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo (http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric)) + * + * (foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => foo http://www.pokemon.com/Pikachu_(Electric) + */ + + while (i < link_end) { + if (data[i] == copen) + opening++; + else if (data[i] == cclose) + closing++; + + i++; + } + + if (closing != opening) + link_end--; + } + + return link_end; } static size_t check_domain(uint8_t *data, size_t size, int allow_short) { - size_t i, np = 0; - - if (!isalnum(data[0])) - return 0; - - for (i = 1; i < size - 1; ++i) { - if (strchr(".:", data[i]) != NULL) np++; - else if (!isalnum(data[i]) && data[i] != '-') break; - } - - if (allow_short) { - /* We don't need a valid domain in the strict sense (with - * least one dot; so just make sure it's composed of valid - * domain characters and return the length of the the valid - * sequence. */ - return i; - } else { - /* a valid domain needs to have at least a dot. - * that's as far as we get */ - return np ? i : 0; - } + size_t i, np = 0; + + if (!isalnum(data[0])) + return 0; + + for (i = 1; i < size - 1; ++i) { + if (strchr(".:", data[i]) != NULL) np++; + else if (!isalnum(data[i]) && data[i] != '-') break; + } + + if (allow_short) { + /* We don't need a valid domain in the strict sense (with + * least one dot; so just make sure it's composed of valid + * domain characters and return the length of the the valid + * sequence. */ + return i; + } else { + /* a valid domain needs to have at least a dot. + * that's as far as we get */ + return np ? i : 0; + } } size_t hoedown_autolink__www( - size_t *rewind_p, - hoedown_buffer *link, - uint8_t *data, - size_t max_rewind, - size_t size, - unsigned int flags) + size_t *rewind_p, + hoedown_buffer *link, + uint8_t *data, + size_t max_rewind, + size_t size, + unsigned int flags) { - size_t link_end; + size_t link_end; - if (max_rewind > 0 && !ispunct(data[-1]) && !isspace(data[-1])) - return 0; + if (max_rewind > 0 && !ispunct(data[-1]) && !isspace(data[-1])) + return 0; - if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0) - return 0; + if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0) + return 0; - link_end = check_domain(data, size, 0); + link_end = check_domain(data, size, 0); - if (link_end == 0) - return 0; + if (link_end == 0) + return 0; - while (link_end < size && !isspace(data[link_end])) - link_end++; + while (link_end < size && !isspace(data[link_end])) + link_end++; - link_end = autolink_delim(data, link_end, max_rewind, size); + link_end = autolink_delim(data, link_end, max_rewind, size); - if (link_end == 0) - return 0; + if (link_end == 0) + return 0; - hoedown_buffer_put(link, data, link_end); - *rewind_p = 0; + hoedown_buffer_put(link, data, link_end); + *rewind_p = 0; - return (int)link_end; + return (int)link_end; } size_t hoedown_autolink__email( - size_t *rewind_p, - hoedown_buffer *link, - uint8_t *data, - size_t max_rewind, - size_t size, - unsigned int flags) + size_t *rewind_p, + hoedown_buffer *link, + uint8_t *data, + size_t max_rewind, + size_t size, + unsigned int flags) { - size_t link_end, rewind; - int nb = 0, np = 0; + size_t link_end, rewind; + int nb = 0, np = 0; - for (rewind = 0; rewind < max_rewind; ++rewind) { - uint8_t c = data[-1 - rewind]; + for (rewind = 0; rewind < max_rewind; ++rewind) { + uint8_t c = data[-1 - rewind]; - if (isalnum(c)) - continue; + if (isalnum(c)) + continue; - if (strchr(".+-_", c) != NULL) - continue; + if (strchr(".+-_", c) != NULL) + continue; - break; - } + break; + } - if (rewind == 0) - return 0; + if (rewind == 0) + return 0; - for (link_end = 0; link_end < size; ++link_end) { - uint8_t c = data[link_end]; + for (link_end = 0; link_end < size; ++link_end) { + uint8_t c = data[link_end]; - if (isalnum(c)) - continue; + if (isalnum(c)) + continue; - if (c == '@') - nb++; - else if (c == '.' && link_end < size - 1) - np++; - else if (c != '-' && c != '_') - break; - } + if (c == '@') + nb++; + else if (c == '.' && link_end < size - 1) + np++; + else if (c != '-' && c != '_') + break; + } - if (link_end < 2 || nb != 1 || np == 0 || - !isalpha(data[link_end - 1])) - return 0; + if (link_end < 2 || nb != 1 || np == 0 || + !isalpha(data[link_end - 1])) + return 0; - link_end = autolink_delim(data, link_end, max_rewind, size); + link_end = autolink_delim(data, link_end, max_rewind, size); - if (link_end == 0) - return 0; + if (link_end == 0) + return 0; - hoedown_buffer_put(link, data - rewind, link_end + rewind); - *rewind_p = rewind; + hoedown_buffer_put(link, data - rewind, link_end + rewind); + *rewind_p = rewind; - return link_end; + return link_end; } size_t hoedown_autolink__url( - size_t *rewind_p, - hoedown_buffer *link, - uint8_t *data, - size_t max_rewind, - size_t size, - unsigned int flags) + size_t *rewind_p, + hoedown_buffer *link, + uint8_t *data, + size_t max_rewind, + size_t size, + unsigned int flags) { - size_t link_end, rewind = 0, domain_len; + size_t link_end, rewind = 0, domain_len; - if (size < 4 || data[1] != '/' || data[2] != '/') - return 0; + if (size < 4 || data[1] != '/' || data[2] != '/') + return 0; - while (rewind < max_rewind && isalpha(data[-1 - rewind])) - rewind++; + while (rewind < max_rewind && isalpha(data[-1 - rewind])) + rewind++; - if (!hoedown_autolink_is_safe(data - rewind, size + rewind)) - return 0; + if (!hoedown_autolink_is_safe(data - rewind, size + rewind)) + return 0; - link_end = strlen("://"); + link_end = strlen("://"); - domain_len = check_domain( - data + link_end, - size - link_end, - flags & HOEDOWN_AUTOLINK_SHORT_DOMAINS); + domain_len = check_domain( + data + link_end, + size - link_end, + flags & HOEDOWN_AUTOLINK_SHORT_DOMAINS); - if (domain_len == 0) - return 0; + if (domain_len == 0) + return 0; - link_end += domain_len; - while (link_end < size && !isspace(data[link_end])) - link_end++; + link_end += domain_len; + while (link_end < size && !isspace(data[link_end])) + link_end++; - link_end = autolink_delim(data, link_end, max_rewind, size); + link_end = autolink_delim(data, link_end, max_rewind, size); - if (link_end == 0) - return 0; + if (link_end == 0) + return 0; - hoedown_buffer_put(link, data - rewind, link_end + rewind); - *rewind_p = rewind; + hoedown_buffer_put(link, data - rewind, link_end + rewind); + *rewind_p = rewind; - return link_end; + return link_end; } diff --git a/libraries/hoedown/src/buffer.c b/libraries/hoedown/src/buffer.c index 1c7ba55a..024a8bcc 100644 --- a/libraries/hoedown/src/buffer.c +++ b/libraries/hoedown/src/buffer.c @@ -8,301 +8,301 @@ void * hoedown_malloc(size_t size) { - void *ret = malloc(size); + void *ret = malloc(size); - if (!ret) { - fprintf(stderr, "Allocation failed.\n"); - abort(); - } + if (!ret) { + fprintf(stderr, "Allocation failed.\n"); + abort(); + } - return ret; + return ret; } void * hoedown_calloc(size_t nmemb, size_t size) { - void *ret = calloc(nmemb, size); + void *ret = calloc(nmemb, size); - if (!ret) { - fprintf(stderr, "Allocation failed.\n"); - abort(); - } + if (!ret) { + fprintf(stderr, "Allocation failed.\n"); + abort(); + } - return ret; + return ret; } void * hoedown_realloc(void *ptr, size_t size) { - void *ret = realloc(ptr, size); + void *ret = realloc(ptr, size); - if (!ret) { - fprintf(stderr, "Allocation failed.\n"); - abort(); - } + if (!ret) { + fprintf(stderr, "Allocation failed.\n"); + abort(); + } - return ret; + return ret; } void hoedown_buffer_init( - hoedown_buffer *buf, - size_t unit, - hoedown_realloc_callback data_realloc, - hoedown_free_callback data_free, - hoedown_free_callback buffer_free) + hoedown_buffer *buf, + size_t unit, + hoedown_realloc_callback data_realloc, + hoedown_free_callback data_free, + hoedown_free_callback buffer_free) { - assert(buf); - - buf->data = NULL; - buf->size = buf->asize = 0; - buf->unit = unit; - buf->data_realloc = data_realloc; - buf->data_free = data_free; - buf->buffer_free = buffer_free; + assert(buf); + + buf->data = NULL; + buf->size = buf->asize = 0; + buf->unit = unit; + buf->data_realloc = data_realloc; + buf->data_free = data_free; + buf->buffer_free = buffer_free; } void hoedown_buffer_uninit(hoedown_buffer *buf) { - assert(buf && buf->unit); - buf->data_free(buf->data); + assert(buf && buf->unit); + buf->data_free(buf->data); } hoedown_buffer * hoedown_buffer_new(size_t unit) { - hoedown_buffer *ret = hoedown_malloc(sizeof (hoedown_buffer)); - hoedown_buffer_init(ret, unit, hoedown_realloc, free, free); - return ret; + hoedown_buffer *ret = hoedown_malloc(sizeof (hoedown_buffer)); + hoedown_buffer_init(ret, unit, hoedown_realloc, free, free); + return ret; } void hoedown_buffer_free(hoedown_buffer *buf) { - if (!buf) return; - assert(buf && buf->unit); + if (!buf) return; + assert(buf && buf->unit); - buf->data_free(buf->data); + buf->data_free(buf->data); - if (buf->buffer_free) - buf->buffer_free(buf); + if (buf->buffer_free) + buf->buffer_free(buf); } void hoedown_buffer_reset(hoedown_buffer *buf) { - assert(buf && buf->unit); + assert(buf && buf->unit); - buf->data_free(buf->data); - buf->data = NULL; - buf->size = buf->asize = 0; + buf->data_free(buf->data); + buf->data = NULL; + buf->size = buf->asize = 0; } void hoedown_buffer_grow(hoedown_buffer *buf, size_t neosz) { - size_t neoasz; - assert(buf && buf->unit); + size_t neoasz; + assert(buf && buf->unit); - if (buf->asize >= neosz) - return; + if (buf->asize >= neosz) + return; - neoasz = buf->asize + buf->unit; - while (neoasz < neosz) - neoasz += buf->unit; + neoasz = buf->asize + buf->unit; + while (neoasz < neosz) + neoasz += buf->unit; - buf->data = (uint8_t *) buf->data_realloc(buf->data, neoasz); - buf->asize = neoasz; + buf->data = (uint8_t *) buf->data_realloc(buf->data, neoasz); + buf->asize = neoasz; } void hoedown_buffer_put(hoedown_buffer *buf, const uint8_t *data, size_t size) { - assert(buf && buf->unit); + assert(buf && buf->unit); - if (buf->size + size > buf->asize) - hoedown_buffer_grow(buf, buf->size + size); + if (buf->size + size > buf->asize) + hoedown_buffer_grow(buf, buf->size + size); - memcpy(buf->data + buf->size, data, size); - buf->size += size; + memcpy(buf->data + buf->size, data, size); + buf->size += size; } void hoedown_buffer_puts(hoedown_buffer *buf, const char *str) { - hoedown_buffer_put(buf, (const uint8_t *)str, strlen(str)); + hoedown_buffer_put(buf, (const uint8_t *)str, strlen(str)); } void hoedown_buffer_putc(hoedown_buffer *buf, uint8_t c) { - assert(buf && buf->unit); + assert(buf && buf->unit); - if (buf->size >= buf->asize) - hoedown_buffer_grow(buf, buf->size + 1); + if (buf->size >= buf->asize) + hoedown_buffer_grow(buf, buf->size + 1); - buf->data[buf->size] = c; - buf->size += 1; + buf->data[buf->size] = c; + buf->size += 1; } int hoedown_buffer_putf(hoedown_buffer *buf, FILE *file) { - assert(buf && buf->unit); + assert(buf && buf->unit); - while (!(feof(file) || ferror(file))) { - hoedown_buffer_grow(buf, buf->size + buf->unit); - buf->size += fread(buf->data + buf->size, 1, buf->unit, file); - } + while (!(feof(file) || ferror(file))) { + hoedown_buffer_grow(buf, buf->size + buf->unit); + buf->size += fread(buf->data + buf->size, 1, buf->unit, file); + } - return ferror(file); + return ferror(file); } void hoedown_buffer_set(hoedown_buffer *buf, const uint8_t *data, size_t size) { - assert(buf && buf->unit); + assert(buf && buf->unit); - if (size > buf->asize) - hoedown_buffer_grow(buf, size); + if (size > buf->asize) + hoedown_buffer_grow(buf, size); - memcpy(buf->data, data, size); - buf->size = size; + memcpy(buf->data, data, size); + buf->size = size; } void hoedown_buffer_sets(hoedown_buffer *buf, const char *str) { - hoedown_buffer_set(buf, (const uint8_t *)str, strlen(str)); + hoedown_buffer_set(buf, (const uint8_t *)str, strlen(str)); } int hoedown_buffer_eq(const hoedown_buffer *buf, const uint8_t *data, size_t size) { - if (buf->size != size) return 0; - return memcmp(buf->data, data, size) == 0; + if (buf->size != size) return 0; + return memcmp(buf->data, data, size) == 0; } int hoedown_buffer_eqs(const hoedown_buffer *buf, const char *str) { - return hoedown_buffer_eq(buf, (const uint8_t *)str, strlen(str)); + return hoedown_buffer_eq(buf, (const uint8_t *)str, strlen(str)); } int hoedown_buffer_prefix(const hoedown_buffer *buf, const char *prefix) { - size_t i; + size_t i; - for (i = 0; i < buf->size; ++i) { - if (prefix[i] == 0) - return 0; + for (i = 0; i < buf->size; ++i) { + if (prefix[i] == 0) + return 0; - if (buf->data[i] != prefix[i]) - return buf->data[i] - prefix[i]; - } + if (buf->data[i] != prefix[i]) + return buf->data[i] - prefix[i]; + } - return 0; + return 0; } void hoedown_buffer_slurp(hoedown_buffer *buf, size_t size) { - assert(buf && buf->unit); + assert(buf && buf->unit); - if (size >= buf->size) { - buf->size = 0; - return; - } + if (size >= buf->size) { + buf->size = 0; + return; + } - buf->size -= size; - memmove(buf->data, buf->data + size, buf->size); + buf->size -= size; + memmove(buf->data, buf->data + size, buf->size); } const char * hoedown_buffer_cstr(hoedown_buffer *buf) { - assert(buf && buf->unit); + assert(buf && buf->unit); - if (buf->size < buf->asize && buf->data[buf->size] == 0) - return (char *)buf->data; + if (buf->size < buf->asize && buf->data[buf->size] == 0) + return (char *)buf->data; - hoedown_buffer_grow(buf, buf->size + 1); - buf->data[buf->size] = 0; + hoedown_buffer_grow(buf, buf->size + 1); + buf->data[buf->size] = 0; - return (char *)buf->data; + return (char *)buf->data; } void hoedown_buffer_printf(hoedown_buffer *buf, const char *fmt, ...) { - va_list ap; - int n; + va_list ap; + int n; - assert(buf && buf->unit); + assert(buf && buf->unit); - if (buf->size >= buf->asize) - hoedown_buffer_grow(buf, buf->size + 1); + if (buf->size >= buf->asize) + hoedown_buffer_grow(buf, buf->size + 1); - va_start(ap, fmt); - n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap); - va_end(ap); + va_start(ap, fmt); + n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap); + va_end(ap); - if (n < 0) { + if (n < 0) { #ifndef _MSC_VER - return; + return; #else - va_start(ap, fmt); - n = _vscprintf(fmt, ap); - va_end(ap); + va_start(ap, fmt); + n = _vscprintf(fmt, ap); + va_end(ap); #endif - } + } - if ((size_t)n >= buf->asize - buf->size) { - hoedown_buffer_grow(buf, buf->size + n + 1); + if ((size_t)n >= buf->asize - buf->size) { + hoedown_buffer_grow(buf, buf->size + n + 1); - va_start(ap, fmt); - n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap); - va_end(ap); - } + va_start(ap, fmt); + n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap); + va_end(ap); + } - if (n < 0) - return; + if (n < 0) + return; - buf->size += n; + buf->size += n; } void hoedown_buffer_put_utf8(hoedown_buffer *buf, unsigned int c) { - unsigned char unichar[4]; - - assert(buf && buf->unit); - - if (c < 0x80) { - hoedown_buffer_putc(buf, c); - } - else if (c < 0x800) { - unichar[0] = 192 + (c / 64); - unichar[1] = 128 + (c % 64); - hoedown_buffer_put(buf, unichar, 2); - } - else if (c - 0xd800u < 0x800) { - HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd"); - } - else if (c < 0x10000) { - unichar[0] = 224 + (c / 4096); - unichar[1] = 128 + (c / 64) % 64; - unichar[2] = 128 + (c % 64); - hoedown_buffer_put(buf, unichar, 3); - } - else if (c < 0x110000) { - unichar[0] = 240 + (c / 262144); - unichar[1] = 128 + (c / 4096) % 64; - unichar[2] = 128 + (c / 64) % 64; - unichar[3] = 128 + (c % 64); - hoedown_buffer_put(buf, unichar, 4); - } - else { - HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd"); - } + unsigned char unichar[4]; + + assert(buf && buf->unit); + + if (c < 0x80) { + hoedown_buffer_putc(buf, c); + } + else if (c < 0x800) { + unichar[0] = 192 + (c / 64); + unichar[1] = 128 + (c % 64); + hoedown_buffer_put(buf, unichar, 2); + } + else if (c - 0xd800u < 0x800) { + HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd"); + } + else if (c < 0x10000) { + unichar[0] = 224 + (c / 4096); + unichar[1] = 128 + (c / 64) % 64; + unichar[2] = 128 + (c % 64); + hoedown_buffer_put(buf, unichar, 3); + } + else if (c < 0x110000) { + unichar[0] = 240 + (c / 262144); + unichar[1] = 128 + (c / 4096) % 64; + unichar[2] = 128 + (c / 64) % 64; + unichar[3] = 128 + (c % 64); + hoedown_buffer_put(buf, unichar, 4); + } + else { + HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd"); + } } diff --git a/libraries/hoedown/src/document.c b/libraries/hoedown/src/document.c index 8ba82e47..e9e2ab11 100644 --- a/libraries/hoedown/src/document.c +++ b/libraries/hoedown/src/document.c @@ -10,7 +10,7 @@ #ifndef _MSC_VER #include #else -#define strncasecmp _strnicmp +#define strncasecmp _strnicmp #endif #define REF_TABLE_SIZE 8 @@ -18,7 +18,7 @@ #define BUFFER_BLOCK 0 #define BUFFER_SPAN 1 -#define HOEDOWN_LI_END 8 /* internal list flag */ +#define HOEDOWN_LI_END 8 /* internal list flag */ const char *hoedown_find_block_tag(const char *str, unsigned int len); @@ -28,35 +28,35 @@ const char *hoedown_find_block_tag(const char *str, unsigned int len); /* link_ref: reference to a link */ struct link_ref { - unsigned int id; + unsigned int id; - hoedown_buffer *link; - hoedown_buffer *title; + hoedown_buffer *link; + hoedown_buffer *title; - struct link_ref *next; + struct link_ref *next; }; /* footnote_ref: reference to a footnote */ struct footnote_ref { - unsigned int id; + unsigned int id; - int is_used; - unsigned int num; + int is_used; + unsigned int num; - hoedown_buffer *contents; + hoedown_buffer *contents; }; /* footnote_item: an item in a footnote_list */ struct footnote_item { - struct footnote_ref *ref; - struct footnote_item *next; + struct footnote_ref *ref; + struct footnote_item *next; }; /* footnote_list: linked list of footnote_item */ struct footnote_list { - unsigned int count; - struct footnote_item *head; - struct footnote_item *tail; + unsigned int count; + struct footnote_item *head; + struct footnote_item *tail; }; /* char_trigger: function pointer to render active chars */ @@ -81,51 +81,51 @@ static size_t char_superscript(hoedown_buffer *ob, hoedown_document *doc, uint8_ static size_t char_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size); enum markdown_char_t { - MD_CHAR_NONE = 0, - MD_CHAR_EMPHASIS, - MD_CHAR_CODESPAN, - MD_CHAR_LINEBREAK, - MD_CHAR_LINK, - MD_CHAR_LANGLE, - MD_CHAR_ESCAPE, - MD_CHAR_ENTITY, - MD_CHAR_AUTOLINK_URL, - MD_CHAR_AUTOLINK_EMAIL, - MD_CHAR_AUTOLINK_WWW, - MD_CHAR_SUPERSCRIPT, - MD_CHAR_QUOTE, - MD_CHAR_MATH + MD_CHAR_NONE = 0, + MD_CHAR_EMPHASIS, + MD_CHAR_CODESPAN, + MD_CHAR_LINEBREAK, + MD_CHAR_LINK, + MD_CHAR_LANGLE, + MD_CHAR_ESCAPE, + MD_CHAR_ENTITY, + MD_CHAR_AUTOLINK_URL, + MD_CHAR_AUTOLINK_EMAIL, + MD_CHAR_AUTOLINK_WWW, + MD_CHAR_SUPERSCRIPT, + MD_CHAR_QUOTE, + MD_CHAR_MATH }; static char_trigger markdown_char_ptrs[] = { - NULL, - &char_emphasis, - &char_codespan, - &char_linebreak, - &char_link, - &char_langle_tag, - &char_escape, - &char_entity, - &char_autolink_url, - &char_autolink_email, - &char_autolink_www, - &char_superscript, - &char_quote, - &char_math + NULL, + &char_emphasis, + &char_codespan, + &char_linebreak, + &char_link, + &char_langle_tag, + &char_escape, + &char_entity, + &char_autolink_url, + &char_autolink_email, + &char_autolink_www, + &char_superscript, + &char_quote, + &char_math }; struct hoedown_document { - hoedown_renderer md; - hoedown_renderer_data data; - - struct link_ref *refs[REF_TABLE_SIZE]; - struct footnote_list footnotes_found; - struct footnote_list footnotes_used; - uint8_t active_char[256]; - hoedown_stack work_bufs[2]; - hoedown_extensions ext_flags; - size_t max_nesting; - int in_link_body; + hoedown_renderer md; + hoedown_renderer_data data; + + struct link_ref *refs[REF_TABLE_SIZE]; + struct footnote_list footnotes_found; + struct footnote_list footnotes_used; + uint8_t active_char[256]; + hoedown_stack work_bufs[2]; + hoedown_extensions ext_flags; + size_t max_nesting; + int in_link_body; }; /*************************** @@ -135,177 +135,177 @@ struct hoedown_document { static hoedown_buffer * newbuf(hoedown_document *doc, int type) { - static const size_t buf_size[2] = {256, 64}; - hoedown_buffer *work = NULL; - hoedown_stack *pool = &doc->work_bufs[type]; + static const size_t buf_size[2] = {256, 64}; + hoedown_buffer *work = NULL; + hoedown_stack *pool = &doc->work_bufs[type]; - if (pool->size < pool->asize && - pool->item[pool->size] != NULL) { - work = pool->item[pool->size++]; - work->size = 0; - } else { - work = hoedown_buffer_new(buf_size[type]); - hoedown_stack_push(pool, work); - } + if (pool->size < pool->asize && + pool->item[pool->size] != NULL) { + work = pool->item[pool->size++]; + work->size = 0; + } else { + work = hoedown_buffer_new(buf_size[type]); + hoedown_stack_push(pool, work); + } - return work; + return work; } static void popbuf(hoedown_document *doc, int type) { - doc->work_bufs[type].size--; + doc->work_bufs[type].size--; } static void unscape_text(hoedown_buffer *ob, hoedown_buffer *src) { - size_t i = 0, org; - while (i < src->size) { - org = i; - while (i < src->size && src->data[i] != '\\') - i++; + size_t i = 0, org; + while (i < src->size) { + org = i; + while (i < src->size && src->data[i] != '\\') + i++; - if (i > org) - hoedown_buffer_put(ob, src->data + org, i - org); + if (i > org) + hoedown_buffer_put(ob, src->data + org, i - org); - if (i + 1 >= src->size) - break; + if (i + 1 >= src->size) + break; - hoedown_buffer_putc(ob, src->data[i + 1]); - i += 2; - } + hoedown_buffer_putc(ob, src->data[i + 1]); + i += 2; + } } static unsigned int hash_link_ref(const uint8_t *link_ref, size_t length) { - size_t i; - unsigned int hash = 0; + size_t i; + unsigned int hash = 0; - for (i = 0; i < length; ++i) - hash = tolower(link_ref[i]) + (hash << 6) + (hash << 16) - hash; + for (i = 0; i < length; ++i) + hash = tolower(link_ref[i]) + (hash << 6) + (hash << 16) - hash; - return hash; + return hash; } static struct link_ref * add_link_ref( - struct link_ref **references, - const uint8_t *name, size_t name_size) + struct link_ref **references, + const uint8_t *name, size_t name_size) { - struct link_ref *ref = hoedown_calloc(1, sizeof(struct link_ref)); + struct link_ref *ref = hoedown_calloc(1, sizeof(struct link_ref)); - ref->id = hash_link_ref(name, name_size); - ref->next = references[ref->id % REF_TABLE_SIZE]; + ref->id = hash_link_ref(name, name_size); + ref->next = references[ref->id % REF_TABLE_SIZE]; - references[ref->id % REF_TABLE_SIZE] = ref; - return ref; + references[ref->id % REF_TABLE_SIZE] = ref; + return ref; } static struct link_ref * find_link_ref(struct link_ref **references, uint8_t *name, size_t length) { - unsigned int hash = hash_link_ref(name, length); - struct link_ref *ref = NULL; + unsigned int hash = hash_link_ref(name, length); + struct link_ref *ref = NULL; - ref = references[hash % REF_TABLE_SIZE]; + ref = references[hash % REF_TABLE_SIZE]; - while (ref != NULL) { - if (ref->id == hash) - return ref; + while (ref != NULL) { + if (ref->id == hash) + return ref; - ref = ref->next; - } + ref = ref->next; + } - return NULL; + return NULL; } static void free_link_refs(struct link_ref **references) { - size_t i; + size_t i; - for (i = 0; i < REF_TABLE_SIZE; ++i) { - struct link_ref *r = references[i]; - struct link_ref *next; + for (i = 0; i < REF_TABLE_SIZE; ++i) { + struct link_ref *r = references[i]; + struct link_ref *next; - while (r) { - next = r->next; - hoedown_buffer_free(r->link); - hoedown_buffer_free(r->title); - free(r); - r = next; - } - } + while (r) { + next = r->next; + hoedown_buffer_free(r->link); + hoedown_buffer_free(r->title); + free(r); + r = next; + } + } } static struct footnote_ref * create_footnote_ref(struct footnote_list *list, const uint8_t *name, size_t name_size) { - struct footnote_ref *ref = hoedown_calloc(1, sizeof(struct footnote_ref)); + struct footnote_ref *ref = hoedown_calloc(1, sizeof(struct footnote_ref)); - ref->id = hash_link_ref(name, name_size); + ref->id = hash_link_ref(name, name_size); - return ref; + return ref; } static int add_footnote_ref(struct footnote_list *list, struct footnote_ref *ref) { - struct footnote_item *item = hoedown_calloc(1, sizeof(struct footnote_item)); - if (!item) - return 0; - item->ref = ref; + struct footnote_item *item = hoedown_calloc(1, sizeof(struct footnote_item)); + if (!item) + return 0; + item->ref = ref; - if (list->head == NULL) { - list->head = list->tail = item; - } else { - list->tail->next = item; - list->tail = item; - } - list->count++; + if (list->head == NULL) { + list->head = list->tail = item; + } else { + list->tail->next = item; + list->tail = item; + } + list->count++; - return 1; + return 1; } static struct footnote_ref * find_footnote_ref(struct footnote_list *list, uint8_t *name, size_t length) { - unsigned int hash = hash_link_ref(name, length); - struct footnote_item *item = NULL; + unsigned int hash = hash_link_ref(name, length); + struct footnote_item *item = NULL; - item = list->head; + item = list->head; - while (item != NULL) { - if (item->ref->id == hash) - return item->ref; - item = item->next; - } + while (item != NULL) { + if (item->ref->id == hash) + return item->ref; + item = item->next; + } - return NULL; + return NULL; } static void free_footnote_ref(struct footnote_ref *ref) { - hoedown_buffer_free(ref->contents); - free(ref); + hoedown_buffer_free(ref->contents); + free(ref); } static void free_footnote_list(struct footnote_list *list, int free_refs) { - struct footnote_item *item = list->head; - struct footnote_item *next; + struct footnote_item *item = list->head; + struct footnote_item *next; - while (item) { - next = item->next; - if (free_refs) - free_footnote_ref(item->ref); - free(item); - item = next; - } + while (item) { + next = item->next; + if (free_refs) + free_footnote_ref(item->ref); + free(item); + item = next; + } } @@ -323,16 +323,16 @@ free_footnote_list(struct footnote_list *list, int free_refs) static int _isspace(int c) { - return c == ' ' || c == '\n'; + return c == ' ' || c == '\n'; } /* is_empty_all: verify that all the data is spacing */ static int is_empty_all(const uint8_t *data, size_t size) { - size_t i = 0; - while (i < size && _isspace(data[i])) i++; - return i == size; + size_t i = 0; + while (i < size && _isspace(data[i])) i++; + return i == size; } /* @@ -342,19 +342,19 @@ is_empty_all(const uint8_t *data, size_t size) static void replace_spacing(hoedown_buffer *ob, const uint8_t *data, size_t size) { - size_t i = 0, mark; - hoedown_buffer_grow(ob, size); - while (1) { - mark = i; - while (i < size && data[i] != '\n') i++; - hoedown_buffer_put(ob, data + mark, i - mark); + size_t i = 0, mark; + hoedown_buffer_grow(ob, size); + while (1) { + mark = i; + while (i < size && data[i] != '\n') i++; + hoedown_buffer_put(ob, data + mark, i - mark); - if (i >= size) break; + if (i >= size) break; - if (!(i > 0 && data[i-1] == ' ')) - hoedown_buffer_putc(ob, ' '); - i++; - } + if (!(i > 0 && data[i-1] == ' ')) + hoedown_buffer_putc(ob, ' '); + i++; + } } /**************************** @@ -366,237 +366,237 @@ replace_spacing(hoedown_buffer *ob, const uint8_t *data, size_t size) static size_t is_mail_autolink(uint8_t *data, size_t size) { - size_t i = 0, nb = 0; + size_t i = 0, nb = 0; - /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */ - for (i = 0; i < size; ++i) { - if (isalnum(data[i])) - continue; + /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */ + for (i = 0; i < size; ++i) { + if (isalnum(data[i])) + continue; - switch (data[i]) { - case '@': - nb++; + switch (data[i]) { + case '@': + nb++; - case '-': - case '.': - case '_': - break; + case '-': + case '.': + case '_': + break; - case '>': - return (nb == 1) ? i + 1 : 0; + case '>': + return (nb == 1) ? i + 1 : 0; - default: - return 0; - } - } + default: + return 0; + } + } - return 0; + return 0; } /* tag_length • returns the length of the given tag, or 0 is it's not valid */ static size_t tag_length(uint8_t *data, size_t size, hoedown_autolink_type *autolink) { - size_t i, j; + size_t i, j; - /* a valid tag can't be shorter than 3 chars */ - if (size < 3) return 0; + /* a valid tag can't be shorter than 3 chars */ + if (size < 3) return 0; - /* begins with a '<' optionally followed by '/', followed by letter or number */ - if (data[0] != '<') return 0; - i = (data[1] == '/') ? 2 : 1; + /* begins with a '<' optionally followed by '/', followed by letter or number */ + if (data[0] != '<') return 0; + i = (data[1] == '/') ? 2 : 1; - if (!isalnum(data[i])) - return 0; + if (!isalnum(data[i])) + return 0; - /* scheme test */ - *autolink = HOEDOWN_AUTOLINK_NONE; + /* scheme test */ + *autolink = HOEDOWN_AUTOLINK_NONE; - /* try to find the beginning of an URI */ - while (i < size && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-')) - i++; + /* try to find the beginning of an URI */ + while (i < size && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-')) + i++; - if (i > 1 && data[i] == '@') { - if ((j = is_mail_autolink(data + i, size - i)) != 0) { - *autolink = HOEDOWN_AUTOLINK_EMAIL; - return i + j; - } - } + if (i > 1 && data[i] == '@') { + if ((j = is_mail_autolink(data + i, size - i)) != 0) { + *autolink = HOEDOWN_AUTOLINK_EMAIL; + return i + j; + } + } - if (i > 2 && data[i] == ':') { - *autolink = HOEDOWN_AUTOLINK_NORMAL; - i++; - } + if (i > 2 && data[i] == ':') { + *autolink = HOEDOWN_AUTOLINK_NORMAL; + i++; + } - /* completing autolink test: no spacing or ' or " */ - if (i >= size) - *autolink = HOEDOWN_AUTOLINK_NONE; + /* completing autolink test: no spacing or ' or " */ + if (i >= size) + *autolink = HOEDOWN_AUTOLINK_NONE; - else if (*autolink) { - j = i; + else if (*autolink) { + j = i; - while (i < size) { - if (data[i] == '\\') i += 2; - else if (data[i] == '>' || data[i] == '\'' || - data[i] == '"' || data[i] == ' ' || data[i] == '\n') - break; - else i++; - } + while (i < size) { + if (data[i] == '\\') i += 2; + else if (data[i] == '>' || data[i] == '\'' || + data[i] == '"' || data[i] == ' ' || data[i] == '\n') + break; + else i++; + } - if (i >= size) return 0; - if (i > j && data[i] == '>') return i + 1; - /* one of the forbidden chars has been found */ - *autolink = HOEDOWN_AUTOLINK_NONE; - } + if (i >= size) return 0; + if (i > j && data[i] == '>') return i + 1; + /* one of the forbidden chars has been found */ + *autolink = HOEDOWN_AUTOLINK_NONE; + } - /* looking for something looking like a tag end */ - while (i < size && data[i] != '>') i++; - if (i >= size) return 0; - return i + 1; + /* looking for something looking like a tag end */ + while (i < size && data[i] != '>') i++; + if (i >= size) return 0; + return i + 1; } /* parse_inline • parses inline markdown elements */ static void parse_inline(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size) { - size_t i = 0, end = 0, consumed = 0; - hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL }; - uint8_t *active_char = doc->active_char; - - if (doc->work_bufs[BUFFER_SPAN].size + - doc->work_bufs[BUFFER_BLOCK].size > doc->max_nesting) - return; - - while (i < size) { - /* copying inactive chars into the output */ - while (end < size && active_char[data[end]] == 0) - end++; - - if (doc->md.normal_text) { - work.data = data + i; - work.size = end - i; - doc->md.normal_text(ob, &work, &doc->data); - } - else - hoedown_buffer_put(ob, data + i, end - i); - - if (end >= size) break; - i = end; - - end = markdown_char_ptrs[ (int)active_char[data[end]] ](ob, doc, data + i, i - consumed, size - i); - if (!end) /* no action from the callback */ - end = i + 1; - else { - i += end; - end = i; - consumed = i; - } - } + size_t i = 0, end = 0, consumed = 0; + hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL }; + uint8_t *active_char = doc->active_char; + + if (doc->work_bufs[BUFFER_SPAN].size + + doc->work_bufs[BUFFER_BLOCK].size > doc->max_nesting) + return; + + while (i < size) { + /* copying inactive chars into the output */ + while (end < size && active_char[data[end]] == 0) + end++; + + if (doc->md.normal_text) { + work.data = data + i; + work.size = end - i; + doc->md.normal_text(ob, &work, &doc->data); + } + else + hoedown_buffer_put(ob, data + i, end - i); + + if (end >= size) break; + i = end; + + end = markdown_char_ptrs[ (int)active_char[data[end]] ](ob, doc, data + i, i - consumed, size - i); + if (!end) /* no action from the callback */ + end = i + 1; + else { + i += end; + end = i; + consumed = i; + } + } } /* is_escaped • returns whether special char at data[loc] is escaped by '\\' */ static int is_escaped(uint8_t *data, size_t loc) { - size_t i = loc; - while (i >= 1 && data[i - 1] == '\\') - i--; + size_t i = loc; + while (i >= 1 && data[i - 1] == '\\') + i--; - /* odd numbers of backslashes escapes data[loc] */ - return (loc - i) % 2; + /* odd numbers of backslashes escapes data[loc] */ + return (loc - i) % 2; } /* find_emph_char • looks for the next emph uint8_t, skipping other constructs */ static size_t find_emph_char(uint8_t *data, size_t size, uint8_t c) { - size_t i = 0; + size_t i = 0; - while (i < size) { - while (i < size && data[i] != c && data[i] != '[' && data[i] != '`') - i++; + while (i < size) { + while (i < size && data[i] != c && data[i] != '[' && data[i] != '`') + i++; - if (i == size) - return 0; + if (i == size) + return 0; - /* not counting escaped chars */ - if (is_escaped(data, i)) { - i++; continue; - } + /* not counting escaped chars */ + if (is_escaped(data, i)) { + i++; continue; + } - if (data[i] == c) - return i; + if (data[i] == c) + return i; - /* skipping a codespan */ - if (data[i] == '`') { - size_t span_nb = 0, bt; - size_t tmp_i = 0; + /* skipping a codespan */ + if (data[i] == '`') { + size_t span_nb = 0, bt; + size_t tmp_i = 0; - /* counting the number of opening backticks */ - while (i < size && data[i] == '`') { - i++; span_nb++; - } + /* counting the number of opening backticks */ + while (i < size && data[i] == '`') { + i++; span_nb++; + } - if (i >= size) return 0; + if (i >= size) return 0; - /* finding the matching closing sequence */ - bt = 0; - while (i < size && bt < span_nb) { - if (!tmp_i && data[i] == c) tmp_i = i; - if (data[i] == '`') bt++; - else bt = 0; - i++; - } + /* finding the matching closing sequence */ + bt = 0; + while (i < size && bt < span_nb) { + if (!tmp_i && data[i] == c) tmp_i = i; + if (data[i] == '`') bt++; + else bt = 0; + i++; + } - /* not a well-formed codespan; use found matching emph char */ - if (i >= size) return tmp_i; - } - /* skipping a link */ - else if (data[i] == '[') { - size_t tmp_i = 0; - uint8_t cc; + /* not a well-formed codespan; use found matching emph char */ + if (i >= size) return tmp_i; + } + /* skipping a link */ + else if (data[i] == '[') { + size_t tmp_i = 0; + uint8_t cc; - i++; - while (i < size && data[i] != ']') { - if (!tmp_i && data[i] == c) tmp_i = i; - i++; - } + i++; + while (i < size && data[i] != ']') { + if (!tmp_i && data[i] == c) tmp_i = i; + i++; + } - i++; - while (i < size && _isspace(data[i])) - i++; + i++; + while (i < size && _isspace(data[i])) + i++; - if (i >= size) - return tmp_i; + if (i >= size) + return tmp_i; - switch (data[i]) { - case '[': - cc = ']'; break; + switch (data[i]) { + case '[': + cc = ']'; break; - case '(': - cc = ')'; break; + case '(': + cc = ')'; break; - default: - if (tmp_i) - return tmp_i; - else - continue; - } + default: + if (tmp_i) + return tmp_i; + else + continue; + } - i++; - while (i < size && data[i] != cc) { - if (!tmp_i && data[i] == c) tmp_i = i; - i++; - } + i++; + while (i < size && data[i] != cc) { + if (!tmp_i && data[i] == c) tmp_i = i; + i++; + } - if (i >= size) - return tmp_i; + if (i >= size) + return tmp_i; - i++; - } - } + i++; + } + } - return 0; + return 0; } /* parse_emph1 • parsing single emphase */ @@ -604,72 +604,72 @@ find_emph_char(uint8_t *data, size_t size, uint8_t c) static size_t parse_emph1(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c) { - size_t i = 0, len; - hoedown_buffer *work = 0; - int r; + size_t i = 0, len; + hoedown_buffer *work = 0; + int r; - /* skipping one symbol if coming from emph3 */ - if (size > 1 && data[0] == c && data[1] == c) i = 1; + /* skipping one symbol if coming from emph3 */ + if (size > 1 && data[0] == c && data[1] == c) i = 1; - while (i < size) { - len = find_emph_char(data + i, size - i, c); - if (!len) return 0; - i += len; - if (i >= size) return 0; + while (i < size) { + len = find_emph_char(data + i, size - i, c); + if (!len) return 0; + i += len; + if (i >= size) return 0; - if (data[i] == c && !_isspace(data[i - 1])) { + if (data[i] == c && !_isspace(data[i - 1])) { - if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) { - if (i + 1 < size && isalnum(data[i + 1])) - continue; - } + if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) { + if (i + 1 < size && isalnum(data[i + 1])) + continue; + } - work = newbuf(doc, BUFFER_SPAN); - parse_inline(work, doc, data, i); + work = newbuf(doc, BUFFER_SPAN); + parse_inline(work, doc, data, i); - if (doc->ext_flags & HOEDOWN_EXT_UNDERLINE && c == '_') - r = doc->md.underline(ob, work, &doc->data); - else - r = doc->md.emphasis(ob, work, &doc->data); + if (doc->ext_flags & HOEDOWN_EXT_UNDERLINE && c == '_') + r = doc->md.underline(ob, work, &doc->data); + else + r = doc->md.emphasis(ob, work, &doc->data); - popbuf(doc, BUFFER_SPAN); - return r ? i + 1 : 0; - } - } + popbuf(doc, BUFFER_SPAN); + return r ? i + 1 : 0; + } + } - return 0; + return 0; } /* parse_emph2 • parsing single emphase */ static size_t parse_emph2(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c) { - size_t i = 0, len; - hoedown_buffer *work = 0; - int r; + size_t i = 0, len; + hoedown_buffer *work = 0; + int r; - while (i < size) { - len = find_emph_char(data + i, size - i, c); - if (!len) return 0; - i += len; + while (i < size) { + len = find_emph_char(data + i, size - i, c); + if (!len) return 0; + i += len; - if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) { - work = newbuf(doc, BUFFER_SPAN); - parse_inline(work, doc, data, i); + if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) { + work = newbuf(doc, BUFFER_SPAN); + parse_inline(work, doc, data, i); - if (c == '~') - r = doc->md.strikethrough(ob, work, &doc->data); - else if (c == '=') - r = doc->md.highlight(ob, work, &doc->data); - else - r = doc->md.double_emphasis(ob, work, &doc->data); + if (c == '~') + r = doc->md.strikethrough(ob, work, &doc->data); + else if (c == '=') + r = doc->md.highlight(ob, work, &doc->data); + else + r = doc->md.double_emphasis(ob, work, &doc->data); - popbuf(doc, BUFFER_SPAN); - return r ? i + 2 : 0; - } - i++; - } - return 0; + popbuf(doc, BUFFER_SPAN); + return r ? i + 2 : 0; + } + i++; + } + return 0; } /* parse_emph3 • parsing single emphase */ @@ -677,121 +677,121 @@ parse_emph2(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t siz static size_t parse_emph3(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c) { - size_t i = 0, len; - int r; + size_t i = 0, len; + int r; - while (i < size) { - len = find_emph_char(data + i, size - i, c); - if (!len) return 0; - i += len; + while (i < size) { + len = find_emph_char(data + i, size - i, c); + if (!len) return 0; + i += len; - /* skip spacing preceded symbols */ - if (data[i] != c || _isspace(data[i - 1])) - continue; + /* skip spacing preceded symbols */ + if (data[i] != c || _isspace(data[i - 1])) + continue; - if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && doc->md.triple_emphasis) { - /* triple symbol found */ - hoedown_buffer *work = newbuf(doc, BUFFER_SPAN); + if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && doc->md.triple_emphasis) { + /* triple symbol found */ + hoedown_buffer *work = newbuf(doc, BUFFER_SPAN); - parse_inline(work, doc, data, i); - r = doc->md.triple_emphasis(ob, work, &doc->data); - popbuf(doc, BUFFER_SPAN); - return r ? i + 3 : 0; + parse_inline(work, doc, data, i); + r = doc->md.triple_emphasis(ob, work, &doc->data); + popbuf(doc, BUFFER_SPAN); + return r ? i + 3 : 0; - } else if (i + 1 < size && data[i + 1] == c) { - /* double symbol found, handing over to emph1 */ - len = parse_emph1(ob, doc, data - 2, size + 2, c); - if (!len) return 0; - else return len - 2; + } else if (i + 1 < size && data[i + 1] == c) { + /* double symbol found, handing over to emph1 */ + len = parse_emph1(ob, doc, data - 2, size + 2, c); + if (!len) return 0; + else return len - 2; - } else { - /* single symbol found, handing over to emph2 */ - len = parse_emph2(ob, doc, data - 1, size + 1, c); - if (!len) return 0; - else return len - 1; - } - } - return 0; + } else { + /* single symbol found, handing over to emph2 */ + len = parse_emph2(ob, doc, data - 1, size + 1, c); + if (!len) return 0; + else return len - 1; + } + } + return 0; } /* parse_math • parses a math span until the given ending delimiter */ static size_t parse_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size, const char *end, size_t delimsz, int displaymode) { - hoedown_buffer text = { NULL, 0, 0, 0, NULL, NULL, NULL }; - size_t i = delimsz; + hoedown_buffer text = { NULL, 0, 0, 0, NULL, NULL, NULL }; + size_t i = delimsz; - if (!doc->md.math) - return 0; + if (!doc->md.math) + return 0; - /* find ending delimiter */ - while (1) { - while (i < size && data[i] != (uint8_t)end[0]) - i++; + /* find ending delimiter */ + while (1) { + while (i < size && data[i] != (uint8_t)end[0]) + i++; - if (i >= size) - return 0; + if (i >= size) + return 0; - if (!is_escaped(data, i) && !(i + delimsz > size) - && memcmp(data + i, end, delimsz) == 0) - break; + if (!is_escaped(data, i) && !(i + delimsz > size) + && memcmp(data + i, end, delimsz) == 0) + break; - i++; - } + i++; + } - /* prepare buffers */ - text.data = data + delimsz; - text.size = i - delimsz; + /* prepare buffers */ + text.data = data + delimsz; + text.size = i - delimsz; - /* if this is a $$ and MATH_EXPLICIT is not active, - * guess whether displaymode should be enabled from the context */ - i += delimsz; - if (delimsz == 2 && !(doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT)) - displaymode = is_empty_all(data - offset, offset) && is_empty_all(data + i, size - i); + /* if this is a $$ and MATH_EXPLICIT is not active, + * guess whether displaymode should be enabled from the context */ + i += delimsz; + if (delimsz == 2 && !(doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT)) + displaymode = is_empty_all(data - offset, offset) && is_empty_all(data + i, size - i); - /* call callback */ - if (doc->md.math(ob, &text, displaymode, &doc->data)) - return i; + /* call callback */ + if (doc->md.math(ob, &text, displaymode, &doc->data)) + return i; - return 0; + return 0; } /* char_emphasis • single and double emphasis parsing */ static size_t char_emphasis(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - uint8_t c = data[0]; - size_t ret; + uint8_t c = data[0]; + size_t ret; - if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) { - if (offset > 0 && !_isspace(data[-1]) && data[-1] != '>' && data[-1] != '(') - return 0; - } + if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) { + if (offset > 0 && !_isspace(data[-1]) && data[-1] != '>' && data[-1] != '(') + return 0; + } - if (size > 2 && data[1] != c) { - /* spacing cannot follow an opening emphasis; - * strikethrough and highlight only takes two characters '~~' */ - if (c == '~' || c == '=' || _isspace(data[1]) || (ret = parse_emph1(ob, doc, data + 1, size - 1, c)) == 0) - return 0; + if (size > 2 && data[1] != c) { + /* spacing cannot follow an opening emphasis; + * strikethrough and highlight only takes two characters '~~' */ + if (c == '~' || c == '=' || _isspace(data[1]) || (ret = parse_emph1(ob, doc, data + 1, size - 1, c)) == 0) + return 0; - return ret + 1; - } + return ret + 1; + } - if (size > 3 && data[1] == c && data[2] != c) { - if (_isspace(data[2]) || (ret = parse_emph2(ob, doc, data + 2, size - 2, c)) == 0) - return 0; + if (size > 3 && data[1] == c && data[2] != c) { + if (_isspace(data[2]) || (ret = parse_emph2(ob, doc, data + 2, size - 2, c)) == 0) + return 0; - return ret + 2; - } + return ret + 2; + } - if (size > 4 && data[1] == c && data[2] == c && data[3] != c) { - if (c == '~' || c == '=' || _isspace(data[3]) || (ret = parse_emph3(ob, doc, data + 3, size - 3, c)) == 0) - return 0; + if (size > 4 && data[1] == c && data[2] == c && data[3] != c) { + if (c == '~' || c == '=' || _isspace(data[3]) || (ret = parse_emph3(ob, doc, data + 3, size - 3, c)) == 0) + return 0; - return ret + 3; - } + return ret + 3; + } - return 0; + return 0; } @@ -799,14 +799,14 @@ char_emphasis(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t o static size_t char_linebreak(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - if (offset < 2 || data[-1] != ' ' || data[-2] != ' ') - return 0; + if (offset < 2 || data[-1] != ' ' || data[-2] != ' ') + return 0; - /* removing the last space from ob and rendering */ - while (ob->size && ob->data[ob->size - 1] == ' ') - ob->size--; + /* removing the last space from ob and rendering */ + while (ob->size && ob->data[ob->size - 1] == ' ') + ob->size--; - return doc->md.linebreak(ob, &doc->data) ? 1 : 0; + return doc->md.linebreak(ob, &doc->data) ? 1 : 0; } @@ -814,91 +814,91 @@ char_linebreak(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t static size_t char_codespan(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; - size_t end, nb = 0, i, f_begin, f_end; + hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; + size_t end, nb = 0, i, f_begin, f_end; - /* counting the number of backticks in the delimiter */ - while (nb < size && data[nb] == '`') - nb++; + /* counting the number of backticks in the delimiter */ + while (nb < size && data[nb] == '`') + nb++; - /* finding the next delimiter */ - i = 0; - for (end = nb; end < size && i < nb; end++) { - if (data[end] == '`') i++; - else i = 0; - } + /* finding the next delimiter */ + i = 0; + for (end = nb; end < size && i < nb; end++) { + if (data[end] == '`') i++; + else i = 0; + } - if (i < nb && end >= size) - return 0; /* no matching delimiter */ + if (i < nb && end >= size) + return 0; /* no matching delimiter */ - /* trimming outside spaces */ - f_begin = nb; - while (f_begin < end && data[f_begin] == ' ') - f_begin++; + /* trimming outside spaces */ + f_begin = nb; + while (f_begin < end && data[f_begin] == ' ') + f_begin++; - f_end = end - nb; - while (f_end > nb && data[f_end-1] == ' ') - f_end--; + f_end = end - nb; + while (f_end > nb && data[f_end-1] == ' ') + f_end--; - /* real code span */ - if (f_begin < f_end) { - work.data = data + f_begin; - work.size = f_end - f_begin; + /* real code span */ + if (f_begin < f_end) { + work.data = data + f_begin; + work.size = f_end - f_begin; - if (!doc->md.codespan(ob, &work, &doc->data)) - end = 0; - } else { - if (!doc->md.codespan(ob, 0, &doc->data)) - end = 0; - } + if (!doc->md.codespan(ob, &work, &doc->data)) + end = 0; + } else { + if (!doc->md.codespan(ob, 0, &doc->data)) + end = 0; + } - return end; + return end; } /* char_quote • '"' parsing a quote */ static size_t char_quote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - size_t end, nq = 0, i, f_begin, f_end; + size_t end, nq = 0, i, f_begin, f_end; - /* counting the number of quotes in the delimiter */ - while (nq < size && data[nq] == '"') - nq++; + /* counting the number of quotes in the delimiter */ + while (nq < size && data[nq] == '"') + nq++; - /* finding the next delimiter */ - end = nq; - while (1) { - i = end; - end += find_emph_char(data + end, size - end, '"'); - if (end == i) return 0; /* no matching delimiter */ - i = end; - while (end < size && data[end] == '"' && end - i < nq) end++; - if (end - i >= nq) break; - } + /* finding the next delimiter */ + end = nq; + while (1) { + i = end; + end += find_emph_char(data + end, size - end, '"'); + if (end == i) return 0; /* no matching delimiter */ + i = end; + while (end < size && data[end] == '"' && end - i < nq) end++; + if (end - i >= nq) break; + } - /* trimming outside spaces */ - f_begin = nq; - while (f_begin < end && data[f_begin] == ' ') - f_begin++; + /* trimming outside spaces */ + f_begin = nq; + while (f_begin < end && data[f_begin] == ' ') + f_begin++; - f_end = end - nq; - while (f_end > nq && data[f_end-1] == ' ') - f_end--; + f_end = end - nq; + while (f_end > nq && data[f_end-1] == ' ') + f_end--; - /* real quote */ - if (f_begin < f_end) { - hoedown_buffer *work = newbuf(doc, BUFFER_SPAN); - parse_inline(work, doc, data + f_begin, f_end - f_begin); + /* real quote */ + if (f_begin < f_end) { + hoedown_buffer *work = newbuf(doc, BUFFER_SPAN); + parse_inline(work, doc, data + f_begin, f_end - f_begin); - if (!doc->md.quote(ob, work, &doc->data)) - end = 0; - popbuf(doc, BUFFER_SPAN); - } else { - if (!doc->md.quote(ob, 0, &doc->data)) - end = 0; - } + if (!doc->md.quote(ob, work, &doc->data)) + end = 0; + popbuf(doc, BUFFER_SPAN); + } else { + if (!doc->md.quote(ob, 0, &doc->data)) + end = 0; + } - return end; + return end; } @@ -906,32 +906,32 @@ char_quote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offs static size_t char_escape(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~=\"$"; - hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL }; - size_t w; + static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~=\"$"; + hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL }; + size_t w; - if (size > 1) { - if (data[1] == '\\' && (doc->ext_flags & HOEDOWN_EXT_MATH) && - size > 2 && (data[2] == '(' || data[2] == '[')) { - const char *end = (data[2] == '[') ? "\\\\]" : "\\\\)"; - w = parse_math(ob, doc, data, offset, size, end, 3, data[2] == '['); - if (w) return w; - } + if (size > 1) { + if (data[1] == '\\' && (doc->ext_flags & HOEDOWN_EXT_MATH) && + size > 2 && (data[2] == '(' || data[2] == '[')) { + const char *end = (data[2] == '[') ? "\\\\]" : "\\\\)"; + w = parse_math(ob, doc, data, offset, size, end, 3, data[2] == '['); + if (w) return w; + } - if (strchr(escape_chars, data[1]) == NULL) - return 0; + if (strchr(escape_chars, data[1]) == NULL) + return 0; - if (doc->md.normal_text) { - work.data = data + 1; - work.size = 1; - doc->md.normal_text(ob, &work, &doc->data); - } - else hoedown_buffer_putc(ob, data[1]); - } else if (size == 1) { - hoedown_buffer_putc(ob, data[0]); - } + if (doc->md.normal_text) { + work.data = data + 1; + work.size = 1; + doc->md.normal_text(ob, &work, &doc->data); + } + else hoedown_buffer_putc(ob, data[1]); + } else if (size == 1) { + hoedown_buffer_putc(ob, data[0]); + } - return 2; + return 2; } /* char_entity • '&' escaped when it doesn't belong to an entity */ @@ -939,403 +939,403 @@ char_escape(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t off static size_t char_entity(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - size_t end = 1; - hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL }; + size_t end = 1; + hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL }; - if (end < size && data[end] == '#') - end++; + if (end < size && data[end] == '#') + end++; - while (end < size && isalnum(data[end])) - end++; + while (end < size && isalnum(data[end])) + end++; - if (end < size && data[end] == ';') - end++; /* real entity */ - else - return 0; /* lone '&' */ + if (end < size && data[end] == ';') + end++; /* real entity */ + else + return 0; /* lone '&' */ - if (doc->md.entity) { - work.data = data; - work.size = end; - doc->md.entity(ob, &work, &doc->data); - } - else hoedown_buffer_put(ob, data, end); + if (doc->md.entity) { + work.data = data; + work.size = end; + doc->md.entity(ob, &work, &doc->data); + } + else hoedown_buffer_put(ob, data, end); - return end; + return end; } /* char_langle_tag • '<' when tags or autolinks are allowed */ static size_t char_langle_tag(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; - hoedown_autolink_type altype = HOEDOWN_AUTOLINK_NONE; - size_t end = tag_length(data, size, &altype); - int ret = 0; + hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; + hoedown_autolink_type altype = HOEDOWN_AUTOLINK_NONE; + size_t end = tag_length(data, size, &altype); + int ret = 0; - work.data = data; - work.size = end; + work.data = data; + work.size = end; - if (end > 2) { - if (doc->md.autolink && altype != HOEDOWN_AUTOLINK_NONE) { - hoedown_buffer *u_link = newbuf(doc, BUFFER_SPAN); - work.data = data + 1; - work.size = end - 2; - unscape_text(u_link, &work); - ret = doc->md.autolink(ob, u_link, altype, &doc->data); - popbuf(doc, BUFFER_SPAN); - } - else if (doc->md.raw_html) - ret = doc->md.raw_html(ob, &work, &doc->data); - } + if (end > 2) { + if (doc->md.autolink && altype != HOEDOWN_AUTOLINK_NONE) { + hoedown_buffer *u_link = newbuf(doc, BUFFER_SPAN); + work.data = data + 1; + work.size = end - 2; + unscape_text(u_link, &work); + ret = doc->md.autolink(ob, u_link, altype, &doc->data); + popbuf(doc, BUFFER_SPAN); + } + else if (doc->md.raw_html) + ret = doc->md.raw_html(ob, &work, &doc->data); + } - if (!ret) return 0; - else return end; + if (!ret) return 0; + else return end; } static size_t char_autolink_www(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - hoedown_buffer *link, *link_url, *link_text; - size_t link_len, rewind; + hoedown_buffer *link, *link_url, *link_text; + size_t link_len, rewind; - if (!doc->md.link || doc->in_link_body) - return 0; + if (!doc->md.link || doc->in_link_body) + return 0; - link = newbuf(doc, BUFFER_SPAN); + link = newbuf(doc, BUFFER_SPAN); - if ((link_len = hoedown_autolink__www(&rewind, link, data, offset, size, HOEDOWN_AUTOLINK_SHORT_DOMAINS)) > 0) { - link_url = newbuf(doc, BUFFER_SPAN); - HOEDOWN_BUFPUTSL(link_url, "http://"); - hoedown_buffer_put(link_url, link->data, link->size); + if ((link_len = hoedown_autolink__www(&rewind, link, data, offset, size, HOEDOWN_AUTOLINK_SHORT_DOMAINS)) > 0) { + link_url = newbuf(doc, BUFFER_SPAN); + HOEDOWN_BUFPUTSL(link_url, "http://"); + hoedown_buffer_put(link_url, link->data, link->size); - ob->size -= rewind; - if (doc->md.normal_text) { - link_text = newbuf(doc, BUFFER_SPAN); - doc->md.normal_text(link_text, link, &doc->data); - doc->md.link(ob, link_text, link_url, NULL, &doc->data); - popbuf(doc, BUFFER_SPAN); - } else { - doc->md.link(ob, link, link_url, NULL, &doc->data); - } - popbuf(doc, BUFFER_SPAN); - } + ob->size -= rewind; + if (doc->md.normal_text) { + link_text = newbuf(doc, BUFFER_SPAN); + doc->md.normal_text(link_text, link, &doc->data); + doc->md.link(ob, link_text, link_url, NULL, &doc->data); + popbuf(doc, BUFFER_SPAN); + } else { + doc->md.link(ob, link, link_url, NULL, &doc->data); + } + popbuf(doc, BUFFER_SPAN); + } - popbuf(doc, BUFFER_SPAN); - return link_len; + popbuf(doc, BUFFER_SPAN); + return link_len; } static size_t char_autolink_email(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - hoedown_buffer *link; - size_t link_len, rewind; + hoedown_buffer *link; + size_t link_len, rewind; - if (!doc->md.autolink || doc->in_link_body) - return 0; + if (!doc->md.autolink || doc->in_link_body) + return 0; - link = newbuf(doc, BUFFER_SPAN); + link = newbuf(doc, BUFFER_SPAN); - if ((link_len = hoedown_autolink__email(&rewind, link, data, offset, size, 0)) > 0) { - ob->size -= rewind; - doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_EMAIL, &doc->data); - } + if ((link_len = hoedown_autolink__email(&rewind, link, data, offset, size, 0)) > 0) { + ob->size -= rewind; + doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_EMAIL, &doc->data); + } - popbuf(doc, BUFFER_SPAN); - return link_len; + popbuf(doc, BUFFER_SPAN); + return link_len; } static size_t char_autolink_url(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - hoedown_buffer *link; - size_t link_len, rewind; + hoedown_buffer *link; + size_t link_len, rewind; - if (!doc->md.autolink || doc->in_link_body) - return 0; + if (!doc->md.autolink || doc->in_link_body) + return 0; - link = newbuf(doc, BUFFER_SPAN); + link = newbuf(doc, BUFFER_SPAN); - if ((link_len = hoedown_autolink__url(&rewind, link, data, offset, size, 0)) > 0) { - ob->size -= rewind; - doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_NORMAL, &doc->data); - } + if ((link_len = hoedown_autolink__url(&rewind, link, data, offset, size, 0)) > 0) { + ob->size -= rewind; + doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_NORMAL, &doc->data); + } - popbuf(doc, BUFFER_SPAN); - return link_len; + popbuf(doc, BUFFER_SPAN); + return link_len; } /* char_link • '[': parsing a link, a footnote or an image */ static size_t char_link(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - int is_img = (offset && data[-1] == '!' && !is_escaped(data - offset, offset - 1)); - int is_footnote = (doc->ext_flags & HOEDOWN_EXT_FOOTNOTES && data[1] == '^'); - size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0; - hoedown_buffer *content = NULL; - hoedown_buffer *link = NULL; - hoedown_buffer *title = NULL; - hoedown_buffer *u_link = NULL; - size_t org_work_size = doc->work_bufs[BUFFER_SPAN].size; - int ret = 0, in_title = 0, qtype = 0; - - /* checking whether the correct renderer exists */ - if ((is_footnote && !doc->md.footnote_ref) || (is_img && !doc->md.image) - || (!is_img && !is_footnote && !doc->md.link)) - goto cleanup; - - /* looking for the matching closing bracket */ - i += find_emph_char(data + i, size - i, ']'); - txt_e = i; - - if (i < size && data[i] == ']') i++; - else goto cleanup; - - /* footnote link */ - if (is_footnote) { - hoedown_buffer id = { NULL, 0, 0, 0, NULL, NULL, NULL }; - struct footnote_ref *fr; - - if (txt_e < 3) - goto cleanup; - - id.data = data + 2; - id.size = txt_e - 2; - - fr = find_footnote_ref(&doc->footnotes_found, id.data, id.size); - - /* mark footnote used */ - if (fr && !fr->is_used) { - if(!add_footnote_ref(&doc->footnotes_used, fr)) - goto cleanup; - fr->is_used = 1; - fr->num = doc->footnotes_used.count; - - /* render */ - if (doc->md.footnote_ref) - ret = doc->md.footnote_ref(ob, fr->num, &doc->data); - } - - goto cleanup; - } - - /* skip any amount of spacing */ - /* (this is much more laxist than original markdown syntax) */ - while (i < size && _isspace(data[i])) - i++; - - /* inline style link */ - if (i < size && data[i] == '(') { - size_t nb_p; - - /* skipping initial spacing */ - i++; - - while (i < size && _isspace(data[i])) - i++; - - link_b = i; - - /* looking for link end: ' " ) */ - /* Count the number of open parenthesis */ - nb_p = 0; - - while (i < size) { - if (data[i] == '\\') i += 2; - else if (data[i] == '(' && i != 0) { - nb_p++; i++; - } - else if (data[i] == ')') { - if (nb_p == 0) break; - else nb_p--; i++; - } else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break; - else i++; - } - - if (i >= size) goto cleanup; - link_e = i; - - /* looking for title end if present */ - if (data[i] == '\'' || data[i] == '"') { - qtype = data[i]; - in_title = 1; - i++; - title_b = i; - - while (i < size) { - if (data[i] == '\\') i += 2; - else if (data[i] == qtype) {in_title = 0; i++;} - else if ((data[i] == ')') && !in_title) break; - else i++; - } - - if (i >= size) goto cleanup; - - /* skipping spacing after title */ - title_e = i - 1; - while (title_e > title_b && _isspace(data[title_e])) - title_e--; - - /* checking for closing quote presence */ - if (data[title_e] != '\'' && data[title_e] != '"') { - title_b = title_e = 0; - link_e = i; - } - } - - /* remove spacing at the end of the link */ - while (link_e > link_b && _isspace(data[link_e - 1])) - link_e--; - - /* remove optional angle brackets around the link */ - if (data[link_b] == '<') link_b++; - if (data[link_e - 1] == '>') link_e--; - - /* building escaped link and title */ - if (link_e > link_b) { - link = newbuf(doc, BUFFER_SPAN); - hoedown_buffer_put(link, data + link_b, link_e - link_b); - } - - if (title_e > title_b) { - title = newbuf(doc, BUFFER_SPAN); - hoedown_buffer_put(title, data + title_b, title_e - title_b); - } - - i++; - } - - /* reference style link */ - else if (i < size && data[i] == '[') { - hoedown_buffer *id = newbuf(doc, BUFFER_SPAN); - struct link_ref *lr; - - /* looking for the id */ - i++; - link_b = i; - while (i < size && data[i] != ']') i++; - if (i >= size) goto cleanup; - link_e = i; - - /* finding the link_ref */ - if (link_b == link_e) - replace_spacing(id, data + 1, txt_e - 1); - else - hoedown_buffer_put(id, data + link_b, link_e - link_b); - - lr = find_link_ref(doc->refs, id->data, id->size); - if (!lr) - goto cleanup; - - /* keeping link and title from link_ref */ - link = lr->link; - title = lr->title; - i++; - } - - /* shortcut reference style link */ - else { - hoedown_buffer *id = newbuf(doc, BUFFER_SPAN); - struct link_ref *lr; - - /* crafting the id */ - replace_spacing(id, data + 1, txt_e - 1); - - /* finding the link_ref */ - lr = find_link_ref(doc->refs, id->data, id->size); - if (!lr) - goto cleanup; - - /* keeping link and title from link_ref */ - link = lr->link; - title = lr->title; - - /* rewinding the spacing */ - i = txt_e + 1; - } - - /* building content: img alt is kept, only link content is parsed */ - if (txt_e > 1) { - content = newbuf(doc, BUFFER_SPAN); - if (is_img) { - hoedown_buffer_put(content, data + 1, txt_e - 1); - } else { - /* disable autolinking when parsing inline the - * content of a link */ - doc->in_link_body = 1; - parse_inline(content, doc, data + 1, txt_e - 1); - doc->in_link_body = 0; - } - } - - if (link) { - u_link = newbuf(doc, BUFFER_SPAN); - unscape_text(u_link, link); - } - - /* calling the relevant rendering function */ - if (is_img) { - if (ob->size && ob->data[ob->size - 1] == '!') - ob->size -= 1; - - ret = doc->md.image(ob, u_link, title, content, &doc->data); - } else { - ret = doc->md.link(ob, content, u_link, title, &doc->data); - } - - /* cleanup */ + int is_img = (offset && data[-1] == '!' && !is_escaped(data - offset, offset - 1)); + int is_footnote = (doc->ext_flags & HOEDOWN_EXT_FOOTNOTES && data[1] == '^'); + size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0; + hoedown_buffer *content = NULL; + hoedown_buffer *link = NULL; + hoedown_buffer *title = NULL; + hoedown_buffer *u_link = NULL; + size_t org_work_size = doc->work_bufs[BUFFER_SPAN].size; + int ret = 0, in_title = 0, qtype = 0; + + /* checking whether the correct renderer exists */ + if ((is_footnote && !doc->md.footnote_ref) || (is_img && !doc->md.image) + || (!is_img && !is_footnote && !doc->md.link)) + goto cleanup; + + /* looking for the matching closing bracket */ + i += find_emph_char(data + i, size - i, ']'); + txt_e = i; + + if (i < size && data[i] == ']') i++; + else goto cleanup; + + /* footnote link */ + if (is_footnote) { + hoedown_buffer id = { NULL, 0, 0, 0, NULL, NULL, NULL }; + struct footnote_ref *fr; + + if (txt_e < 3) + goto cleanup; + + id.data = data + 2; + id.size = txt_e - 2; + + fr = find_footnote_ref(&doc->footnotes_found, id.data, id.size); + + /* mark footnote used */ + if (fr && !fr->is_used) { + if(!add_footnote_ref(&doc->footnotes_used, fr)) + goto cleanup; + fr->is_used = 1; + fr->num = doc->footnotes_used.count; + + /* render */ + if (doc->md.footnote_ref) + ret = doc->md.footnote_ref(ob, fr->num, &doc->data); + } + + goto cleanup; + } + + /* skip any amount of spacing */ + /* (this is much more laxist than original markdown syntax) */ + while (i < size && _isspace(data[i])) + i++; + + /* inline style link */ + if (i < size && data[i] == '(') { + size_t nb_p; + + /* skipping initial spacing */ + i++; + + while (i < size && _isspace(data[i])) + i++; + + link_b = i; + + /* looking for link end: ' " ) */ + /* Count the number of open parenthesis */ + nb_p = 0; + + while (i < size) { + if (data[i] == '\\') i += 2; + else if (data[i] == '(' && i != 0) { + nb_p++; i++; + } + else if (data[i] == ')') { + if (nb_p == 0) break; + else nb_p--; i++; + } else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break; + else i++; + } + + if (i >= size) goto cleanup; + link_e = i; + + /* looking for title end if present */ + if (data[i] == '\'' || data[i] == '"') { + qtype = data[i]; + in_title = 1; + i++; + title_b = i; + + while (i < size) { + if (data[i] == '\\') i += 2; + else if (data[i] == qtype) {in_title = 0; i++;} + else if ((data[i] == ')') && !in_title) break; + else i++; + } + + if (i >= size) goto cleanup; + + /* skipping spacing after title */ + title_e = i - 1; + while (title_e > title_b && _isspace(data[title_e])) + title_e--; + + /* checking for closing quote presence */ + if (data[title_e] != '\'' && data[title_e] != '"') { + title_b = title_e = 0; + link_e = i; + } + } + + /* remove spacing at the end of the link */ + while (link_e > link_b && _isspace(data[link_e - 1])) + link_e--; + + /* remove optional angle brackets around the link */ + if (data[link_b] == '<') link_b++; + if (data[link_e - 1] == '>') link_e--; + + /* building escaped link and title */ + if (link_e > link_b) { + link = newbuf(doc, BUFFER_SPAN); + hoedown_buffer_put(link, data + link_b, link_e - link_b); + } + + if (title_e > title_b) { + title = newbuf(doc, BUFFER_SPAN); + hoedown_buffer_put(title, data + title_b, title_e - title_b); + } + + i++; + } + + /* reference style link */ + else if (i < size && data[i] == '[') { + hoedown_buffer *id = newbuf(doc, BUFFER_SPAN); + struct link_ref *lr; + + /* looking for the id */ + i++; + link_b = i; + while (i < size && data[i] != ']') i++; + if (i >= size) goto cleanup; + link_e = i; + + /* finding the link_ref */ + if (link_b == link_e) + replace_spacing(id, data + 1, txt_e - 1); + else + hoedown_buffer_put(id, data + link_b, link_e - link_b); + + lr = find_link_ref(doc->refs, id->data, id->size); + if (!lr) + goto cleanup; + + /* keeping link and title from link_ref */ + link = lr->link; + title = lr->title; + i++; + } + + /* shortcut reference style link */ + else { + hoedown_buffer *id = newbuf(doc, BUFFER_SPAN); + struct link_ref *lr; + + /* crafting the id */ + replace_spacing(id, data + 1, txt_e - 1); + + /* finding the link_ref */ + lr = find_link_ref(doc->refs, id->data, id->size); + if (!lr) + goto cleanup; + + /* keeping link and title from link_ref */ + link = lr->link; + title = lr->title; + + /* rewinding the spacing */ + i = txt_e + 1; + } + + /* building content: img alt is kept, only link content is parsed */ + if (txt_e > 1) { + content = newbuf(doc, BUFFER_SPAN); + if (is_img) { + hoedown_buffer_put(content, data + 1, txt_e - 1); + } else { + /* disable autolinking when parsing inline the + * content of a link */ + doc->in_link_body = 1; + parse_inline(content, doc, data + 1, txt_e - 1); + doc->in_link_body = 0; + } + } + + if (link) { + u_link = newbuf(doc, BUFFER_SPAN); + unscape_text(u_link, link); + } + + /* calling the relevant rendering function */ + if (is_img) { + if (ob->size && ob->data[ob->size - 1] == '!') + ob->size -= 1; + + ret = doc->md.image(ob, u_link, title, content, &doc->data); + } else { + ret = doc->md.link(ob, content, u_link, title, &doc->data); + } + + /* cleanup */ cleanup: - doc->work_bufs[BUFFER_SPAN].size = (int)org_work_size; - return ret ? i : 0; + doc->work_bufs[BUFFER_SPAN].size = (int)org_work_size; + return ret ? i : 0; } static size_t char_superscript(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - size_t sup_start, sup_len; - hoedown_buffer *sup; + size_t sup_start, sup_len; + hoedown_buffer *sup; - if (!doc->md.superscript) - return 0; + if (!doc->md.superscript) + return 0; - if (size < 2) - return 0; + if (size < 2) + return 0; - if (data[1] == '(') { - sup_start = 2; - sup_len = find_emph_char(data + 2, size - 2, ')') + 2; + if (data[1] == '(') { + sup_start = 2; + sup_len = find_emph_char(data + 2, size - 2, ')') + 2; - if (sup_len == size) - return 0; - } else { - sup_start = sup_len = 1; + if (sup_len == size) + return 0; + } else { + sup_start = sup_len = 1; - while (sup_len < size && !_isspace(data[sup_len])) - sup_len++; - } + while (sup_len < size && !_isspace(data[sup_len])) + sup_len++; + } - if (sup_len - sup_start == 0) - return (sup_start == 2) ? 3 : 0; + if (sup_len - sup_start == 0) + return (sup_start == 2) ? 3 : 0; - sup = newbuf(doc, BUFFER_SPAN); - parse_inline(sup, doc, data + sup_start, sup_len - sup_start); - doc->md.superscript(ob, sup, &doc->data); - popbuf(doc, BUFFER_SPAN); + sup = newbuf(doc, BUFFER_SPAN); + parse_inline(sup, doc, data + sup_start, sup_len - sup_start); + doc->md.superscript(ob, sup, &doc->data); + popbuf(doc, BUFFER_SPAN); - return (sup_start == 2) ? sup_len + 1 : sup_len; + return (sup_start == 2) ? sup_len + 1 : sup_len; } static size_t char_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size) { - /* double dollar */ - if (size > 1 && data[1] == '$') - return parse_math(ob, doc, data, offset, size, "$$", 2, 1); + /* double dollar */ + if (size > 1 && data[1] == '$') + return parse_math(ob, doc, data, offset, size, "$$", 2, 1); - /* single dollar allowed only with MATH_EXPLICIT flag */ - if (doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT) - return parse_math(ob, doc, data, offset, size, "$", 1, 0); + /* single dollar allowed only with MATH_EXPLICIT flag */ + if (doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT) + return parse_math(ob, doc, data, offset, size, "$", 1, 0); - return 0; + return 0; } /********************************* @@ -1346,44 +1346,44 @@ char_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offse static size_t is_empty(const uint8_t *data, size_t size) { - size_t i; + size_t i; - for (i = 0; i < size && data[i] != '\n'; i++) - if (data[i] != ' ') - return 0; + for (i = 0; i < size && data[i] != '\n'; i++) + if (data[i] != ' ') + return 0; - return i + 1; + return i + 1; } /* is_hrule • returns whether a line is a horizontal rule */ static int is_hrule(uint8_t *data, size_t size) { - size_t i = 0, n = 0; - uint8_t c; + size_t i = 0, n = 0; + uint8_t c; - /* skipping initial spaces */ - if (size < 3) return 0; - if (data[0] == ' ') { i++; - if (data[1] == ' ') { i++; - if (data[2] == ' ') { i++; } } } + /* skipping initial spaces */ + if (size < 3) return 0; + if (data[0] == ' ') { i++; + if (data[1] == ' ') { i++; + if (data[2] == ' ') { i++; } } } - /* looking at the hrule uint8_t */ - if (i + 2 >= size - || (data[i] != '*' && data[i] != '-' && data[i] != '_')) - return 0; - c = data[i]; + /* looking at the hrule uint8_t */ + if (i + 2 >= size + || (data[i] != '*' && data[i] != '-' && data[i] != '_')) + return 0; + c = data[i]; - /* the whole line must be the char or space */ - while (i < size && data[i] != '\n') { - if (data[i] == c) n++; - else if (data[i] != ' ') - return 0; + /* the whole line must be the char or space */ + while (i < size && data[i] != '\n') { + if (data[i] == c) n++; + else if (data[i] != ' ') + return 0; - i++; - } + i++; + } - return n >= 3; + return n >= 3; } /* check if a line is a code fence; return the @@ -1392,239 +1392,239 @@ is_hrule(uint8_t *data, size_t size) static size_t is_codefence(uint8_t *data, size_t size, size_t *width, uint8_t *chr) { - size_t i = 0, n = 1; - uint8_t c; + size_t i = 0, n = 1; + uint8_t c; - /* skipping initial spaces */ - if (size < 3) - return 0; + /* skipping initial spaces */ + if (size < 3) + return 0; - if (data[0] == ' ') { i++; - if (data[1] == ' ') { i++; - if (data[2] == ' ') { i++; } } } + if (data[0] == ' ') { i++; + if (data[1] == ' ') { i++; + if (data[2] == ' ') { i++; } } } - /* looking at the hrule uint8_t */ - c = data[i]; - if (i + 2 >= size || !(c=='~' || c=='`')) - return 0; + /* looking at the hrule uint8_t */ + c = data[i]; + if (i + 2 >= size || !(c=='~' || c=='`')) + return 0; - /* the fence must be that same character */ - while (++i < size && data[i] == c) - ++n; + /* the fence must be that same character */ + while (++i < size && data[i] == c) + ++n; - if (n < 3) - return 0; + if (n < 3) + return 0; - if (width) *width = n; - if (chr) *chr = c; - return i; + if (width) *width = n; + if (chr) *chr = c; + return i; } /* expects single line, checks if it's a codefence and extracts language */ static size_t parse_codefence(uint8_t *data, size_t size, hoedown_buffer *lang, size_t *width, uint8_t *chr) { - size_t i, w, lang_start; + size_t i, w, lang_start; - i = w = is_codefence(data, size, width, chr); - if (i == 0) - return 0; + i = w = is_codefence(data, size, width, chr); + if (i == 0) + return 0; - while (i < size && _isspace(data[i])) - i++; + while (i < size && _isspace(data[i])) + i++; - lang_start = i; + lang_start = i; - while (i < size && !_isspace(data[i])) - i++; + while (i < size && !_isspace(data[i])) + i++; - lang->data = data + lang_start; - lang->size = i - lang_start; + lang->data = data + lang_start; + lang->size = i - lang_start; - /* Avoid parsing a codespan as a fence */ - i = lang_start + 2; - while (i < size && !(data[i] == *chr && data[i-1] == *chr && data[i-2] == *chr)) i++; - if (i < size) return 0; + /* Avoid parsing a codespan as a fence */ + i = lang_start + 2; + while (i < size && !(data[i] == *chr && data[i-1] == *chr && data[i-2] == *chr)) i++; + if (i < size) return 0; - return w; + return w; } /* is_atxheader • returns whether the line is a hash-prefixed header */ static int is_atxheader(hoedown_document *doc, uint8_t *data, size_t size) { - if (data[0] != '#') - return 0; + if (data[0] != '#') + return 0; - if (doc->ext_flags & HOEDOWN_EXT_SPACE_HEADERS) { - size_t level = 0; + if (doc->ext_flags & HOEDOWN_EXT_SPACE_HEADERS) { + size_t level = 0; - while (level < size && level < 6 && data[level] == '#') - level++; + while (level < size && level < 6 && data[level] == '#') + level++; - if (level < size && data[level] != ' ') - return 0; - } + if (level < size && data[level] != ' ') + return 0; + } - return 1; + return 1; } /* is_headerline • returns whether the line is a setext-style hdr underline */ static int is_headerline(uint8_t *data, size_t size) { - size_t i = 0; + size_t i = 0; - /* test of level 1 header */ - if (data[i] == '=') { - for (i = 1; i < size && data[i] == '='; i++); - while (i < size && data[i] == ' ') i++; - return (i >= size || data[i] == '\n') ? 1 : 0; } + /* test of level 1 header */ + if (data[i] == '=') { + for (i = 1; i < size && data[i] == '='; i++); + while (i < size && data[i] == ' ') i++; + return (i >= size || data[i] == '\n') ? 1 : 0; } - /* test of level 2 header */ - if (data[i] == '-') { - for (i = 1; i < size && data[i] == '-'; i++); - while (i < size && data[i] == ' ') i++; - return (i >= size || data[i] == '\n') ? 2 : 0; } + /* test of level 2 header */ + if (data[i] == '-') { + for (i = 1; i < size && data[i] == '-'; i++); + while (i < size && data[i] == ' ') i++; + return (i >= size || data[i] == '\n') ? 2 : 0; } - return 0; + return 0; } static int is_next_headerline(uint8_t *data, size_t size) { - size_t i = 0; + size_t i = 0; - while (i < size && data[i] != '\n') - i++; + while (i < size && data[i] != '\n') + i++; - if (++i >= size) - return 0; + if (++i >= size) + return 0; - return is_headerline(data + i, size - i); + return is_headerline(data + i, size - i); } /* prefix_quote • returns blockquote prefix length */ static size_t prefix_quote(uint8_t *data, size_t size) { - size_t i = 0; - if (i < size && data[i] == ' ') i++; - if (i < size && data[i] == ' ') i++; - if (i < size && data[i] == ' ') i++; + size_t i = 0; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; - if (i < size && data[i] == '>') { - if (i + 1 < size && data[i + 1] == ' ') - return i + 2; + if (i < size && data[i] == '>') { + if (i + 1 < size && data[i + 1] == ' ') + return i + 2; - return i + 1; - } + return i + 1; + } - return 0; + return 0; } /* prefix_code • returns prefix length for block code*/ static size_t prefix_code(uint8_t *data, size_t size) { - if (size > 3 && data[0] == ' ' && data[1] == ' ' - && data[2] == ' ' && data[3] == ' ') return 4; + if (size > 3 && data[0] == ' ' && data[1] == ' ' + && data[2] == ' ' && data[3] == ' ') return 4; - return 0; + return 0; } /* prefix_oli • returns ordered list item prefix */ static size_t prefix_oli(uint8_t *data, size_t size) { - size_t i = 0; + size_t i = 0; - if (i < size && data[i] == ' ') i++; - if (i < size && data[i] == ' ') i++; - if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; - if (i >= size || data[i] < '0' || data[i] > '9') - return 0; + if (i >= size || data[i] < '0' || data[i] > '9') + return 0; - while (i < size && data[i] >= '0' && data[i] <= '9') - i++; + while (i < size && data[i] >= '0' && data[i] <= '9') + i++; - if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ') - return 0; + if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ') + return 0; - if (is_next_headerline(data + i, size - i)) - return 0; + if (is_next_headerline(data + i, size - i)) + return 0; - return i + 2; + return i + 2; } /* prefix_uli • returns ordered list item prefix */ static size_t prefix_uli(uint8_t *data, size_t size) { - size_t i = 0; + size_t i = 0; - if (i < size && data[i] == ' ') i++; - if (i < size && data[i] == ' ') i++; - if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; - if (i + 1 >= size || - (data[i] != '*' && data[i] != '+' && data[i] != '-') || - data[i + 1] != ' ') - return 0; + if (i + 1 >= size || + (data[i] != '*' && data[i] != '+' && data[i] != '-') || + data[i + 1] != ' ') + return 0; - if (is_next_headerline(data + i, size - i)) - return 0; + if (is_next_headerline(data + i, size - i)) + return 0; - return i + 2; + return i + 2; } /* parse_block • parsing of one block, returning next uint8_t to parse */ static void parse_block(hoedown_buffer *ob, hoedown_document *doc, - uint8_t *data, size_t size); + uint8_t *data, size_t size); /* parse_blockquote • handles parsing of a blockquote fragment */ static size_t parse_blockquote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size) { - size_t beg, end = 0, pre, work_size = 0; - uint8_t *work_data = 0; - hoedown_buffer *out = 0; + size_t beg, end = 0, pre, work_size = 0; + uint8_t *work_data = 0; + hoedown_buffer *out = 0; - out = newbuf(doc, BUFFER_BLOCK); - beg = 0; - while (beg < size) { - for (end = beg + 1; end < size && data[end - 1] != '\n'; end++); + out = newbuf(doc, BUFFER_BLOCK); + beg = 0; + while (beg < size) { + for (end = beg + 1; end < size && data[end - 1] != '\n'; end++); - pre = prefix_quote(data + beg, end - beg); + pre = prefix_quote(data + beg, end - beg); - if (pre) - beg += pre; /* skipping prefix */ + if (pre) + beg += pre; /* skipping prefix */ - /* empty line followed by non-quote line */ - else if (is_empty(data + beg, end - beg) && - (end >= size || (prefix_quote(data + end, size - end) == 0 && - !is_empty(data + end, size - end)))) - break; + /* empty line followed by non-quote line */ + else if (is_empty(data + beg, end - beg) && + (end >= size || (prefix_quote(data + end, size - end) == 0 && + !is_empty(data + end, size - end)))) + break; - if (beg < end) { /* copy into the in-place working buffer */ - /* hoedown_buffer_put(work, data + beg, end - beg); */ - if (!work_data) - work_data = data + beg; - else if (data + beg != work_data + work_size) - memmove(work_data + work_size, data + beg, end - beg); - work_size += end - beg; - } - beg = end; - } + if (beg < end) { /* copy into the in-place working buffer */ + /* hoedown_buffer_put(work, data + beg, end - beg); */ + if (!work_data) + work_data = data + beg; + else if (data + beg != work_data + work_size) + memmove(work_data + work_size, data + beg, end - beg); + work_size += end - beg; + } + beg = end; + } - parse_block(out, doc, work_data, work_size); - if (doc->md.blockquote) - doc->md.blockquote(ob, out, &doc->data); - popbuf(doc, BUFFER_BLOCK); - return end; + parse_block(out, doc, work_data, work_size); + if (doc->md.blockquote) + doc->md.blockquote(ob, out, &doc->data); + popbuf(doc, BUFFER_BLOCK); + return end; } static size_t @@ -1634,301 +1634,301 @@ parse_htmlblock(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t static size_t parse_paragraph(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size) { - hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; - size_t i = 0, end = 0; - int level = 0; + hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; + size_t i = 0, end = 0; + int level = 0; - work.data = data; + work.data = data; - while (i < size) { - for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */; + while (i < size) { + for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */; - if (is_empty(data + i, size - i)) - break; + if (is_empty(data + i, size - i)) + break; - if ((level = is_headerline(data + i, size - i)) != 0) - break; + if ((level = is_headerline(data + i, size - i)) != 0) + break; - if (is_atxheader(doc, data + i, size - i) || - is_hrule(data + i, size - i) || - prefix_quote(data + i, size - i)) { - end = i; - break; - } + if (is_atxheader(doc, data + i, size - i) || + is_hrule(data + i, size - i) || + prefix_quote(data + i, size - i)) { + end = i; + break; + } - i = end; - } + i = end; + } - work.size = i; - while (work.size && data[work.size - 1] == '\n') - work.size--; + work.size = i; + while (work.size && data[work.size - 1] == '\n') + work.size--; - if (!level) { - hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK); - parse_inline(tmp, doc, work.data, work.size); - if (doc->md.paragraph) - doc->md.paragraph(ob, tmp, &doc->data); - popbuf(doc, BUFFER_BLOCK); - } else { - hoedown_buffer *header_work; + if (!level) { + hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK); + parse_inline(tmp, doc, work.data, work.size); + if (doc->md.paragraph) + doc->md.paragraph(ob, tmp, &doc->data); + popbuf(doc, BUFFER_BLOCK); + } else { + hoedown_buffer *header_work; - if (work.size) { - size_t beg; - i = work.size; - work.size -= 1; + if (work.size) { + size_t beg; + i = work.size; + work.size -= 1; - while (work.size && data[work.size] != '\n') - work.size -= 1; + while (work.size && data[work.size] != '\n') + work.size -= 1; - beg = work.size + 1; - while (work.size && data[work.size - 1] == '\n') - work.size -= 1; + beg = work.size + 1; + while (work.size && data[work.size - 1] == '\n') + work.size -= 1; - if (work.size > 0) { - hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK); - parse_inline(tmp, doc, work.data, work.size); + if (work.size > 0) { + hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK); + parse_inline(tmp, doc, work.data, work.size); - if (doc->md.paragraph) - doc->md.paragraph(ob, tmp, &doc->data); + if (doc->md.paragraph) + doc->md.paragraph(ob, tmp, &doc->data); - popbuf(doc, BUFFER_BLOCK); - work.data += beg; - work.size = i - beg; - } - else work.size = i; - } + popbuf(doc, BUFFER_BLOCK); + work.data += beg; + work.size = i - beg; + } + else work.size = i; + } - header_work = newbuf(doc, BUFFER_SPAN); - parse_inline(header_work, doc, work.data, work.size); + header_work = newbuf(doc, BUFFER_SPAN); + parse_inline(header_work, doc, work.data, work.size); - if (doc->md.header) - doc->md.header(ob, header_work, (int)level, &doc->data); + if (doc->md.header) + doc->md.header(ob, header_work, (int)level, &doc->data); - popbuf(doc, BUFFER_SPAN); - } + popbuf(doc, BUFFER_SPAN); + } - return end; + return end; } /* parse_fencedcode • handles parsing of a block-level code fragment */ static size_t parse_fencedcode(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size) { - hoedown_buffer text = { 0, 0, 0, 0, NULL, NULL, NULL }; - hoedown_buffer lang = { 0, 0, 0, 0, NULL, NULL, NULL }; - size_t i = 0, text_start, line_start; - size_t w, w2; - size_t width, width2; - uint8_t chr, chr2; + hoedown_buffer text = { 0, 0, 0, 0, NULL, NULL, NULL }; + hoedown_buffer lang = { 0, 0, 0, 0, NULL, NULL, NULL }; + size_t i = 0, text_start, line_start; + size_t w, w2; + size_t width, width2; + uint8_t chr, chr2; - /* parse codefence line */ - while (i < size && data[i] != '\n') - i++; + /* parse codefence line */ + while (i < size && data[i] != '\n') + i++; - w = parse_codefence(data, i, &lang, &width, &chr); - if (!w) - return 0; + w = parse_codefence(data, i, &lang, &width, &chr); + if (!w) + return 0; - /* search for end */ - i++; - text_start = i; - while ((line_start = i) < size) { - while (i < size && data[i] != '\n') - i++; + /* search for end */ + i++; + text_start = i; + while ((line_start = i) < size) { + while (i < size && data[i] != '\n') + i++; - w2 = is_codefence(data + line_start, i - line_start, &width2, &chr2); - if (w == w2 && width == width2 && chr == chr2 && - is_empty(data + (line_start+w), i - (line_start+w))) - break; + w2 = is_codefence(data + line_start, i - line_start, &width2, &chr2); + if (w == w2 && width == width2 && chr == chr2 && + is_empty(data + (line_start+w), i - (line_start+w))) + break; - i++; - } + i++; + } - text.data = data + text_start; - text.size = line_start - text_start; + text.data = data + text_start; + text.size = line_start - text_start; - if (doc->md.blockcode) - doc->md.blockcode(ob, text.size ? &text : NULL, lang.size ? &lang : NULL, &doc->data); + if (doc->md.blockcode) + doc->md.blockcode(ob, text.size ? &text : NULL, lang.size ? &lang : NULL, &doc->data); - return i; + return i; } static size_t parse_blockcode(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size) { - size_t beg, end, pre; - hoedown_buffer *work = 0; + size_t beg, end, pre; + hoedown_buffer *work = 0; - work = newbuf(doc, BUFFER_BLOCK); + work = newbuf(doc, BUFFER_BLOCK); - beg = 0; - while (beg < size) { - for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {}; - pre = prefix_code(data + beg, end - beg); + beg = 0; + while (beg < size) { + for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {}; + pre = prefix_code(data + beg, end - beg); - if (pre) - beg += pre; /* skipping prefix */ - else if (!is_empty(data + beg, end - beg)) - /* non-empty non-prefixed line breaks the pre */ - break; + if (pre) + beg += pre; /* skipping prefix */ + else if (!is_empty(data + beg, end - beg)) + /* non-empty non-prefixed line breaks the pre */ + break; - if (beg < end) { - /* verbatim copy to the working buffer, - escaping entities */ - if (is_empty(data + beg, end - beg)) - hoedown_buffer_putc(work, '\n'); - else hoedown_buffer_put(work, data + beg, end - beg); - } - beg = end; - } + if (beg < end) { + /* verbatim copy to the working buffer, + escaping entities */ + if (is_empty(data + beg, end - beg)) + hoedown_buffer_putc(work, '\n'); + else hoedown_buffer_put(work, data + beg, end - beg); + } + beg = end; + } - while (work->size && work->data[work->size - 1] == '\n') - work->size -= 1; + while (work->size && work->data[work->size - 1] == '\n') + work->size -= 1; - hoedown_buffer_putc(work, '\n'); + hoedown_buffer_putc(work, '\n'); - if (doc->md.blockcode) - doc->md.blockcode(ob, work, NULL, &doc->data); + if (doc->md.blockcode) + doc->md.blockcode(ob, work, NULL, &doc->data); - popbuf(doc, BUFFER_BLOCK); - return beg; + popbuf(doc, BUFFER_BLOCK); + return beg; } /* parse_listitem • parsing of a single list item */ -/* assuming initial prefix is already removed */ +/* assuming initial prefix is already removed */ static size_t parse_listitem(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, hoedown_list_flags *flags) { - hoedown_buffer *work = 0, *inter = 0; - size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i; - int in_empty = 0, has_inside_empty = 0, in_fence = 0; - - /* keeping track of the first indentation prefix */ - while (orgpre < 3 && orgpre < size && data[orgpre] == ' ') - orgpre++; - - beg = prefix_uli(data, size); - if (!beg) - beg = prefix_oli(data, size); - - if (!beg) - return 0; - - /* skipping to the beginning of the following line */ - end = beg; - while (end < size && data[end - 1] != '\n') - end++; - - /* getting working buffers */ - work = newbuf(doc, BUFFER_SPAN); - inter = newbuf(doc, BUFFER_SPAN); - - /* putting the first line into the working buffer */ - hoedown_buffer_put(work, data + beg, end - beg); - beg = end; - - /* process the following lines */ - while (beg < size) { - size_t has_next_uli = 0, has_next_oli = 0; - - end++; - - while (end < size && data[end - 1] != '\n') - end++; - - /* process an empty line */ - if (is_empty(data + beg, end - beg)) { - in_empty = 1; - beg = end; - continue; - } - - /* calculating the indentation */ - i = 0; - while (i < 4 && beg + i < end && data[beg + i] == ' ') - i++; - - pre = i; - - if (doc->ext_flags & HOEDOWN_EXT_FENCED_CODE) { - if (is_codefence(data + beg + i, end - beg - i, NULL, NULL)) - in_fence = !in_fence; - } - - /* Only check for new list items if we are **not** inside - * a fenced code block */ - if (!in_fence) { - has_next_uli = prefix_uli(data + beg + i, end - beg - i); - has_next_oli = prefix_oli(data + beg + i, end - beg - i); - } - - /* checking for a new item */ - if ((has_next_uli && !is_hrule(data + beg + i, end - beg - i)) || has_next_oli) { - if (in_empty) - has_inside_empty = 1; - - /* the following item must have the same (or less) indentation */ - if (pre <= orgpre) { - /* if the following item has different list type, we end this list */ - if (in_empty && ( - ((*flags & HOEDOWN_LIST_ORDERED) && has_next_uli) || - (!(*flags & HOEDOWN_LIST_ORDERED) && has_next_oli))) - *flags |= HOEDOWN_LI_END; - - break; - } - - if (!sublist) - sublist = work->size; - } - /* joining only indented stuff after empty lines; - * note that now we only require 1 space of indentation - * to continue a list */ - else if (in_empty && pre == 0) { - *flags |= HOEDOWN_LI_END; - break; - } - - if (in_empty) { - hoedown_buffer_putc(work, '\n'); - has_inside_empty = 1; - in_empty = 0; - } - - /* adding the line without prefix into the working buffer */ - hoedown_buffer_put(work, data + beg + i, end - beg - i); - beg = end; - } - - /* render of li contents */ - if (has_inside_empty) - *flags |= HOEDOWN_LI_BLOCK; - - if (*flags & HOEDOWN_LI_BLOCK) { - /* intermediate render of block li */ - if (sublist && sublist < work->size) { - parse_block(inter, doc, work->data, sublist); - parse_block(inter, doc, work->data + sublist, work->size - sublist); - } - else - parse_block(inter, doc, work->data, work->size); - } else { - /* intermediate render of inline li */ - if (sublist && sublist < work->size) { - parse_inline(inter, doc, work->data, sublist); - parse_block(inter, doc, work->data + sublist, work->size - sublist); - } - else - parse_inline(inter, doc, work->data, work->size); - } - - /* render of li itself */ - if (doc->md.listitem) - doc->md.listitem(ob, inter, *flags, &doc->data); - - popbuf(doc, BUFFER_SPAN); - popbuf(doc, BUFFER_SPAN); - return beg; + hoedown_buffer *work = 0, *inter = 0; + size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i; + int in_empty = 0, has_inside_empty = 0, in_fence = 0; + + /* keeping track of the first indentation prefix */ + while (orgpre < 3 && orgpre < size && data[orgpre] == ' ') + orgpre++; + + beg = prefix_uli(data, size); + if (!beg) + beg = prefix_oli(data, size); + + if (!beg) + return 0; + + /* skipping to the beginning of the following line */ + end = beg; + while (end < size && data[end - 1] != '\n') + end++; + + /* getting working buffers */ + work = newbuf(doc, BUFFER_SPAN); + inter = newbuf(doc, BUFFER_SPAN); + + /* putting the first line into the working buffer */ + hoedown_buffer_put(work, data + beg, end - beg); + beg = end; + + /* process the following lines */ + while (beg < size) { + size_t has_next_uli = 0, has_next_oli = 0; + + end++; + + while (end < size && data[end - 1] != '\n') + end++; + + /* process an empty line */ + if (is_empty(data + beg, end - beg)) { + in_empty = 1; + beg = end; + continue; + } + + /* calculating the indentation */ + i = 0; + while (i < 4 && beg + i < end && data[beg + i] == ' ') + i++; + + pre = i; + + if (doc->ext_flags & HOEDOWN_EXT_FENCED_CODE) { + if (is_codefence(data + beg + i, end - beg - i, NULL, NULL)) + in_fence = !in_fence; + } + + /* Only check for new list items if we are **not** inside + * a fenced code block */ + if (!in_fence) { + has_next_uli = prefix_uli(data + beg + i, end - beg - i); + has_next_oli = prefix_oli(data + beg + i, end - beg - i); + } + + /* checking for a new item */ + if ((has_next_uli && !is_hrule(data + beg + i, end - beg - i)) || has_next_oli) { + if (in_empty) + has_inside_empty = 1; + + /* the following item must have the same (or less) indentation */ + if (pre <= orgpre) { + /* if the following item has different list type, we end this list */ + if (in_empty && ( + ((*flags & HOEDOWN_LIST_ORDERED) && has_next_uli) || + (!(*flags & HOEDOWN_LIST_ORDERED) && has_next_oli))) + *flags |= HOEDOWN_LI_END; + + break; + } + + if (!sublist) + sublist = work->size; + } + /* joining only indented stuff after empty lines; + * note that now we only require 1 space of indentation + * to continue a list */ + else if (in_empty && pre == 0) { + *flags |= HOEDOWN_LI_END; + break; + } + + if (in_empty) { + hoedown_buffer_putc(work, '\n'); + has_inside_empty = 1; + in_empty = 0; + } + + /* adding the line without prefix into the working buffer */ + hoedown_buffer_put(work, data + beg + i, end - beg - i); + beg = end; + } + + /* render of li contents */ + if (has_inside_empty) + *flags |= HOEDOWN_LI_BLOCK; + + if (*flags & HOEDOWN_LI_BLOCK) { + /* intermediate render of block li */ + if (sublist && sublist < work->size) { + parse_block(inter, doc, work->data, sublist); + parse_block(inter, doc, work->data + sublist, work->size - sublist); + } + else + parse_block(inter, doc, work->data, work->size); + } else { + /* intermediate render of inline li */ + if (sublist && sublist < work->size) { + parse_inline(inter, doc, work->data, sublist); + parse_block(inter, doc, work->data + sublist, work->size - sublist); + } + else + parse_inline(inter, doc, work->data, work->size); + } + + /* render of li itself */ + if (doc->md.listitem) + doc->md.listitem(ob, inter, *flags, &doc->data); + + popbuf(doc, BUFFER_SPAN); + popbuf(doc, BUFFER_SPAN); + return beg; } @@ -1936,460 +1936,460 @@ parse_listitem(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t static size_t parse_list(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, hoedown_list_flags flags) { - hoedown_buffer *work = 0; - size_t i = 0, j; + hoedown_buffer *work = 0; + size_t i = 0, j; - work = newbuf(doc, BUFFER_BLOCK); + work = newbuf(doc, BUFFER_BLOCK); - while (i < size) { - j = parse_listitem(work, doc, data + i, size - i, &flags); - i += j; + while (i < size) { + j = parse_listitem(work, doc, data + i, size - i, &flags); + i += j; - if (!j || (flags & HOEDOWN_LI_END)) - break; - } + if (!j || (flags & HOEDOWN_LI_END)) + break; + } - if (doc->md.list) - doc->md.list(ob, work, flags, &doc->data); - popbuf(doc, BUFFER_BLOCK); - return i; + if (doc->md.list) + doc->md.list(ob, work, flags, &doc->data); + popbuf(doc, BUFFER_BLOCK); + return i; } /* parse_atxheader • parsing of atx-style headers */ static size_t parse_atxheader(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size) { - size_t level = 0; - size_t i, end, skip; + size_t level = 0; + size_t i, end, skip; - while (level < size && level < 6 && data[level] == '#') - level++; + while (level < size && level < 6 && data[level] == '#') + level++; - for (i = level; i < size && data[i] == ' '; i++); + for (i = level; i < size && data[i] == ' '; i++); - for (end = i; end < size && data[end] != '\n'; end++); - skip = end; + for (end = i; end < size && data[end] != '\n'; end++); + skip = end; - while (end && data[end - 1] == '#') - end--; + while (end && data[end - 1] == '#') + end--; - while (end && data[end - 1] == ' ') - end--; + while (end && data[end - 1] == ' ') + end--; - if (end > i) { - hoedown_buffer *work = newbuf(doc, BUFFER_SPAN); + if (end > i) { + hoedown_buffer *work = newbuf(doc, BUFFER_SPAN); - parse_inline(work, doc, data + i, end - i); + parse_inline(work, doc, data + i, end - i); - if (doc->md.header) - doc->md.header(ob, work, (int)level, &doc->data); + if (doc->md.header) + doc->md.header(ob, work, (int)level, &doc->data); - popbuf(doc, BUFFER_SPAN); - } + popbuf(doc, BUFFER_SPAN); + } - return skip; + return skip; } /* parse_footnote_def • parse a single footnote definition */ static void parse_footnote_def(hoedown_buffer *ob, hoedown_document *doc, unsigned int num, uint8_t *data, size_t size) { - hoedown_buffer *work = 0; - work = newbuf(doc, BUFFER_SPAN); + hoedown_buffer *work = 0; + work = newbuf(doc, BUFFER_SPAN); - parse_block(work, doc, data, size); + parse_block(work, doc, data, size); - if (doc->md.footnote_def) - doc->md.footnote_def(ob, work, num, &doc->data); - popbuf(doc, BUFFER_SPAN); + if (doc->md.footnote_def) + doc->md.footnote_def(ob, work, num, &doc->data); + popbuf(doc, BUFFER_SPAN); } /* parse_footnote_list • render the contents of the footnotes */ static void parse_footnote_list(hoedown_buffer *ob, hoedown_document *doc, struct footnote_list *footnotes) { - hoedown_buffer *work = 0; - struct footnote_item *item; - struct footnote_ref *ref; + hoedown_buffer *work = 0; + struct footnote_item *item; + struct footnote_ref *ref; - if (footnotes->count == 0) - return; + if (footnotes->count == 0) + return; - work = newbuf(doc, BUFFER_BLOCK); + work = newbuf(doc, BUFFER_BLOCK); - item = footnotes->head; - while (item) { - ref = item->ref; - parse_footnote_def(work, doc, ref->num, ref->contents->data, ref->contents->size); - item = item->next; - } + item = footnotes->head; + while (item) { + ref = item->ref; + parse_footnote_def(work, doc, ref->num, ref->contents->data, ref->contents->size); + item = item->next; + } - if (doc->md.footnotes) - doc->md.footnotes(ob, work, &doc->data); - popbuf(doc, BUFFER_BLOCK); + if (doc->md.footnotes) + doc->md.footnotes(ob, work, &doc->data); + popbuf(doc, BUFFER_BLOCK); } /* htmlblock_is_end • check for end of HTML block : ( *)\n */ -/* returns tag length on match, 0 otherwise */ -/* assumes data starts with "<" */ +/* returns tag length on match, 0 otherwise */ +/* assumes data starts with "<" */ static size_t htmlblock_is_end( - const char *tag, - size_t tag_len, - hoedown_document *doc, - uint8_t *data, - size_t size) + const char *tag, + size_t tag_len, + hoedown_document *doc, + uint8_t *data, + size_t size) { - size_t i = tag_len + 3, w; + size_t i = tag_len + 3, w; - /* try to match the end tag */ - /* note: we're not considering tags like "" which are still valid */ - if (i > size || - data[1] != '/' || - strncasecmp((char *)data + 2, tag, tag_len) != 0 || - data[tag_len + 2] != '>') - return 0; + /* try to match the end tag */ + /* note: we're not considering tags like "" which are still valid */ + if (i > size || + data[1] != '/' || + strncasecmp((char *)data + 2, tag, tag_len) != 0 || + data[tag_len + 2] != '>') + return 0; - /* rest of the line must be empty */ - if ((w = is_empty(data + i, size - i)) == 0 && i < size) - return 0; + /* rest of the line must be empty */ + if ((w = is_empty(data + i, size - i)) == 0 && i < size) + return 0; - return i + w; + return i + w; } /* htmlblock_find_end • try to find HTML block ending tag */ -/* returns the length on match, 0 otherwise */ +/* returns the length on match, 0 otherwise */ static size_t htmlblock_find_end( - const char *tag, - size_t tag_len, - hoedown_document *doc, - uint8_t *data, - size_t size) + const char *tag, + size_t tag_len, + hoedown_document *doc, + uint8_t *data, + size_t size) { - size_t i = 0, w; + size_t i = 0, w; - while (1) { - while (i < size && data[i] != '<') i++; - if (i >= size) return 0; + while (1) { + while (i < size && data[i] != '<') i++; + if (i >= size) return 0; - w = htmlblock_is_end(tag, tag_len, doc, data + i, size - i); - if (w) return i + w; - i++; - } + w = htmlblock_is_end(tag, tag_len, doc, data + i, size - i); + if (w) return i + w; + i++; + } } /* htmlblock_find_end_strict • try to find end of HTML block in strict mode */ -/* (it must be an unindented line, and have a blank line afterwads) */ -/* returns the length on match, 0 otherwise */ +/* (it must be an unindented line, and have a blank line afterwads) */ +/* returns the length on match, 0 otherwise */ static size_t htmlblock_find_end_strict( - const char *tag, - size_t tag_len, - hoedown_document *doc, - uint8_t *data, - size_t size) + const char *tag, + size_t tag_len, + hoedown_document *doc, + uint8_t *data, + size_t size) { - size_t i = 0, mark; + size_t i = 0, mark; - while (1) { - mark = i; - while (i < size && data[i] != '\n') i++; - if (i < size) i++; - if (i == mark) return 0; + while (1) { + mark = i; + while (i < size && data[i] != '\n') i++; + if (i < size) i++; + if (i == mark) return 0; - if (data[mark] == ' ' && mark > 0) continue; - mark += htmlblock_find_end(tag, tag_len, doc, data + mark, i - mark); - if (mark == i && (is_empty(data + i, size - i) || i >= size)) break; - } + if (data[mark] == ' ' && mark > 0) continue; + mark += htmlblock_find_end(tag, tag_len, doc, data + mark, i - mark); + if (mark == i && (is_empty(data + i, size - i) || i >= size)) break; + } - return i; + return i; } /* parse_htmlblock • parsing of inline HTML block */ static size_t parse_htmlblock(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, int do_render) { - hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; - size_t i, j = 0, tag_len, tag_end; - const char *curtag = NULL; + hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL }; + size_t i, j = 0, tag_len, tag_end; + const char *curtag = NULL; - work.data = data; + work.data = data; - /* identification of the opening tag */ - if (size < 2 || data[0] != '<') - return 0; + /* identification of the opening tag */ + if (size < 2 || data[0] != '<') + return 0; - i = 1; - while (i < size && data[i] != '>' && data[i] != ' ') - i++; + i = 1; + while (i < size && data[i] != '>' && data[i] != ' ') + i++; - if (i < size) - curtag = hoedown_find_block_tag((char *)data + 1, (int)i - 1); + if (i < size) + curtag = hoedown_find_block_tag((char *)data + 1, (int)i - 1); - /* handling of special cases */ - if (!curtag) { + /* handling of special cases */ + if (!curtag) { - /* HTML comment, laxist form */ - if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') { - i = 5; + /* HTML comment, laxist form */ + if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') { + i = 5; - while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>')) - i++; + while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>')) + i++; - i++; + i++; - if (i < size) - j = is_empty(data + i, size - i); + if (i < size) + j = is_empty(data + i, size - i); - if (j) { - work.size = i + j; - if (do_render && doc->md.blockhtml) - doc->md.blockhtml(ob, &work, &doc->data); - return work.size; - } - } + if (j) { + work.size = i + j; + if (do_render && doc->md.blockhtml) + doc->md.blockhtml(ob, &work, &doc->data); + return work.size; + } + } - /* HR, which is the only self-closing block tag considered */ - if (size > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) { - i = 3; - while (i < size && data[i] != '>') - i++; + /* HR, which is the only self-closing block tag considered */ + if (size > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) { + i = 3; + while (i < size && data[i] != '>') + i++; - if (i + 1 < size) { - i++; - j = is_empty(data + i, size - i); - if (j) { - work.size = i + j; - if (do_render && doc->md.blockhtml) - doc->md.blockhtml(ob, &work, &doc->data); - return work.size; - } - } - } + if (i + 1 < size) { + i++; + j = is_empty(data + i, size - i); + if (j) { + work.size = i + j; + if (do_render && doc->md.blockhtml) + doc->md.blockhtml(ob, &work, &doc->data); + return work.size; + } + } + } - /* no special case recognised */ - return 0; - } + /* no special case recognised */ + return 0; + } - /* looking for a matching closing tag in strict mode */ - tag_len = strlen(curtag); - tag_end = htmlblock_find_end_strict(curtag, tag_len, doc, data, size); + /* looking for a matching closing tag in strict mode */ + tag_len = strlen(curtag); + tag_end = htmlblock_find_end_strict(curtag, tag_len, doc, data, size); - /* if not found, trying a second pass looking for indented match */ - /* but not if tag is "ins" or "del" (following original Markdown.pl) */ - if (!tag_end && strcmp(curtag, "ins") != 0 && strcmp(curtag, "del") != 0) - tag_end = htmlblock_find_end(curtag, tag_len, doc, data, size); + /* if not found, trying a second pass looking for indented match */ + /* but not if tag is "ins" or "del" (following original Markdown.pl) */ + if (!tag_end && strcmp(curtag, "ins") != 0 && strcmp(curtag, "del") != 0) + tag_end = htmlblock_find_end(curtag, tag_len, doc, data, size); - if (!tag_end) - return 0; + if (!tag_end) + return 0; - /* the end of the block has been found */ - work.size = tag_end; - if (do_render && doc->md.blockhtml) - doc->md.blockhtml(ob, &work, &doc->data); + /* the end of the block has been found */ + work.size = tag_end; + if (do_render && doc->md.blockhtml) + doc->md.blockhtml(ob, &work, &doc->data); - return tag_end; + return tag_end; } static void parse_table_row( - hoedown_buffer *ob, - hoedown_document *doc, - uint8_t *data, - size_t size, - size_t columns, - hoedown_table_flags *col_data, - hoedown_table_flags header_flag) + hoedown_buffer *ob, + hoedown_document *doc, + uint8_t *data, + size_t size, + size_t columns, + hoedown_table_flags *col_data, + hoedown_table_flags header_flag) { - size_t i = 0, col, len; - hoedown_buffer *row_work = 0; + size_t i = 0, col, len; + hoedown_buffer *row_work = 0; - if (!doc->md.table_cell || !doc->md.table_row) - return; + if (!doc->md.table_cell || !doc->md.table_row) + return; - row_work = newbuf(doc, BUFFER_SPAN); + row_work = newbuf(doc, BUFFER_SPAN); - if (i < size && data[i] == '|') - i++; + if (i < size && data[i] == '|') + i++; - for (col = 0; col < columns && i < size; ++col) { - size_t cell_start, cell_end; - hoedown_buffer *cell_work; + for (col = 0; col < columns && i < size; ++col) { + size_t cell_start, cell_end; + hoedown_buffer *cell_work; - cell_work = newbuf(doc, BUFFER_SPAN); + cell_work = newbuf(doc, BUFFER_SPAN); - while (i < size && _isspace(data[i])) - i++; + while (i < size && _isspace(data[i])) + i++; - cell_start = i; + cell_start = i; - len = find_emph_char(data + i, size - i, '|'); - i += len ? len : size - i; + len = find_emph_char(data + i, size - i, '|'); + i += len ? len : size - i; - cell_end = i - 1; + cell_end = i - 1; - while (cell_end > cell_start && _isspace(data[cell_end])) - cell_end--; + while (cell_end > cell_start && _isspace(data[cell_end])) + cell_end--; - parse_inline(cell_work, doc, data + cell_start, 1 + cell_end - cell_start); - doc->md.table_cell(row_work, cell_work, col_data[col] | header_flag, &doc->data); + parse_inline(cell_work, doc, data + cell_start, 1 + cell_end - cell_start); + doc->md.table_cell(row_work, cell_work, col_data[col] | header_flag, &doc->data); - popbuf(doc, BUFFER_SPAN); - i++; - } + popbuf(doc, BUFFER_SPAN); + i++; + } - for (; col < columns; ++col) { - hoedown_buffer empty_cell = { 0, 0, 0, 0, NULL, NULL, NULL }; - doc->md.table_cell(row_work, &empty_cell, col_data[col] | header_flag, &doc->data); - } + for (; col < columns; ++col) { + hoedown_buffer empty_cell = { 0, 0, 0, 0, NULL, NULL, NULL }; + doc->md.table_cell(row_work, &empty_cell, col_data[col] | header_flag, &doc->data); + } - doc->md.table_row(ob, row_work, &doc->data); + doc->md.table_row(ob, row_work, &doc->data); - popbuf(doc, BUFFER_SPAN); + popbuf(doc, BUFFER_SPAN); } static size_t parse_table_header( - hoedown_buffer *ob, - hoedown_document *doc, - uint8_t *data, - size_t size, - size_t *columns, - hoedown_table_flags **column_data) + hoedown_buffer *ob, + hoedown_document *doc, + uint8_t *data, + size_t size, + size_t *columns, + hoedown_table_flags **column_data) { - int pipes; - size_t i = 0, col, header_end, under_end; + int pipes; + size_t i = 0, col, header_end, under_end; - pipes = 0; - while (i < size && data[i] != '\n') - if (data[i++] == '|') - pipes++; + pipes = 0; + while (i < size && data[i] != '\n') + if (data[i++] == '|') + pipes++; - if (i == size || pipes == 0) - return 0; + if (i == size || pipes == 0) + return 0; - header_end = i; + header_end = i; - while (header_end > 0 && _isspace(data[header_end - 1])) - header_end--; + while (header_end > 0 && _isspace(data[header_end - 1])) + header_end--; - if (data[0] == '|') - pipes--; + if (data[0] == '|') + pipes--; - if (header_end && data[header_end - 1] == '|') - pipes--; + if (header_end && data[header_end - 1] == '|') + pipes--; - if (pipes < 0) - return 0; + if (pipes < 0) + return 0; - *columns = pipes + 1; - *column_data = hoedown_calloc(*columns, sizeof(hoedown_table_flags)); + *columns = pipes + 1; + *column_data = hoedown_calloc(*columns, sizeof(hoedown_table_flags)); - /* Parse the header underline */ - i++; - if (i < size && data[i] == '|') - i++; + /* Parse the header underline */ + i++; + if (i < size && data[i] == '|') + i++; - under_end = i; - while (under_end < size && data[under_end] != '\n') - under_end++; + under_end = i; + while (under_end < size && data[under_end] != '\n') + under_end++; - for (col = 0; col < *columns && i < under_end; ++col) { - size_t dashes = 0; + for (col = 0; col < *columns && i < under_end; ++col) { + size_t dashes = 0; - while (i < under_end && data[i] == ' ') - i++; + while (i < under_end && data[i] == ' ') + i++; - if (data[i] == ':') { - i++; (*column_data)[col] |= HOEDOWN_TABLE_ALIGN_LEFT; - dashes++; - } + if (data[i] == ':') { + i++; (*column_data)[col] |= HOEDOWN_TABLE_ALIGN_LEFT; + dashes++; + } - while (i < under_end && data[i] == '-') { - i++; dashes++; - } + while (i < under_end && data[i] == '-') { + i++; dashes++; + } - if (i < under_end && data[i] == ':') { - i++; (*column_data)[col] |= HOEDOWN_TABLE_ALIGN_RIGHT; - dashes++; - } + if (i < under_end && data[i] == ':') { + i++; (*column_data)[col] |= HOEDOWN_TABLE_ALIGN_RIGHT; + dashes++; + } - while (i < under_end && data[i] == ' ') - i++; + while (i < under_end && data[i] == ' ') + i++; - if (i < under_end && data[i] != '|' && data[i] != '+') - break; + if (i < under_end && data[i] != '|' && data[i] != '+') + break; - if (dashes < 3) - break; + if (dashes < 3) + break; - i++; - } + i++; + } - if (col < *columns) - return 0; + if (col < *columns) + return 0; - parse_table_row( - ob, doc, data, - header_end, - *columns, - *column_data, - HOEDOWN_TABLE_HEADER - ); + parse_table_row( + ob, doc, data, + header_end, + *columns, + *column_data, + HOEDOWN_TABLE_HEADER + ); - return under_end + 1; + return under_end + 1; } static size_t parse_table( - hoedown_buffer *ob, - hoedown_document *doc, - uint8_t *data, - size_t size) + hoedown_buffer *ob, + hoedown_document *doc, + uint8_t *data, + size_t size) { - size_t i; + size_t i; - hoedown_buffer *work = 0; - hoedown_buffer *header_work = 0; - hoedown_buffer *body_work = 0; + hoedown_buffer *work = 0; + hoedown_buffer *header_work = 0; + hoedown_buffer *body_work = 0; - size_t columns; - hoedown_table_flags *col_data = NULL; + size_t columns; + hoedown_table_flags *col_data = NULL; - work = newbuf(doc, BUFFER_BLOCK); - header_work = newbuf(doc, BUFFER_SPAN); - body_work = newbuf(doc, BUFFER_BLOCK); + work = newbuf(doc, BUFFER_BLOCK); + header_work = newbuf(doc, BUFFER_SPAN); + body_work = newbuf(doc, BUFFER_BLOCK); - i = parse_table_header(header_work, doc, data, size, &columns, &col_data); - if (i > 0) { + i = parse_table_header(header_work, doc, data, size, &columns, &col_data); + if (i > 0) { - while (i < size) { - size_t row_start; - int pipes = 0; + while (i < size) { + size_t row_start; + int pipes = 0; - row_start = i; + row_start = i; - while (i < size && data[i] != '\n') - if (data[i++] == '|') - pipes++; + while (i < size && data[i] != '\n') + if (data[i++] == '|') + pipes++; - if (pipes == 0 || i == size) { - i = row_start; - break; - } + if (pipes == 0 || i == size) { + i = row_start; + break; + } - parse_table_row( - body_work, - doc, - data + row_start, - i - row_start, - columns, - col_data, 0 - ); + parse_table_row( + body_work, + doc, + data + row_start, + i - row_start, + columns, + col_data, 0 + ); - i++; - } + i++; + } if (doc->md.table_header) doc->md.table_header(work, header_work, &doc->data); @@ -2397,76 +2397,76 @@ parse_table( if (doc->md.table_body) doc->md.table_body(work, body_work, &doc->data); - if (doc->md.table) - doc->md.table(ob, work, &doc->data); - } + if (doc->md.table) + doc->md.table(ob, work, &doc->data); + } - free(col_data); - popbuf(doc, BUFFER_SPAN); - popbuf(doc, BUFFER_BLOCK); - popbuf(doc, BUFFER_BLOCK); - return i; + free(col_data); + popbuf(doc, BUFFER_SPAN); + popbuf(doc, BUFFER_BLOCK); + popbuf(doc, BUFFER_BLOCK); + return i; } /* parse_block • parsing of one block, returning next uint8_t to parse */ static void parse_block(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size) { - size_t beg, end, i; - uint8_t *txt_data; - beg = 0; + size_t beg, end, i; + uint8_t *txt_data; + beg = 0; - if (doc->work_bufs[BUFFER_SPAN].size + - doc->work_bufs[BUFFER_BLOCK].size > doc->max_nesting) - return; + if (doc->work_bufs[BUFFER_SPAN].size + + doc->work_bufs[BUFFER_BLOCK].size > doc->max_nesting) + return; - while (beg < size) { - txt_data = data + beg; - end = size - beg; + while (beg < size) { + txt_data = data + beg; + end = size - beg; - if (is_atxheader(doc, txt_data, end)) - beg += parse_atxheader(ob, doc, txt_data, end); + if (is_atxheader(doc, txt_data, end)) + beg += parse_atxheader(ob, doc, txt_data, end); - else if (data[beg] == '<' && doc->md.blockhtml && - (i = parse_htmlblock(ob, doc, txt_data, end, 1)) != 0) - beg += i; + else if (data[beg] == '<' && doc->md.blockhtml && + (i = parse_htmlblock(ob, doc, txt_data, end, 1)) != 0) + beg += i; - else if ((i = is_empty(txt_data, end)) != 0) - beg += i; + else if ((i = is_empty(txt_data, end)) != 0) + beg += i; - else if (is_hrule(txt_data, end)) { - if (doc->md.hrule) - doc->md.hrule(ob, &doc->data); + else if (is_hrule(txt_data, end)) { + if (doc->md.hrule) + doc->md.hrule(ob, &doc->data); - while (beg < size && data[beg] != '\n') - beg++; + while (beg < size && data[beg] != '\n') + beg++; - beg++; - } + beg++; + } - else if ((doc->ext_flags & HOEDOWN_EXT_FENCED_CODE) != 0 && - (i = parse_fencedcode(ob, doc, txt_data, end)) != 0) - beg += i; + else if ((doc->ext_flags & HOEDOWN_EXT_FENCED_CODE) != 0 && + (i = parse_fencedcode(ob, doc, txt_data, end)) != 0) + beg += i; - else if ((doc->ext_flags & HOEDOWN_EXT_TABLES) != 0 && - (i = parse_table(ob, doc, txt_data, end)) != 0) - beg += i; + else if ((doc->ext_flags & HOEDOWN_EXT_TABLES) != 0 && + (i = parse_table(ob, doc, txt_data, end)) != 0) + beg += i; - else if (prefix_quote(txt_data, end)) - beg += parse_blockquote(ob, doc, txt_data, end); + else if (prefix_quote(txt_data, end)) + beg += parse_blockquote(ob, doc, txt_data, end); - else if (!(doc->ext_flags & HOEDOWN_EXT_DISABLE_INDENTED_CODE) && prefix_code(txt_data, end)) - beg += parse_blockcode(ob, doc, txt_data, end); + else if (!(doc->ext_flags & HOEDOWN_EXT_DISABLE_INDENTED_CODE) && prefix_code(txt_data, end)) + beg += parse_blockcode(ob, doc, txt_data, end); - else if (prefix_uli(txt_data, end)) - beg += parse_list(ob, doc, txt_data, end, 0); + else if (prefix_uli(txt_data, end)) + beg += parse_list(ob, doc, txt_data, end, 0); - else if (prefix_oli(txt_data, end)) - beg += parse_list(ob, doc, txt_data, end, HOEDOWN_LIST_ORDERED); + else if (prefix_oli(txt_data, end)) + beg += parse_list(ob, doc, txt_data, end, HOEDOWN_LIST_ORDERED); - else - beg += parse_paragraph(ob, doc, txt_data, end); - } + else + beg += parse_paragraph(ob, doc, txt_data, end); + } } @@ -2479,253 +2479,253 @@ parse_block(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t siz static int is_footnote(const uint8_t *data, size_t beg, size_t end, size_t *last, struct footnote_list *list) { - size_t i = 0; - hoedown_buffer *contents = 0; - size_t ind = 0; - int in_empty = 0; - size_t start = 0; - - size_t id_offset, id_end; - - /* up to 3 optional leading spaces */ - if (beg + 3 >= end) return 0; - if (data[beg] == ' ') { i = 1; - if (data[beg + 1] == ' ') { i = 2; - if (data[beg + 2] == ' ') { i = 3; - if (data[beg + 3] == ' ') return 0; } } } - i += beg; - - /* id part: caret followed by anything between brackets */ - if (data[i] != '[') return 0; - i++; - if (i >= end || data[i] != '^') return 0; - i++; - id_offset = i; - while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') - i++; - if (i >= end || data[i] != ']') return 0; - id_end = i; - - /* spacer: colon (space | tab)* newline? (space | tab)* */ - i++; - if (i >= end || data[i] != ':') return 0; - i++; - - /* getting content buffer */ - contents = hoedown_buffer_new(64); - - start = i; - - /* process lines similar to a list item */ - while (i < end) { - while (i < end && data[i] != '\n' && data[i] != '\r') i++; - - /* process an empty line */ - if (is_empty(data + start, i - start)) { - in_empty = 1; - if (i < end && (data[i] == '\n' || data[i] == '\r')) { - i++; - if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++; - } - start = i; - continue; - } - - /* calculating the indentation */ - ind = 0; - while (ind < 4 && start + ind < end && data[start + ind] == ' ') - ind++; - - /* joining only indented stuff after empty lines; - * note that now we only require 1 space of indentation - * to continue, just like lists */ - if (ind == 0) { - if (start == id_end + 2 && data[start] == '\t') {} - else break; - } - else if (in_empty) { - hoedown_buffer_putc(contents, '\n'); - } - - in_empty = 0; - - /* adding the line into the content buffer */ - hoedown_buffer_put(contents, data + start + ind, i - start - ind); - /* add carriage return */ - if (i < end) { - hoedown_buffer_putc(contents, '\n'); - if (i < end && (data[i] == '\n' || data[i] == '\r')) { - i++; - if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++; - } - } - start = i; - } - - if (last) - *last = start; - - if (list) { - struct footnote_ref *ref; - ref = create_footnote_ref(list, data + id_offset, id_end - id_offset); - if (!ref) - return 0; - if (!add_footnote_ref(list, ref)) { - free_footnote_ref(ref); - return 0; - } - ref->contents = contents; - } - - return 1; + size_t i = 0; + hoedown_buffer *contents = 0; + size_t ind = 0; + int in_empty = 0; + size_t start = 0; + + size_t id_offset, id_end; + + /* up to 3 optional leading spaces */ + if (beg + 3 >= end) return 0; + if (data[beg] == ' ') { i = 1; + if (data[beg + 1] == ' ') { i = 2; + if (data[beg + 2] == ' ') { i = 3; + if (data[beg + 3] == ' ') return 0; } } } + i += beg; + + /* id part: caret followed by anything between brackets */ + if (data[i] != '[') return 0; + i++; + if (i >= end || data[i] != '^') return 0; + i++; + id_offset = i; + while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') + i++; + if (i >= end || data[i] != ']') return 0; + id_end = i; + + /* spacer: colon (space | tab)* newline? (space | tab)* */ + i++; + if (i >= end || data[i] != ':') return 0; + i++; + + /* getting content buffer */ + contents = hoedown_buffer_new(64); + + start = i; + + /* process lines similar to a list item */ + while (i < end) { + while (i < end && data[i] != '\n' && data[i] != '\r') i++; + + /* process an empty line */ + if (is_empty(data + start, i - start)) { + in_empty = 1; + if (i < end && (data[i] == '\n' || data[i] == '\r')) { + i++; + if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++; + } + start = i; + continue; + } + + /* calculating the indentation */ + ind = 0; + while (ind < 4 && start + ind < end && data[start + ind] == ' ') + ind++; + + /* joining only indented stuff after empty lines; + * note that now we only require 1 space of indentation + * to continue, just like lists */ + if (ind == 0) { + if (start == id_end + 2 && data[start] == '\t') {} + else break; + } + else if (in_empty) { + hoedown_buffer_putc(contents, '\n'); + } + + in_empty = 0; + + /* adding the line into the content buffer */ + hoedown_buffer_put(contents, data + start + ind, i - start - ind); + /* add carriage return */ + if (i < end) { + hoedown_buffer_putc(contents, '\n'); + if (i < end && (data[i] == '\n' || data[i] == '\r')) { + i++; + if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++; + } + } + start = i; + } + + if (last) + *last = start; + + if (list) { + struct footnote_ref *ref; + ref = create_footnote_ref(list, data + id_offset, id_end - id_offset); + if (!ref) + return 0; + if (!add_footnote_ref(list, ref)) { + free_footnote_ref(ref); + return 0; + } + ref->contents = contents; + } + + return 1; } /* is_ref • returns whether a line is a reference or not */ static int is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_ref **refs) { -/* int n; */ - size_t i = 0; - size_t id_offset, id_end; - size_t link_offset, link_end; - size_t title_offset, title_end; - size_t line_end; - - /* up to 3 optional leading spaces */ - if (beg + 3 >= end) return 0; - if (data[beg] == ' ') { i = 1; - if (data[beg + 1] == ' ') { i = 2; - if (data[beg + 2] == ' ') { i = 3; - if (data[beg + 3] == ' ') return 0; } } } - i += beg; - - /* id part: anything but a newline between brackets */ - if (data[i] != '[') return 0; - i++; - id_offset = i; - while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') - i++; - if (i >= end || data[i] != ']') return 0; - id_end = i; - - /* spacer: colon (space | tab)* newline? (space | tab)* */ - i++; - if (i >= end || data[i] != ':') return 0; - i++; - while (i < end && data[i] == ' ') i++; - if (i < end && (data[i] == '\n' || data[i] == '\r')) { - i++; - if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; } - while (i < end && data[i] == ' ') i++; - if (i >= end) return 0; - - /* link: spacing-free sequence, optionally between angle brackets */ - if (data[i] == '<') - i++; - - link_offset = i; - - while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r') - i++; - - if (data[i - 1] == '>') link_end = i - 1; - else link_end = i; - - /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */ - while (i < end && data[i] == ' ') i++; - if (i < end && data[i] != '\n' && data[i] != '\r' - && data[i] != '\'' && data[i] != '"' && data[i] != '(') - return 0; - line_end = 0; - /* computing end-of-line */ - if (i >= end || data[i] == '\r' || data[i] == '\n') line_end = i; - if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') - line_end = i + 1; - - /* optional (space|tab)* spacer after a newline */ - if (line_end) { - i = line_end + 1; - while (i < end && data[i] == ' ') i++; } - - /* optional title: any non-newline sequence enclosed in '"() - alone on its line */ - title_offset = title_end = 0; - if (i + 1 < end - && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) { - i++; - title_offset = i; - /* looking for EOL */ - while (i < end && data[i] != '\n' && data[i] != '\r') i++; - if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') - title_end = i + 1; - else title_end = i; - /* stepping back */ - i -= 1; - while (i > title_offset && data[i] == ' ') - i -= 1; - if (i > title_offset - && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) { - line_end = title_end; - title_end = i; } } - - if (!line_end || link_end == link_offset) - return 0; /* garbage after the link empty link */ - - /* a valid ref has been found, filling-in return structures */ - if (last) - *last = line_end; - - if (refs) { - struct link_ref *ref; - - ref = add_link_ref(refs, data + id_offset, id_end - id_offset); - if (!ref) - return 0; - - ref->link = hoedown_buffer_new(link_end - link_offset); - hoedown_buffer_put(ref->link, data + link_offset, link_end - link_offset); - - if (title_end > title_offset) { - ref->title = hoedown_buffer_new(title_end - title_offset); - hoedown_buffer_put(ref->title, data + title_offset, title_end - title_offset); - } - } - - return 1; +/* int n; */ + size_t i = 0; + size_t id_offset, id_end; + size_t link_offset, link_end; + size_t title_offset, title_end; + size_t line_end; + + /* up to 3 optional leading spaces */ + if (beg + 3 >= end) return 0; + if (data[beg] == ' ') { i = 1; + if (data[beg + 1] == ' ') { i = 2; + if (data[beg + 2] == ' ') { i = 3; + if (data[beg + 3] == ' ') return 0; } } } + i += beg; + + /* id part: anything but a newline between brackets */ + if (data[i] != '[') return 0; + i++; + id_offset = i; + while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') + i++; + if (i >= end || data[i] != ']') return 0; + id_end = i; + + /* spacer: colon (space | tab)* newline? (space | tab)* */ + i++; + if (i >= end || data[i] != ':') return 0; + i++; + while (i < end && data[i] == ' ') i++; + if (i < end && (data[i] == '\n' || data[i] == '\r')) { + i++; + if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; } + while (i < end && data[i] == ' ') i++; + if (i >= end) return 0; + + /* link: spacing-free sequence, optionally between angle brackets */ + if (data[i] == '<') + i++; + + link_offset = i; + + while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r') + i++; + + if (data[i - 1] == '>') link_end = i - 1; + else link_end = i; + + /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */ + while (i < end && data[i] == ' ') i++; + if (i < end && data[i] != '\n' && data[i] != '\r' + && data[i] != '\'' && data[i] != '"' && data[i] != '(') + return 0; + line_end = 0; + /* computing end-of-line */ + if (i >= end || data[i] == '\r' || data[i] == '\n') line_end = i; + if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') + line_end = i + 1; + + /* optional (space|tab)* spacer after a newline */ + if (line_end) { + i = line_end + 1; + while (i < end && data[i] == ' ') i++; } + + /* optional title: any non-newline sequence enclosed in '"() + alone on its line */ + title_offset = title_end = 0; + if (i + 1 < end + && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) { + i++; + title_offset = i; + /* looking for EOL */ + while (i < end && data[i] != '\n' && data[i] != '\r') i++; + if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') + title_end = i + 1; + else title_end = i; + /* stepping back */ + i -= 1; + while (i > title_offset && data[i] == ' ') + i -= 1; + if (i > title_offset + && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) { + line_end = title_end; + title_end = i; } } + + if (!line_end || link_end == link_offset) + return 0; /* garbage after the link empty link */ + + /* a valid ref has been found, filling-in return structures */ + if (last) + *last = line_end; + + if (refs) { + struct link_ref *ref; + + ref = add_link_ref(refs, data + id_offset, id_end - id_offset); + if (!ref) + return 0; + + ref->link = hoedown_buffer_new(link_end - link_offset); + hoedown_buffer_put(ref->link, data + link_offset, link_end - link_offset); + + if (title_end > title_offset) { + ref->title = hoedown_buffer_new(title_end - title_offset); + hoedown_buffer_put(ref->title, data + title_offset, title_end - title_offset); + } + } + + return 1; } static void expand_tabs(hoedown_buffer *ob, const uint8_t *line, size_t size) { - /* This code makes two assumptions: - * - Input is valid UTF-8. (Any byte with top two bits 10 is skipped, - * whether or not it is a valid UTF-8 continuation byte.) - * - Input contains no combining characters. (Combining characters - * should be skipped but are not.) - */ - size_t i = 0, tab = 0; + /* This code makes two assumptions: + * - Input is valid UTF-8. (Any byte with top two bits 10 is skipped, + * whether or not it is a valid UTF-8 continuation byte.) + * - Input contains no combining characters. (Combining characters + * should be skipped but are not.) + */ + size_t i = 0, tab = 0; - while (i < size) { - size_t org = i; + while (i < size) { + size_t org = i; - while (i < size && line[i] != '\t') { - /* ignore UTF-8 continuation bytes */ - if ((line[i] & 0xc0) != 0x80) - tab++; - i++; - } + while (i < size && line[i] != '\t') { + /* ignore UTF-8 continuation bytes */ + if ((line[i] & 0xc0) != 0x80) + tab++; + i++; + } - if (i > org) - hoedown_buffer_put(ob, line + org, i - org); + if (i > org) + hoedown_buffer_put(ob, line + org, i - org); - if (i >= size) - break; + if (i >= size) + break; - do { - hoedown_buffer_putc(ob, ' '); tab++; - } while (tab % 4); + do { + hoedown_buffer_putc(ob, ' '); tab++; + } while (tab % 4); - i++; - } + i++; + } } /********************** @@ -2734,225 +2734,225 @@ static void expand_tabs(hoedown_buffer *ob, const uint8_t *line, size_t size) hoedown_document * hoedown_document_new( - const hoedown_renderer *renderer, - hoedown_extensions extensions, - size_t max_nesting) + const hoedown_renderer *renderer, + hoedown_extensions extensions, + size_t max_nesting) { - hoedown_document *doc = NULL; + hoedown_document *doc = NULL; - assert(max_nesting > 0 && renderer); + assert(max_nesting > 0 && renderer); - doc = hoedown_malloc(sizeof(hoedown_document)); - memcpy(&doc->md, renderer, sizeof(hoedown_renderer)); + doc = hoedown_malloc(sizeof(hoedown_document)); + memcpy(&doc->md, renderer, sizeof(hoedown_renderer)); - doc->data.opaque = renderer->opaque; + doc->data.opaque = renderer->opaque; - hoedown_stack_init(&doc->work_bufs[BUFFER_BLOCK], 4); - hoedown_stack_init(&doc->work_bufs[BUFFER_SPAN], 8); + hoedown_stack_init(&doc->work_bufs[BUFFER_BLOCK], 4); + hoedown_stack_init(&doc->work_bufs[BUFFER_SPAN], 8); - memset(doc->active_char, 0x0, 256); + memset(doc->active_char, 0x0, 256); - if (extensions & HOEDOWN_EXT_UNDERLINE && doc->md.underline) { - doc->active_char['_'] = MD_CHAR_EMPHASIS; - } + if (extensions & HOEDOWN_EXT_UNDERLINE && doc->md.underline) { + doc->active_char['_'] = MD_CHAR_EMPHASIS; + } - if (doc->md.emphasis || doc->md.double_emphasis || doc->md.triple_emphasis) { - doc->active_char['*'] = MD_CHAR_EMPHASIS; - doc->active_char['_'] = MD_CHAR_EMPHASIS; - if (extensions & HOEDOWN_EXT_STRIKETHROUGH) - doc->active_char['~'] = MD_CHAR_EMPHASIS; - if (extensions & HOEDOWN_EXT_HIGHLIGHT) - doc->active_char['='] = MD_CHAR_EMPHASIS; - } + if (doc->md.emphasis || doc->md.double_emphasis || doc->md.triple_emphasis) { + doc->active_char['*'] = MD_CHAR_EMPHASIS; + doc->active_char['_'] = MD_CHAR_EMPHASIS; + if (extensions & HOEDOWN_EXT_STRIKETHROUGH) + doc->active_char['~'] = MD_CHAR_EMPHASIS; + if (extensions & HOEDOWN_EXT_HIGHLIGHT) + doc->active_char['='] = MD_CHAR_EMPHASIS; + } - if (doc->md.codespan) - doc->active_char['`'] = MD_CHAR_CODESPAN; + if (doc->md.codespan) + doc->active_char['`'] = MD_CHAR_CODESPAN; - if (doc->md.linebreak) - doc->active_char['\n'] = MD_CHAR_LINEBREAK; + if (doc->md.linebreak) + doc->active_char['\n'] = MD_CHAR_LINEBREAK; - if (doc->md.image || doc->md.link || doc->md.footnotes || doc->md.footnote_ref) - doc->active_char['['] = MD_CHAR_LINK; + if (doc->md.image || doc->md.link || doc->md.footnotes || doc->md.footnote_ref) + doc->active_char['['] = MD_CHAR_LINK; - doc->active_char['<'] = MD_CHAR_LANGLE; - doc->active_char['\\'] = MD_CHAR_ESCAPE; - doc->active_char['&'] = MD_CHAR_ENTITY; + doc->active_char['<'] = MD_CHAR_LANGLE; + doc->active_char['\\'] = MD_CHAR_ESCAPE; + doc->active_char['&'] = MD_CHAR_ENTITY; - if (extensions & HOEDOWN_EXT_AUTOLINK) { - doc->active_char[':'] = MD_CHAR_AUTOLINK_URL; - doc->active_char['@'] = MD_CHAR_AUTOLINK_EMAIL; - doc->active_char['w'] = MD_CHAR_AUTOLINK_WWW; - } + if (extensions & HOEDOWN_EXT_AUTOLINK) { + doc->active_char[':'] = MD_CHAR_AUTOLINK_URL; + doc->active_char['@'] = MD_CHAR_AUTOLINK_EMAIL; + doc->active_char['w'] = MD_CHAR_AUTOLINK_WWW; + } - if (extensions & HOEDOWN_EXT_SUPERSCRIPT) - doc->active_char['^'] = MD_CHAR_SUPERSCRIPT; + if (extensions & HOEDOWN_EXT_SUPERSCRIPT) + doc->active_char['^'] = MD_CHAR_SUPERSCRIPT; - if (extensions & HOEDOWN_EXT_QUOTE) - doc->active_char['"'] = MD_CHAR_QUOTE; + if (extensions & HOEDOWN_EXT_QUOTE) + doc->active_char['"'] = MD_CHAR_QUOTE; - if (extensions & HOEDOWN_EXT_MATH) - doc->active_char['$'] = MD_CHAR_MATH; + if (extensions & HOEDOWN_EXT_MATH) + doc->active_char['$'] = MD_CHAR_MATH; - /* Extension data */ - doc->ext_flags = extensions; - doc->max_nesting = max_nesting; - doc->in_link_body = 0; + /* Extension data */ + doc->ext_flags = extensions; + doc->max_nesting = max_nesting; + doc->in_link_body = 0; - return doc; + return doc; } void hoedown_document_render(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size) { - static const uint8_t UTF8_BOM[] = {0xEF, 0xBB, 0xBF}; + static const uint8_t UTF8_BOM[] = {0xEF, 0xBB, 0xBF}; - hoedown_buffer *text; - size_t beg, end; + hoedown_buffer *text; + size_t beg, end; - int footnotes_enabled; + int footnotes_enabled; - text = hoedown_buffer_new(64); + text = hoedown_buffer_new(64); - /* Preallocate enough space for our buffer to avoid expanding while copying */ - hoedown_buffer_grow(text, size); + /* Preallocate enough space for our buffer to avoid expanding while copying */ + hoedown_buffer_grow(text, size); - /* reset the references table */ - memset(&doc->refs, 0x0, REF_TABLE_SIZE * sizeof(void *)); + /* reset the references table */ + memset(&doc->refs, 0x0, REF_TABLE_SIZE * sizeof(void *)); - footnotes_enabled = doc->ext_flags & HOEDOWN_EXT_FOOTNOTES; + footnotes_enabled = doc->ext_flags & HOEDOWN_EXT_FOOTNOTES; - /* reset the footnotes lists */ - if (footnotes_enabled) { - memset(&doc->footnotes_found, 0x0, sizeof(doc->footnotes_found)); - memset(&doc->footnotes_used, 0x0, sizeof(doc->footnotes_used)); - } + /* reset the footnotes lists */ + if (footnotes_enabled) { + memset(&doc->footnotes_found, 0x0, sizeof(doc->footnotes_found)); + memset(&doc->footnotes_used, 0x0, sizeof(doc->footnotes_used)); + } - /* first pass: looking for references, copying everything else */ - beg = 0; + /* first pass: looking for references, copying everything else */ + beg = 0; - /* Skip a possible UTF-8 BOM, even though the Unicode standard - * discourages having these in UTF-8 documents */ - if (size >= 3 && memcmp(data, UTF8_BOM, 3) == 0) - beg += 3; + /* Skip a possible UTF-8 BOM, even though the Unicode standard + * discourages having these in UTF-8 documents */ + if (size >= 3 && memcmp(data, UTF8_BOM, 3) == 0) + beg += 3; - while (beg < size) /* iterating over lines */ - if (footnotes_enabled && is_footnote(data, beg, size, &end, &doc->footnotes_found)) - beg = end; - else if (is_ref(data, beg, size, &end, doc->refs)) - beg = end; - else { /* skipping to the next line */ - end = beg; - while (end < size && data[end] != '\n' && data[end] != '\r') - end++; + while (beg < size) /* iterating over lines */ + if (footnotes_enabled && is_footnote(data, beg, size, &end, &doc->footnotes_found)) + beg = end; + else if (is_ref(data, beg, size, &end, doc->refs)) + beg = end; + else { /* skipping to the next line */ + end = beg; + while (end < size && data[end] != '\n' && data[end] != '\r') + end++; - /* adding the line body if present */ - if (end > beg) - expand_tabs(text, data + beg, end - beg); + /* adding the line body if present */ + if (end > beg) + expand_tabs(text, data + beg, end - beg); - while (end < size && (data[end] == '\n' || data[end] == '\r')) { - /* add one \n per newline */ - if (data[end] == '\n' || (end + 1 < size && data[end + 1] != '\n')) - hoedown_buffer_putc(text, '\n'); - end++; - } + while (end < size && (data[end] == '\n' || data[end] == '\r')) { + /* add one \n per newline */ + if (data[end] == '\n' || (end + 1 < size && data[end + 1] != '\n')) + hoedown_buffer_putc(text, '\n'); + end++; + } - beg = end; - } + beg = end; + } - /* pre-grow the output buffer to minimize allocations */ - hoedown_buffer_grow(ob, text->size + (text->size >> 1)); + /* pre-grow the output buffer to minimize allocations */ + hoedown_buffer_grow(ob, text->size + (text->size >> 1)); - /* second pass: actual rendering */ - if (doc->md.doc_header) - doc->md.doc_header(ob, 0, &doc->data); + /* second pass: actual rendering */ + if (doc->md.doc_header) + doc->md.doc_header(ob, 0, &doc->data); - if (text->size) { - /* adding a final newline if not already present */ - if (text->data[text->size - 1] != '\n' && text->data[text->size - 1] != '\r') - hoedown_buffer_putc(text, '\n'); + if (text->size) { + /* adding a final newline if not already present */ + if (text->data[text->size - 1] != '\n' && text->data[text->size - 1] != '\r') + hoedown_buffer_putc(text, '\n'); - parse_block(ob, doc, text->data, text->size); - } + parse_block(ob, doc, text->data, text->size); + } - /* footnotes */ - if (footnotes_enabled) - parse_footnote_list(ob, doc, &doc->footnotes_used); + /* footnotes */ + if (footnotes_enabled) + parse_footnote_list(ob, doc, &doc->footnotes_used); - if (doc->md.doc_footer) - doc->md.doc_footer(ob, 0, &doc->data); + if (doc->md.doc_footer) + doc->md.doc_footer(ob, 0, &doc->data); - /* clean-up */ - hoedown_buffer_free(text); - free_link_refs(doc->refs); - if (footnotes_enabled) { - free_footnote_list(&doc->footnotes_found, 1); - free_footnote_list(&doc->footnotes_used, 0); - } + /* clean-up */ + hoedown_buffer_free(text); + free_link_refs(doc->refs); + if (footnotes_enabled) { + free_footnote_list(&doc->footnotes_found, 1); + free_footnote_list(&doc->footnotes_used, 0); + } - assert(doc->work_bufs[BUFFER_SPAN].size == 0); - assert(doc->work_bufs[BUFFER_BLOCK].size == 0); + assert(doc->work_bufs[BUFFER_SPAN].size == 0); + assert(doc->work_bufs[BUFFER_BLOCK].size == 0); } void hoedown_document_render_inline(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size) { - size_t i = 0, mark; - hoedown_buffer *text = hoedown_buffer_new(64); + size_t i = 0, mark; + hoedown_buffer *text = hoedown_buffer_new(64); - /* reset the references table */ - memset(&doc->refs, 0x0, REF_TABLE_SIZE * sizeof(void *)); + /* reset the references table */ + memset(&doc->refs, 0x0, REF_TABLE_SIZE * sizeof(void *)); - /* first pass: expand tabs and process newlines */ - hoedown_buffer_grow(text, size); - while (1) { - mark = i; - while (i < size && data[i] != '\n' && data[i] != '\r') - i++; + /* first pass: expand tabs and process newlines */ + hoedown_buffer_grow(text, size); + while (1) { + mark = i; + while (i < size && data[i] != '\n' && data[i] != '\r') + i++; - expand_tabs(text, data + mark, i - mark); + expand_tabs(text, data + mark, i - mark); - if (i >= size) - break; + if (i >= size) + break; - while (i < size && (data[i] == '\n' || data[i] == '\r')) { - /* add one \n per newline */ - if (data[i] == '\n' || (i + 1 < size && data[i + 1] != '\n')) - hoedown_buffer_putc(text, '\n'); - i++; - } - } + while (i < size && (data[i] == '\n' || data[i] == '\r')) { + /* add one \n per newline */ + if (data[i] == '\n' || (i + 1 < size && data[i + 1] != '\n')) + hoedown_buffer_putc(text, '\n'); + i++; + } + } - /* second pass: actual rendering */ - hoedown_buffer_grow(ob, text->size + (text->size >> 1)); + /* second pass: actual rendering */ + hoedown_buffer_grow(ob, text->size + (text->size >> 1)); - if (doc->md.doc_header) - doc->md.doc_header(ob, 1, &doc->data); + if (doc->md.doc_header) + doc->md.doc_header(ob, 1, &doc->data); - parse_inline(ob, doc, text->data, text->size); + parse_inline(ob, doc, text->data, text->size); - if (doc->md.doc_footer) - doc->md.doc_footer(ob, 1, &doc->data); + if (doc->md.doc_footer) + doc->md.doc_footer(ob, 1, &doc->data); - /* clean-up */ - hoedown_buffer_free(text); + /* clean-up */ + hoedown_buffer_free(text); - assert(doc->work_bufs[BUFFER_SPAN].size == 0); - assert(doc->work_bufs[BUFFER_BLOCK].size == 0); + assert(doc->work_bufs[BUFFER_SPAN].size == 0); + assert(doc->work_bufs[BUFFER_BLOCK].size == 0); } void hoedown_document_free(hoedown_document *doc) { - size_t i; + size_t i; - for (i = 0; i < (size_t)doc->work_bufs[BUFFER_SPAN].asize; ++i) - hoedown_buffer_free(doc->work_bufs[BUFFER_SPAN].item[i]); + for (i = 0; i < (size_t)doc->work_bufs[BUFFER_SPAN].asize; ++i) + hoedown_buffer_free(doc->work_bufs[BUFFER_SPAN].item[i]); - for (i = 0; i < (size_t)doc->work_bufs[BUFFER_BLOCK].asize; ++i) - hoedown_buffer_free(doc->work_bufs[BUFFER_BLOCK].item[i]); + for (i = 0; i < (size_t)doc->work_bufs[BUFFER_BLOCK].asize; ++i) + hoedown_buffer_free(doc->work_bufs[BUFFER_BLOCK].item[i]); - hoedown_stack_uninit(&doc->work_bufs[BUFFER_SPAN]); - hoedown_stack_uninit(&doc->work_bufs[BUFFER_BLOCK]); + hoedown_stack_uninit(&doc->work_bufs[BUFFER_SPAN]); + hoedown_stack_uninit(&doc->work_bufs[BUFFER_BLOCK]); - free(doc); + free(doc); } diff --git a/libraries/hoedown/src/escape.c b/libraries/hoedown/src/escape.c index b4a275ba..ce25dd54 100644 --- a/libraries/hoedown/src/escape.c +++ b/libraries/hoedown/src/escape.c @@ -12,13 +12,13 @@ /* * The following characters will not be escaped: * - * -_.+!*'(),%#@?=;:/,+&$ alphanum + * -_.+!*'(),%#@?=;:/,+&$ alphanum * * Note that this character set is the addition of: * - * - The characters which are safe to be in an URL - * - The characters which are *not* safe to be in - * an URL because they are RESERVED characters. + * - The characters which are safe to be in an URL + * - The characters which are *not* safe to be in + * an URL because they are RESERVED characters. * * We assume (lazily) that any RESERVED char that * appears inside an URL is actually meant to @@ -35,84 +35,84 @@ * */ static const uint8_t HREF_SAFE[UINT8_MAX+1] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; void hoedown_escape_href(hoedown_buffer *ob, const uint8_t *data, size_t size) { - static const char hex_chars[] = "0123456789ABCDEF"; - size_t i = 0, mark; - char hex_str[3]; - - hex_str[0] = '%'; - - while (i < size) { - mark = i; - while (i < size && HREF_SAFE[data[i]]) i++; - - /* Optimization for cases where there's nothing to escape */ - if (mark == 0 && i >= size) { - hoedown_buffer_put(ob, data, size); - return; - } - - if (likely(i > mark)) { - hoedown_buffer_put(ob, data + mark, i - mark); - } - - /* escaping */ - if (i >= size) - break; - - switch (data[i]) { - /* amp appears all the time in URLs, but needs - * HTML-entity escaping to be inside an href */ - case '&': - HOEDOWN_BUFPUTSL(ob, "&"); - break; - - /* the single quote is a valid URL character - * according to the standard; it needs HTML - * entity escaping too */ - case '\'': - HOEDOWN_BUFPUTSL(ob, "'"); - break; - - /* the space can be escaped to %20 or a plus - * sign. we're going with the generic escape - * for now. the plus thing is more commonly seen - * when building GET strings */ + static const char hex_chars[] = "0123456789ABCDEF"; + size_t i = 0, mark; + char hex_str[3]; + + hex_str[0] = '%'; + + while (i < size) { + mark = i; + while (i < size && HREF_SAFE[data[i]]) i++; + + /* Optimization for cases where there's nothing to escape */ + if (mark == 0 && i >= size) { + hoedown_buffer_put(ob, data, size); + return; + } + + if (likely(i > mark)) { + hoedown_buffer_put(ob, data + mark, i - mark); + } + + /* escaping */ + if (i >= size) + break; + + switch (data[i]) { + /* amp appears all the time in URLs, but needs + * HTML-entity escaping to be inside an href */ + case '&': + HOEDOWN_BUFPUTSL(ob, "&"); + break; + + /* the single quote is a valid URL character + * according to the standard; it needs HTML + * entity escaping too */ + case '\'': + HOEDOWN_BUFPUTSL(ob, "'"); + break; + + /* the space can be escaped to %20 or a plus + * sign. we're going with the generic escape + * for now. the plus thing is more commonly seen + * when building GET strings */ #if 0 - case ' ': - hoedown_buffer_putc(ob, '+'); - break; + case ' ': + hoedown_buffer_putc(ob, '+'); + break; #endif - /* every other character goes with a %XX escaping */ - default: - hex_str[1] = hex_chars[(data[i] >> 4) & 0xF]; - hex_str[2] = hex_chars[data[i] & 0xF]; - hoedown_buffer_put(ob, (uint8_t *)hex_str, 3); - } + /* every other character goes with a %XX escaping */ + default: + hex_str[1] = hex_chars[(data[i] >> 4) & 0xF]; + hex_str[2] = hex_chars[data[i] & 0xF]; + hoedown_buffer_put(ob, (uint8_t *)hex_str, 3); + } - i++; - } + i++; + } } @@ -128,22 +128,22 @@ hoedown_escape_href(hoedown_buffer *ob, const uint8_t *data, size_t size) * */ static const uint8_t HTML_ESCAPE_TABLE[UINT8_MAX+1] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const char *HTML_ESCAPES[] = { @@ -159,30 +159,30 @@ static const char *HTML_ESCAPES[] = { void hoedown_escape_html(hoedown_buffer *ob, const uint8_t *data, size_t size, int secure) { - size_t i = 0, mark; + size_t i = 0, mark; - while (1) { - mark = i; - while (i < size && HTML_ESCAPE_TABLE[data[i]] == 0) i++; + while (1) { + mark = i; + while (i < size && HTML_ESCAPE_TABLE[data[i]] == 0) i++; - /* Optimization for cases where there's nothing to escape */ - if (mark == 0 && i >= size) { - hoedown_buffer_put(ob, data, size); - return; - } + /* Optimization for cases where there's nothing to escape */ + if (mark == 0 && i >= size) { + hoedown_buffer_put(ob, data, size); + return; + } - if (likely(i > mark)) - hoedown_buffer_put(ob, data + mark, i - mark); + if (likely(i > mark)) + hoedown_buffer_put(ob, data + mark, i - mark); - if (i >= size) break; + if (i >= size) break; - /* The forward slash is only escaped in secure mode */ - if (!secure && data[i] == '/') { - hoedown_buffer_putc(ob, '/'); - } else { - hoedown_buffer_puts(ob, HTML_ESCAPES[HTML_ESCAPE_TABLE[data[i]]]); - } + /* The forward slash is only escaped in secure mode */ + if (!secure && data[i] == '/') { + hoedown_buffer_putc(ob, '/'); + } else { + hoedown_buffer_puts(ob, HTML_ESCAPES[HTML_ESCAPE_TABLE[data[i]]]); + } - i++; - } + i++; + } } diff --git a/libraries/hoedown/src/html.c b/libraries/hoedown/src/html.c index 4b18d804..8bf3358e 100644 --- a/libraries/hoedown/src/html.c +++ b/libraries/hoedown/src/html.c @@ -12,44 +12,44 @@ hoedown_html_tag hoedown_html_is_tag(const uint8_t *data, size_t size, const char *tagname) { - size_t i; - int closed = 0; + size_t i; + int closed = 0; - if (size < 3 || data[0] != '<') - return HOEDOWN_HTML_TAG_NONE; + if (size < 3 || data[0] != '<') + return HOEDOWN_HTML_TAG_NONE; - i = 1; + i = 1; - if (data[i] == '/') { - closed = 1; - i++; - } + if (data[i] == '/') { + closed = 1; + i++; + } - for (; i < size; ++i, ++tagname) { - if (*tagname == 0) - break; + for (; i < size; ++i, ++tagname) { + if (*tagname == 0) + break; - if (data[i] != *tagname) - return HOEDOWN_HTML_TAG_NONE; - } + if (data[i] != *tagname) + return HOEDOWN_HTML_TAG_NONE; + } - if (i == size) - return HOEDOWN_HTML_TAG_NONE; + if (i == size) + return HOEDOWN_HTML_TAG_NONE; - if (isspace(data[i]) || data[i] == '>') - return closed ? HOEDOWN_HTML_TAG_CLOSE : HOEDOWN_HTML_TAG_OPEN; + if (isspace(data[i]) || data[i] == '>') + return closed ? HOEDOWN_HTML_TAG_CLOSE : HOEDOWN_HTML_TAG_OPEN; - return HOEDOWN_HTML_TAG_NONE; + return HOEDOWN_HTML_TAG_NONE; } static void escape_html(hoedown_buffer *ob, const uint8_t *source, size_t length) { - hoedown_escape_html(ob, source, length, 0); + hoedown_escape_html(ob, source, length, 0); } static void escape_href(hoedown_buffer *ob, const uint8_t *source, size_t length) { - hoedown_escape_href(ob, source, length); + hoedown_escape_href(ob, source, length); } /******************** @@ -58,353 +58,353 @@ static void escape_href(hoedown_buffer *ob, const uint8_t *source, size_t length static int rndr_autolink(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; + hoedown_html_renderer_state *state = data->opaque; - if (!link || !link->size) - return 0; + if (!link || !link->size) + return 0; - HOEDOWN_BUFPUTSL(ob, "data, link->size); + HOEDOWN_BUFPUTSL(ob, "data, link->size); - if (state->link_attributes) { - hoedown_buffer_putc(ob, '\"'); - state->link_attributes(ob, link, data); - hoedown_buffer_putc(ob, '>'); - } else { - HOEDOWN_BUFPUTSL(ob, "\">"); - } + if (state->link_attributes) { + hoedown_buffer_putc(ob, '\"'); + state->link_attributes(ob, link, data); + hoedown_buffer_putc(ob, '>'); + } else { + HOEDOWN_BUFPUTSL(ob, "\">"); + } - /* - * Pretty printing: if we get an email address as - * an actual URI, e.g. `mailto:foo@bar.com`, we don't - * want to print the `mailto:` prefix - */ - if (hoedown_buffer_prefix(link, "mailto:") == 0) { - escape_html(ob, link->data + 7, link->size - 7); - } else { - escape_html(ob, link->data, link->size); - } + /* + * Pretty printing: if we get an email address as + * an actual URI, e.g. `mailto:foo@bar.com`, we don't + * want to print the `mailto:` prefix + */ + if (hoedown_buffer_prefix(link, "mailto:") == 0) { + escape_html(ob, link->data + 7, link->size - 7); + } else { + escape_html(ob, link->data, link->size); + } - HOEDOWN_BUFPUTSL(ob, ""); + HOEDOWN_BUFPUTSL(ob, ""); - return 1; + return 1; } static void rndr_blockcode(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data) { - if (ob->size) hoedown_buffer_putc(ob, '\n'); + if (ob->size) hoedown_buffer_putc(ob, '\n'); - if (lang) { - HOEDOWN_BUFPUTSL(ob, "
    data, lang->size);
    -		HOEDOWN_BUFPUTSL(ob, "\">");
    -	} else {
    -		HOEDOWN_BUFPUTSL(ob, "
    ");
    -	}
    +    if (lang) {
    +        HOEDOWN_BUFPUTSL(ob, "
    data, lang->size);
    +        HOEDOWN_BUFPUTSL(ob, "\">");
    +    } else {
    +        HOEDOWN_BUFPUTSL(ob, "
    ");
    +    }
     
    -	if (text)
    -		escape_html(ob, text->data, text->size);
    +    if (text)
    +        escape_html(ob, text->data, text->size);
     
    -	HOEDOWN_BUFPUTSL(ob, "
    \n"); + HOEDOWN_BUFPUTSL(ob, "
    \n"); } static void rndr_blockquote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (ob->size) hoedown_buffer_putc(ob, '\n'); - HOEDOWN_BUFPUTSL(ob, "
    \n"); - if (content) hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, "
    \n"); + if (ob->size) hoedown_buffer_putc(ob, '\n'); + HOEDOWN_BUFPUTSL(ob, "
    \n"); + if (content) hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, "
    \n"); } static int rndr_codespan(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data) { - HOEDOWN_BUFPUTSL(ob, ""); - if (text) escape_html(ob, text->data, text->size); - HOEDOWN_BUFPUTSL(ob, ""); - return 1; + HOEDOWN_BUFPUTSL(ob, ""); + if (text) escape_html(ob, text->data, text->size); + HOEDOWN_BUFPUTSL(ob, ""); + return 1; } static int rndr_strikethrough(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) - return 0; + if (!content || !content->size) + return 0; - HOEDOWN_BUFPUTSL(ob, ""); - hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); - return 1; + HOEDOWN_BUFPUTSL(ob, ""); + hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); + return 1; } static int rndr_double_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) - return 0; + if (!content || !content->size) + return 0; - HOEDOWN_BUFPUTSL(ob, ""); - hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); + HOEDOWN_BUFPUTSL(ob, ""); + hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); - return 1; + return 1; } static int rndr_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) return 0; - HOEDOWN_BUFPUTSL(ob, ""); - if (content) hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); - return 1; + if (!content || !content->size) return 0; + HOEDOWN_BUFPUTSL(ob, ""); + if (content) hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); + return 1; } static int rndr_underline(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) - return 0; + if (!content || !content->size) + return 0; - HOEDOWN_BUFPUTSL(ob, ""); - hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); + HOEDOWN_BUFPUTSL(ob, ""); + hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); - return 1; + return 1; } static int rndr_highlight(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) - return 0; + if (!content || !content->size) + return 0; - HOEDOWN_BUFPUTSL(ob, ""); - hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); + HOEDOWN_BUFPUTSL(ob, ""); + hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); - return 1; + return 1; } static int rndr_quote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) - return 0; + if (!content || !content->size) + return 0; - HOEDOWN_BUFPUTSL(ob, ""); - hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); + HOEDOWN_BUFPUTSL(ob, ""); + hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); - return 1; + return 1; } static int rndr_linebreak(hoedown_buffer *ob, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; - hoedown_buffer_puts(ob, USE_XHTML(state) ? "
    \n" : "
    \n"); - return 1; + hoedown_html_renderer_state *state = data->opaque; + hoedown_buffer_puts(ob, USE_XHTML(state) ? "
    \n" : "
    \n"); + return 1; } static void rndr_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; + hoedown_html_renderer_state *state = data->opaque; - if (ob->size) - hoedown_buffer_putc(ob, '\n'); + if (ob->size) + hoedown_buffer_putc(ob, '\n'); - if (level <= state->toc_data.nesting_level) - hoedown_buffer_printf(ob, "", level, state->toc_data.header_count++); - else - hoedown_buffer_printf(ob, "", level); + if (level <= state->toc_data.nesting_level) + hoedown_buffer_printf(ob, "", level, state->toc_data.header_count++); + else + hoedown_buffer_printf(ob, "", level); - if (content) hoedown_buffer_put(ob, content->data, content->size); - hoedown_buffer_printf(ob, "\n", level); + if (content) hoedown_buffer_put(ob, content->data, content->size); + hoedown_buffer_printf(ob, "\n", level); } static int rndr_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; + hoedown_html_renderer_state *state = data->opaque; - HOEDOWN_BUFPUTSL(ob, "size) - escape_href(ob, link->data, link->size); + if (link && link->size) + escape_href(ob, link->data, link->size); - if (title && title->size) { - HOEDOWN_BUFPUTSL(ob, "\" title=\""); - escape_html(ob, title->data, title->size); - } + if (title && title->size) { + HOEDOWN_BUFPUTSL(ob, "\" title=\""); + escape_html(ob, title->data, title->size); + } - if (state->link_attributes) { - hoedown_buffer_putc(ob, '\"'); - state->link_attributes(ob, link, data); - hoedown_buffer_putc(ob, '>'); - } else { - HOEDOWN_BUFPUTSL(ob, "\">"); - } + if (state->link_attributes) { + hoedown_buffer_putc(ob, '\"'); + state->link_attributes(ob, link, data); + hoedown_buffer_putc(ob, '>'); + } else { + HOEDOWN_BUFPUTSL(ob, "\">"); + } - if (content && content->size) hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); - return 1; + if (content && content->size) hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); + return 1; } static void rndr_list(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data) { - if (ob->size) hoedown_buffer_putc(ob, '\n'); - hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "
      \n" : "
        \n"), 5); - if (content) hoedown_buffer_put(ob, content->data, content->size); - hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "
    \n" : "\n"), 6); + if (ob->size) hoedown_buffer_putc(ob, '\n'); + hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "
      \n" : "
        \n"), 5); + if (content) hoedown_buffer_put(ob, content->data, content->size); + hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "
    \n" : "\n"), 6); } static void rndr_listitem(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data) { - HOEDOWN_BUFPUTSL(ob, "
  • "); - if (content) { - size_t size = content->size; - while (size && content->data[size - 1] == '\n') - size--; + HOEDOWN_BUFPUTSL(ob, "
  • "); + if (content) { + size_t size = content->size; + while (size && content->data[size - 1] == '\n') + size--; - hoedown_buffer_put(ob, content->data, size); - } - HOEDOWN_BUFPUTSL(ob, "
  • \n"); + hoedown_buffer_put(ob, content->data, size); + } + HOEDOWN_BUFPUTSL(ob, "
  • \n"); } static void rndr_paragraph(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; - size_t i = 0; + hoedown_html_renderer_state *state = data->opaque; + size_t i = 0; - if (ob->size) hoedown_buffer_putc(ob, '\n'); + if (ob->size) hoedown_buffer_putc(ob, '\n'); - if (!content || !content->size) - return; + if (!content || !content->size) + return; - while (i < content->size && isspace(content->data[i])) i++; + while (i < content->size && isspace(content->data[i])) i++; - if (i == content->size) - return; + if (i == content->size) + return; - HOEDOWN_BUFPUTSL(ob, "

    "); - if (state->flags & HOEDOWN_HTML_HARD_WRAP) { - size_t org; - while (i < content->size) { - org = i; - while (i < content->size && content->data[i] != '\n') - i++; + HOEDOWN_BUFPUTSL(ob, "

    "); + if (state->flags & HOEDOWN_HTML_HARD_WRAP) { + size_t org; + while (i < content->size) { + org = i; + while (i < content->size && content->data[i] != '\n') + i++; - if (i > org) - hoedown_buffer_put(ob, content->data + org, i - org); + if (i > org) + hoedown_buffer_put(ob, content->data + org, i - org); - /* - * do not insert a line break if this newline - * is the last character on the paragraph - */ - if (i >= content->size - 1) - break; + /* + * do not insert a line break if this newline + * is the last character on the paragraph + */ + if (i >= content->size - 1) + break; - rndr_linebreak(ob, data); - i++; - } - } else { - hoedown_buffer_put(ob, content->data + i, content->size - i); - } - HOEDOWN_BUFPUTSL(ob, "

    \n"); + rndr_linebreak(ob, data); + i++; + } + } else { + hoedown_buffer_put(ob, content->data + i, content->size - i); + } + HOEDOWN_BUFPUTSL(ob, "

    \n"); } static void rndr_raw_block(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data) { - size_t org, sz; + size_t org, sz; - if (!text) - return; + if (!text) + return; - /* FIXME: Do we *really* need to trim the HTML? How does that make a difference? */ - sz = text->size; - while (sz > 0 && text->data[sz - 1] == '\n') - sz--; + /* FIXME: Do we *really* need to trim the HTML? How does that make a difference? */ + sz = text->size; + while (sz > 0 && text->data[sz - 1] == '\n') + sz--; - org = 0; - while (org < sz && text->data[org] == '\n') - org++; + org = 0; + while (org < sz && text->data[org] == '\n') + org++; - if (org >= sz) - return; + if (org >= sz) + return; - if (ob->size) - hoedown_buffer_putc(ob, '\n'); + if (ob->size) + hoedown_buffer_putc(ob, '\n'); - hoedown_buffer_put(ob, text->data + org, sz - org); - hoedown_buffer_putc(ob, '\n'); + hoedown_buffer_put(ob, text->data + org, sz - org); + hoedown_buffer_putc(ob, '\n'); } static int rndr_triple_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) return 0; - HOEDOWN_BUFPUTSL(ob, ""); - hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); - return 1; + if (!content || !content->size) return 0; + HOEDOWN_BUFPUTSL(ob, ""); + hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); + return 1; } static void rndr_hrule(hoedown_buffer *ob, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; - if (ob->size) hoedown_buffer_putc(ob, '\n'); - hoedown_buffer_puts(ob, USE_XHTML(state) ? "
    \n" : "
    \n"); + hoedown_html_renderer_state *state = data->opaque; + if (ob->size) hoedown_buffer_putc(ob, '\n'); + hoedown_buffer_puts(ob, USE_XHTML(state) ? "
    \n" : "
    \n"); } static int rndr_image(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; - if (!link || !link->size) return 0; + hoedown_html_renderer_state *state = data->opaque; + if (!link || !link->size) return 0; - HOEDOWN_BUFPUTSL(ob, "data, link->size); - HOEDOWN_BUFPUTSL(ob, "\" alt=\""); + HOEDOWN_BUFPUTSL(ob, "data, link->size); + HOEDOWN_BUFPUTSL(ob, "\" alt=\""); - if (alt && alt->size) - escape_html(ob, alt->data, alt->size); + if (alt && alt->size) + escape_html(ob, alt->data, alt->size); - if (title && title->size) { - HOEDOWN_BUFPUTSL(ob, "\" title=\""); - escape_html(ob, title->data, title->size); } + if (title && title->size) { + HOEDOWN_BUFPUTSL(ob, "\" title=\""); + escape_html(ob, title->data, title->size); } - hoedown_buffer_puts(ob, USE_XHTML(state) ? "\"/>" : "\">"); - return 1; + hoedown_buffer_puts(ob, USE_XHTML(state) ? "\"/>" : "\">"); + return 1; } static int rndr_raw_html(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; + hoedown_html_renderer_state *state = data->opaque; - /* ESCAPE overrides SKIP_HTML. It doesn't look to see if - * there are any valid tags, just escapes all of them. */ - if((state->flags & HOEDOWN_HTML_ESCAPE) != 0) { - escape_html(ob, text->data, text->size); - return 1; - } + /* ESCAPE overrides SKIP_HTML. It doesn't look to see if + * there are any valid tags, just escapes all of them. */ + if((state->flags & HOEDOWN_HTML_ESCAPE) != 0) { + escape_html(ob, text->data, text->size); + return 1; + } - if ((state->flags & HOEDOWN_HTML_SKIP_HTML) != 0) - return 1; + if ((state->flags & HOEDOWN_HTML_SKIP_HTML) != 0) + return 1; - hoedown_buffer_put(ob, text->data, text->size); - return 1; + hoedown_buffer_put(ob, text->data, text->size); + return 1; } static void @@ -437,318 +437,318 @@ rndr_table_body(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown static void rndr_tablerow(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - HOEDOWN_BUFPUTSL(ob, "\n"); - if (content) hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, "\n"); + HOEDOWN_BUFPUTSL(ob, "\n"); + if (content) hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, "\n"); } static void rndr_tablecell(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data) { - if (flags & HOEDOWN_TABLE_HEADER) { - HOEDOWN_BUFPUTSL(ob, ""); - break; + switch (flags & HOEDOWN_TABLE_ALIGNMASK) { + case HOEDOWN_TABLE_ALIGN_CENTER: + HOEDOWN_BUFPUTSL(ob, " style=\"text-align: center\">"); + break; - case HOEDOWN_TABLE_ALIGN_LEFT: - HOEDOWN_BUFPUTSL(ob, " style=\"text-align: left\">"); - break; + case HOEDOWN_TABLE_ALIGN_LEFT: + HOEDOWN_BUFPUTSL(ob, " style=\"text-align: left\">"); + break; - case HOEDOWN_TABLE_ALIGN_RIGHT: - HOEDOWN_BUFPUTSL(ob, " style=\"text-align: right\">"); - break; + case HOEDOWN_TABLE_ALIGN_RIGHT: + HOEDOWN_BUFPUTSL(ob, " style=\"text-align: right\">"); + break; - default: - HOEDOWN_BUFPUTSL(ob, ">"); - } + default: + HOEDOWN_BUFPUTSL(ob, ">"); + } - if (content) - hoedown_buffer_put(ob, content->data, content->size); + if (content) + hoedown_buffer_put(ob, content->data, content->size); - if (flags & HOEDOWN_TABLE_HEADER) { - HOEDOWN_BUFPUTSL(ob, "\n"); - } else { - HOEDOWN_BUFPUTSL(ob, "\n"); - } + if (flags & HOEDOWN_TABLE_HEADER) { + HOEDOWN_BUFPUTSL(ob, "\n"); + } else { + HOEDOWN_BUFPUTSL(ob, "\n"); + } } static int rndr_superscript(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (!content || !content->size) return 0; - HOEDOWN_BUFPUTSL(ob, ""); - hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, ""); - return 1; + if (!content || !content->size) return 0; + HOEDOWN_BUFPUTSL(ob, ""); + hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, ""); + return 1; } static void rndr_normal_text(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - if (content) - escape_html(ob, content->data, content->size); + if (content) + escape_html(ob, content->data, content->size); } static void rndr_footnotes(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; + hoedown_html_renderer_state *state = data->opaque; - if (ob->size) hoedown_buffer_putc(ob, '\n'); - HOEDOWN_BUFPUTSL(ob, "
    \n"); - hoedown_buffer_puts(ob, USE_XHTML(state) ? "
    \n" : "
    \n"); - HOEDOWN_BUFPUTSL(ob, "
      \n"); + if (ob->size) hoedown_buffer_putc(ob, '\n'); + HOEDOWN_BUFPUTSL(ob, "
      \n"); + hoedown_buffer_puts(ob, USE_XHTML(state) ? "
      \n" : "
      \n"); + HOEDOWN_BUFPUTSL(ob, "
        \n"); - if (content) hoedown_buffer_put(ob, content->data, content->size); + if (content) hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, "\n
      \n
      \n"); + HOEDOWN_BUFPUTSL(ob, "\n
    \n
    \n"); } static void rndr_footnote_def(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data) { - size_t i = 0; - int pfound = 0; - - /* insert anchor at the end of first paragraph block */ - if (content) { - while ((i+3) < content->size) { - if (content->data[i++] != '<') continue; - if (content->data[i++] != '/') continue; - if (content->data[i++] != 'p' && content->data[i] != 'P') continue; - if (content->data[i] != '>') continue; - i -= 3; - pfound = 1; - break; - } - } - - hoedown_buffer_printf(ob, "\n
  • \n", num); - if (pfound) { - hoedown_buffer_put(ob, content->data, i); - hoedown_buffer_printf(ob, " ", num); - hoedown_buffer_put(ob, content->data + i, content->size - i); - } else if (content) { - hoedown_buffer_put(ob, content->data, content->size); - } - HOEDOWN_BUFPUTSL(ob, "
  • \n"); + size_t i = 0; + int pfound = 0; + + /* insert anchor at the end of first paragraph block */ + if (content) { + while ((i+3) < content->size) { + if (content->data[i++] != '<') continue; + if (content->data[i++] != '/') continue; + if (content->data[i++] != 'p' && content->data[i] != 'P') continue; + if (content->data[i] != '>') continue; + i -= 3; + pfound = 1; + break; + } + } + + hoedown_buffer_printf(ob, "\n
  • \n", num); + if (pfound) { + hoedown_buffer_put(ob, content->data, i); + hoedown_buffer_printf(ob, " ", num); + hoedown_buffer_put(ob, content->data + i, content->size - i); + } else if (content) { + hoedown_buffer_put(ob, content->data, content->size); + } + HOEDOWN_BUFPUTSL(ob, "
  • \n"); } static int rndr_footnote_ref(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data) { - hoedown_buffer_printf(ob, "%d", num, num, num); - return 1; + hoedown_buffer_printf(ob, "%d", num, num, num); + return 1; } static int rndr_math(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data) { - hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\[" : "\\("), 2); - escape_html(ob, text->data, text->size); - hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\]" : "\\)"), 2); - return 1; + hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\[" : "\\("), 2); + escape_html(ob, text->data, text->size); + hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\]" : "\\)"), 2); + return 1; } static void toc_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state = data->opaque; - - if (level <= state->toc_data.nesting_level) { - /* set the level offset if this is the first header - * we're parsing for the document */ - if (state->toc_data.current_level == 0) - state->toc_data.level_offset = level - 1; - - level -= state->toc_data.level_offset; - - if (level > state->toc_data.current_level) { - while (level > state->toc_data.current_level) { - HOEDOWN_BUFPUTSL(ob, "
      \n
    • \n"); - state->toc_data.current_level++; - } - } else if (level < state->toc_data.current_level) { - HOEDOWN_BUFPUTSL(ob, "
    • \n"); - while (level < state->toc_data.current_level) { - HOEDOWN_BUFPUTSL(ob, "
    \n\n"); - state->toc_data.current_level--; - } - HOEDOWN_BUFPUTSL(ob,"
  • \n"); - } else { - HOEDOWN_BUFPUTSL(ob,"
  • \n
  • \n"); - } - - hoedown_buffer_printf(ob, "", state->toc_data.header_count++); - if (content) hoedown_buffer_put(ob, content->data, content->size); - HOEDOWN_BUFPUTSL(ob, "\n"); - } + hoedown_html_renderer_state *state = data->opaque; + + if (level <= state->toc_data.nesting_level) { + /* set the level offset if this is the first header + * we're parsing for the document */ + if (state->toc_data.current_level == 0) + state->toc_data.level_offset = level - 1; + + level -= state->toc_data.level_offset; + + if (level > state->toc_data.current_level) { + while (level > state->toc_data.current_level) { + HOEDOWN_BUFPUTSL(ob, "
      \n
    • \n"); + state->toc_data.current_level++; + } + } else if (level < state->toc_data.current_level) { + HOEDOWN_BUFPUTSL(ob, "
    • \n"); + while (level < state->toc_data.current_level) { + HOEDOWN_BUFPUTSL(ob, "
    \n
  • \n"); + state->toc_data.current_level--; + } + HOEDOWN_BUFPUTSL(ob,"
  • \n"); + } else { + HOEDOWN_BUFPUTSL(ob,"
  • \n
  • \n"); + } + + hoedown_buffer_printf(ob, "", state->toc_data.header_count++); + if (content) hoedown_buffer_put(ob, content->data, content->size); + HOEDOWN_BUFPUTSL(ob, "\n"); + } } static int toc_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data) { - if (content && content->size) hoedown_buffer_put(ob, content->data, content->size); - return 1; + if (content && content->size) hoedown_buffer_put(ob, content->data, content->size); + return 1; } static void toc_finalize(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data) { - hoedown_html_renderer_state *state; + hoedown_html_renderer_state *state; - if (inline_render) - return; + if (inline_render) + return; - state = data->opaque; + state = data->opaque; - while (state->toc_data.current_level > 0) { - HOEDOWN_BUFPUTSL(ob, "
  • \n\n"); - state->toc_data.current_level--; - } + while (state->toc_data.current_level > 0) { + HOEDOWN_BUFPUTSL(ob, "\n\n"); + state->toc_data.current_level--; + } - state->toc_data.header_count = 0; + state->toc_data.header_count = 0; } hoedown_renderer * hoedown_html_toc_renderer_new(int nesting_level) { - static const hoedown_renderer cb_default = { - NULL, - - NULL, - NULL, - toc_header, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - - NULL, - rndr_codespan, - rndr_double_emphasis, - rndr_emphasis, - rndr_underline, - rndr_highlight, - rndr_quote, - NULL, - NULL, - toc_link, - rndr_triple_emphasis, - rndr_strikethrough, - rndr_superscript, - NULL, - NULL, - NULL, - - NULL, - rndr_normal_text, - - NULL, - toc_finalize - }; - - hoedown_html_renderer_state *state; - hoedown_renderer *renderer; - - /* Prepare the state pointer */ - state = hoedown_malloc(sizeof(hoedown_html_renderer_state)); - memset(state, 0x0, sizeof(hoedown_html_renderer_state)); - - state->toc_data.nesting_level = nesting_level; - - /* Prepare the renderer */ - renderer = hoedown_malloc(sizeof(hoedown_renderer)); - memcpy(renderer, &cb_default, sizeof(hoedown_renderer)); - - renderer->opaque = state; - return renderer; + static const hoedown_renderer cb_default = { + NULL, + + NULL, + NULL, + toc_header, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + + NULL, + rndr_codespan, + rndr_double_emphasis, + rndr_emphasis, + rndr_underline, + rndr_highlight, + rndr_quote, + NULL, + NULL, + toc_link, + rndr_triple_emphasis, + rndr_strikethrough, + rndr_superscript, + NULL, + NULL, + NULL, + + NULL, + rndr_normal_text, + + NULL, + toc_finalize + }; + + hoedown_html_renderer_state *state; + hoedown_renderer *renderer; + + /* Prepare the state pointer */ + state = hoedown_malloc(sizeof(hoedown_html_renderer_state)); + memset(state, 0x0, sizeof(hoedown_html_renderer_state)); + + state->toc_data.nesting_level = nesting_level; + + /* Prepare the renderer */ + renderer = hoedown_malloc(sizeof(hoedown_renderer)); + memcpy(renderer, &cb_default, sizeof(hoedown_renderer)); + + renderer->opaque = state; + return renderer; } hoedown_renderer * hoedown_html_renderer_new(hoedown_html_flags render_flags, int nesting_level) { - static const hoedown_renderer cb_default = { - NULL, - - rndr_blockcode, - rndr_blockquote, - rndr_header, - rndr_hrule, - rndr_list, - rndr_listitem, - rndr_paragraph, - rndr_table, - rndr_table_header, - rndr_table_body, - rndr_tablerow, - rndr_tablecell, - rndr_footnotes, - rndr_footnote_def, - rndr_raw_block, - - rndr_autolink, - rndr_codespan, - rndr_double_emphasis, - rndr_emphasis, - rndr_underline, - rndr_highlight, - rndr_quote, - rndr_image, - rndr_linebreak, - rndr_link, - rndr_triple_emphasis, - rndr_strikethrough, - rndr_superscript, - rndr_footnote_ref, - rndr_math, - rndr_raw_html, - - NULL, - rndr_normal_text, - - NULL, - NULL - }; - - hoedown_html_renderer_state *state; - hoedown_renderer *renderer; - - /* Prepare the state pointer */ - state = hoedown_malloc(sizeof(hoedown_html_renderer_state)); - memset(state, 0x0, sizeof(hoedown_html_renderer_state)); - - state->flags = render_flags; - state->toc_data.nesting_level = nesting_level; - - /* Prepare the renderer */ - renderer = hoedown_malloc(sizeof(hoedown_renderer)); - memcpy(renderer, &cb_default, sizeof(hoedown_renderer)); - - if (render_flags & HOEDOWN_HTML_SKIP_HTML || render_flags & HOEDOWN_HTML_ESCAPE) - renderer->blockhtml = NULL; - - renderer->opaque = state; - return renderer; + static const hoedown_renderer cb_default = { + NULL, + + rndr_blockcode, + rndr_blockquote, + rndr_header, + rndr_hrule, + rndr_list, + rndr_listitem, + rndr_paragraph, + rndr_table, + rndr_table_header, + rndr_table_body, + rndr_tablerow, + rndr_tablecell, + rndr_footnotes, + rndr_footnote_def, + rndr_raw_block, + + rndr_autolink, + rndr_codespan, + rndr_double_emphasis, + rndr_emphasis, + rndr_underline, + rndr_highlight, + rndr_quote, + rndr_image, + rndr_linebreak, + rndr_link, + rndr_triple_emphasis, + rndr_strikethrough, + rndr_superscript, + rndr_footnote_ref, + rndr_math, + rndr_raw_html, + + NULL, + rndr_normal_text, + + NULL, + NULL + }; + + hoedown_html_renderer_state *state; + hoedown_renderer *renderer; + + /* Prepare the state pointer */ + state = hoedown_malloc(sizeof(hoedown_html_renderer_state)); + memset(state, 0x0, sizeof(hoedown_html_renderer_state)); + + state->flags = render_flags; + state->toc_data.nesting_level = nesting_level; + + /* Prepare the renderer */ + renderer = hoedown_malloc(sizeof(hoedown_renderer)); + memcpy(renderer, &cb_default, sizeof(hoedown_renderer)); + + if (render_flags & HOEDOWN_HTML_SKIP_HTML || render_flags & HOEDOWN_HTML_ESCAPE) + renderer->blockhtml = NULL; + + renderer->opaque = state; + return renderer; } void hoedown_html_renderer_free(hoedown_renderer *renderer) { - free(renderer->opaque); - free(renderer); + free(renderer->opaque); + free(renderer); } diff --git a/libraries/hoedown/src/html_smartypants.c b/libraries/hoedown/src/html_smartypants.c index e24b6bf0..d89624f3 100644 --- a/libraries/hoedown/src/html_smartypants.c +++ b/libraries/hoedown/src/html_smartypants.c @@ -10,8 +10,8 @@ #endif struct smartypants_data { - int in_squote; - int in_dquote; + int in_squote; + int in_dquote; }; static size_t smartypants_cb__ltag(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); @@ -26,353 +26,353 @@ static size_t smartypants_cb__backtick(hoedown_buffer *ob, struct smartypants_da static size_t smartypants_cb__escape(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t (*smartypants_cb_ptrs[]) - (hoedown_buffer *, struct smartypants_data *, uint8_t, const uint8_t *, size_t) = + (hoedown_buffer *, struct smartypants_data *, uint8_t, const uint8_t *, size_t) = { - NULL, /* 0 */ - smartypants_cb__dash, /* 1 */ - smartypants_cb__parens, /* 2 */ - smartypants_cb__squote, /* 3 */ - smartypants_cb__dquote, /* 4 */ - smartypants_cb__amp, /* 5 */ - smartypants_cb__period, /* 6 */ - smartypants_cb__number, /* 7 */ - smartypants_cb__ltag, /* 8 */ - smartypants_cb__backtick, /* 9 */ - smartypants_cb__escape, /* 10 */ + NULL, /* 0 */ + smartypants_cb__dash, /* 1 */ + smartypants_cb__parens, /* 2 */ + smartypants_cb__squote, /* 3 */ + smartypants_cb__dquote, /* 4 */ + smartypants_cb__amp, /* 5 */ + smartypants_cb__period, /* 6 */ + smartypants_cb__number, /* 7 */ + smartypants_cb__ltag, /* 8 */ + smartypants_cb__backtick, /* 9 */ + smartypants_cb__escape, /* 10 */ }; static const uint8_t smartypants_cb_chars[UINT8_MAX+1] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0, - 0, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, - 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0, + 0, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static int word_boundary(uint8_t c) { - return c == 0 || isspace(c) || ispunct(c); + return c == 0 || isspace(c) || ispunct(c); } /* - If 'text' begins with any kind of single quote (e.g. "'" or "'" etc.), - returns the length of the sequence of characters that makes up the single- - quote. Otherwise, returns zero. + If 'text' begins with any kind of single quote (e.g. "'" or "'" etc.), + returns the length of the sequence of characters that makes up the single- + quote. Otherwise, returns zero. */ static size_t squote_len(const uint8_t *text, size_t size) { - static char* single_quote_list[] = { "'", "'", "'", "'", NULL }; - char** p; + static char* single_quote_list[] = { "'", "'", "'", "'", NULL }; + char** p; - for (p = single_quote_list; *p; ++p) { - size_t len = strlen(*p); - if (size >= len && memcmp(text, *p, len) == 0) { - return len; - } - } + for (p = single_quote_list; *p; ++p) { + size_t len = strlen(*p); + if (size >= len && memcmp(text, *p, len) == 0) { + return len; + } + } - return 0; + return 0; } /* Converts " or ' at very beginning or end of a word to left or right quote */ static int smartypants_quotes(hoedown_buffer *ob, uint8_t previous_char, uint8_t next_char, uint8_t quote, int *is_open) { - char ent[8]; + char ent[8]; - if (*is_open && !word_boundary(next_char)) - return 0; + if (*is_open && !word_boundary(next_char)) + return 0; - if (!(*is_open) && !word_boundary(previous_char)) - return 0; + if (!(*is_open) && !word_boundary(previous_char)) + return 0; - snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote); - *is_open = !(*is_open); - hoedown_buffer_puts(ob, ent); - return 1; + snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote); + *is_open = !(*is_open); + hoedown_buffer_puts(ob, ent); + return 1; } /* - Converts ' to left or right single quote; but the initial ' might be in - different forms, e.g. ' or ' or '. - 'squote_text' points to the original single quote, and 'squote_size' is its length. - 'text' points at the last character of the single-quote, e.g. ' or ; + Converts ' to left or right single quote; but the initial ' might be in + different forms, e.g. ' or ' or '. + 'squote_text' points to the original single quote, and 'squote_size' is its length. + 'text' points at the last character of the single-quote, e.g. ' or ; */ static size_t smartypants_squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size, - const uint8_t *squote_text, size_t squote_size) + const uint8_t *squote_text, size_t squote_size) { - if (size >= 2) { - uint8_t t1 = tolower(text[1]); - size_t next_squote_len = squote_len(text+1, size-1); - - /* convert '' to “ or ” */ - if (next_squote_len > 0) { - uint8_t next_char = (size > 1+next_squote_len) ? text[1+next_squote_len] : 0; - if (smartypants_quotes(ob, previous_char, next_char, 'd', &smrt->in_dquote)) - return next_squote_len; - } - - /* Tom's, isn't, I'm, I'd */ - if ((t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && - (size == 3 || word_boundary(text[2]))) { - HOEDOWN_BUFPUTSL(ob, "’"); - return 0; - } - - /* you're, you'll, you've */ - if (size >= 3) { - uint8_t t2 = tolower(text[2]); - - if (((t1 == 'r' && t2 == 'e') || - (t1 == 'l' && t2 == 'l') || - (t1 == 'v' && t2 == 'e')) && - (size == 4 || word_boundary(text[3]))) { - HOEDOWN_BUFPUTSL(ob, "’"); - return 0; - } - } - } - - if (smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 's', &smrt->in_squote)) - return 0; - - hoedown_buffer_put(ob, squote_text, squote_size); - return 0; + if (size >= 2) { + uint8_t t1 = tolower(text[1]); + size_t next_squote_len = squote_len(text+1, size-1); + + /* convert '' to “ or ” */ + if (next_squote_len > 0) { + uint8_t next_char = (size > 1+next_squote_len) ? text[1+next_squote_len] : 0; + if (smartypants_quotes(ob, previous_char, next_char, 'd', &smrt->in_dquote)) + return next_squote_len; + } + + /* Tom's, isn't, I'm, I'd */ + if ((t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && + (size == 3 || word_boundary(text[2]))) { + HOEDOWN_BUFPUTSL(ob, "’"); + return 0; + } + + /* you're, you'll, you've */ + if (size >= 3) { + uint8_t t2 = tolower(text[2]); + + if (((t1 == 'r' && t2 == 'e') || + (t1 == 'l' && t2 == 'l') || + (t1 == 'v' && t2 == 'e')) && + (size == 4 || word_boundary(text[3]))) { + HOEDOWN_BUFPUTSL(ob, "’"); + return 0; + } + } + } + + if (smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 's', &smrt->in_squote)) + return 0; + + hoedown_buffer_put(ob, squote_text, squote_size); + return 0; } /* Converts ' to left or right single quote. */ static size_t smartypants_cb__squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - return smartypants_squote(ob, smrt, previous_char, text, size, text, 1); + return smartypants_squote(ob, smrt, previous_char, text, size, text, 1); } /* Converts (c), (r), (tm) */ static size_t smartypants_cb__parens(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - if (size >= 3) { - uint8_t t1 = tolower(text[1]); - uint8_t t2 = tolower(text[2]); - - if (t1 == 'c' && t2 == ')') { - HOEDOWN_BUFPUTSL(ob, "©"); - return 2; - } - - if (t1 == 'r' && t2 == ')') { - HOEDOWN_BUFPUTSL(ob, "®"); - return 2; - } - - if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') { - HOEDOWN_BUFPUTSL(ob, "™"); - return 3; - } - } - - hoedown_buffer_putc(ob, text[0]); - return 0; + if (size >= 3) { + uint8_t t1 = tolower(text[1]); + uint8_t t2 = tolower(text[2]); + + if (t1 == 'c' && t2 == ')') { + HOEDOWN_BUFPUTSL(ob, "©"); + return 2; + } + + if (t1 == 'r' && t2 == ')') { + HOEDOWN_BUFPUTSL(ob, "®"); + return 2; + } + + if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') { + HOEDOWN_BUFPUTSL(ob, "™"); + return 3; + } + } + + hoedown_buffer_putc(ob, text[0]); + return 0; } /* Converts "--" to em-dash, etc. */ static size_t smartypants_cb__dash(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - if (size >= 3 && text[1] == '-' && text[2] == '-') { - HOEDOWN_BUFPUTSL(ob, "—"); - return 2; - } - - if (size >= 2 && text[1] == '-') { - HOEDOWN_BUFPUTSL(ob, "–"); - return 1; - } - - hoedown_buffer_putc(ob, text[0]); - return 0; + if (size >= 3 && text[1] == '-' && text[2] == '-') { + HOEDOWN_BUFPUTSL(ob, "—"); + return 2; + } + + if (size >= 2 && text[1] == '-') { + HOEDOWN_BUFPUTSL(ob, "–"); + return 1; + } + + hoedown_buffer_putc(ob, text[0]); + return 0; } /* Converts " etc. */ static size_t smartypants_cb__amp(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - size_t len; - if (size >= 6 && memcmp(text, """, 6) == 0) { - if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote)) - return 5; - } - - len = squote_len(text, size); - if (len > 0) { - return (len-1) + smartypants_squote(ob, smrt, previous_char, text+(len-1), size-(len-1), text, len); - } - - if (size >= 4 && memcmp(text, "�", 4) == 0) - return 3; - - hoedown_buffer_putc(ob, '&'); - return 0; + size_t len; + if (size >= 6 && memcmp(text, """, 6) == 0) { + if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote)) + return 5; + } + + len = squote_len(text, size); + if (len > 0) { + return (len-1) + smartypants_squote(ob, smrt, previous_char, text+(len-1), size-(len-1), text, len); + } + + if (size >= 4 && memcmp(text, "�", 4) == 0) + return 3; + + hoedown_buffer_putc(ob, '&'); + return 0; } /* Converts "..." to ellipsis */ static size_t smartypants_cb__period(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - if (size >= 3 && text[1] == '.' && text[2] == '.') { - HOEDOWN_BUFPUTSL(ob, "…"); - return 2; - } - - if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') { - HOEDOWN_BUFPUTSL(ob, "…"); - return 4; - } - - hoedown_buffer_putc(ob, text[0]); - return 0; + if (size >= 3 && text[1] == '.' && text[2] == '.') { + HOEDOWN_BUFPUTSL(ob, "…"); + return 2; + } + + if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') { + HOEDOWN_BUFPUTSL(ob, "…"); + return 4; + } + + hoedown_buffer_putc(ob, text[0]); + return 0; } /* Converts `` to opening double quote */ static size_t smartypants_cb__backtick(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - if (size >= 2 && text[1] == '`') { - if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote)) - return 1; - } + if (size >= 2 && text[1] == '`') { + if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote)) + return 1; + } - hoedown_buffer_putc(ob, text[0]); - return 0; + hoedown_buffer_putc(ob, text[0]); + return 0; } /* Converts 1/2, 1/4, 3/4 */ static size_t smartypants_cb__number(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - if (word_boundary(previous_char) && size >= 3) { - if (text[0] == '1' && text[1] == '/' && text[2] == '2') { - if (size == 3 || word_boundary(text[3])) { - HOEDOWN_BUFPUTSL(ob, "½"); - return 2; - } - } - - if (text[0] == '1' && text[1] == '/' && text[2] == '4') { - if (size == 3 || word_boundary(text[3]) || - (size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) { - HOEDOWN_BUFPUTSL(ob, "¼"); - return 2; - } - } - - if (text[0] == '3' && text[1] == '/' && text[2] == '4') { - if (size == 3 || word_boundary(text[3]) || - (size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) { - HOEDOWN_BUFPUTSL(ob, "¾"); - return 2; - } - } - } - - hoedown_buffer_putc(ob, text[0]); - return 0; + if (word_boundary(previous_char) && size >= 3) { + if (text[0] == '1' && text[1] == '/' && text[2] == '2') { + if (size == 3 || word_boundary(text[3])) { + HOEDOWN_BUFPUTSL(ob, "½"); + return 2; + } + } + + if (text[0] == '1' && text[1] == '/' && text[2] == '4') { + if (size == 3 || word_boundary(text[3]) || + (size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) { + HOEDOWN_BUFPUTSL(ob, "¼"); + return 2; + } + } + + if (text[0] == '3' && text[1] == '/' && text[2] == '4') { + if (size == 3 || word_boundary(text[3]) || + (size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) { + HOEDOWN_BUFPUTSL(ob, "¾"); + return 2; + } + } + } + + hoedown_buffer_putc(ob, text[0]); + return 0; } /* Converts " to left or right double quote */ static size_t smartypants_cb__dquote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote)) - HOEDOWN_BUFPUTSL(ob, """); + if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote)) + HOEDOWN_BUFPUTSL(ob, """); - return 0; + return 0; } static size_t smartypants_cb__ltag(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - static const char *skip_tags[] = { - "pre", "code", "var", "samp", "kbd", "math", "script", "style" - }; - static const size_t skip_tags_count = 8; - - size_t tag, i = 0; - - /* This is a comment. Copy everything verbatim until --> or EOF is seen. */ - if (i + 4 < size && memcmp(text, "", 3) != 0) - i++; - i += 3; - hoedown_buffer_put(ob, text, i + 1); - return i; - } - - while (i < size && text[i] != '>') - i++; - - for (tag = 0; tag < skip_tags_count; ++tag) { - if (hoedown_html_is_tag(text, size, skip_tags[tag]) == HOEDOWN_HTML_TAG_OPEN) - break; - } - - if (tag < skip_tags_count) { - for (;;) { - while (i < size && text[i] != '<') - i++; - - if (i == size) - break; - - if (hoedown_html_is_tag(text + i, size - i, skip_tags[tag]) == HOEDOWN_HTML_TAG_CLOSE) - break; - - i++; - } - - while (i < size && text[i] != '>') - i++; - } - - hoedown_buffer_put(ob, text, i + 1); - return i; + static const char *skip_tags[] = { + "pre", "code", "var", "samp", "kbd", "math", "script", "style" + }; + static const size_t skip_tags_count = 8; + + size_t tag, i = 0; + + /* This is a comment. Copy everything verbatim until --> or EOF is seen. */ + if (i + 4 < size && memcmp(text, "", 3) != 0) + i++; + i += 3; + hoedown_buffer_put(ob, text, i + 1); + return i; + } + + while (i < size && text[i] != '>') + i++; + + for (tag = 0; tag < skip_tags_count; ++tag) { + if (hoedown_html_is_tag(text, size, skip_tags[tag]) == HOEDOWN_HTML_TAG_OPEN) + break; + } + + if (tag < skip_tags_count) { + for (;;) { + while (i < size && text[i] != '<') + i++; + + if (i == size) + break; + + if (hoedown_html_is_tag(text + i, size - i, skip_tags[tag]) == HOEDOWN_HTML_TAG_CLOSE) + break; + + i++; + } + + while (i < size && text[i] != '>') + i++; + } + + hoedown_buffer_put(ob, text, i + 1); + return i; } static size_t smartypants_cb__escape(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { - if (size < 2) - return 0; - - switch (text[1]) { - case '\\': - case '"': - case '\'': - case '.': - case '-': - case '`': - hoedown_buffer_putc(ob, text[1]); - return 1; - - default: - hoedown_buffer_putc(ob, '\\'); - return 0; - } + if (size < 2) + return 0; + + switch (text[1]) { + case '\\': + case '"': + case '\'': + case '.': + case '-': + case '`': + hoedown_buffer_putc(ob, text[1]); + return 1; + + default: + hoedown_buffer_putc(ob, '\\'); + return 0; + } } #if 0 @@ -408,28 +408,28 @@ static struct { void hoedown_html_smartypants(hoedown_buffer *ob, const uint8_t *text, size_t size) { - size_t i; - struct smartypants_data smrt = {0, 0}; + size_t i; + struct smartypants_data smrt = {0, 0}; - if (!text) - return; + if (!text) + return; - hoedown_buffer_grow(ob, size); + hoedown_buffer_grow(ob, size); - for (i = 0; i < size; ++i) { - size_t org; - uint8_t action = 0; + for (i = 0; i < size; ++i) { + size_t org; + uint8_t action = 0; - org = i; - while (i < size && (action = smartypants_cb_chars[text[i]]) == 0) - i++; + org = i; + while (i < size && (action = smartypants_cb_chars[text[i]]) == 0) + i++; - if (i > org) - hoedown_buffer_put(ob, text + org, i - org); + if (i > org) + hoedown_buffer_put(ob, text + org, i - org); - if (i < size) { - i += smartypants_cb_ptrs[(int)action] - (ob, &smrt, i ? text[i - 1] : 0, text + i, size - i); - } - } + if (i < size) { + i += smartypants_cb_ptrs[(int)action] + (ob, &smrt, i ? text[i - 1] : 0, text + i, size - i); + } + } } diff --git a/libraries/hoedown/src/stack.c b/libraries/hoedown/src/stack.c index 46ead232..0523c11b 100644 --- a/libraries/hoedown/src/stack.c +++ b/libraries/hoedown/src/stack.c @@ -9,71 +9,71 @@ void hoedown_stack_init(hoedown_stack *st, size_t initial_size) { - assert(st); + assert(st); - st->item = NULL; - st->size = st->asize = 0; + st->item = NULL; + st->size = st->asize = 0; - if (!initial_size) - initial_size = 8; + if (!initial_size) + initial_size = 8; - hoedown_stack_grow(st, initial_size); + hoedown_stack_grow(st, initial_size); } void hoedown_stack_uninit(hoedown_stack *st) { - assert(st); + assert(st); - free(st->item); + free(st->item); } void hoedown_stack_grow(hoedown_stack *st, size_t neosz) { - assert(st); + assert(st); - if (st->asize >= neosz) - return; + if (st->asize >= neosz) + return; - st->item = hoedown_realloc(st->item, neosz * sizeof(void *)); - memset(st->item + st->asize, 0x0, (neosz - st->asize) * sizeof(void *)); + st->item = hoedown_realloc(st->item, neosz * sizeof(void *)); + memset(st->item + st->asize, 0x0, (neosz - st->asize) * sizeof(void *)); - st->asize = neosz; + st->asize = neosz; - if (st->size > neosz) - st->size = neosz; + if (st->size > neosz) + st->size = neosz; } void hoedown_stack_push(hoedown_stack *st, void *item) { - assert(st); + assert(st); - if (st->size >= st->asize) - hoedown_stack_grow(st, st->size * 2); + if (st->size >= st->asize) + hoedown_stack_grow(st, st->size * 2); - st->item[st->size++] = item; + st->item[st->size++] = item; } void * hoedown_stack_pop(hoedown_stack *st) { - assert(st); + assert(st); - if (!st->size) - return NULL; + if (!st->size) + return NULL; - return st->item[--st->size]; + return st->item[--st->size]; } void * hoedown_stack_top(const hoedown_stack *st) { - assert(st); + assert(st); - if (!st->size) - return NULL; + if (!st->size) + return NULL; - return st->item[st->size - 1]; + return st->item[st->size - 1]; } diff --git a/libraries/hoedown/src/version.c b/libraries/hoedown/src/version.c index 625ed196..10d36cb9 100644 --- a/libraries/hoedown/src/version.c +++ b/libraries/hoedown/src/version.c @@ -3,7 +3,7 @@ void hoedown_version(int *major, int *minor, int *revision) { - *major = HOEDOWN_VERSION_MAJOR; - *minor = HOEDOWN_VERSION_MINOR; - *revision = HOEDOWN_VERSION_REVISION; + *major = HOEDOWN_VERSION_MAJOR; + *minor = HOEDOWN_VERSION_MINOR; + *revision = HOEDOWN_VERSION_REVISION; } diff --git a/libraries/iconfix/CMakeLists.txt b/libraries/iconfix/CMakeLists.txt index 93bfdd06..ccf0edea 100644 --- a/libraries/iconfix/CMakeLists.txt +++ b/libraries/iconfix/CMakeLists.txt @@ -15,14 +15,14 @@ internal/qiconloader_p.h add_library(MultiMC_iconfix SHARED ${ICONFIX_SOURCES}) target_include_directories(MultiMC_iconfix PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}" ) -qt5_use_modules(MultiMC_iconfix Core Widgets) +target_link_libraries(MultiMC_iconfix Qt5::Core Qt5::Widgets) set_target_properties(MultiMC_iconfix PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1) generate_export_header(MultiMC_iconfix) # Install it install( - TARGETS MultiMC_iconfix - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} + TARGETS MultiMC_iconfix + RUNTIME DESTINATION ${LIBRARY_DEST_DIR} + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} ) \ No newline at end of file diff --git a/libraries/iconfix/internal/qhexstring_p.h b/libraries/iconfix/internal/qhexstring_p.h index f01b4cdd..c81904e5 100644 --- a/libraries/iconfix/internal/qhexstring_p.h +++ b/libraries/iconfix/internal/qhexstring_p.h @@ -61,40 +61,40 @@ // internal helper. Converts an integer value to an unique string token template struct HexString { - inline HexString(const T t) : val(t) - { - } + inline HexString(const T t) : val(t) + { + } - inline void write(QChar *&dest) const - { - const ushort hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - const char *c = reinterpret_cast(&val); - for (uint i = 0; i < sizeof(T); ++i) - { - *dest++ = hexChars[*c & 0xf]; - *dest++ = hexChars[(*c & 0xf0) >> 4]; - ++c; - } - } - const T val; + inline void write(QChar *&dest) const + { + const ushort hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + const char *c = reinterpret_cast(&val); + for (uint i = 0; i < sizeof(T); ++i) + { + *dest++ = hexChars[*c & 0xf]; + *dest++ = hexChars[(*c & 0xf0) >> 4]; + ++c; + } + } + const T val; }; // specialization to enable fast concatenating of our string tokens to a string template struct QConcatenable> { - typedef HexString type; - enum - { - ExactSize = true - }; - static int size(const HexString &) - { - return sizeof(T) * 2; - } - static inline void appendTo(const HexString &str, QChar *&out) - { - str.write(out); - } - typedef QString ConvertTo; + typedef HexString type; + enum + { + ExactSize = true + }; + static int size(const HexString &) + { + return sizeof(T) * 2; + } + static inline void appendTo(const HexString &str, QChar *&out) + { + str.write(out); + } + typedef QString ConvertTo; }; diff --git a/libraries/iconfix/internal/qiconloader.cpp b/libraries/iconfix/internal/qiconloader.cpp index b1195893..41cf3d50 100644 --- a/libraries/iconfix/internal/qiconloader.cpp +++ b/libraries/iconfix/internal/qiconloader.cpp @@ -55,7 +55,7 @@ Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) static QString fallbackTheme() { - return QString("hicolor"); + return QString("hicolor"); } QIconLoader::QIconLoader() : m_themeKey(1), m_supportsSvg(false), m_initialized(false) @@ -67,403 +67,403 @@ QIconLoader::QIconLoader() : m_themeKey(1), m_supportsSvg(false), m_initialized( static inline QString systemThemeName() { - return QIcon::themeName(); + return QIcon::themeName(); } static inline QStringList systemIconSearchPaths() { - auto paths = QIcon::themeSearchPaths(); - paths.push_front(":/icons"); - return paths; + auto paths = QIcon::themeSearchPaths(); + paths.push_front(":/icons"); + return paths; } void QIconLoader::ensureInitialized() { - if (!m_initialized) - { - m_initialized = true; + if (!m_initialized) + { + m_initialized = true; - Q_ASSERT(qApp); + Q_ASSERT(qApp); - m_systemTheme = QIcon::themeName(); + m_systemTheme = QIcon::themeName(); - if (m_systemTheme.isEmpty()) - m_systemTheme = fallbackTheme(); - m_supportsSvg = true; - } + if (m_systemTheme.isEmpty()) + m_systemTheme = fallbackTheme(); + m_supportsSvg = true; + } } QIconLoader *QIconLoader::instance() { - iconLoaderInstance()->ensureInitialized(); - return iconLoaderInstance(); + iconLoaderInstance()->ensureInitialized(); + return iconLoaderInstance(); } // Queries the system theme and invalidates existing // icons if the theme has changed. void QIconLoader::updateSystemTheme() { - // Only change if this is not explicitly set by the user - if (m_userTheme.isEmpty()) - { - QString theme = systemThemeName(); - if (theme.isEmpty()) - theme = fallbackTheme(); - if (theme != m_systemTheme) - { - m_systemTheme = theme; - invalidateKey(); - } - } + // Only change if this is not explicitly set by the user + if (m_userTheme.isEmpty()) + { + QString theme = systemThemeName(); + if (theme.isEmpty()) + theme = fallbackTheme(); + if (theme != m_systemTheme) + { + m_systemTheme = theme; + invalidateKey(); + } + } } void QIconLoader::setThemeName(const QString &themeName) { - m_userTheme = themeName; - invalidateKey(); + m_userTheme = themeName; + invalidateKey(); } void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) { - m_iconDirs = searchPaths; - themeList.clear(); - invalidateKey(); + m_iconDirs = searchPaths; + themeList.clear(); + invalidateKey(); } QStringList QIconLoader::themeSearchPaths() const { - if (m_iconDirs.isEmpty()) - { - m_iconDirs = systemIconSearchPaths(); - } - return m_iconDirs; + if (m_iconDirs.isEmpty()) + { + m_iconDirs = systemIconSearchPaths(); + } + return m_iconDirs; } QIconTheme::QIconTheme(const QString &themeName) : m_valid(false) { - QFile themeIndex; - - QStringList iconDirs = systemIconSearchPaths(); - for (int i = 0; i < iconDirs.size(); ++i) - { - QDir iconDir(iconDirs[i]); - QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; - themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); - if (themeIndex.exists()) - { - m_contentDir = themeDir; - m_valid = true; - - foreach (QString path, iconDirs) - { - if (QFileInfo(path).isDir()) - m_contentDirs.append(path + QLatin1Char('/') + themeName); - } - - break; - } - } - - // if there is no index file, abscond. - if (!themeIndex.exists()) - return; - - // otherwise continue reading index file - const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); - QStringListIterator keyIterator(indexReader.allKeys()); - while (keyIterator.hasNext()) - { - const QString key = keyIterator.next(); - if (!key.endsWith(QLatin1String("/Size"))) - continue; - - // Note the QSettings ini-format does not accept - // slashes in key names, hence we have to cheat - int size = indexReader.value(key).toInt(); - if (!size) - continue; - - QString directoryKey = key.left(key.size() - 5); - QIconDirInfo dirInfo(directoryKey); - dirInfo.size = size; - QString type = - indexReader.value(directoryKey + QLatin1String("/Type")).toString(); - - if (type == QLatin1String("Fixed")) - dirInfo.type = QIconDirInfo::Fixed; - else if (type == QLatin1String("Scalable")) - dirInfo.type = QIconDirInfo::Scalable; - else - dirInfo.type = QIconDirInfo::Threshold; - - dirInfo.threshold = - indexReader.value(directoryKey + QLatin1String("/Threshold"), 2) - .toInt(); - - dirInfo.minSize = - indexReader.value(directoryKey + QLatin1String("/MinSize"), size) - .toInt(); - - dirInfo.maxSize = - indexReader.value(directoryKey + QLatin1String("/MaxSize"), size) - .toInt(); - m_keyList.append(dirInfo); - } - - // Parent themes provide fallbacks for missing icons - m_parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toStringList(); - m_parents.removeAll(QString()); - - // Ensure a default platform fallback for all themes - if (m_parents.isEmpty()) - { - const QString fallback = fallbackTheme(); - if (!fallback.isEmpty()) - m_parents.append(fallback); - } - - // Ensure that all themes fall back to hicolor - if (!m_parents.contains(QLatin1String("hicolor"))) - m_parents.append(QLatin1String("hicolor")); + QFile themeIndex; + + QStringList iconDirs = systemIconSearchPaths(); + for (int i = 0; i < iconDirs.size(); ++i) + { + QDir iconDir(iconDirs[i]); + QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; + themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); + if (themeIndex.exists()) + { + m_contentDir = themeDir; + m_valid = true; + + foreach (QString path, iconDirs) + { + if (QFileInfo(path).isDir()) + m_contentDirs.append(path + QLatin1Char('/') + themeName); + } + + break; + } + } + + // if there is no index file, abscond. + if (!themeIndex.exists()) + return; + + // otherwise continue reading index file + const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); + QStringListIterator keyIterator(indexReader.allKeys()); + while (keyIterator.hasNext()) + { + const QString key = keyIterator.next(); + if (!key.endsWith(QLatin1String("/Size"))) + continue; + + // Note the QSettings ini-format does not accept + // slashes in key names, hence we have to cheat + int size = indexReader.value(key).toInt(); + if (!size) + continue; + + QString directoryKey = key.left(key.size() - 5); + QIconDirInfo dirInfo(directoryKey); + dirInfo.size = size; + QString type = + indexReader.value(directoryKey + QLatin1String("/Type")).toString(); + + if (type == QLatin1String("Fixed")) + dirInfo.type = QIconDirInfo::Fixed; + else if (type == QLatin1String("Scalable")) + dirInfo.type = QIconDirInfo::Scalable; + else + dirInfo.type = QIconDirInfo::Threshold; + + dirInfo.threshold = + indexReader.value(directoryKey + QLatin1String("/Threshold"), 2) + .toInt(); + + dirInfo.minSize = + indexReader.value(directoryKey + QLatin1String("/MinSize"), size) + .toInt(); + + dirInfo.maxSize = + indexReader.value(directoryKey + QLatin1String("/MaxSize"), size) + .toInt(); + m_keyList.append(dirInfo); + } + + // Parent themes provide fallbacks for missing icons + m_parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toStringList(); + m_parents.removeAll(QString()); + + // Ensure a default platform fallback for all themes + if (m_parents.isEmpty()) + { + const QString fallback = fallbackTheme(); + if (!fallback.isEmpty()) + m_parents.append(fallback); + } + + // Ensure that all themes fall back to hicolor + if (!m_parents.contains(QLatin1String("hicolor"))) + m_parents.append(QLatin1String("hicolor")); } QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName, const QString &iconName, - QStringList &visited) const -{ - QThemeIconEntries entries; - Q_ASSERT(!themeName.isEmpty()); - - QPixmap pixmap; - - // Used to protect against potential recursions - visited << themeName; - - QIconTheme theme = themeList.value(themeName); - if (!theme.isValid()) - { - theme = QIconTheme(themeName); - if (!theme.isValid()) - theme = QIconTheme(fallbackTheme()); - - themeList.insert(themeName, theme); - } - - QStringList contentDirs = theme.contentDirs(); - const QVector subDirs = theme.keyList(); - - const QString svgext(QLatin1String(".svg")); - const QString pngext(QLatin1String(".png")); - const QString xpmext(QLatin1String(".xpm")); - - // Add all relevant files - for (int i = 0; i < subDirs.size(); ++i) - { - const QIconDirInfo &dirInfo = subDirs.at(i); - QString subdir = dirInfo.path; - - foreach (QString contentDir, contentDirs) - { - QDir currentDir(contentDir + '/' + subdir); - - if (currentDir.exists(iconName + pngext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + pngext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.prepend(iconEntry); - } - else if (m_supportsSvg && currentDir.exists(iconName + svgext)) - { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + svgext); - entries.append(iconEntry); - break; - } - else if (currentDir.exists(iconName + xpmext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + xpmext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.append(iconEntry); - break; - } - } - } - - if (entries.isEmpty()) - { - const QStringList parents = theme.parents(); - // Search recursively through inherited themes - for (int i = 0; i < parents.size(); ++i) - { - - const QString parentTheme = parents.at(i).trimmed(); - - if (!visited.contains(parentTheme)) // guard against recursion - entries = findIconHelper(parentTheme, iconName, visited); - - if (!entries.isEmpty()) // success - break; - } - } + QStringList &visited) const +{ + QThemeIconEntries entries; + Q_ASSERT(!themeName.isEmpty()); + + QPixmap pixmap; + + // Used to protect against potential recursions + visited << themeName; + + QIconTheme theme = themeList.value(themeName); + if (!theme.isValid()) + { + theme = QIconTheme(themeName); + if (!theme.isValid()) + theme = QIconTheme(fallbackTheme()); + + themeList.insert(themeName, theme); + } + + QStringList contentDirs = theme.contentDirs(); + const QVector subDirs = theme.keyList(); + + const QString svgext(QLatin1String(".svg")); + const QString pngext(QLatin1String(".png")); + const QString xpmext(QLatin1String(".xpm")); + + // Add all relevant files + for (int i = 0; i < subDirs.size(); ++i) + { + const QIconDirInfo &dirInfo = subDirs.at(i); + QString subdir = dirInfo.path; + + foreach (QString contentDir, contentDirs) + { + QDir currentDir(contentDir + '/' + subdir); + + if (currentDir.exists(iconName + pngext)) + { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + pngext); + // Notice we ensure that pixmap entries always come before + // scalable to preserve search order afterwards + entries.prepend(iconEntry); + } + else if (m_supportsSvg && currentDir.exists(iconName + svgext)) + { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + entries.append(iconEntry); + break; + } + else if (currentDir.exists(iconName + xpmext)) + { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + xpmext); + // Notice we ensure that pixmap entries always come before + // scalable to preserve search order afterwards + entries.append(iconEntry); + break; + } + } + } + + if (entries.isEmpty()) + { + const QStringList parents = theme.parents(); + // Search recursively through inherited themes + for (int i = 0; i < parents.size(); ++i) + { + + const QString parentTheme = parents.at(i).trimmed(); + + if (!visited.contains(parentTheme)) // guard against recursion + entries = findIconHelper(parentTheme, iconName, visited); + + if (!entries.isEmpty()) // success + break; + } + } /********************************************************************* Author: Kaitlin Rupert Date: Aug 12, 2010 Description: Make it so that the QIcon loader honors /usr/share/pixmaps - directory. This is a valid directory per the Freedesktop.org - icon theme specification. + directory. This is a valid directory per the Freedesktop.org + icon theme specification. Bug: https://bugreports.qt.nokia.com/browse/QTBUG-12874 *********************************************************************/ #ifdef Q_OS_LINUX - /* Freedesktop standard says to look in /usr/share/pixmaps last */ - if (entries.isEmpty()) - { - const QString pixmaps(QLatin1String("/usr/share/pixmaps")); - - QDir currentDir(pixmaps); - QIconDirInfo dirInfo(pixmaps); - if (currentDir.exists(iconName + pngext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + pngext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.prepend(iconEntry); - } - else if (m_supportsSvg && currentDir.exists(iconName + svgext)) - { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + svgext); - entries.append(iconEntry); - } - else if (currentDir.exists(iconName + xpmext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->dir = dirInfo; - iconEntry->filename = currentDir.filePath(iconName + xpmext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.append(iconEntry); - } - } + /* Freedesktop standard says to look in /usr/share/pixmaps last */ + if (entries.isEmpty()) + { + const QString pixmaps(QLatin1String("/usr/share/pixmaps")); + + QDir currentDir(pixmaps); + QIconDirInfo dirInfo(pixmaps); + if (currentDir.exists(iconName + pngext)) + { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + pngext); + // Notice we ensure that pixmap entries always come before + // scalable to preserve search order afterwards + entries.prepend(iconEntry); + } + else if (m_supportsSvg && currentDir.exists(iconName + svgext)) + { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + entries.append(iconEntry); + } + else if (currentDir.exists(iconName + xpmext)) + { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + xpmext); + // Notice we ensure that pixmap entries always come before + // scalable to preserve search order afterwards + entries.append(iconEntry); + } + } #endif - if (entries.isEmpty()) - { - // Search for unthemed icons in main dir of search paths - QStringList themeSearchPaths = QIcon::themeSearchPaths(); - foreach (QString contentDir, themeSearchPaths) - { - QDir currentDir(contentDir); - - if (currentDir.exists(iconName + pngext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->filename = currentDir.filePath(iconName + pngext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.prepend(iconEntry); - } - else if (m_supportsSvg && currentDir.exists(iconName + svgext)) - { - ScalableEntry *iconEntry = new ScalableEntry; - iconEntry->filename = currentDir.filePath(iconName + svgext); - entries.append(iconEntry); - break; - } - else if (currentDir.exists(iconName + xpmext)) - { - PixmapEntry *iconEntry = new PixmapEntry; - iconEntry->filename = currentDir.filePath(iconName + xpmext); - // Notice we ensure that pixmap entries always come before - // scalable to preserve search order afterwards - entries.append(iconEntry); - break; - } - } - } - return entries; + if (entries.isEmpty()) + { + // Search for unthemed icons in main dir of search paths + QStringList themeSearchPaths = QIcon::themeSearchPaths(); + foreach (QString contentDir, themeSearchPaths) + { + QDir currentDir(contentDir); + + if (currentDir.exists(iconName + pngext)) + { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->filename = currentDir.filePath(iconName + pngext); + // Notice we ensure that pixmap entries always come before + // scalable to preserve search order afterwards + entries.prepend(iconEntry); + } + else if (m_supportsSvg && currentDir.exists(iconName + svgext)) + { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->filename = currentDir.filePath(iconName + svgext); + entries.append(iconEntry); + break; + } + else if (currentDir.exists(iconName + xpmext)) + { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->filename = currentDir.filePath(iconName + xpmext); + // Notice we ensure that pixmap entries always come before + // scalable to preserve search order afterwards + entries.append(iconEntry); + break; + } + } + } + return entries; } QThemeIconEntries QIconLoader::loadIcon(const QString &name) const { - if (!themeName().isEmpty()) - { - QStringList visited; - return findIconHelper(themeName(), name, visited); - } + if (!themeName().isEmpty()) + { + QStringList visited; + return findIconHelper(themeName(), name, visited); + } - return QThemeIconEntries(); + return QThemeIconEntries(); } // -------- Icon Loader Engine -------- // QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QString &iconName) - : m_iconName(iconName), m_key(0) + : m_iconName(iconName), m_key(0) { } QIconLoaderEngineFixed::~QIconLoaderEngineFixed() { - qDeleteAll(m_entries); + qDeleteAll(m_entries); } QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other) - : QIconEngine(other), m_iconName(other.m_iconName), m_key(0) + : QIconEngine(other), m_iconName(other.m_iconName), m_key(0) { } QIconEngine *QIconLoaderEngineFixed::clone() const { - return new QIconLoaderEngineFixed(*this); + return new QIconLoaderEngineFixed(*this); } bool QIconLoaderEngineFixed::read(QDataStream &in) { - in >> m_iconName; - return true; + in >> m_iconName; + return true; } bool QIconLoaderEngineFixed::write(QDataStream &out) const { - out << m_iconName; - return true; + out << m_iconName; + return true; } bool QIconLoaderEngineFixed::hasIcon() const { - return !(m_entries.isEmpty()); + return !(m_entries.isEmpty()); } // Lazily load the icon void QIconLoaderEngineFixed::ensureLoaded() { - if (!(QIconLoader::instance()->themeKey() == m_key)) - { + if (!(QIconLoader::instance()->themeKey() == m_key)) + { - qDeleteAll(m_entries); + qDeleteAll(m_entries); - m_entries = QIconLoader::instance()->loadIcon(m_iconName); - m_key = QIconLoader::instance()->themeKey(); - } + m_entries = QIconLoader::instance()->loadIcon(m_iconName); + m_key = QIconLoader::instance()->themeKey(); + } } void QIconLoaderEngineFixed::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, - QIcon::State state) + QIcon::State state) { - QSize pixmapSize = rect.size(); + QSize pixmapSize = rect.size(); #if defined(Q_WS_MAC) - pixmapSize *= qt_mac_get_scalefactor(); + pixmapSize *= qt_mac_get_scalefactor(); #endif - painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); + painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); } /* @@ -472,21 +472,21 @@ void QIconLoaderEngineFixed::paint(QPainter *painter, const QRect &rect, QIcon:: */ static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) { - if (dir.type == QIconDirInfo::Fixed) - { - return dir.size == iconsize; - } - else if (dir.type == QIconDirInfo::Scalable) - { - return dir.size <= dir.maxSize && iconsize >= dir.minSize; - } - else if (dir.type == QIconDirInfo::Threshold) - { - return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold; - } - - Q_ASSERT(1); // Not a valid value - return false; + if (dir.type == QIconDirInfo::Fixed) + { + return dir.size == iconsize; + } + else if (dir.type == QIconDirInfo::Scalable) + { + return dir.size <= dir.maxSize && iconsize >= dir.minSize; + } + else if (dir.type == QIconDirInfo::Threshold) + { + return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold; + } + + Q_ASSERT(1); // Not a valid value + return false; } /* @@ -495,66 +495,66 @@ static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) */ static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) { - if (dir.type == QIconDirInfo::Fixed) - { - return qAbs(dir.size - iconsize); - } - else if (dir.type == QIconDirInfo::Scalable) - { - if (iconsize < dir.minSize) - return dir.minSize - iconsize; - else if (iconsize > dir.maxSize) - return iconsize - dir.maxSize; - else - return 0; - } - else if (dir.type == QIconDirInfo::Threshold) - { - if (iconsize < dir.size - dir.threshold) - return dir.minSize - iconsize; - else if (iconsize > dir.size + dir.threshold) - return iconsize - dir.maxSize; - else - return 0; - } - - Q_ASSERT(1); // Not a valid value - return INT_MAX; + if (dir.type == QIconDirInfo::Fixed) + { + return qAbs(dir.size - iconsize); + } + else if (dir.type == QIconDirInfo::Scalable) + { + if (iconsize < dir.minSize) + return dir.minSize - iconsize; + else if (iconsize > dir.maxSize) + return iconsize - dir.maxSize; + else + return 0; + } + else if (dir.type == QIconDirInfo::Threshold) + { + if (iconsize < dir.size - dir.threshold) + return dir.minSize - iconsize; + else if (iconsize > dir.size + dir.threshold) + return iconsize - dir.maxSize; + else + return 0; + } + + Q_ASSERT(1); // Not a valid value + return INT_MAX; } QIconLoaderEngineEntry *QIconLoaderEngineFixed::entryForSize(const QSize &size) { - int iconsize = qMin(size.width(), size.height()); - - // Note that m_entries are sorted so that png-files - // come first - - const int numEntries = m_entries.size(); - - // Search for exact matches first - for (int i = 0; i < numEntries; ++i) - { - QIconLoaderEngineEntry *entry = m_entries.at(i); - if (directoryMatchesSize(entry->dir, iconsize)) - { - return entry; - } - } - - // Find the minimum distance icon - int minimalSize = INT_MAX; - QIconLoaderEngineEntry *closestMatch = 0; - for (int i = 0; i < numEntries; ++i) - { - QIconLoaderEngineEntry *entry = m_entries.at(i); - int distance = directorySizeDistance(entry->dir, iconsize); - if (distance < minimalSize) - { - minimalSize = distance; - closestMatch = entry; - } - } - return closestMatch; + int iconsize = qMin(size.width(), size.height()); + + // Note that m_entries are sorted so that png-files + // come first + + const int numEntries = m_entries.size(); + + // Search for exact matches first + for (int i = 0; i < numEntries; ++i) + { + QIconLoaderEngineEntry *entry = m_entries.at(i); + if (directoryMatchesSize(entry->dir, iconsize)) + { + return entry; + } + } + + // Find the minimum distance icon + int minimalSize = INT_MAX; + QIconLoaderEngineEntry *closestMatch = 0; + for (int i = 0; i < numEntries; ++i) + { + QIconLoaderEngineEntry *entry = m_entries.at(i); + int distance = directorySizeDistance(entry->dir, iconsize); + if (distance < minimalSize) + { + minimalSize = distance; + closestMatch = entry; + } + } + return closestMatch; } /* @@ -564,125 +564,125 @@ QIconLoaderEngineEntry *QIconLoaderEngineFixed::entryForSize(const QSize &size) * */ QSize QIconLoaderEngineFixed::actualSize(const QSize &size, QIcon::Mode mode, - QIcon::State state) -{ - ensureLoaded(); - - QIconLoaderEngineEntry *entry = entryForSize(size); - if (entry) - { - const QIconDirInfo &dir = entry->dir; - if (dir.type == QIconDirInfo::Scalable) - return size; - else - { - int result = qMin(dir.size, qMin(size.width(), size.height())); - return QSize(result, result); - } - } - return QIconEngine::actualSize(size, mode, state); + QIcon::State state) +{ + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) + { + const QIconDirInfo &dir = entry->dir; + if (dir.type == QIconDirInfo::Scalable) + return size; + else + { + int result = qMin(dir.size, qMin(size.width(), size.height())); + return QSize(result, result); + } + } + return QIconEngine::actualSize(size, mode, state); } QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { - Q_UNUSED(state); - - // Ensure that basePixmap is lazily initialized before generating the - // key, otherwise the cache key is not unique - if (basePixmap.isNull()) - basePixmap.load(filename); - - QSize actualSize = basePixmap.size(); - if (!actualSize.isNull() && - (actualSize.width() > size.width() || actualSize.height() > size.height())) - actualSize.scale(size, Qt::KeepAspectRatio); - - QString key = QLatin1String("$qt_theme_") % HexString(basePixmap.cacheKey()) % - HexString(mode) % - HexString(QGuiApplication::palette().cacheKey()) % - HexString(actualSize.width()) % HexString(actualSize.height()); - - QPixmap cachedPixmap; - if (QPixmapCache::find(key, &cachedPixmap)) - { - return cachedPixmap; - } - else - { - if (basePixmap.size() != actualSize) - { - cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - else - { - cachedPixmap = basePixmap; - } - QPixmapCache::insert(key, cachedPixmap); - } - return cachedPixmap; + Q_UNUSED(state); + + // Ensure that basePixmap is lazily initialized before generating the + // key, otherwise the cache key is not unique + if (basePixmap.isNull()) + basePixmap.load(filename); + + QSize actualSize = basePixmap.size(); + if (!actualSize.isNull() && + (actualSize.width() > size.width() || actualSize.height() > size.height())) + actualSize.scale(size, Qt::KeepAspectRatio); + + QString key = QLatin1String("$qt_theme_") % HexString(basePixmap.cacheKey()) % + HexString(mode) % + HexString(QGuiApplication::palette().cacheKey()) % + HexString(actualSize.width()) % HexString(actualSize.height()); + + QPixmap cachedPixmap; + if (QPixmapCache::find(key, &cachedPixmap)) + { + return cachedPixmap; + } + else + { + if (basePixmap.size() != actualSize) + { + cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + else + { + cachedPixmap = basePixmap; + } + QPixmapCache::insert(key, cachedPixmap); + } + return cachedPixmap; } QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { - if (svgIcon.isNull()) - { - svgIcon = QIcon(filename); - } + if (svgIcon.isNull()) + { + svgIcon = QIcon(filename); + } - // Simply reuse svg icon engine - return svgIcon.pixmap(size, mode, state); + // Simply reuse svg icon engine + return svgIcon.pixmap(size, mode, state); } QPixmap QIconLoaderEngineFixed::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { - ensureLoaded(); + ensureLoaded(); - QIconLoaderEngineEntry *entry = entryForSize(size); - if (entry) - { - return entry->pixmap(size, mode, state); - } + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) + { + return entry->pixmap(size, mode, state); + } - return QPixmap(); + return QPixmap(); } QString QIconLoaderEngineFixed::key() const { - return QLatin1String("QIconLoaderEngineFixed"); + return QLatin1String("QIconLoaderEngineFixed"); } void QIconLoaderEngineFixed::virtual_hook(int id, void *data) { - ensureLoaded(); - - switch (id) - { - case QIconEngine::AvailableSizesHook: - { - QIconEngine::AvailableSizesArgument &arg = - *reinterpret_cast(data); - const int N = m_entries.size(); - QList sizes; - sizes.reserve(N); - - // Gets all sizes from the DirectoryInfo entries - for (int i = 0; i < N; ++i) - { - int size = m_entries.at(i)->dir.size; - sizes.append(QSize(size, size)); - } - arg.sizes.swap(sizes); // commit - } - break; - case QIconEngine::IconNameHook: - { - QString &name = *reinterpret_cast(data); - name = m_iconName; - } - break; - default: - QIconEngine::virtual_hook(id, data); - } + ensureLoaded(); + + switch (id) + { + case QIconEngine::AvailableSizesHook: + { + QIconEngine::AvailableSizesArgument &arg = + *reinterpret_cast(data); + const int N = m_entries.size(); + QList sizes; + sizes.reserve(N); + + // Gets all sizes from the DirectoryInfo entries + for (int i = 0; i < N; ++i) + { + int size = m_entries.at(i)->dir.size; + sizes.append(QSize(size, size)); + } + arg.sizes.swap(sizes); // commit + } + break; + case QIconEngine::IconNameHook: + { + QString &name = *reinterpret_cast(data); + name = m_iconName; + } + break; + default: + QIconEngine::virtual_hook(id, data); + } } } // QtXdg diff --git a/libraries/iconfix/internal/qiconloader_p.h b/libraries/iconfix/internal/qiconloader_p.h index b71bdd83..e45a08d6 100644 --- a/libraries/iconfix/internal/qiconloader_p.h +++ b/libraries/iconfix/internal/qiconloader_p.h @@ -61,46 +61,46 @@ class QIconLoader; struct QIconDirInfo { - enum Type - { - Fixed, - Scalable, - Threshold - }; - QIconDirInfo(const QString &_path = QString()) - : path(_path), size(0), maxSize(0), minSize(0), threshold(0), type(Threshold) - { - } - QString path; - short size; - short maxSize; - short minSize; - short threshold; - Type type : 4; + enum Type + { + Fixed, + Scalable, + Threshold + }; + QIconDirInfo(const QString &_path = QString()) + : path(_path), size(0), maxSize(0), minSize(0), threshold(0), type(Threshold) + { + } + QString path; + short size; + short maxSize; + short minSize; + short threshold; + Type type : 4; }; class QIconLoaderEngineEntry { public: - virtual ~QIconLoaderEngineEntry() - { - } - virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) = 0; - QString filename; - QIconDirInfo dir; - static int count; + virtual ~QIconLoaderEngineEntry() + { + } + virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) = 0; + QString filename; + QIconDirInfo dir; + static int count; }; struct ScalableEntry : public QIconLoaderEngineEntry { - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; - QIcon svgIcon; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; + QIcon svgIcon; }; struct PixmapEntry : public QIconLoaderEngineEntry { - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; - QPixmap basePixmap; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE; + QPixmap basePixmap; }; typedef QList QThemeIconEntries; @@ -109,107 +109,107 @@ typedef QList QThemeIconEntries; class QIconLoaderEngineFixed : public QIconEngine { public: - QIconLoaderEngineFixed(const QString &iconName = QString()); - ~QIconLoaderEngineFixed(); + QIconLoaderEngineFixed(const QString &iconName = QString()); + ~QIconLoaderEngineFixed(); - void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); - QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); - QIconEngine *clone() const; - bool read(QDataStream &in); - bool write(QDataStream &out) const; + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); + QIconEngine *clone() const; + bool read(QDataStream &in); + bool write(QDataStream &out) const; private: - QString key() const; - bool hasIcon() const; - void ensureLoaded(); - void virtual_hook(int id, void *data); - QIconLoaderEngineEntry *entryForSize(const QSize &size); - QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other); - QThemeIconEntries m_entries; - QString m_iconName; - uint m_key; - - friend class QIconLoader; + QString key() const; + bool hasIcon() const; + void ensureLoaded(); + void virtual_hook(int id, void *data); + QIconLoaderEngineEntry *entryForSize(const QSize &size); + QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other); + QThemeIconEntries m_entries; + QString m_iconName; + uint m_key; + + friend class QIconLoader; }; class QIconTheme { public: - QIconTheme(const QString &name); - QIconTheme() : m_valid(false) - { - } - QStringList parents() - { - return m_parents; - } - QVector keyList() - { - return m_keyList; - } - QString contentDir() - { - return m_contentDir; - } - QStringList contentDirs() - { - return m_contentDirs; - } - bool isValid() - { - return m_valid; - } + QIconTheme(const QString &name); + QIconTheme() : m_valid(false) + { + } + QStringList parents() + { + return m_parents; + } + QVector keyList() + { + return m_keyList; + } + QString contentDir() + { + return m_contentDir; + } + QStringList contentDirs() + { + return m_contentDirs; + } + bool isValid() + { + return m_valid; + } private: - QString m_contentDir; - QStringList m_contentDirs; - QVector m_keyList; - QStringList m_parents; - bool m_valid; + QString m_contentDir; + QStringList m_contentDirs; + QVector m_keyList; + QStringList m_parents; + bool m_valid; }; class QIconLoader { public: - QIconLoader(); - QThemeIconEntries loadIcon(const QString &iconName) const; - uint themeKey() const - { - return m_themeKey; - } - - QString themeName() const - { - return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; - } - void setThemeName(const QString &themeName); - QIconTheme theme() - { - return themeList.value(themeName()); - } - void setThemeSearchPath(const QStringList &searchPaths); - QStringList themeSearchPaths() const; - QIconDirInfo dirInfo(int dirindex); - static QIconLoader *instance(); - void updateSystemTheme(); - void invalidateKey() - { - m_themeKey++; - } - void ensureInitialized(); + QIconLoader(); + QThemeIconEntries loadIcon(const QString &iconName) const; + uint themeKey() const + { + return m_themeKey; + } + + QString themeName() const + { + return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; + } + void setThemeName(const QString &themeName); + QIconTheme theme() + { + return themeList.value(themeName()); + } + void setThemeSearchPath(const QStringList &searchPaths); + QStringList themeSearchPaths() const; + QIconDirInfo dirInfo(int dirindex); + static QIconLoader *instance(); + void updateSystemTheme(); + void invalidateKey() + { + m_themeKey++; + } + void ensureInitialized(); private: - QThemeIconEntries findIconHelper(const QString &themeName, const QString &iconName, - QStringList &visited) const; - uint m_themeKey; - bool m_supportsSvg; - bool m_initialized; - - mutable QString m_userTheme; - mutable QString m_systemTheme; - mutable QStringList m_iconDirs; - mutable QHash themeList; + QThemeIconEntries findIconHelper(const QString &themeName, const QString &iconName, + QStringList &visited) const; + uint m_themeKey; + bool m_supportsSvg; + bool m_initialized; + + mutable QString m_userTheme; + mutable QString m_systemTheme; + mutable QStringList m_iconDirs; + mutable QHash themeList; }; } // QtXdg diff --git a/libraries/iconfix/xdgicon.cpp b/libraries/iconfix/xdgicon.cpp index a36d80a9..36fb7d42 100644 --- a/libraries/iconfix/xdgicon.cpp +++ b/libraries/iconfix/xdgicon.cpp @@ -46,17 +46,17 @@ namespace { struct QtIconCache : public IconCache { - QtIconCache() - { - qAddPostRoutine(qt_cleanup_icon_cache); - } + QtIconCache() + { + qAddPostRoutine(qt_cleanup_icon_cache); + } }; } -Q_GLOBAL_STATIC(IconCache, qtIconCache); +Q_GLOBAL_STATIC(IconCache, qtIconCache) static void qt_cleanup_icon_cache() { - qtIconCache()->clear(); + qtIconCache()->clear(); } /************************************************ @@ -78,7 +78,7 @@ XdgIcon::~XdgIcon() ************************************************/ QString XdgIcon::themeName() { - return QIcon::themeName(); + return QIcon::themeName(); } /************************************************ @@ -86,8 +86,8 @@ QString XdgIcon::themeName() ************************************************/ void XdgIcon::setThemeName(const QString &themeName) { - QIcon::setThemeName(themeName); - QtXdg::QIconLoader::instance()->updateSystemTheme(); + QIcon::setThemeName(themeName); + QtXdg::QIconLoader::instance()->updateSystemTheme(); } /************************************************ @@ -96,43 +96,43 @@ void XdgIcon::setThemeName(const QString &themeName) ************************************************/ QIcon XdgIcon::fromTheme(const QString &iconName, const QIcon &fallback) { - if (iconName.isEmpty()) - return fallback; - - bool isAbsolute = (iconName[0] == '/'); - - QString name = QFileInfo(iconName).fileName(); - if (name.endsWith(".png", Qt::CaseInsensitive) || - name.endsWith(".svg", Qt::CaseInsensitive) || - name.endsWith(".xpm", Qt::CaseInsensitive)) - { - name.truncate(name.length() - 4); - } - - QIcon icon; - - if (qtIconCache()->contains(name)) - { - icon = *qtIconCache()->object(name); - } - else - { - QIcon *cachedIcon; - if (!isAbsolute) - cachedIcon = new QIcon(new QtXdg::QIconLoaderEngineFixed(name)); - else - cachedIcon = new QIcon(iconName); - qtIconCache()->insert(name, cachedIcon); - icon = *cachedIcon; - } - - // Note the qapp check is to allow lazy loading of static icons - // Supporting fallbacks will not work for this case. - if (qApp && !isAbsolute && icon.availableSizes().isEmpty()) - { - return fallback; - } - return icon; + if (iconName.isEmpty()) + return fallback; + + bool isAbsolute = (iconName[0] == '/'); + + QString name = QFileInfo(iconName).fileName(); + if (name.endsWith(".png", Qt::CaseInsensitive) || + name.endsWith(".svg", Qt::CaseInsensitive) || + name.endsWith(".xpm", Qt::CaseInsensitive)) + { + name.truncate(name.length() - 4); + } + + QIcon icon; + + if (qtIconCache()->contains(name)) + { + icon = *qtIconCache()->object(name); + } + else + { + QIcon *cachedIcon; + if (!isAbsolute) + cachedIcon = new QIcon(new QtXdg::QIconLoaderEngineFixed(name)); + else + cachedIcon = new QIcon(iconName); + qtIconCache()->insert(name, cachedIcon); + icon = *cachedIcon; + } + + // Note the qapp check is to allow lazy loading of static icons + // Supporting fallbacks will not work for this case. + if (qApp && !isAbsolute && icon.availableSizes().isEmpty()) + { + return fallback; + } + return icon; } /************************************************ @@ -141,12 +141,12 @@ QIcon XdgIcon::fromTheme(const QString &iconName, const QIcon &fallback) ************************************************/ QIcon XdgIcon::fromTheme(const QStringList &iconNames, const QIcon &fallback) { - foreach (QString iconName, iconNames) - { - QIcon icon = fromTheme(iconName); - if (!icon.isNull()) - return icon; - } - - return fallback; + foreach (QString iconName, iconNames) + { + QIcon icon = fromTheme(iconName); + if (!icon.isNull()) + return icon; + } + + return fallback; } diff --git a/libraries/iconfix/xdgicon.h b/libraries/iconfix/xdgicon.h index df8f026e..1380607c 100644 --- a/libraries/iconfix/xdgicon.h +++ b/libraries/iconfix/xdgicon.h @@ -36,13 +36,13 @@ class MULTIMC_ICONFIX_EXPORT XdgIcon { public: - static QIcon fromTheme(const QString &iconName, const QIcon &fallback = QIcon()); - static QIcon fromTheme(const QStringList &iconNames, const QIcon &fallback = QIcon()); + static QIcon fromTheme(const QString &iconName, const QIcon &fallback = QIcon()); + static QIcon fromTheme(const QStringList &iconNames, const QIcon &fallback = QIcon()); - static QString themeName(); - static void setThemeName(const QString &themeName); + static QString themeName(); + static void setThemeName(const QString &themeName); protected: - explicit XdgIcon(); - virtual ~XdgIcon(); + explicit XdgIcon(); + virtual ~XdgIcon(); }; diff --git a/libraries/javacheck/CMakeLists.txt b/libraries/javacheck/CMakeLists.txt index 381efe08..dba5a1ae 100644 --- a/libraries/javacheck/CMakeLists.txt +++ b/libraries/javacheck/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_JAVA_JAR_ENTRY_POINT JavaCheck) set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked) set(SRC - JavaCheck.java + JavaCheck.java ) add_jar(JavaCheck ${SRC}) diff --git a/libraries/javacheck/JavaCheck.java b/libraries/javacheck/JavaCheck.java index 11420b86..69933040 100644 --- a/libraries/javacheck/JavaCheck.java +++ b/libraries/javacheck/JavaCheck.java @@ -2,23 +2,23 @@ import java.lang.Integer; public class JavaCheck { - private static final String[] keys = {"os.arch", "java.version"}; - public static void main (String [] args) - { - int ret = 0; - for(String key : keys) - { - String property = System.getProperty(key); - if(property != null) - { - System.out.println(key + "=" + property); - } - else - { - ret = 1; - } - } - - System.exit(ret); - } + private static final String[] keys = {"os.arch", "java.version"}; + public static void main (String [] args) + { + int ret = 0; + for(String key : keys) + { + String property = System.getProperty(key); + if(property != null) + { + System.out.println(key + "=" + property); + } + else + { + ret = 1; + } + } + + System.exit(ret); + } } diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index a4f52edb..a64c601d 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -7,15 +7,15 @@ set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint) set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked) set(SRC - org/multimc/EntryPoint.java - org/multimc/Launcher.java - org/multimc/LegacyFrame.java - org/multimc/NotFoundException.java - org/multimc/ParamBucket.java - org/multimc/ParseException.java - org/multimc/Utils.java - org/multimc/onesix/OneSixLauncher.java - net/minecraft/Launcher.java + org/multimc/EntryPoint.java + org/multimc/Launcher.java + org/multimc/LegacyFrame.java + org/multimc/NotFoundException.java + org/multimc/ParamBucket.java + org/multimc/ParseException.java + org/multimc/Utils.java + org/multimc/onesix/OneSixLauncher.java + net/minecraft/Launcher.java ) add_jar(NewLaunch ${SRC}) install_jar(NewLaunch "${JARS_DEST_DIR}") diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java index 0c991cf5..dd704484 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,138 +28,138 @@ import java.net.MalformedURLException; public class Launcher extends Applet implements AppletStub { - private Applet wrappedApplet; - private URL documentBase; - private boolean active = false; - private final Map params; - - public Launcher(Applet applet, URL documentBase) - { - params = new TreeMap(); - - this.setLayout(new BorderLayout()); - this.add(applet, "Center"); - this.wrappedApplet = applet; - this.documentBase = documentBase; - } - - public void setParameter(String name, String value) - { - params.put(name, value); - } - - public void replace(Applet applet) - { - this.wrappedApplet = applet; - - applet.setStub(this); - applet.setSize(getWidth(), getHeight()); - - this.setLayout(new BorderLayout()); - this.add(applet, "Center"); - - applet.init(); - active = true; - applet.start(); - validate(); - } - - @Override - public String getParameter(String name) - { - String param = params.get(name); - if (param != null) - return param; - try - { - return super.getParameter(name); - } catch (Exception ignore){} - return null; - } - - @Override - public boolean isActive() - { - return active; - } - - @Override - public void appletResize(int width, int height) - { - wrappedApplet.resize(width, height); - } - - @Override - public void resize(int width, int height) - { - wrappedApplet.resize(width, height); - } - - @Override - public void resize(Dimension d) - { - wrappedApplet.resize(d); - } - - @Override - public void init() - { - if (wrappedApplet != null) - { - wrappedApplet.init(); - } - } - - @Override - public void start() - { - wrappedApplet.start(); - active = true; - } - - @Override - public void stop() - { - wrappedApplet.stop(); - active = false; - } - - public void destroy() - { - wrappedApplet.destroy(); - } - - @Override - public URL getCodeBase() { - try { - return new URL("http://www.minecraft.net/game/"); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - return null; - } - - @Override - public URL getDocumentBase() - { - try { - return new URL("http://www.minecraft.net/game/"); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - return null; - } - - @Override - public void setVisible(boolean b) - { - super.setVisible(b); - wrappedApplet.setVisible(b); - } - public void update(Graphics paramGraphics) - { - } - public void paint(Graphics paramGraphics) - { - } + private Applet wrappedApplet; + private URL documentBase; + private boolean active = false; + private final Map params; + + public Launcher(Applet applet, URL documentBase) + { + params = new TreeMap(); + + this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + this.wrappedApplet = applet; + this.documentBase = documentBase; + } + + public void setParameter(String name, String value) + { + params.put(name, value); + } + + public void replace(Applet applet) + { + this.wrappedApplet = applet; + + applet.setStub(this); + applet.setSize(getWidth(), getHeight()); + + this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + + applet.init(); + active = true; + applet.start(); + validate(); + } + + @Override + public String getParameter(String name) + { + String param = params.get(name); + if (param != null) + return param; + try + { + return super.getParameter(name); + } catch (Exception ignore){} + return null; + } + + @Override + public boolean isActive() + { + return active; + } + + @Override + public void appletResize(int width, int height) + { + wrappedApplet.resize(width, height); + } + + @Override + public void resize(int width, int height) + { + wrappedApplet.resize(width, height); + } + + @Override + public void resize(Dimension d) + { + wrappedApplet.resize(d); + } + + @Override + public void init() + { + if (wrappedApplet != null) + { + wrappedApplet.init(); + } + } + + @Override + public void start() + { + wrappedApplet.start(); + active = true; + } + + @Override + public void stop() + { + wrappedApplet.stop(); + active = false; + } + + public void destroy() + { + wrappedApplet.destroy(); + } + + @Override + public URL getCodeBase() { + try { + return new URL("http://www.minecraft.net/game/"); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public URL getDocumentBase() + { + try { + return new URL("http://www.minecraft.net/game/"); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void setVisible(boolean b) + { + super.setVisible(b); + wrappedApplet.setVisible(b); + } + public void update(Graphics paramGraphics) + { + } + public void paint(Graphics paramGraphics) + { + } } \ No newline at end of file diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java index 8c9b8074..12a494b9 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -1,5 +1,5 @@ package org.multimc;/* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,131 +21,131 @@ import java.nio.charset.Charset; public class EntryPoint { - private enum Action - { - Proceed, - Launch, - Abort - } + private enum Action + { + Proceed, + Launch, + Abort + } - public static void main(String[] args) - { - EntryPoint listener = new EntryPoint(); - int retCode = listener.listen(); - if (retCode != 0) - { - System.out.println("Exiting with " + retCode); - System.exit(retCode); - } - } + public static void main(String[] args) + { + EntryPoint listener = new EntryPoint(); + int retCode = listener.listen(); + if (retCode != 0) + { + System.out.println("Exiting with " + retCode); + System.exit(retCode); + } + } - private Action parseLine(String inData) throws ParseException - { - String[] pair = inData.split(" ", 2); + private Action parseLine(String inData) throws ParseException + { + String[] pair = inData.split(" ", 2); - if(pair.length == 1) - { - String command = pair[0]; - if (pair[0].equals("launch")) - return Action.Launch; + if(pair.length == 1) + { + String command = pair[0]; + if (pair[0].equals("launch")) + return Action.Launch; - else if (pair[0].equals("abort")) - return Action.Abort; + else if (pair[0].equals("abort")) + return Action.Abort; - else throw new ParseException(); - } + else throw new ParseException("Error while parsing:" + pair[0]); + } - if(pair.length != 2) - throw new ParseException(); + if(pair.length != 2) + throw new ParseException("Pair length is not 2."); - String command = pair[0]; - String param = pair[1]; + String command = pair[0]; + String param = pair[1]; - if(command.equals("launcher")) - { - if(param.equals("onesix")) - { - m_launcher = new OneSixLauncher(); - Utils.log("Using onesix launcher."); - Utils.log(); - return Action.Proceed; - } - else - throw new ParseException(); - } + if(command.equals("launcher")) + { + if(param.equals("onesix")) + { + m_launcher = new OneSixLauncher(); + Utils.log("Using onesix launcher."); + Utils.log(); + return Action.Proceed; + } + else + throw new ParseException("Invalid launcher type: " + param); + } - m_params.add(command, param); - //System.out.println(command + " : " + param); - return Action.Proceed; - } + m_params.add(command, param); + //System.out.println(command + " : " + param); + return Action.Proceed; + } - public int listen() - { - BufferedReader buffer; - try - { - buffer = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); - } catch (UnsupportedEncodingException e) - { - System.err.println("For some reason, your java does not support UTF-8. Consider living in the current century."); - e.printStackTrace(); - return 1; - } - boolean isListening = true; - boolean isAborted = false; - // Main loop - while (isListening) - { - String inData; - try - { - // Read from the pipe one line at a time - inData = buffer.readLine(); - if (inData != null) - { - Action a = parseLine(inData); - if(a == Action.Abort) - { - isListening = false; - isAborted = true; - } - if(a == Action.Launch) - { - isListening = false; - } - } - else - { - isListening = false; - isAborted = true; - } - } - catch (IOException e) - { - System.err.println("Launcher ABORT due to IO exception:"); - e.printStackTrace(); - return 1; - } - catch (ParseException e) - { - System.err.println("Launcher ABORT due to PARSE exception:"); - e.printStackTrace(); - return 1; - } - } - if(isAborted) - { - System.err.println("Launch aborted by MultiMC."); - return 1; - } - if(m_launcher != null) - { - return m_launcher.launch(m_params); - } - System.err.println("No valid launcher implementation specified."); - return 1; - } + public int listen() + { + BufferedReader buffer; + try + { + buffer = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); + } catch (UnsupportedEncodingException e) + { + System.err.println("For some reason, your java does not support UTF-8. Consider living in the current century."); + e.printStackTrace(); + return 1; + } + boolean isListening = true; + boolean isAborted = false; + // Main loop + while (isListening) + { + String inData; + try + { + // Read from the pipe one line at a time + inData = buffer.readLine(); + if (inData != null) + { + Action a = parseLine(inData); + if(a == Action.Abort) + { + isListening = false; + isAborted = true; + } + if(a == Action.Launch) + { + isListening = false; + } + } + else + { + isListening = false; + isAborted = true; + } + } + catch (IOException e) + { + System.err.println("Launcher ABORT due to IO exception:"); + e.printStackTrace(); + return 1; + } + catch (ParseException e) + { + System.err.println("Launcher ABORT due to PARSE exception:"); + e.printStackTrace(); + return 1; + } + } + if(isAborted) + { + System.err.println("Launch aborted by MultiMC."); + return 1; + } + if(m_launcher != null) + { + return m_launcher.launch(m_params); + } + System.err.println("No valid launcher implementation specified."); + return 1; + } - private ParamBucket m_params = new ParamBucket(); - private org.multimc.Launcher m_launcher; + private ParamBucket m_params = new ParamBucket(); + private org.multimc.Launcher m_launcher; } diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/multimc/Launcher.java index 2e851d18..8f9b043f 100644 --- a/libraries/launcher/org/multimc/Launcher.java +++ b/libraries/launcher/org/multimc/Launcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,5 +18,5 @@ package org.multimc; public interface Launcher { - abstract int launch(ParamBucket params); + abstract int launch(ParamBucket params); } diff --git a/libraries/launcher/org/multimc/LegacyFrame.java b/libraries/launcher/org/multimc/LegacyFrame.java index 9842eb0e..19cfdfb7 100644 --- a/libraries/launcher/org/multimc/LegacyFrame.java +++ b/libraries/launcher/org/multimc/LegacyFrame.java @@ -1,5 +1,5 @@ package org.multimc;/* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/libraries/launcher/org/multimc/NotFoundException.java b/libraries/launcher/org/multimc/NotFoundException.java index 2c5da6de..c1084fe6 100644 --- a/libraries/launcher/org/multimc/NotFoundException.java +++ b/libraries/launcher/org/multimc/NotFoundException.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/libraries/launcher/org/multimc/ParamBucket.java b/libraries/launcher/org/multimc/ParamBucket.java index 5e9c3ff6..f5b40c40 100644 --- a/libraries/launcher/org/multimc/ParamBucket.java +++ b/libraries/launcher/org/multimc/ParamBucket.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,65 +22,65 @@ import java.util.List; public class ParamBucket { - public void add(String key, String value) - { - List coll = null; - if(!m_params.containsKey(key)) - { - coll = new ArrayList(); - m_params.put(key, coll); - } - else - { - coll = m_params.get(key); - } - coll.add(value); - } + public void add(String key, String value) + { + List coll = null; + if(!m_params.containsKey(key)) + { + coll = new ArrayList(); + m_params.put(key, coll); + } + else + { + coll = m_params.get(key); + } + coll.add(value); + } - public List all(String key) throws NotFoundException - { - if(!m_params.containsKey(key)) - throw new NotFoundException(); - return m_params.get(key); - } + public List all(String key) throws NotFoundException + { + if(!m_params.containsKey(key)) + throw new NotFoundException(); + return m_params.get(key); + } - public List allSafe(String key, List def) - { - if(!m_params.containsKey(key) || m_params.get(key).size() < 1) - { - return def; - } - return m_params.get(key); - } + public List allSafe(String key, List def) + { + if(!m_params.containsKey(key) || m_params.get(key).size() < 1) + { + return def; + } + return m_params.get(key); + } - public List allSafe(String key) - { - return allSafe(key, new ArrayList()); - } + public List allSafe(String key) + { + return allSafe(key, new ArrayList()); + } - public String first(String key) throws NotFoundException - { - List list = all(key); - if(list.size() < 1) - { - throw new NotFoundException(); - } - return list.get(0); - } + public String first(String key) throws NotFoundException + { + List list = all(key); + if(list.size() < 1) + { + throw new NotFoundException(); + } + return list.get(0); + } - public String firstSafe(String key, String def) - { - if(!m_params.containsKey(key) || m_params.get(key).size() < 1) - { - return def; - } - return m_params.get(key).get(0); - } + public String firstSafe(String key, String def) + { + if(!m_params.containsKey(key) || m_params.get(key).size() < 1) + { + return def; + } + return m_params.get(key).get(0); + } - public String firstSafe(String key) - { - return firstSafe(key, ""); - } + public String firstSafe(String key) + { + return firstSafe(key, ""); + } - private HashMap> m_params = new HashMap>(); + private HashMap> m_params = new HashMap>(); } diff --git a/libraries/launcher/org/multimc/ParseException.java b/libraries/launcher/org/multimc/ParseException.java index 9a8fe521..e9c84f6e 100644 --- a/libraries/launcher/org/multimc/ParseException.java +++ b/libraries/launcher/org/multimc/ParseException.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,5 +18,8 @@ package org.multimc; public class ParseException extends java.lang.Exception { - + public ParseException() { super(); } + public ParseException(String message) { + super(message); + } } diff --git a/libraries/launcher/org/multimc/Utils.java b/libraries/launcher/org/multimc/Utils.java index c5292eaf..b0a2d5b0 100644 --- a/libraries/launcher/org/multimc/Utils.java +++ b/libraries/launcher/org/multimc/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 MultiMC Contributors + * Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,86 +34,86 @@ import java.util.zip.ZipFile; public class Utils { - /** - * Combine two parts of a path. - * - * @param path1 - * @param path2 - * @return the paths, combined - */ - public static String combine(String path1, String path2) - { - File file1 = new File(path1); - File file2 = new File(file1, path2); - return file2.getPath(); - } + /** + * Combine two parts of a path. + * + * @param path1 + * @param path2 + * @return the paths, combined + */ + public static String combine(String path1, String path2) + { + File file1 = new File(path1); + File file2 = new File(file1, path2); + return file2.getPath(); + } - /** - * Join a list of strings into a string using a separator! - * - * @param strings the string list to join - * @param separator the glue - * @return the result. - */ - public static String join(List strings, String separator) - { - StringBuilder sb = new StringBuilder(); - String sep = ""; - for (String s : strings) - { - sb.append(sep).append(s); - sep = separator; - } - return sb.toString(); - } + /** + * Join a list of strings into a string using a separator! + * + * @param strings the string list to join + * @param separator the glue + * @return the result. + */ + public static String join(List strings, String separator) + { + StringBuilder sb = new StringBuilder(); + String sep = ""; + for (String s : strings) + { + sb.append(sep).append(s); + sep = separator; + } + return sb.toString(); + } - /** - * Finds a field that looks like a Minecraft base folder in a supplied class - * - * @param mc the class to scan - */ - public static Field getMCPathField(Class mc) - { - Field[] fields = mc.getDeclaredFields(); + /** + * Finds a field that looks like a Minecraft base folder in a supplied class + * + * @param mc the class to scan + */ + public static Field getMCPathField(Class mc) + { + Field[] fields = mc.getDeclaredFields(); - for (Field f : fields) - { - if (f.getType() != File.class) - { - // Has to be File - continue; - } - if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC)) - { - // And Private Static. - continue; - } - return f; - } - return null; - } + for (Field f : fields) + { + if (f.getType() != File.class) + { + // Has to be File + continue; + } + if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC)) + { + // And Private Static. + continue; + } + return f; + } + return null; + } - /** - * Log to the MultiMC console - * - * @param message A String containing the message - * @param level A String containing the level name. See MinecraftLauncher::getLevel() - */ - public static void log(String message, String level) - { - // Kinda dirty - String tag = "!![" + level + "]!"; - System.out.println(tag + message.replace("\n", "\n" + tag)); - } + /** + * Log to the MultiMC console + * + * @param message A String containing the message + * @param level A String containing the level name. See MinecraftLauncher::getLevel() + */ + public static void log(String message, String level) + { + // Kinda dirty + String tag = "!![" + level + "]!"; + System.out.println(tag + message.replace("\n", "\n" + tag)); + } - public static void log(String message) - { - log(message, "MultiMC"); - } + public static void log(String message) + { + log(message, "MultiMC"); + } - public static void log() - { - System.out.println(); - } + public static void log() + { + System.out.println(); + } } diff --git a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java index 9667297d..ec688ee3 100644 --- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java +++ b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java @@ -1,4 +1,4 @@ -/* Copyright 2012-2018 MultiMC Contributors +/* Copyright 2012-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,208 +27,208 @@ import java.util.List; public class OneSixLauncher implements Launcher { - // parameters, separated from ParamBucket - private List libraries; - private List mcparams; - private List mods; - private List jarmods; - private List coremods; - private List traits; - private String appletClass; - private String mainClass; - private String nativePath; - private String userName, sessionId; - private String windowTitle; - private String windowParams; - - // secondary parameters - private int winSizeW; - private int winSizeH; - private boolean maximize; - private String cwd; - - // the much abused system classloader, for convenience (for further abuse) - private ClassLoader cl; - - private void processParams(ParamBucket params) throws NotFoundException - { - libraries = params.all("cp"); - mcparams = params.allSafe("param", new ArrayList() ); - mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); - appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); - traits = params.allSafe("traits", new ArrayList()); - nativePath = params.first("natives"); - - userName = params.first("userName"); - sessionId = params.first("sessionId"); - windowTitle = params.firstSafe("windowTitle", "Minecraft"); - windowParams = params.firstSafe("windowParams", "854x480"); - - cwd = System.getProperty("user.dir"); - - winSizeW = 854; - winSizeH = 480; - maximize = false; - - String[] dimStrings = windowParams.split("x"); - - if (windowParams.equalsIgnoreCase("max")) - { - maximize = true; - } - else if (dimStrings.length == 2) - { - try - { - winSizeW = Integer.parseInt(dimStrings[0]); - winSizeH = Integer.parseInt(dimStrings[1]); - } catch (NumberFormatException ignored) {} - } - } - - int legacyLaunch() - { - // Get the Minecraft Class and set the base folder - Class mc; - try - { - mc = cl.loadClass(mainClass); - - Field f = Utils.getMCPathField(mc); - - if (f == null) - { - System.err.println("Could not find Minecraft path field."); - } - else - { - f.setAccessible(true); - f.set(null, new File(cwd)); - } - } catch (Exception e) - { - System.err.println("Could not set base folder. Failed to find/access Minecraft main class:"); - e.printStackTrace(System.err); - return -1; - } - - System.setProperty("minecraft.applet.TargetDirectory", cwd); - - if(!traits.contains("noapplet")) - { - Utils.log("Launching with applet wrapper..."); - try - { - Class MCAppletClass = cl.loadClass(appletClass); - Applet mcappl = (Applet) MCAppletClass.newInstance(); - LegacyFrame mcWindow = new LegacyFrame(windowTitle); - mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize); - return 0; - } catch (Exception e) - { - Utils.log("Applet wrapper failed:", "Error"); - e.printStackTrace(System.err); - Utils.log(); - Utils.log("Falling back to using main class."); - } - } - - // init params for the main method to chomp on. - String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); - try - { - mc.getMethod("main", String[].class).invoke(null, (Object) paramsArray); - return 0; - } catch (Exception e) - { - Utils.log("Failed to invoke the Minecraft main class:", "Fatal"); - e.printStackTrace(System.err); - return -1; - } - } - - int launchWithMainClass() - { - // window size, title and state, onesix - if (maximize) - { - // FIXME: there is no good way to maximize the minecraft window in onesix. - // the following often breaks linux screen setups - // mcparams.add("--fullscreen"); - } - else - { - mcparams.add("--width"); - mcparams.add(Integer.toString(winSizeW)); - mcparams.add("--height"); - mcparams.add(Integer.toString(winSizeH)); - } - - // Get the Minecraft Class. - Class mc; - try - { - mc = cl.loadClass(mainClass); - } catch (ClassNotFoundException e) - { - System.err.println("Failed to find Minecraft main class:"); - e.printStackTrace(System.err); - return -1; - } - - // get the main method. - Method meth; - try - { - meth = mc.getMethod("main", String[].class); - } catch (NoSuchMethodException e) - { - System.err.println("Failed to acquire the main method:"); - e.printStackTrace(System.err); - return -1; - } - - // init params for the main method to chomp on. - String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); - try - { - // static method doesn't have an instance - meth.invoke(null, (Object) paramsArray); - } catch (Exception e) - { - System.err.println("Failed to start Minecraft:"); - e.printStackTrace(System.err); - return -1; - } - return 0; - } - - @Override - public int launch(ParamBucket params) - { - // get and process the launch script params - try - { - processParams(params); - } catch (NotFoundException e) - { - System.err.println("Not enough arguments."); - e.printStackTrace(System.err); - return -1; - } - - // grab the system classloader and ... - cl = ClassLoader.getSystemClassLoader(); - - if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") ) - { - // legacy launch uses the applet wrapper - return legacyLaunch(); - } - else - { - // normal launch just calls main() - return launchWithMainClass(); - } - } + // parameters, separated from ParamBucket + private List libraries; + private List mcparams; + private List mods; + private List jarmods; + private List coremods; + private List traits; + private String appletClass; + private String mainClass; + private String nativePath; + private String userName, sessionId; + private String windowTitle; + private String windowParams; + + // secondary parameters + private int winSizeW; + private int winSizeH; + private boolean maximize; + private String cwd; + + // the much abused system classloader, for convenience (for further abuse) + private ClassLoader cl; + + private void processParams(ParamBucket params) throws NotFoundException + { + libraries = params.all("cp"); + mcparams = params.allSafe("param", new ArrayList() ); + mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); + appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); + traits = params.allSafe("traits", new ArrayList()); + nativePath = params.first("natives"); + + userName = params.first("userName"); + sessionId = params.first("sessionId"); + windowTitle = params.firstSafe("windowTitle", "Minecraft"); + windowParams = params.firstSafe("windowParams", "854x480"); + + cwd = System.getProperty("user.dir"); + + winSizeW = 854; + winSizeH = 480; + maximize = false; + + String[] dimStrings = windowParams.split("x"); + + if (windowParams.equalsIgnoreCase("max")) + { + maximize = true; + } + else if (dimStrings.length == 2) + { + try + { + winSizeW = Integer.parseInt(dimStrings[0]); + winSizeH = Integer.parseInt(dimStrings[1]); + } catch (NumberFormatException ignored) {} + } + } + + int legacyLaunch() + { + // Get the Minecraft Class and set the base folder + Class mc; + try + { + mc = cl.loadClass(mainClass); + + Field f = Utils.getMCPathField(mc); + + if (f == null) + { + System.err.println("Could not find Minecraft path field."); + } + else + { + f.setAccessible(true); + f.set(null, new File(cwd)); + } + } catch (Exception e) + { + System.err.println("Could not set base folder. Failed to find/access Minecraft main class:"); + e.printStackTrace(System.err); + return -1; + } + + System.setProperty("minecraft.applet.TargetDirectory", cwd); + + if(!traits.contains("noapplet")) + { + Utils.log("Launching with applet wrapper..."); + try + { + Class MCAppletClass = cl.loadClass(appletClass); + Applet mcappl = (Applet) MCAppletClass.newInstance(); + LegacyFrame mcWindow = new LegacyFrame(windowTitle); + mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize); + return 0; + } catch (Exception e) + { + Utils.log("Applet wrapper failed:", "Error"); + e.printStackTrace(System.err); + Utils.log(); + Utils.log("Falling back to using main class."); + } + } + + // init params for the main method to chomp on. + String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); + try + { + mc.getMethod("main", String[].class).invoke(null, (Object) paramsArray); + return 0; + } catch (Exception e) + { + Utils.log("Failed to invoke the Minecraft main class:", "Fatal"); + e.printStackTrace(System.err); + return -1; + } + } + + int launchWithMainClass() + { + // window size, title and state, onesix + if (maximize) + { + // FIXME: there is no good way to maximize the minecraft window in onesix. + // the following often breaks linux screen setups + // mcparams.add("--fullscreen"); + } + else + { + mcparams.add("--width"); + mcparams.add(Integer.toString(winSizeW)); + mcparams.add("--height"); + mcparams.add(Integer.toString(winSizeH)); + } + + // Get the Minecraft Class. + Class mc; + try + { + mc = cl.loadClass(mainClass); + } catch (ClassNotFoundException e) + { + System.err.println("Failed to find Minecraft main class:"); + e.printStackTrace(System.err); + return -1; + } + + // get the main method. + Method meth; + try + { + meth = mc.getMethod("main", String[].class); + } catch (NoSuchMethodException e) + { + System.err.println("Failed to acquire the main method:"); + e.printStackTrace(System.err); + return -1; + } + + // init params for the main method to chomp on. + String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); + try + { + // static method doesn't have an instance + meth.invoke(null, (Object) paramsArray); + } catch (Exception e) + { + System.err.println("Failed to start Minecraft:"); + e.printStackTrace(System.err); + return -1; + } + return 0; + } + + @Override + public int launch(ParamBucket params) + { + // get and process the launch script params + try + { + processParams(params); + } catch (NotFoundException e) + { + System.err.println("Not enough arguments."); + e.printStackTrace(System.err); + return -1; + } + + // grab the system classloader and ... + cl = ClassLoader.getSystemClassLoader(); + + if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") ) + { + // legacy launch uses the applet wrapper + return legacyLaunch(); + } + else + { + // normal launch just calls main() + return launchWithMainClass(); + } + } } diff --git a/libraries/pack200/CMakeLists.txt b/libraries/pack200/CMakeLists.txt index b568e506..31eb0f73 100644 --- a/libraries/pack200/CMakeLists.txt +++ b/libraries/pack200/CMakeLists.txt @@ -8,22 +8,22 @@ option(PACK200_BUILD_BINARY "Build a tiny utility that decompresses pack200 stre find_package(ZLIB REQUIRED) set(PACK200_SRC - include/unpack200.h - src/bands.cpp - src/bands.h - src/bytes.cpp - src/bytes.h - src/coding.cpp - src/coding.h - src/constants.h - src/defines.h - src/unpack200.cpp - src/unpack.cpp - src/unpack.h - src/utils.cpp - src/utils.h - src/zip.cpp - src/zip.h + include/unpack200.h + src/bands.cpp + src/bands.h + src/bytes.cpp + src/bytes.h + src/coding.cpp + src/coding.h + src/constants.h + src/defines.h + src/unpack200.cpp + src/unpack.cpp + src/unpack.h + src/utils.cpp + src/utils.h + src/zip.cpp + src/zip.h ) if (Qt5_POSITION_INDEPENDENT_CODE) @@ -39,12 +39,12 @@ generate_export_header(MultiMC_unpack200) # Install it install( - TARGETS MultiMC_unpack200 - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} + TARGETS MultiMC_unpack200 + RUNTIME DESTINATION ${LIBRARY_DEST_DIR} + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} ) if(PACK200_BUILD_BINARY) - add_executable(anti200 anti200.cpp) - target_link_libraries(anti200 MultiMC_unpack200) + add_executable(anti200 anti200.cpp) + target_link_libraries(anti200 MultiMC_unpack200) endif() diff --git a/libraries/pack200/anti200.cpp b/libraries/pack200/anti200.cpp index 944e80e5..1e672847 100644 --- a/libraries/pack200/anti200.cpp +++ b/libraries/pack200/anti200.cpp @@ -8,36 +8,36 @@ int main(int argc, char **argv) { - if (argc != 3) - { - std::cerr << "Simple pack200 unpacker!" << std::endl << "Run like this:" << std::endl - << " " << argv[0] << " input.jar.lzma output.jar" << std::endl; - return EXIT_FAILURE; - } + if (argc != 3) + { + std::cerr << "Simple pack200 unpacker!" << std::endl << "Run like this:" << std::endl + << " " << argv[0] << " input.jar.lzma output.jar" << std::endl; + return EXIT_FAILURE; + } - FILE *input = fopen(argv[1], "rb"); - if (!input) - { - std::cerr << "Can't open input file"; - return EXIT_FAILURE; - } - FILE *output = fopen(argv[2], "wb"); - if (!output) - { - fclose(input); - std::cerr << "Can't open output file"; - return EXIT_FAILURE; - } - try - { - unpack_200(input, output); - } - catch (std::runtime_error &e) - { - std::cerr << "Bad things happened: " << e.what() << std::endl; - fclose(input); - fclose(output); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; + FILE *input = fopen(argv[1], "rb"); + if (!input) + { + std::cerr << "Can't open input file"; + return EXIT_FAILURE; + } + FILE *output = fopen(argv[2], "wb"); + if (!output) + { + fclose(input); + std::cerr << "Can't open output file"; + return EXIT_FAILURE; + } + try + { + unpack_200(input, output); + } + catch (const std::runtime_error &e) + { + std::cerr << "Bad things happened: " << e.what() << std::endl; + fclose(input); + fclose(output); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } diff --git a/libraries/pack200/src/bands.cpp b/libraries/pack200/src/bands.cpp index 1608d838..e82613b5 100644 --- a/libraries/pack200/src/bands.cpp +++ b/libraries/pack200/src/bands.cpp @@ -47,188 +47,188 @@ void band::readData(int expectedLength) { - assert(expectedLength >= 0); - assert(vs[0].cmk == cmk_ERROR); - if (expectedLength != 0) - { - assert(length == 0); - length = expectedLength; - } - if (length == 0) - { - assert((rplimit = cm.vs0.rp = u->rp) != nullptr); - return; - } - assert(length > 0); + assert(expectedLength >= 0); + assert(vs[0].cmk == cmk_ERROR); + if (expectedLength != 0) + { + assert(length == 0); + length = expectedLength; + } + if (length == 0) + { + assert((rplimit = cm.vs0.rp = u->rp) != nullptr); + return; + } + assert(length > 0); - bool is_BYTE1 = (defc->spec == BYTE1_spec); + bool is_BYTE1 = (defc->spec == BYTE1_spec); - if (is_BYTE1) - { - // No possibility of coding change. Sizing is exact. - u->ensure_input(length); - } - else - { - // Make a conservatively generous estimate of band size in bytes. - // Assume B == 5 everywhere. - // Assume awkward pop with all {U} values (2*5 per value) - int64_t generous = (int64_t)length * (B_MAX * 3 + 1) + C_SLOP; - u->ensure_input(generous); - } + if (is_BYTE1) + { + // No possibility of coding change. Sizing is exact. + u->ensure_input(length); + } + else + { + // Make a conservatively generous estimate of band size in bytes. + // Assume B == 5 everywhere. + // Assume awkward pop with all {U} values (2*5 per value) + int64_t generous = (int64_t)length * (B_MAX * 3 + 1) + C_SLOP; + u->ensure_input(generous); + } - // Read one value to see what it might be. - int XB = _meta_default; - if (!is_BYTE1) - { - // must be a variable-length coding - assert(defc->B() > 1 && defc->L() > 0); + // Read one value to see what it might be. + int XB = _meta_default; + if (!is_BYTE1) + { + // must be a variable-length coding + assert(defc->B() > 1 && defc->L() > 0); - value_stream xvs; - coding *valc = defc; - if (valc->D() != 0) - { - valc = coding::findBySpec(defc->B(), defc->H(), defc->S()); - assert(!valc->isMalloc); - } - xvs.init(u->rp, u->rplimit, valc); - int X = xvs.getInt(); - if (valc->S() != 0) - { - assert(valc->min <= -256); - XB = -1 - X; - } - else - { - int L = valc->L(); - assert(valc->max >= L + 255); - XB = X - L; - } - if (0 <= XB && XB < 256) - { - // Skip over the escape value. - u->rp = xvs.rp; - } - else - { - // No, it's still default. - XB = _meta_default; - } - } + value_stream xvs; + coding *valc = defc; + if (valc->D() != 0) + { + valc = coding::findBySpec(defc->B(), defc->H(), defc->S()); + assert(!valc->isMalloc); + } + xvs.init(u->rp, u->rplimit, valc); + int X = xvs.getInt(); + if (valc->S() != 0) + { + assert(valc->min <= -256); + XB = -1 - X; + } + else + { + int L = valc->L(); + assert(valc->max >= L + 255); + XB = X - L; + } + if (0 <= XB && XB < 256) + { + // Skip over the escape value. + u->rp = xvs.rp; + } + else + { + // No, it's still default. + XB = _meta_default; + } + } - if (XB <= _meta_canon_max) - { - byte XB_byte = (byte)XB; - byte *XB_ptr = &XB_byte; - cm.init(u->rp, u->rplimit, XB_ptr, 0, defc, length, nullptr); - } - else - { - assert(u->meta_rp != nullptr); - // Scribble the initial byte onto the band. - byte *save_meta_rp = --u->meta_rp; - byte save_meta_xb = (*save_meta_rp); - (*save_meta_rp) = (byte)XB; - cm.init(u->rp, u->rplimit, u->meta_rp, 0, defc, length, nullptr); - (*save_meta_rp) = save_meta_xb; // put it back, just to be tidy - } - rplimit = u->rp; + if (XB <= _meta_canon_max) + { + byte XB_byte = (byte)XB; + byte *XB_ptr = &XB_byte; + cm.init(u->rp, u->rplimit, XB_ptr, 0, defc, length, nullptr); + } + else + { + assert(u->meta_rp != nullptr); + // Scribble the initial byte onto the band. + byte *save_meta_rp = --u->meta_rp; + byte save_meta_xb = (*save_meta_rp); + (*save_meta_rp) = (byte)XB; + cm.init(u->rp, u->rplimit, u->meta_rp, 0, defc, length, nullptr); + (*save_meta_rp) = save_meta_xb; // put it back, just to be tidy + } + rplimit = u->rp; - rewind(); + rewind(); } void band::setIndex(cpindex *ix_) { - assert(ix_ == nullptr || ixTag == ix_->ixTag); - ix = ix_; + assert(ix_ == nullptr || ixTag == ix_->ixTag); + ix = ix_; } void band::setIndexByTag(byte tag) { - setIndex(u->cp.getIndex(tag)); + setIndex(u->cp.getIndex(tag)); } entry *band::getRefCommon(cpindex *ix_, bool nullOKwithCaller) { - assert(ix_->ixTag == ixTag || - (ixTag == CONSTANT_Literal && ix_->ixTag >= CONSTANT_Integer && - ix_->ixTag <= CONSTANT_String)); - int n = vs[0].getInt() - nullOK; - // Note: band-local nullOK means nullptr encodes as 0. - // But nullOKwithCaller means caller is willing to tolerate a nullptr. - entry *ref = ix_->get(n); - if (ref == nullptr && !(nullOKwithCaller && n == -1)) - unpack_abort(n == -1 ? "nullptr ref" : "bad ref"); - return ref; + assert(ix_->ixTag == ixTag || + (ixTag == CONSTANT_Literal && ix_->ixTag >= CONSTANT_Integer && + ix_->ixTag <= CONSTANT_String)); + int n = vs[0].getInt() - nullOK; + // Note: band-local nullOK means nullptr encodes as 0. + // But nullOKwithCaller means caller is willing to tolerate a nullptr. + entry *ref = ix_->get(n); + if (ref == nullptr && !(nullOKwithCaller && n == -1)) + unpack_abort(n == -1 ? "nullptr ref" : "bad ref"); + return ref; } int64_t band::getLong(band &lo_band, bool have_hi) { - band &hi_band = (*this); - assert(lo_band.bn == hi_band.bn + 1); - uint32_t lo = lo_band.getInt(); - if (!have_hi) - { - assert(hi_band.length == 0); - return makeLong(0, lo); - } - uint32_t hi = hi_band.getInt(); - return makeLong(hi, lo); + band &hi_band = (*this); + assert(lo_band.bn == hi_band.bn + 1); + uint32_t lo = lo_band.getInt(); + if (!have_hi) + { + assert(hi_band.length == 0); + return makeLong(0, lo); + } + uint32_t hi = hi_band.getInt(); + return makeLong(hi, lo); } int band::getIntTotal() { - if (length == 0) - return 0; - if (total_memo > 0) - return total_memo - 1; - int total = getInt(); - // overflow checks require that none of the addends are <0, - // and that the partial sums never overflow (wrap negative) - if (total < 0) - { - unpack_abort("overflow detected"); - } - for (int k = length - 1; k > 0; k--) - { - int prev_total = total; - total += vs[0].getInt(); - if (total < prev_total) - { - unpack_abort("overflow detected"); - } - } - rewind(); - total_memo = total + 1; - return total; + if (length == 0) + return 0; + if (total_memo > 0) + return total_memo - 1; + int total = getInt(); + // overflow checks require that none of the addends are <0, + // and that the partial sums never overflow (wrap negative) + if (total < 0) + { + unpack_abort("overflow detected"); + } + for (int k = length - 1; k > 0; k--) + { + int prev_total = total; + total += vs[0].getInt(); + if (total < prev_total) + { + unpack_abort("overflow detected"); + } + } + rewind(); + total_memo = total + 1; + return total; } int band::getIntCount(int tag) { - if (length == 0) - return 0; - if (tag >= HIST0_MIN && tag <= HIST0_MAX) - { - if (hist0 == nullptr) - { - // Lazily calculate an approximate histogram. - hist0 = U_NEW(int, (HIST0_MAX - HIST0_MIN) + 1); - for (int k = length; k > 0; k--) - { - int x = vs[0].getInt(); - if (x >= HIST0_MIN && x <= HIST0_MAX) - hist0[x - HIST0_MIN] += 1; - } - rewind(); - } - return hist0[tag - HIST0_MIN]; - } - int total = 0; - for (int k = length; k > 0; k--) - { - total += (vs[0].getInt() == tag) ? 1 : 0; - } - rewind(); - return total; + if (length == 0) + return 0; + if (tag >= HIST0_MIN && tag <= HIST0_MAX) + { + if (hist0 == nullptr) + { + // Lazily calculate an approximate histogram. + hist0 = U_NEW(int, (HIST0_MAX - HIST0_MIN) + 1); + for (int k = length; k > 0; k--) + { + int x = vs[0].getInt(); + if (x >= HIST0_MIN && x <= HIST0_MAX) + hist0[x - HIST0_MIN] += 1; + } + rewind(); + } + return hist0[tag - HIST0_MIN]; + } + int total = 0; + for (int k = length; k > 0; k--) + { + total += (vs[0].getInt() == tag) ? 1 : 0; + } + rewind(); + return total; } #define INDEX_INIT(tag, nullOK, subindex) ((tag) + (subindex) * SUBINDEX_BIT + (nullOK) * 256) @@ -240,184 +240,184 @@ int band::getIntCount(int tag) struct band_init { - int defc; - int index; + int defc; + int index; }; #define BAND_INIT(name, cspec, ix) \ - { \ - cspec, ix \ - } + { \ + cspec, ix \ + } const band_init all_band_inits[] = - { - // BAND_INIT(archive_magic, BYTE1_spec, 0), - // BAND_INIT(archive_header, UNSIGNED5_spec, 0), - // BAND_INIT(band_headers, BYTE1_spec, 0), - BAND_INIT(cp_Utf8_prefix, DELTA5_spec, 0), BAND_INIT(cp_Utf8_suffix, UNSIGNED5_spec, 0), - BAND_INIT(cp_Utf8_chars, CHAR3_spec, 0), BAND_INIT(cp_Utf8_big_suffix, DELTA5_spec, 0), - BAND_INIT(cp_Utf8_big_chars, DELTA5_spec, 0), BAND_INIT(cp_Int, UDELTA5_spec, 0), - BAND_INIT(cp_Float, UDELTA5_spec, 0), BAND_INIT(cp_Long_hi, UDELTA5_spec, 0), - BAND_INIT(cp_Long_lo, DELTA5_spec, 0), BAND_INIT(cp_Double_hi, UDELTA5_spec, 0), - BAND_INIT(cp_Double_lo, DELTA5_spec, 0), - BAND_INIT(cp_String, UDELTA5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(cp_Class, UDELTA5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(cp_Signature_form, DELTA5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(cp_Signature_classes, UDELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(cp_Descr_name, DELTA5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(cp_Descr_type, UDELTA5_spec, INDEX(CONSTANT_Signature)), - BAND_INIT(cp_Field_class, DELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(cp_Field_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), - BAND_INIT(cp_Method_class, DELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(cp_Method_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), - BAND_INIT(cp_Imethod_class, DELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(cp_Imethod_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), - BAND_INIT(attr_definition_headers, BYTE1_spec, 0), - BAND_INIT(attr_definition_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(attr_definition_layout, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(ic_this_class, UDELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(ic_flags, UNSIGNED5_spec, 0), - BAND_INIT(ic_outer_class, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Class)), - BAND_INIT(ic_name, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), - BAND_INIT(class_this, DELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(class_super, DELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(class_interface_count, DELTA5_spec, 0), - BAND_INIT(class_interface, DELTA5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(class_field_count, DELTA5_spec, 0), - BAND_INIT(class_method_count, DELTA5_spec, 0), - BAND_INIT(field_descr, DELTA5_spec, INDEX(CONSTANT_NameandType)), - BAND_INIT(field_flags_hi, UNSIGNED5_spec, 0), - BAND_INIT(field_flags_lo, UNSIGNED5_spec, 0), - BAND_INIT(field_attr_count, UNSIGNED5_spec, 0), - BAND_INIT(field_attr_indexes, UNSIGNED5_spec, 0), - BAND_INIT(field_attr_calls, UNSIGNED5_spec, 0), - BAND_INIT(field_ConstantValue_KQ, UNSIGNED5_spec, INDEX(CONSTANT_Literal)), - BAND_INIT(field_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), - BAND_INIT(field_metadata_bands, -1, -1), BAND_INIT(field_attr_bands, -1, -1), - BAND_INIT(method_descr, MDELTA5_spec, INDEX(CONSTANT_NameandType)), - BAND_INIT(method_flags_hi, UNSIGNED5_spec, 0), - BAND_INIT(method_flags_lo, UNSIGNED5_spec, 0), - BAND_INIT(method_attr_count, UNSIGNED5_spec, 0), - BAND_INIT(method_attr_indexes, UNSIGNED5_spec, 0), - BAND_INIT(method_attr_calls, UNSIGNED5_spec, 0), - BAND_INIT(method_Exceptions_N, UNSIGNED5_spec, 0), - BAND_INIT(method_Exceptions_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(method_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), - BAND_INIT(method_metadata_bands, -1, -1), BAND_INIT(method_attr_bands, -1, -1), - BAND_INIT(class_flags_hi, UNSIGNED5_spec, 0), - BAND_INIT(class_flags_lo, UNSIGNED5_spec, 0), - BAND_INIT(class_attr_count, UNSIGNED5_spec, 0), - BAND_INIT(class_attr_indexes, UNSIGNED5_spec, 0), - BAND_INIT(class_attr_calls, UNSIGNED5_spec, 0), - BAND_INIT(class_SourceFile_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), - BAND_INIT(class_EnclosingMethod_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(class_EnclosingMethod_RDN, UNSIGNED5_spec, - NULL_OR_INDEX(CONSTANT_NameandType)), - BAND_INIT(class_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), - BAND_INIT(class_metadata_bands, -1, -1), - BAND_INIT(class_InnerClasses_N, UNSIGNED5_spec, 0), - BAND_INIT(class_InnerClasses_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(class_InnerClasses_F, UNSIGNED5_spec, 0), - BAND_INIT(class_InnerClasses_outer_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), - BAND_INIT(class_InnerClasses_name_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), - BAND_INIT(class_ClassFile_version_minor_H, UNSIGNED5_spec, 0), - BAND_INIT(class_ClassFile_version_major_H, UNSIGNED5_spec, 0), - BAND_INIT(class_attr_bands, -1, -1), BAND_INIT(code_headers, BYTE1_spec, 0), - BAND_INIT(code_max_stack, UNSIGNED5_spec, 0), - BAND_INIT(code_max_na_locals, UNSIGNED5_spec, 0), - BAND_INIT(code_handler_count, UNSIGNED5_spec, 0), - BAND_INIT(code_handler_start_P, BCI5_spec, 0), - BAND_INIT(code_handler_end_PO, BRANCH5_spec, 0), - BAND_INIT(code_handler_catch_PO, BRANCH5_spec, 0), - BAND_INIT(code_handler_class_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), - BAND_INIT(code_flags_hi, UNSIGNED5_spec, 0), - BAND_INIT(code_flags_lo, UNSIGNED5_spec, 0), - BAND_INIT(code_attr_count, UNSIGNED5_spec, 0), - BAND_INIT(code_attr_indexes, UNSIGNED5_spec, 0), - BAND_INIT(code_attr_calls, UNSIGNED5_spec, 0), - BAND_INIT(code_StackMapTable_N, UNSIGNED5_spec, 0), - BAND_INIT(code_StackMapTable_frame_T, BYTE1_spec, 0), - BAND_INIT(code_StackMapTable_local_N, UNSIGNED5_spec, 0), - BAND_INIT(code_StackMapTable_stack_N, UNSIGNED5_spec, 0), - BAND_INIT(code_StackMapTable_offset, UNSIGNED5_spec, 0), - BAND_INIT(code_StackMapTable_T, BYTE1_spec, 0), - BAND_INIT(code_StackMapTable_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), - BAND_INIT(code_StackMapTable_P, BCI5_spec, 0), - BAND_INIT(code_LineNumberTable_N, UNSIGNED5_spec, 0), - BAND_INIT(code_LineNumberTable_bci_P, BCI5_spec, 0), - BAND_INIT(code_LineNumberTable_line, UNSIGNED5_spec, 0), - BAND_INIT(code_LocalVariableTable_N, UNSIGNED5_spec, 0), - BAND_INIT(code_LocalVariableTable_bci_P, BCI5_spec, 0), - BAND_INIT(code_LocalVariableTable_span_O, BRANCH5_spec, 0), - BAND_INIT(code_LocalVariableTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(code_LocalVariableTable_type_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), - BAND_INIT(code_LocalVariableTable_slot, UNSIGNED5_spec, 0), - BAND_INIT(code_LocalVariableTypeTable_N, UNSIGNED5_spec, 0), - BAND_INIT(code_LocalVariableTypeTable_bci_P, BCI5_spec, 0), - BAND_INIT(code_LocalVariableTypeTable_span_O, BRANCH5_spec, 0), - BAND_INIT(code_LocalVariableTypeTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(code_LocalVariableTypeTable_type_RS, UNSIGNED5_spec, - INDEX(CONSTANT_Signature)), - BAND_INIT(code_LocalVariableTypeTable_slot, UNSIGNED5_spec, 0), - BAND_INIT(code_attr_bands, -1, -1), BAND_INIT(bc_codes, BYTE1_spec, 0), - BAND_INIT(bc_case_count, UNSIGNED5_spec, 0), BAND_INIT(bc_case_value, DELTA5_spec, 0), - BAND_INIT(bc_byte, BYTE1_spec, 0), BAND_INIT(bc_short, DELTA5_spec, 0), - BAND_INIT(bc_local, UNSIGNED5_spec, 0), BAND_INIT(bc_label, BRANCH5_spec, 0), - BAND_INIT(bc_intref, DELTA5_spec, INDEX(CONSTANT_Integer)), - BAND_INIT(bc_floatref, DELTA5_spec, INDEX(CONSTANT_Float)), - BAND_INIT(bc_longref, DELTA5_spec, INDEX(CONSTANT_Long)), - BAND_INIT(bc_doubleref, DELTA5_spec, INDEX(CONSTANT_Double)), - BAND_INIT(bc_stringref, DELTA5_spec, INDEX(CONSTANT_String)), - BAND_INIT(bc_classref, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), - BAND_INIT(bc_fieldref, DELTA5_spec, INDEX(CONSTANT_Fieldref)), - BAND_INIT(bc_methodref, UNSIGNED5_spec, INDEX(CONSTANT_Methodref)), - BAND_INIT(bc_imethodref, DELTA5_spec, INDEX(CONSTANT_InterfaceMethodref)), - BAND_INIT(bc_thisfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), - BAND_INIT(bc_superfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), - BAND_INIT(bc_thismethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), - BAND_INIT(bc_supermethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), - BAND_INIT(bc_initref, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), - BAND_INIT(bc_escref, UNSIGNED5_spec, INDEX(CONSTANT_All)), - BAND_INIT(bc_escrefsize, UNSIGNED5_spec, 0), BAND_INIT(bc_escsize, UNSIGNED5_spec, 0), - BAND_INIT(bc_escbyte, BYTE1_spec, 0), - BAND_INIT(file_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), - BAND_INIT(file_size_hi, UNSIGNED5_spec, 0), BAND_INIT(file_size_lo, UNSIGNED5_spec, 0), - BAND_INIT(file_modtime, DELTA5_spec, 0), BAND_INIT(file_options, UNSIGNED5_spec, 0), - // BAND_INIT(file_bits, BYTE1_spec, 0), - {0, 0}}; + { + // BAND_INIT(archive_magic, BYTE1_spec, 0), + // BAND_INIT(archive_header, UNSIGNED5_spec, 0), + // BAND_INIT(band_headers, BYTE1_spec, 0), + BAND_INIT(cp_Utf8_prefix, DELTA5_spec, 0), BAND_INIT(cp_Utf8_suffix, UNSIGNED5_spec, 0), + BAND_INIT(cp_Utf8_chars, CHAR3_spec, 0), BAND_INIT(cp_Utf8_big_suffix, DELTA5_spec, 0), + BAND_INIT(cp_Utf8_big_chars, DELTA5_spec, 0), BAND_INIT(cp_Int, UDELTA5_spec, 0), + BAND_INIT(cp_Float, UDELTA5_spec, 0), BAND_INIT(cp_Long_hi, UDELTA5_spec, 0), + BAND_INIT(cp_Long_lo, DELTA5_spec, 0), BAND_INIT(cp_Double_hi, UDELTA5_spec, 0), + BAND_INIT(cp_Double_lo, DELTA5_spec, 0), + BAND_INIT(cp_String, UDELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Class, UDELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Signature_form, DELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Signature_classes, UDELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Descr_name, DELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Descr_type, UDELTA5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(cp_Field_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Field_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(cp_Method_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Method_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(cp_Imethod_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Imethod_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(attr_definition_headers, BYTE1_spec, 0), + BAND_INIT(attr_definition_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(attr_definition_layout, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(ic_this_class, UDELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(ic_flags, UNSIGNED5_spec, 0), + BAND_INIT(ic_outer_class, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(ic_name, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_this, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_super, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_interface_count, DELTA5_spec, 0), + BAND_INIT(class_interface, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_field_count, DELTA5_spec, 0), + BAND_INIT(class_method_count, DELTA5_spec, 0), + BAND_INIT(field_descr, DELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(field_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(field_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(field_ConstantValue_KQ, UNSIGNED5_spec, INDEX(CONSTANT_Literal)), + BAND_INIT(field_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(field_metadata_bands, -1, -1), BAND_INIT(field_attr_bands, -1, -1), + BAND_INIT(method_descr, MDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(method_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(method_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(method_Exceptions_N, UNSIGNED5_spec, 0), + BAND_INIT(method_Exceptions_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(method_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(method_metadata_bands, -1, -1), BAND_INIT(method_attr_bands, -1, -1), + BAND_INIT(class_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(class_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(class_SourceFile_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_EnclosingMethod_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_EnclosingMethod_RDN, UNSIGNED5_spec, + NULL_OR_INDEX(CONSTANT_NameandType)), + BAND_INIT(class_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(class_metadata_bands, -1, -1), + BAND_INIT(class_InnerClasses_N, UNSIGNED5_spec, 0), + BAND_INIT(class_InnerClasses_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_InnerClasses_F, UNSIGNED5_spec, 0), + BAND_INIT(class_InnerClasses_outer_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(class_InnerClasses_name_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_ClassFile_version_minor_H, UNSIGNED5_spec, 0), + BAND_INIT(class_ClassFile_version_major_H, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_bands, -1, -1), BAND_INIT(code_headers, BYTE1_spec, 0), + BAND_INIT(code_max_stack, UNSIGNED5_spec, 0), + BAND_INIT(code_max_na_locals, UNSIGNED5_spec, 0), + BAND_INIT(code_handler_count, UNSIGNED5_spec, 0), + BAND_INIT(code_handler_start_P, BCI5_spec, 0), + BAND_INIT(code_handler_end_PO, BRANCH5_spec, 0), + BAND_INIT(code_handler_catch_PO, BRANCH5_spec, 0), + BAND_INIT(code_handler_class_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(code_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(code_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_frame_T, BYTE1_spec, 0), + BAND_INIT(code_StackMapTable_local_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_stack_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_offset, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_T, BYTE1_spec, 0), + BAND_INIT(code_StackMapTable_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(code_StackMapTable_P, BCI5_spec, 0), + BAND_INIT(code_LineNumberTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LineNumberTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LineNumberTable_line, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LocalVariableTable_span_O, BRANCH5_spec, 0), + BAND_INIT(code_LocalVariableTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(code_LocalVariableTable_type_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(code_LocalVariableTable_slot, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_span_O, BRANCH5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(code_LocalVariableTypeTable_type_RS, UNSIGNED5_spec, + INDEX(CONSTANT_Signature)), + BAND_INIT(code_LocalVariableTypeTable_slot, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_bands, -1, -1), BAND_INIT(bc_codes, BYTE1_spec, 0), + BAND_INIT(bc_case_count, UNSIGNED5_spec, 0), BAND_INIT(bc_case_value, DELTA5_spec, 0), + BAND_INIT(bc_byte, BYTE1_spec, 0), BAND_INIT(bc_short, DELTA5_spec, 0), + BAND_INIT(bc_local, UNSIGNED5_spec, 0), BAND_INIT(bc_label, BRANCH5_spec, 0), + BAND_INIT(bc_intref, DELTA5_spec, INDEX(CONSTANT_Integer)), + BAND_INIT(bc_floatref, DELTA5_spec, INDEX(CONSTANT_Float)), + BAND_INIT(bc_longref, DELTA5_spec, INDEX(CONSTANT_Long)), + BAND_INIT(bc_doubleref, DELTA5_spec, INDEX(CONSTANT_Double)), + BAND_INIT(bc_stringref, DELTA5_spec, INDEX(CONSTANT_String)), + BAND_INIT(bc_classref, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(bc_fieldref, DELTA5_spec, INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_methodref, UNSIGNED5_spec, INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_imethodref, DELTA5_spec, INDEX(CONSTANT_InterfaceMethodref)), + BAND_INIT(bc_thisfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_superfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_thismethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_supermethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_initref, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_escref, UNSIGNED5_spec, INDEX(CONSTANT_All)), + BAND_INIT(bc_escrefsize, UNSIGNED5_spec, 0), BAND_INIT(bc_escsize, UNSIGNED5_spec, 0), + BAND_INIT(bc_escbyte, BYTE1_spec, 0), + BAND_INIT(file_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(file_size_hi, UNSIGNED5_spec, 0), BAND_INIT(file_size_lo, UNSIGNED5_spec, 0), + BAND_INIT(file_modtime, DELTA5_spec, 0), BAND_INIT(file_options, UNSIGNED5_spec, 0), + // BAND_INIT(file_bits, BYTE1_spec, 0), + {0, 0}}; band *band::makeBands(unpacker *u) { - band *tmp_all_bands = U_NEW(band, BAND_LIMIT); - for (int i = 0; i < BAND_LIMIT; i++) - { - assert((byte *)&all_band_inits[i + 1] < - (byte *)all_band_inits + sizeof(all_band_inits)); - const band_init &bi = all_band_inits[i]; - band &b = tmp_all_bands[i]; - coding *defc = coding::findBySpec(bi.defc); - assert((defc == nullptr) == (bi.defc == -1)); // no garbage, please - assert(defc == nullptr || !defc->isMalloc); - b.init(u, i, defc); - if (bi.index > 0) - { - b.nullOK = ((bi.index >> 8) & 1); - b.ixTag = (bi.index & 0xFF); - } - } - return tmp_all_bands; + band *tmp_all_bands = U_NEW(band, BAND_LIMIT); + for (int i = 0; i < BAND_LIMIT; i++) + { + assert((byte *)&all_band_inits[i + 1] < + (byte *)all_band_inits + sizeof(all_band_inits)); + const band_init &bi = all_band_inits[i]; + band &b = tmp_all_bands[i]; + coding *defc = coding::findBySpec(bi.defc); + assert((defc == nullptr) == (bi.defc == -1)); // no garbage, please + assert(defc == nullptr || !defc->isMalloc); + b.init(u, i, defc); + if (bi.index > 0) + { + b.nullOK = ((bi.index >> 8) & 1); + b.ixTag = (bi.index & 0xFF); + } + } + return tmp_all_bands; } void band::initIndexes(unpacker *u) { - band *tmp_all_bands = u->all_bands; - for (int i = 0; i < BAND_LIMIT; i++) - { - band *scan = &tmp_all_bands[i]; - uint32_t tag = scan->ixTag; // Cf. #define INDEX(tag) above - if (tag != 0 && tag != CONSTANT_Literal && (tag & SUBINDEX_BIT) == 0) - { - scan->setIndex(u->cp.getIndex(tag)); - } - } + band *tmp_all_bands = u->all_bands; + for (int i = 0; i < BAND_LIMIT; i++) + { + band *scan = &tmp_all_bands[i]; + uint32_t tag = scan->ixTag; // Cf. #define INDEX(tag) above + if (tag != 0 && tag != CONSTANT_Literal && (tag & SUBINDEX_BIT) == 0) + { + scan->setIndex(u->cp.getIndex(tag)); + } + } } diff --git a/libraries/pack200/src/bands.h b/libraries/pack200/src/bands.h index a56cd7d5..66c5aec4 100644 --- a/libraries/pack200/src/bands.h +++ b/libraries/pack200/src/bands.h @@ -30,138 +30,138 @@ struct unpacker; struct band { - int bn; // band_number of this band - coding *defc; // default coding method - cpindex *ix; // CP entry mapping, if CPRefBand - byte ixTag; // 0 or 1; nullptr is coded as (nullOK?0:-1) - byte nullOK; // 0 or 1; nullptr is coded as (nullOK?0:-1) - int length; // expected # values - unpacker *u; // back pointer - - value_stream vs[2]; // source of values - coding_method cm; // method used for initial state of vs[0] - byte *rplimit; // end of band (encoded, transmitted) - - int total_memo; // cached value of getIntTotal, or -1 - int *hist0; // approximate. histogram - enum - { - HIST0_MIN = 0, - HIST0_MAX = 255 - }; // catches the usual cases - - // properties for attribute layout elements: - byte le_kind; // EK_XXX - byte le_bci; // 0,EK_BCI,EK_BCD,EK_BCO - byte le_back; // ==EF_BACK - byte le_len; // 0,1,2,4 (size in classfile), or call addr - band **le_body; // body of repl, union, call (nullptr-terminated) + int bn; // band_number of this band + coding *defc; // default coding method + cpindex *ix; // CP entry mapping, if CPRefBand + byte ixTag; // 0 or 1; nullptr is coded as (nullOK?0:-1) + byte nullOK; // 0 or 1; nullptr is coded as (nullOK?0:-1) + int length; // expected # values + unpacker *u; // back pointer + + value_stream vs[2]; // source of values + coding_method cm; // method used for initial state of vs[0] + byte *rplimit; // end of band (encoded, transmitted) + + int total_memo; // cached value of getIntTotal, or -1 + int *hist0; // approximate. histogram + enum + { + HIST0_MIN = 0, + HIST0_MAX = 255 + }; // catches the usual cases + + // properties for attribute layout elements: + byte le_kind; // EK_XXX + byte le_bci; // 0,EK_BCI,EK_BCD,EK_BCO + byte le_back; // ==EF_BACK + byte le_len; // 0,1,2,4 (size in classfile), or call addr + band **le_body; // body of repl, union, call (nullptr-terminated) // Note: EK_CASE elements use hist0 to record union tags. #define le_casetags hist0 - band &nextBand() - { - return this[1]; - } - band &prevBand() - { - return this[-1]; - } - - void init(unpacker *u_, int bn_, coding *defc_) - { - u = u_; - cm.u = u_; - bn = bn_; - defc = defc_; - } - void init(unpacker *u_, int bn_, int defcSpec) - { - init(u_, bn_, coding::findBySpec(defcSpec)); - } - void initRef(int ixTag_ = 0, bool nullOK_ = false) - { - ixTag = ixTag_; - nullOK = nullOK_; - setIndexByTag(ixTag); - } - - void expectMoreLength(int l) - { - assert(length >= 0); // able to accept a length - assert((int)l >= 0); // no overflow - assert(rplimit == nullptr); // readData not yet called - length += l; - assert(length >= l); // no overflow - } - - void setIndex(cpindex *ix_); - void setIndexByTag(byte tag); - - // Parse the band and its meta-coding header. - void readData(int expectedLength = 0); - - // Reset the band for another pass (Cf. Java Band.resetForSecondPass.) - void rewind() - { - cm.reset(&vs[0]); - } - - byte *&curRP() - { - return vs[0].rp; - } - byte *minRP() - { - return cm.vs0.rp; - } - byte *maxRP() - { - return rplimit; - } - size_t size() - { - return maxRP() - minRP(); - } - - int getByte() - { - assert(ix == nullptr); - return vs[0].getByte(); - } - int getInt() - { - assert(ix == nullptr); - return vs[0].getInt(); - } - entry *getRefN() - { - assert(ix != nullptr); - return getRefCommon(ix, true); - } - entry *getRef() - { - assert(ix != nullptr); - return getRefCommon(ix, false); - } - entry *getRefUsing(cpindex *ix2) - { - assert(ix == nullptr); - return getRefCommon(ix2, true); - } - entry *getRefCommon(cpindex *ix, bool nullOK); - int64_t getLong(band &lo_band, bool have_hi); - - static int64_t makeLong(uint32_t hi, uint32_t lo) - { - return ((uint64_t)hi << 32) + (((uint64_t)lo << 32) >> 32); - } - - int getIntTotal(); - int getIntCount(int tag); - - static band *makeBands(unpacker *u); - static void initIndexes(unpacker *u); + band &nextBand() + { + return this[1]; + } + band &prevBand() + { + return this[-1]; + } + + void init(unpacker *u_, int bn_, coding *defc_) + { + u = u_; + cm.u = u_; + bn = bn_; + defc = defc_; + } + void init(unpacker *u_, int bn_, int defcSpec) + { + init(u_, bn_, coding::findBySpec(defcSpec)); + } + void initRef(int ixTag_ = 0, bool nullOK_ = false) + { + ixTag = ixTag_; + nullOK = nullOK_; + setIndexByTag(ixTag); + } + + void expectMoreLength(int l) + { + assert(length >= 0); // able to accept a length + assert((int)l >= 0); // no overflow + assert(rplimit == nullptr); // readData not yet called + length += l; + assert(length >= l); // no overflow + } + + void setIndex(cpindex *ix_); + void setIndexByTag(byte tag); + + // Parse the band and its meta-coding header. + void readData(int expectedLength = 0); + + // Reset the band for another pass (Cf. Java Band.resetForSecondPass.) + void rewind() + { + cm.reset(&vs[0]); + } + + byte *&curRP() + { + return vs[0].rp; + } + byte *minRP() + { + return cm.vs0.rp; + } + byte *maxRP() + { + return rplimit; + } + size_t size() + { + return maxRP() - minRP(); + } + + int getByte() + { + assert(ix == nullptr); + return vs[0].getByte(); + } + int getInt() + { + assert(ix == nullptr); + return vs[0].getInt(); + } + entry *getRefN() + { + assert(ix != nullptr); + return getRefCommon(ix, true); + } + entry *getRef() + { + assert(ix != nullptr); + return getRefCommon(ix, false); + } + entry *getRefUsing(cpindex *ix2) + { + assert(ix == nullptr); + return getRefCommon(ix2, true); + } + entry *getRefCommon(cpindex *ix, bool nullOK); + int64_t getLong(band &lo_band, bool have_hi); + + static int64_t makeLong(uint32_t hi, uint32_t lo) + { + return ((uint64_t)hi << 32) + (((uint64_t)lo << 32) >> 32); + } + + int getIntTotal(); + int getIntCount(int tag); + + static band *makeBands(unpacker *u); + static void initIndexes(unpacker *u); }; extern band all_bands[]; @@ -173,179 +173,179 @@ extern band all_bands[]; // Band schema: enum band_number { - // e_archive_magic, - // e_archive_header, - // e_band_headers, - - // constant pool contents - e_cp_Utf8_prefix, - e_cp_Utf8_suffix, - e_cp_Utf8_chars, - e_cp_Utf8_big_suffix, - e_cp_Utf8_big_chars, - e_cp_Int, - e_cp_Float, - e_cp_Long_hi, - e_cp_Long_lo, - e_cp_Double_hi, - e_cp_Double_lo, - e_cp_String, - e_cp_Class, - e_cp_Signature_form, - e_cp_Signature_classes, - e_cp_Descr_name, - e_cp_Descr_type, - e_cp_Field_class, - e_cp_Field_desc, - e_cp_Method_class, - e_cp_Method_desc, - e_cp_Imethod_class, - e_cp_Imethod_desc, - - // bands which define transmission of attributes - e_attr_definition_headers, - e_attr_definition_name, - e_attr_definition_layout, - - // band for hardwired InnerClasses attribute (shared across the package) - e_ic_this_class, - e_ic_flags, - // These bands contain data only where flags sets ACC_IC_LONG_FORM: - e_ic_outer_class, - e_ic_name, - - // bands for carrying class schema information: - e_class_this, - e_class_super, - e_class_interface_count, - e_class_interface, - - // bands for class members - e_class_field_count, - e_class_method_count, - e_field_descr, - e_field_flags_hi, - e_field_flags_lo, - e_field_attr_count, - e_field_attr_indexes, - e_field_attr_calls, - e_field_ConstantValue_KQ, - e_field_Signature_RS, - e_field_metadata_bands, - e_field_attr_bands, - e_method_descr, - e_method_flags_hi, - e_method_flags_lo, - e_method_attr_count, - e_method_attr_indexes, - e_method_attr_calls, - e_method_Exceptions_N, - e_method_Exceptions_RC, - e_method_Signature_RS, - e_method_metadata_bands, - e_method_attr_bands, - e_class_flags_hi, - e_class_flags_lo, - e_class_attr_count, - e_class_attr_indexes, - e_class_attr_calls, - e_class_SourceFile_RUN, - e_class_EnclosingMethod_RC, - e_class_EnclosingMethod_RDN, - e_class_Signature_RS, - e_class_metadata_bands, - e_class_InnerClasses_N, - e_class_InnerClasses_RC, - e_class_InnerClasses_F, - e_class_InnerClasses_outer_RCN, - e_class_InnerClasses_name_RUN, - e_class_ClassFile_version_minor_H, - e_class_ClassFile_version_major_H, - e_class_attr_bands, - e_code_headers, - e_code_max_stack, - e_code_max_na_locals, - e_code_handler_count, - e_code_handler_start_P, - e_code_handler_end_PO, - e_code_handler_catch_PO, - e_code_handler_class_RCN, - - // code attributes - e_code_flags_hi, - e_code_flags_lo, - e_code_attr_count, - e_code_attr_indexes, - e_code_attr_calls, - e_code_StackMapTable_N, - e_code_StackMapTable_frame_T, - e_code_StackMapTable_local_N, - e_code_StackMapTable_stack_N, - e_code_StackMapTable_offset, - e_code_StackMapTable_T, - e_code_StackMapTable_RC, - e_code_StackMapTable_P, - e_code_LineNumberTable_N, - e_code_LineNumberTable_bci_P, - e_code_LineNumberTable_line, - e_code_LocalVariableTable_N, - e_code_LocalVariableTable_bci_P, - e_code_LocalVariableTable_span_O, - e_code_LocalVariableTable_name_RU, - e_code_LocalVariableTable_type_RS, - e_code_LocalVariableTable_slot, - e_code_LocalVariableTypeTable_N, - e_code_LocalVariableTypeTable_bci_P, - e_code_LocalVariableTypeTable_span_O, - e_code_LocalVariableTypeTable_name_RU, - e_code_LocalVariableTypeTable_type_RS, - e_code_LocalVariableTypeTable_slot, - e_code_attr_bands, - - // bands for bytecodes - e_bc_codes, - // remaining bands provide typed opcode fields required by the bc_codes - e_bc_case_count, - e_bc_case_value, - e_bc_byte, - e_bc_short, - e_bc_local, - e_bc_label, - - // ldc* operands: - e_bc_intref, - e_bc_floatref, - e_bc_longref, - e_bc_doubleref, - e_bc_stringref, - e_bc_classref, - e_bc_fieldref, - e_bc_methodref, - e_bc_imethodref, - - // _self_linker_op family - e_bc_thisfield, - e_bc_superfield, - e_bc_thismethod, - e_bc_supermethod, - - // bc_invokeinit family: - e_bc_initref, - - // bytecode escape sequences - e_bc_escref, - e_bc_escrefsize, - e_bc_escsize, - e_bc_escbyte, - - // file attributes and contents - e_file_name, - e_file_size_hi, - e_file_size_lo, - e_file_modtime, - e_file_options, - // e_file_bits, // handled specially as an appendix - BAND_LIMIT + // e_archive_magic, + // e_archive_header, + // e_band_headers, + + // constant pool contents + e_cp_Utf8_prefix, + e_cp_Utf8_suffix, + e_cp_Utf8_chars, + e_cp_Utf8_big_suffix, + e_cp_Utf8_big_chars, + e_cp_Int, + e_cp_Float, + e_cp_Long_hi, + e_cp_Long_lo, + e_cp_Double_hi, + e_cp_Double_lo, + e_cp_String, + e_cp_Class, + e_cp_Signature_form, + e_cp_Signature_classes, + e_cp_Descr_name, + e_cp_Descr_type, + e_cp_Field_class, + e_cp_Field_desc, + e_cp_Method_class, + e_cp_Method_desc, + e_cp_Imethod_class, + e_cp_Imethod_desc, + + // bands which define transmission of attributes + e_attr_definition_headers, + e_attr_definition_name, + e_attr_definition_layout, + + // band for hardwired InnerClasses attribute (shared across the package) + e_ic_this_class, + e_ic_flags, + // These bands contain data only where flags sets ACC_IC_LONG_FORM: + e_ic_outer_class, + e_ic_name, + + // bands for carrying class schema information: + e_class_this, + e_class_super, + e_class_interface_count, + e_class_interface, + + // bands for class members + e_class_field_count, + e_class_method_count, + e_field_descr, + e_field_flags_hi, + e_field_flags_lo, + e_field_attr_count, + e_field_attr_indexes, + e_field_attr_calls, + e_field_ConstantValue_KQ, + e_field_Signature_RS, + e_field_metadata_bands, + e_field_attr_bands, + e_method_descr, + e_method_flags_hi, + e_method_flags_lo, + e_method_attr_count, + e_method_attr_indexes, + e_method_attr_calls, + e_method_Exceptions_N, + e_method_Exceptions_RC, + e_method_Signature_RS, + e_method_metadata_bands, + e_method_attr_bands, + e_class_flags_hi, + e_class_flags_lo, + e_class_attr_count, + e_class_attr_indexes, + e_class_attr_calls, + e_class_SourceFile_RUN, + e_class_EnclosingMethod_RC, + e_class_EnclosingMethod_RDN, + e_class_Signature_RS, + e_class_metadata_bands, + e_class_InnerClasses_N, + e_class_InnerClasses_RC, + e_class_InnerClasses_F, + e_class_InnerClasses_outer_RCN, + e_class_InnerClasses_name_RUN, + e_class_ClassFile_version_minor_H, + e_class_ClassFile_version_major_H, + e_class_attr_bands, + e_code_headers, + e_code_max_stack, + e_code_max_na_locals, + e_code_handler_count, + e_code_handler_start_P, + e_code_handler_end_PO, + e_code_handler_catch_PO, + e_code_handler_class_RCN, + + // code attributes + e_code_flags_hi, + e_code_flags_lo, + e_code_attr_count, + e_code_attr_indexes, + e_code_attr_calls, + e_code_StackMapTable_N, + e_code_StackMapTable_frame_T, + e_code_StackMapTable_local_N, + e_code_StackMapTable_stack_N, + e_code_StackMapTable_offset, + e_code_StackMapTable_T, + e_code_StackMapTable_RC, + e_code_StackMapTable_P, + e_code_LineNumberTable_N, + e_code_LineNumberTable_bci_P, + e_code_LineNumberTable_line, + e_code_LocalVariableTable_N, + e_code_LocalVariableTable_bci_P, + e_code_LocalVariableTable_span_O, + e_code_LocalVariableTable_name_RU, + e_code_LocalVariableTable_type_RS, + e_code_LocalVariableTable_slot, + e_code_LocalVariableTypeTable_N, + e_code_LocalVariableTypeTable_bci_P, + e_code_LocalVariableTypeTable_span_O, + e_code_LocalVariableTypeTable_name_RU, + e_code_LocalVariableTypeTable_type_RS, + e_code_LocalVariableTypeTable_slot, + e_code_attr_bands, + + // bands for bytecodes + e_bc_codes, + // remaining bands provide typed opcode fields required by the bc_codes + e_bc_case_count, + e_bc_case_value, + e_bc_byte, + e_bc_short, + e_bc_local, + e_bc_label, + + // ldc* operands: + e_bc_intref, + e_bc_floatref, + e_bc_longref, + e_bc_doubleref, + e_bc_stringref, + e_bc_classref, + e_bc_fieldref, + e_bc_methodref, + e_bc_imethodref, + + // _self_linker_op family + e_bc_thisfield, + e_bc_superfield, + e_bc_thismethod, + e_bc_supermethod, + + // bc_invokeinit family: + e_bc_initref, + + // bytecode escape sequences + e_bc_escref, + e_bc_escrefsize, + e_bc_escsize, + e_bc_escbyte, + + // file attributes and contents + e_file_name, + e_file_size_hi, + e_file_size_lo, + e_file_modtime, + e_file_options, + // e_file_bits, // handled specially as an appendix + BAND_LIMIT }; // Symbolic names for bands, as if in a giant global struct: diff --git a/libraries/pack200/src/bytes.cpp b/libraries/pack200/src/bytes.cpp index d3808afa..767fe0a5 100644 --- a/libraries/pack200/src/bytes.cpp +++ b/libraries/pack200/src/bytes.cpp @@ -36,182 +36,182 @@ static byte dummy[1 << 10]; bool bytes::inBounds(const void *p) { - return p >= ptr && p < limit(); + return p >= ptr && p < limit(); } void bytes::malloc(size_t len_) { - len = len_; - ptr = NEW(byte, add_size(len_, 1)); // add trailing zero byte always - if (ptr == nullptr) - { - // set ptr to some victim memory, to ease escape - set(dummy, sizeof(dummy) - 1); - unpack_abort(ERROR_ENOMEM); - } + len = len_; + ptr = NEW(byte, add_size(len_, 1)); // add trailing zero byte always + if (ptr == nullptr) + { + // set ptr to some victim memory, to ease escape + set(dummy, sizeof(dummy) - 1); + unpack_abort(ERROR_ENOMEM); + } } void bytes::realloc(size_t len_) { - if (len == len_) - return; // nothing to do - if (ptr == dummy) - return; // escaping from an error - if (ptr == nullptr) - { - malloc(len_); - return; - } - byte *oldptr = ptr; - ptr = (len_ >= PSIZE_MAX) ? nullptr : (byte *)::realloc(ptr, add_size(len_, 1)); - if (ptr != nullptr) - { - if (len < len_) - memset(ptr + len, 0, len_ - len); - ptr[len_] = 0; - len = len_; - } - else - { - ptr = oldptr; // ease our escape - unpack_abort(ERROR_ENOMEM); - } + if (len == len_) + return; // nothing to do + if (ptr == dummy) + return; // escaping from an error + if (ptr == nullptr) + { + malloc(len_); + return; + } + byte *oldptr = ptr; + ptr = (len_ >= PSIZE_MAX) ? nullptr : (byte *)::realloc(ptr, add_size(len_, 1)); + if (ptr != nullptr) + { + if (len < len_) + memset(ptr + len, 0, len_ - len); + ptr[len_] = 0; + len = len_; + } + else + { + ptr = oldptr; // ease our escape + unpack_abort(ERROR_ENOMEM); + } } void bytes::free() { - if (ptr == dummy) - return; // escaping from an error - if (ptr != nullptr) - { - ::free(ptr); - } - len = 0; - ptr = 0; + if (ptr == dummy) + return; // escaping from an error + if (ptr != nullptr) + { + ::free(ptr); + } + len = 0; + ptr = 0; } int bytes::indexOf(byte c) { - byte *p = (byte *)memchr(ptr, c, len); - return (p == 0) ? -1 : (int)(p - ptr); + byte *p = (byte *)memchr(ptr, c, len); + return (p == 0) ? -1 : (int)(p - ptr); } byte *bytes::writeTo(byte *bp) { - memcpy(bp, ptr, len); - return bp + len; + memcpy(bp, ptr, len); + return bp + len; } int bytes::compareTo(bytes &other) { - size_t l1 = len; - size_t l2 = other.len; - int cmp = memcmp(ptr, other.ptr, (l1 < l2) ? l1 : l2); - if (cmp != 0) - return cmp; - return (l1 < l2) ? -1 : (l1 > l2) ? 1 : 0; + size_t l1 = len; + size_t l2 = other.len; + int cmp = memcmp(ptr, other.ptr, (l1 < l2) ? l1 : l2); + if (cmp != 0) + return cmp; + return (l1 < l2) ? -1 : (l1 > l2) ? 1 : 0; } void bytes::saveFrom(const void *ptr_, size_t len_) { - malloc(len_); - // Save as much as possible. - if (len_ > len) - { - assert(ptr == dummy); // error recovery - len_ = len; - } - copyFrom(ptr_, len_); + malloc(len_); + // Save as much as possible. + if (len_ > len) + { + assert(ptr == dummy); // error recovery + len_ = len; + } + copyFrom(ptr_, len_); } //#TODO: Need to fix for exception handling void bytes::copyFrom(const void *ptr_, size_t len_, size_t offset) { - assert(len_ == 0 || inBounds(ptr + offset)); - assert(len_ == 0 || inBounds(ptr + offset + len_ - 1)); - memcpy(ptr + offset, ptr_, len_); + assert(len_ == 0 || inBounds(ptr + offset)); + assert(len_ == 0 || inBounds(ptr + offset + len_ - 1)); + memcpy(ptr + offset, ptr_, len_); } // Make sure there are 'o' bytes beyond the fill pointer, // advance the fill pointer, and return the old fill pointer. byte *fillbytes::grow(size_t s) { - size_t nlen = add_size(b.len, s); - if (nlen <= allocated) - { - b.len = nlen; - return limit() - s; - } - size_t maxlen = nlen; - if (maxlen < 128) - maxlen = 128; - if (maxlen < allocated * 2) - maxlen = allocated * 2; - if (allocated == 0) - { - // Initial buffer was not malloced. Do not reallocate it. - bytes old = b; - b.malloc(maxlen); - if (b.len == maxlen) - old.writeTo(b.ptr); - } - else - { - b.realloc(maxlen); - } - allocated = b.len; - if (allocated != maxlen) - { - b.len = nlen - s; // back up - return dummy; // scribble during error recov. - } - // after realloc, recompute pointers - b.len = nlen; - assert(b.len <= allocated); - return limit() - s; + size_t nlen = add_size(b.len, s); + if (nlen <= allocated) + { + b.len = nlen; + return limit() - s; + } + size_t maxlen = nlen; + if (maxlen < 128) + maxlen = 128; + if (maxlen < allocated * 2) + maxlen = allocated * 2; + if (allocated == 0) + { + // Initial buffer was not malloced. Do not reallocate it. + bytes old = b; + b.malloc(maxlen); + if (b.len == maxlen) + old.writeTo(b.ptr); + } + else + { + b.realloc(maxlen); + } + allocated = b.len; + if (allocated != maxlen) + { + b.len = nlen - s; // back up + return dummy; // scribble during error recov. + } + // after realloc, recompute pointers + b.len = nlen; + assert(b.len <= allocated); + return limit() - s; } void fillbytes::ensureSize(size_t s) { - if (allocated >= s) - return; - size_t len0 = b.len; - grow(s - size()); - b.len = len0; // put it back + if (allocated >= s) + return; + size_t len0 = b.len; + grow(s - size()); + b.len = len0; // put it back } int ptrlist::indexOf(const void *x) { - int len = length(); - for (int i = 0; i < len; i++) - { - if (get(i) == x) - return i; - } - return -1; + int len = length(); + for (int i = 0; i < len; i++) + { + if (get(i) == x) + return i; + } + return -1; } void ptrlist::freeAll() { - int len = length(); - for (int i = 0; i < len; i++) - { - void *p = (void *)get(i); - if (p != nullptr) - { - ::free(p); - } - } - free(); + int len = length(); + for (int i = 0; i < len; i++) + { + void *p = (void *)get(i); + if (p != nullptr) + { + ::free(p); + } + } + free(); } int intlist::indexOf(int x) { - int len = length(); - for (int i = 0; i < len; i++) - { - if (get(i) == x) - return i; - } - return -1; + int len = length(); + for (int i = 0; i < len; i++) + { + if (get(i) == x) + return i; + } + return -1; } diff --git a/libraries/pack200/src/bytes.h b/libraries/pack200/src/bytes.h index b116efda..2ce1f7f4 100644 --- a/libraries/pack200/src/bytes.h +++ b/libraries/pack200/src/bytes.h @@ -27,225 +27,225 @@ struct bytes { - int8_t *ptr; - size_t len; - int8_t *limit() - { - return ptr + len; - } + int8_t *ptr; + size_t len; + int8_t *limit() + { + return ptr + len; + } - void set(int8_t *ptr_, size_t len_) - { - ptr = ptr_; - len = len_; - } - void set(const char *str) - { - ptr = (int8_t *)str; - len = strlen(str); - } - bool inBounds(const void *p); // p in [ptr, limit) - void malloc(size_t len_); - void realloc(size_t len_); - void free(); - void copyFrom(const void *ptr_, size_t len_, size_t offset = 0); - void saveFrom(const void *ptr_, size_t len_); - void saveFrom(const char *str) - { - saveFrom(str, strlen(str)); - } - void copyFrom(bytes &other, size_t offset = 0) - { - copyFrom(other.ptr, other.len, offset); - } - void saveFrom(bytes &other) - { - saveFrom(other.ptr, other.len); - } - void clear(int fill_byte = 0) - { - memset(ptr, fill_byte, len); - } - int8_t *writeTo(int8_t *bp); - bool equals(bytes &other) - { - return 0 == compareTo(other); - } - int compareTo(bytes &other); - bool contains(int8_t c) - { - return indexOf(c) >= 0; - } - int indexOf(int8_t c); - // substrings: - static bytes of(int8_t *ptr, size_t len) - { - bytes res; - res.set(ptr, len); - return res; - } - bytes slice(size_t beg, size_t end) - { - bytes res; - res.ptr = ptr + beg; - res.len = end - beg; - assert(res.len == 0 ||(inBounds(res.ptr) && inBounds(res.limit() - 1))); - return res; - } - // building C strings inside byte buffers: - bytes &strcat(const char *str) - { - ::strcat((char *)ptr, str); - return *this; - } - bytes &strcat(bytes &other) - { - ::strncat((char *)ptr, (char *)other.ptr, other.len); - return *this; - } - char *strval() - { - assert(strlen((char *)ptr) == len); - return (char *)ptr; - } + void set(int8_t *ptr_, size_t len_) + { + ptr = ptr_; + len = len_; + } + void set(const char *str) + { + ptr = (int8_t *)str; + len = strlen(str); + } + bool inBounds(const void *p); // p in [ptr, limit) + void malloc(size_t len_); + void realloc(size_t len_); + void free(); + void copyFrom(const void *ptr_, size_t len_, size_t offset = 0); + void saveFrom(const void *ptr_, size_t len_); + void saveFrom(const char *str) + { + saveFrom(str, strlen(str)); + } + void copyFrom(bytes &other, size_t offset = 0) + { + copyFrom(other.ptr, other.len, offset); + } + void saveFrom(bytes &other) + { + saveFrom(other.ptr, other.len); + } + void clear(int fill_byte = 0) + { + memset(ptr, fill_byte, len); + } + int8_t *writeTo(int8_t *bp); + bool equals(bytes &other) + { + return 0 == compareTo(other); + } + int compareTo(bytes &other); + bool contains(int8_t c) + { + return indexOf(c) >= 0; + } + int indexOf(int8_t c); + // substrings: + static bytes of(int8_t *ptr, size_t len) + { + bytes res; + res.set(ptr, len); + return res; + } + bytes slice(size_t beg, size_t end) + { + bytes res; + res.ptr = ptr + beg; + res.len = end - beg; + assert(res.len == 0 ||(inBounds(res.ptr) && inBounds(res.limit() - 1))); + return res; + } + // building C strings inside byte buffers: + bytes &strcat(const char *str) + { + ::strcat((char *)ptr, str); + return *this; + } + bytes &strcat(bytes &other) + { + ::strncat((char *)ptr, (char *)other.ptr, other.len); + return *this; + } + char *strval() + { + assert(strlen((char *)ptr) == len); + return (char *)ptr; + } }; #define BYTES_OF(var) (bytes::of((int8_t *)&(var), sizeof(var))) struct fillbytes { - bytes b; - size_t allocated; + bytes b; + size_t allocated; - int8_t *base() - { - return b.ptr; - } - size_t size() - { - return b.len; - } - int8_t *limit() - { - return b.limit(); - } // logical limit - void setLimit(int8_t *lp) - { - assert(isAllocated(lp)); - b.len = lp - b.ptr; - } - int8_t *end() - { - return b.ptr + allocated; - } // physical limit - int8_t *loc(size_t o) - { - assert(o < b.len); - return b.ptr + o; - } - void init() - { - allocated = 0; - b.set(nullptr, 0); - } - void init(size_t s) - { - init(); - ensureSize(s); - } - void free() - { - if (allocated != 0) - b.free(); - allocated = 0; - } - void empty() - { - b.len = 0; - } - int8_t *grow(size_t s); // grow so that limit() += s - int getByte(uint32_t i) - { - return *loc(i) & 0xFF; - } - void addByte(int8_t x) - { - *grow(1) = x; - } - void ensureSize(size_t s); // make sure allocated >= s - void trimToSize() - { - if (allocated > size()) - b.realloc(allocated = size()); - } - bool canAppend(size_t s) - { - return allocated > b.len + s; - } - bool isAllocated(int8_t *p) - { - return p >= base() && p <= end(); - } // asserts - void set(bytes &src) - { - set(src.ptr, src.len); - } + int8_t *base() + { + return b.ptr; + } + size_t size() + { + return b.len; + } + int8_t *limit() + { + return b.limit(); + } // logical limit + void setLimit(int8_t *lp) + { + assert(isAllocated(lp)); + b.len = lp - b.ptr; + } + int8_t *end() + { + return b.ptr + allocated; + } // physical limit + int8_t *loc(size_t o) + { + assert(o < b.len); + return b.ptr + o; + } + void init() + { + allocated = 0; + b.set(nullptr, 0); + } + void init(size_t s) + { + init(); + ensureSize(s); + } + void free() + { + if (allocated != 0) + b.free(); + allocated = 0; + } + void empty() + { + b.len = 0; + } + int8_t *grow(size_t s); // grow so that limit() += s + int getByte(uint32_t i) + { + return *loc(i) & 0xFF; + } + void addByte(int8_t x) + { + *grow(1) = x; + } + void ensureSize(size_t s); // make sure allocated >= s + void trimToSize() + { + if (allocated > size()) + b.realloc(allocated = size()); + } + bool canAppend(size_t s) + { + return allocated > b.len + s; + } + bool isAllocated(int8_t *p) + { + return p >= base() && p <= end(); + } // asserts + void set(bytes &src) + { + set(src.ptr, src.len); + } - void set(int8_t *ptr, size_t len) - { - b.set(ptr, len); - allocated = 0; // mark as not reallocatable - } + void set(int8_t *ptr, size_t len) + { + b.set(ptr, len); + allocated = 0; // mark as not reallocatable + } - // block operations on resizing byte buffer: - fillbytes &append(const void *ptr_, size_t len_) - { - memcpy(grow(len_), ptr_, len_); - return (*this); - } - fillbytes &append(bytes &other) - { - return append(other.ptr, other.len); - } - fillbytes &append(const char *str) - { - return append(str, strlen(str)); - } + // block operations on resizing byte buffer: + fillbytes &append(const void *ptr_, size_t len_) + { + memcpy(grow(len_), ptr_, len_); + return (*this); + } + fillbytes &append(bytes &other) + { + return append(other.ptr, other.len); + } + fillbytes &append(const char *str) + { + return append(str, strlen(str)); + } }; struct ptrlist : fillbytes { - typedef const void *cvptr; - int length() - { - return (int)(size() / sizeof(cvptr)); - } - cvptr *base() - { - return (cvptr *)fillbytes::base(); - } - cvptr &get(int i) - { - return *(cvptr *)loc(i * sizeof(cvptr)); - } - cvptr *limit() - { - return (cvptr *)fillbytes::limit(); - } - void add(cvptr x) - { - *(cvptr *)grow(sizeof(x)) = x; - } - void popTo(int l) - { - assert(l <= length()); - b.len = l * sizeof(cvptr); - } - int indexOf(cvptr x); - bool contains(cvptr x) - { - return indexOf(x) >= 0; - } - void freeAll(); // frees every ptr on the list, plus the list itself + typedef const void *cvptr; + int length() + { + return (int)(size() / sizeof(cvptr)); + } + cvptr *base() + { + return (cvptr *)fillbytes::base(); + } + cvptr &get(int i) + { + return *(cvptr *)loc(i * sizeof(cvptr)); + } + cvptr *limit() + { + return (cvptr *)fillbytes::limit(); + } + void add(cvptr x) + { + *(cvptr *)grow(sizeof(x)) = x; + } + void popTo(int l) + { + assert(l <= length()); + b.len = l * sizeof(cvptr); + } + int indexOf(cvptr x); + bool contains(cvptr x) + { + return indexOf(x) >= 0; + } + void freeAll(); // frees every ptr on the list, plus the list itself }; // Use a macro rather than mess with subtle mismatches // between member and non-member function pointers. @@ -253,34 +253,34 @@ struct ptrlist : fillbytes struct intlist : fillbytes { - int length() - { - return (int)(size() / sizeof(int)); - } - int *base() - { - return (int *)fillbytes::base(); - } - int &get(int i) - { - return *(int *)loc(i * sizeof(int)); - } - int *limit() - { - return (int *)fillbytes::limit(); - } - void add(int x) - { - *(int *)grow(sizeof(x)) = x; - } - void popTo(int l) - { - assert(l <= length()); - b.len = l * sizeof(int); - } - int indexOf(int x); - bool contains(int x) - { - return indexOf(x) >= 0; - } + int length() + { + return (int)(size() / sizeof(int)); + } + int *base() + { + return (int *)fillbytes::base(); + } + int &get(int i) + { + return *(int *)loc(i * sizeof(int)); + } + int *limit() + { + return (int *)fillbytes::limit(); + } + void add(int x) + { + *(int *)grow(sizeof(x)) = x; + } + void popTo(int l) + { + assert(l <= length()); + b.len = l * sizeof(int); + } + int indexOf(int x); + bool contains(int x) + { + return indexOf(x) >= 0; + } }; diff --git a/libraries/pack200/src/coding.cpp b/libraries/pack200/src/coding.cpp index 6bd17a3c..8e872013 100644 --- a/libraries/pack200/src/coding.cpp +++ b/libraries/pack200/src/coding.cpp @@ -48,12 +48,12 @@ extern coding basic_codings[]; #pragma GCC diagnostic ignored "-Wunused-variable" #define CODING_PRIVATE(spec) \ - int spec_ = spec; \ - int B = CODING_B(spec_); \ - int H = CODING_H(spec_); \ - int L = 256 - H; \ - int S = CODING_S(spec_); \ - int D = CODING_D(spec_) + int spec_ = spec; \ + int B = CODING_B(spec_); \ + int H = CODING_H(spec_); \ + int L = 256 - H; \ + int S = CODING_S(spec_); \ + int D = CODING_D(spec_) #define IS_NEG_CODE(S, codeVal) ((((int)(codeVal) + 1) & ((1 << S) - 1)) == 0) @@ -61,568 +61,568 @@ extern coding basic_codings[]; static int decode_sign(int S, uint32_t ux) { // == Coding.decodeSign32 - assert(S > 0); - uint32_t sigbits = (ux >> S); - if (IS_NEG_CODE(S, ux)) - return (int)(~sigbits); - else - return (int)(ux - sigbits); - // Note that (int)(ux-sigbits) can be negative, if ux is large enough. + assert(S > 0); + uint32_t sigbits = (ux >> S); + if (IS_NEG_CODE(S, ux)) + return (int)(~sigbits); + else + return (int)(ux - sigbits); + // Note that (int)(ux-sigbits) can be negative, if ux is large enough. } coding *coding::init() { - if (umax > 0) - return this; // already done - assert(spec != 0); // sanity - - // fill in derived fields - CODING_PRIVATE(spec); - - // Return nullptr if 'arb(BHSD)' parameter constraints are not met: - if (B < 1 || B > B_MAX) - return nullptr; - if (H < 1 || H > 256) - return nullptr; - if (S < 0 || S > 2) - return nullptr; - if (D < 0 || D > 1) - return nullptr; - if (B == 1 && H != 256) - return nullptr; // 1-byte coding must be fixed-size - if (B >= 5 && H == 256) - return nullptr; // no 5-byte fixed-size coding - - // first compute the range of the coding, in 64 bits - int64_t range = 0; - { - int64_t H_i = 1; - for (int i = 0; i < B; i++) - { - range += H_i; - H_i *= H; - } - range *= L; - range += H_i; - } - assert(range > 0); // no useless codings, please - - int this_umax; - - // now, compute min and max - if (range >= ((int64_t)1 << 32)) - { - this_umax = INT_MAX_VALUE; - this->umin = INT_MIN_VALUE; - this->max = INT_MAX_VALUE; - this->min = INT_MIN_VALUE; - } - else - { - this_umax = (range > INT_MAX_VALUE) ? INT_MAX_VALUE : (int)range - 1; - this->max = this_umax; - this->min = this->umin = 0; - if (S != 0 && range != 0) - { - int64_t maxPosCode = range - 1; - int64_t maxNegCode = range - 1; - while (IS_NEG_CODE(S, maxPosCode)) - --maxPosCode; - while (!IS_NEG_CODE(S, maxNegCode)) - --maxNegCode; - int maxPos = decode_sign(S, (uint32_t)maxPosCode); - if (maxPos < 0) - this->max = INT_MAX_VALUE; // 32-bit wraparound - else - this->max = maxPos; - if (maxNegCode < 0) - this->min = 0; // No negative codings at all. - else - this->min = decode_sign(S, (uint32_t)maxNegCode); - } - } - - assert(!(isFullRange | isSigned | isSubrange)); // init - if (min < 0) - this->isSigned = true; - if (max < INT_MAX_VALUE && range <= INT_MAX_VALUE) - this->isSubrange = true; - if (max == INT_MAX_VALUE && min == INT_MIN_VALUE) - this->isFullRange = true; - - // do this last, to reduce MT exposure (should have a membar too) - this->umax = this_umax; - - return this; + if (umax > 0) + return this; // already done + assert(spec != 0); // sanity + + // fill in derived fields + CODING_PRIVATE(spec); + + // Return nullptr if 'arb(BHSD)' parameter constraints are not met: + if (B < 1 || B > B_MAX) + return nullptr; + if (H < 1 || H > 256) + return nullptr; + if (S < 0 || S > 2) + return nullptr; + if (D < 0 || D > 1) + return nullptr; + if (B == 1 && H != 256) + return nullptr; // 1-byte coding must be fixed-size + if (B >= 5 && H == 256) + return nullptr; // no 5-byte fixed-size coding + + // first compute the range of the coding, in 64 bits + int64_t range = 0; + { + int64_t H_i = 1; + for (int i = 0; i < B; i++) + { + range += H_i; + H_i *= H; + } + range *= L; + range += H_i; + } + assert(range > 0); // no useless codings, please + + int this_umax; + + // now, compute min and max + if (range >= ((int64_t)1 << 32)) + { + this_umax = INT_MAX_VALUE; + this->umin = INT_MIN_VALUE; + this->max = INT_MAX_VALUE; + this->min = INT_MIN_VALUE; + } + else + { + this_umax = (range > INT_MAX_VALUE) ? INT_MAX_VALUE : (int)range - 1; + this->max = this_umax; + this->min = this->umin = 0; + if (S != 0 && range != 0) + { + int64_t maxPosCode = range - 1; + int64_t maxNegCode = range - 1; + while (IS_NEG_CODE(S, maxPosCode)) + --maxPosCode; + while (!IS_NEG_CODE(S, maxNegCode)) + --maxNegCode; + int maxPos = decode_sign(S, (uint32_t)maxPosCode); + if (maxPos < 0) + this->max = INT_MAX_VALUE; // 32-bit wraparound + else + this->max = maxPos; + if (maxNegCode < 0) + this->min = 0; // No negative codings at all. + else + this->min = decode_sign(S, (uint32_t)maxNegCode); + } + } + + assert(!(isFullRange | isSigned | isSubrange)); // init + if (min < 0) + this->isSigned = true; + if (max < INT_MAX_VALUE && range <= INT_MAX_VALUE) + this->isSubrange = true; + if (max == INT_MAX_VALUE && min == INT_MIN_VALUE) + this->isFullRange = true; + + // do this last, to reduce MT exposure (should have a membar too) + this->umax = this_umax; + + return this; } coding *coding::findBySpec(int spec) { - for (coding *scan = &basic_codings[0];; scan++) - { - if (scan->spec == spec) - return scan->init(); - if (scan->spec == 0) - break; - } - coding *ptr = NEW(coding, 1); - if (!ptr) - return nullptr; - coding *c = ptr->initFrom(spec); - if (c == nullptr) - { - ::free(ptr); - } - else - // else caller should free it... - c->isMalloc = true; - return c; + for (coding *scan = &basic_codings[0];; scan++) + { + if (scan->spec == spec) + return scan->init(); + if (scan->spec == 0) + break; + } + coding *ptr = NEW(coding, 1); + if (!ptr) + return nullptr; + coding *c = ptr->initFrom(spec); + if (c == nullptr) + { + ::free(ptr); + } + else + // else caller should free it... + c->isMalloc = true; + return c; } coding *coding::findBySpec(int B, int H, int S, int D) { - if (B < 1 || B > B_MAX) - return nullptr; - if (H < 1 || H > 256) - return nullptr; - if (S < 0 || S > 2) - return nullptr; - if (D < 0 || D > 1) - return nullptr; - return findBySpec(CODING_SPEC(B, H, S, D)); + if (B < 1 || B > B_MAX) + return nullptr; + if (H < 1 || H > 256) + return nullptr; + if (S < 0 || S > 2) + return nullptr; + if (D < 0 || D > 1) + return nullptr; + return findBySpec(CODING_SPEC(B, H, S, D)); } void coding::free() { - if (isMalloc) - { - ::free(this); - } + if (isMalloc) + { + ::free(this); + } } void coding_method::reset(value_stream *state) { - assert(state->rp == state->rplimit); // not in mid-stream, please - // assert(this == vs0.cm); - state[0] = vs0; - if (uValues != nullptr) - { - uValues->reset(state->helper()); - } + assert(state->rp == state->rplimit); // not in mid-stream, please + // assert(this == vs0.cm); + state[0] = vs0; + if (uValues != nullptr) + { + uValues->reset(state->helper()); + } } uint32_t coding::parse(byte *&rp, int B, int H) { - int L = 256 - H; - byte *ptr = rp; - // hand peel the i==0 part of the loop: - uint32_t b_i = *ptr++ & 0xFF; - if (B == 1 || b_i < (uint32_t)L) - { - rp = ptr; - return b_i; - } - uint32_t sum = b_i; - uint32_t H_i = H; - assert(B <= B_MAX); - for (int i = 2; i <= B_MAX; i++) - { // easy for compilers to unroll if desired - b_i = *ptr++ & 0xFF; - sum += b_i * H_i; - if (i == B || b_i < (uint32_t)L) - { - rp = ptr; - return sum; - } - H_i *= H; - } - assert(false); - return 0; + int L = 256 - H; + byte *ptr = rp; + // hand peel the i==0 part of the loop: + uint32_t b_i = *ptr++ & 0xFF; + if (B == 1 || b_i < (uint32_t)L) + { + rp = ptr; + return b_i; + } + uint32_t sum = b_i; + uint32_t H_i = H; + assert(B <= B_MAX); + for (int i = 2; i <= B_MAX; i++) + { // easy for compilers to unroll if desired + b_i = *ptr++ & 0xFF; + sum += b_i * H_i; + if (i == B || b_i < (uint32_t)L) + { + rp = ptr; + return sum; + } + H_i *= H; + } + assert(false); + return 0; } uint32_t coding::parse_lgH(byte *&rp, int B, int H, int lgH) { - assert(H == (1 << lgH)); - int L = 256 - (1 << lgH); - byte *ptr = rp; - // hand peel the i==0 part of the loop: - uint32_t b_i = *ptr++ & 0xFF; - if (B == 1 || b_i < (uint32_t)L) - { - rp = ptr; - return b_i; - } - uint32_t sum = b_i; - uint32_t lg_H_i = lgH; - assert(B <= B_MAX); - for (int i = 2; i <= B_MAX; i++) - { // easy for compilers to unroll if desired - b_i = *ptr++ & 0xFF; - sum += b_i << lg_H_i; - if (i == B || b_i < (uint32_t)L) - { - rp = ptr; - return sum; - } - lg_H_i += lgH; - } - assert(false); - return 0; + assert(H == (1 << lgH)); + int L = 256 - (1 << lgH); + byte *ptr = rp; + // hand peel the i==0 part of the loop: + uint32_t b_i = *ptr++ & 0xFF; + if (B == 1 || b_i < (uint32_t)L) + { + rp = ptr; + return b_i; + } + uint32_t sum = b_i; + uint32_t lg_H_i = lgH; + assert(B <= B_MAX); + for (int i = 2; i <= B_MAX; i++) + { // easy for compilers to unroll if desired + b_i = *ptr++ & 0xFF; + sum += b_i << lg_H_i; + if (i == B || b_i < (uint32_t)L) + { + rp = ptr; + return sum; + } + lg_H_i += lgH; + } + assert(false); + return 0; } static const char ERB[] = "EOF reading band"; void coding::parseMultiple(byte *&rp, int N, byte *limit, int B, int H) { - if (N < 0) - { - unpack_abort("bad value count"); - return; - } - byte *ptr = rp; - if (B == 1 || H == 256) - { - size_t len = (size_t)N * B; - if (len / B != (size_t)N || ptr + len > limit) - { - unpack_abort(ERB); - return; - } - rp = ptr + len; - return; - } - // Note: We assume rp has enough zero-padding. - int L = 256 - H; - int n = B; - while (N > 0) - { - ptr += 1; - if (--n == 0) - { - // end of encoding at B bytes, regardless of byte value - } - else - { - int b = (ptr[-1] & 0xFF); - if (b >= L) - { - // keep going, unless we find a byte < L - continue; - } - } - // found the last byte - N -= 1; - n = B; // reset length counter - // do an error check here - if (ptr > limit) - { - unpack_abort(ERB); - return; - } - } - rp = ptr; - return; + if (N < 0) + { + unpack_abort("bad value count"); + return; + } + byte *ptr = rp; + if (B == 1 || H == 256) + { + size_t len = (size_t)N * B; + if (len / B != (size_t)N || ptr + len > limit) + { + unpack_abort(ERB); + return; + } + rp = ptr + len; + return; + } + // Note: We assume rp has enough zero-padding. + int L = 256 - H; + int n = B; + while (N > 0) + { + ptr += 1; + if (--n == 0) + { + // end of encoding at B bytes, regardless of byte value + } + else + { + int b = (ptr[-1] & 0xFF); + if (b >= L) + { + // keep going, unless we find a byte < L + continue; + } + } + // found the last byte + N -= 1; + n = B; // reset length counter + // do an error check here + if (ptr > limit) + { + unpack_abort(ERB); + return; + } + } + rp = ptr; + return; } bool value_stream::hasHelper() { - // If my coding method is a pop-style method, - // then I need a second value stream to transmit - // unfavored values. - // This can be determined by examining fValues. - return cm->fValues != nullptr; + // If my coding method is a pop-style method, + // then I need a second value stream to transmit + // unfavored values. + // This can be determined by examining fValues. + return cm->fValues != nullptr; } void value_stream::init(byte *rp_, byte *rplimit_, coding *defc) { - rp = rp_; - rplimit = rplimit_; - sum = 0; - cm = nullptr; // no need in the simple case - setCoding(defc); + rp = rp_; + rplimit = rplimit_; + sum = 0; + cm = nullptr; // no need in the simple case + setCoding(defc); } void value_stream::setCoding(coding *defc) { - if (defc == nullptr) - { - unpack_abort("bad coding"); - defc = coding::findByIndex(_meta_canon_min); // random pick for recovery - } - - c = (*defc); - - // choose cmk - cmk = cmk_ERROR; - switch (c.spec) - { - case BYTE1_spec: - cmk = cmk_BYTE1; - break; - case CHAR3_spec: - cmk = cmk_CHAR3; - break; - case UNSIGNED5_spec: - cmk = cmk_UNSIGNED5; - break; - case DELTA5_spec: - cmk = cmk_DELTA5; - break; - case BCI5_spec: - cmk = cmk_BCI5; - break; - case BRANCH5_spec: - cmk = cmk_BRANCH5; - break; - default: - if (c.D() == 0) - { - switch (c.S()) - { - case 0: - cmk = cmk_BHS0; - break; - case 1: - cmk = cmk_BHS1; - break; - default: - cmk = cmk_BHS; - break; - } - } - else - { - if (c.S() == 1) - { - if (c.isFullRange) - cmk = cmk_BHS1D1full; - if (c.isSubrange) - cmk = cmk_BHS1D1sub; - } - if (cmk == cmk_ERROR) - cmk = cmk_BHSD1; - } - } + if (defc == nullptr) + { + unpack_abort("bad coding"); + defc = coding::findByIndex(_meta_canon_min); // random pick for recovery + } + + c = (*defc); + + // choose cmk + cmk = cmk_ERROR; + switch (c.spec) + { + case BYTE1_spec: + cmk = cmk_BYTE1; + break; + case CHAR3_spec: + cmk = cmk_CHAR3; + break; + case UNSIGNED5_spec: + cmk = cmk_UNSIGNED5; + break; + case DELTA5_spec: + cmk = cmk_DELTA5; + break; + case BCI5_spec: + cmk = cmk_BCI5; + break; + case BRANCH5_spec: + cmk = cmk_BRANCH5; + break; + default: + if (c.D() == 0) + { + switch (c.S()) + { + case 0: + cmk = cmk_BHS0; + break; + case 1: + cmk = cmk_BHS1; + break; + default: + cmk = cmk_BHS; + break; + } + } + else + { + if (c.S() == 1) + { + if (c.isFullRange) + cmk = cmk_BHS1D1full; + if (c.isSubrange) + cmk = cmk_BHS1D1sub; + } + if (cmk == cmk_ERROR) + cmk = cmk_BHSD1; + } + } } static int getPopValue(value_stream *self, uint32_t uval) { - if (uval > 0) - { - // note that the initial parse performed a range check - assert(uval <= (uint32_t)self->cm->fVlength); - return self->cm->fValues[uval - 1]; - } - else - { - // take an unfavored value - return self->helper()->getInt(); - } + if (uval > 0) + { + // note that the initial parse performed a range check + assert(uval <= (uint32_t)self->cm->fVlength); + return self->cm->fValues[uval - 1]; + } + else + { + // take an unfavored value + return self->helper()->getInt(); + } } int coding::sumInUnsignedRange(int x, int y) { - assert(isSubrange); - int range = (int)(umax + 1); - assert(range > 0); - x += y; - if (x != (int)((int64_t)(x - y) + (int64_t)y)) - { - // 32-bit overflow interferes with range reduction. - // Back off from the overflow by adding a multiple of range: - if (x < 0) - { - x -= range; - assert(x >= 0); - } - else - { - x += range; - assert(x < 0); - } - } - if (x < 0) - { - x += range; - if (x >= 0) - return x; - } - else if (x >= range) - { - x -= range; - if (x < range) - return x; - } - else - { - // in range - return x; - } - // do it the hard way - x %= range; - if (x < 0) - x += range; - return x; + assert(isSubrange); + int range = (int)(umax + 1); + assert(range > 0); + x += y; + if (x != (int)((int64_t)(x - y) + (int64_t)y)) + { + // 32-bit overflow interferes with range reduction. + // Back off from the overflow by adding a multiple of range: + if (x < 0) + { + x -= range; + assert(x >= 0); + } + else + { + x += range; + assert(x < 0); + } + } + if (x < 0) + { + x += range; + if (x >= 0) + return x; + } + else if (x >= range) + { + x -= range; + if (x < range) + return x; + } + else + { + // in range + return x; + } + // do it the hard way + x %= range; + if (x < 0) + x += range; + return x; } static int getDeltaValue(value_stream *self, uint32_t uval, bool isSubrange) { - assert((uint32_t)(self->c.isSubrange) == (uint32_t)isSubrange); - assert(self->c.isSubrange | self->c.isFullRange); - if (isSubrange) - return self->sum = self->c.sumInUnsignedRange(self->sum, (int)uval); - else - return self->sum += (int)uval; + assert((uint32_t)(self->c.isSubrange) == (uint32_t)isSubrange); + assert(self->c.isSubrange | self->c.isFullRange); + if (isSubrange) + return self->sum = self->c.sumInUnsignedRange(self->sum, (int)uval); + else + return self->sum += (int)uval; } bool value_stream::hasValue() { - if (rp < rplimit) - return true; - if (cm == nullptr) - return false; - if (cm->next == nullptr) - return false; - cm->next->reset(this); - return hasValue(); + if (rp < rplimit) + return true; + if (cm == nullptr) + return false; + if (cm->next == nullptr) + return false; + cm->next->reset(this); + return hasValue(); } int value_stream::getInt() { - if (rp >= rplimit) - { - // Advance to next coding segment. - if (rp > rplimit || cm == nullptr || cm->next == nullptr) - { - // Must perform this check and throw an exception on bad input. - unpack_abort(ERB); - return 0; - } - cm->next->reset(this); - return getInt(); - } - - CODING_PRIVATE(c.spec); - uint32_t uval; - enum - { - B5 = 5, - B3 = 3, - H128 = 128, - H64 = 64, - H4 = 4 - }; - switch (cmk) - { - case cmk_BHS: - assert(D == 0); - uval = coding::parse(rp, B, H); - if (S == 0) - return (int)uval; - return decode_sign(S, uval); - - case cmk_BHS0: - assert(S == 0 && D == 0); - uval = coding::parse(rp, B, H); - return (int)uval; - - case cmk_BHS1: - assert(S == 1 && D == 0); - uval = coding::parse(rp, B, H); - return DECODE_SIGN_S1(uval); - - case cmk_BYTE1: - assert(c.spec == BYTE1_spec); - assert(B == 1 && H == 256 && S == 0 && D == 0); - return *rp++ & 0xFF; - - case cmk_CHAR3: - assert(c.spec == CHAR3_spec); - assert(B == B3 && H == H128 && S == 0 && D == 0); - return coding::parse_lgH(rp, B3, H128, 7); - - case cmk_UNSIGNED5: - assert(c.spec == UNSIGNED5_spec); - assert(B == B5 && H == H64 && S == 0 && D == 0); - return coding::parse_lgH(rp, B5, H64, 6); - - case cmk_BHSD1: - assert(D == 1); - uval = coding::parse(rp, B, H); - if (S != 0) - uval = (uint32_t)decode_sign(S, uval); - return getDeltaValue(this, uval, (bool)c.isSubrange); - - case cmk_BHS1D1full: - assert(S == 1 && D == 1 && c.isFullRange); - uval = coding::parse(rp, B, H); - uval = (uint32_t)DECODE_SIGN_S1(uval); - return getDeltaValue(this, uval, false); - - case cmk_BHS1D1sub: - assert(S == 1 && D == 1 && c.isSubrange); - uval = coding::parse(rp, B, H); - uval = (uint32_t)DECODE_SIGN_S1(uval); - return getDeltaValue(this, uval, true); - - case cmk_DELTA5: - assert(c.spec == DELTA5_spec); - assert(B == B5 && H == H64 && S == 1 && D == 1 && c.isFullRange); - uval = coding::parse_lgH(rp, B5, H64, 6); - sum += DECODE_SIGN_S1(uval); - return sum; - - case cmk_BCI5: - assert(c.spec == BCI5_spec); - assert(B == B5 && H == H4 && S == 0 && D == 0); - return coding::parse_lgH(rp, B5, H4, 2); - - case cmk_BRANCH5: - assert(c.spec == BRANCH5_spec); - assert(B == B5 && H == H4 && S == 2 && D == 0); - uval = coding::parse_lgH(rp, B5, H4, 2); - return decode_sign(S, uval); - - case cmk_pop: - uval = coding::parse(rp, B, H); - if (S != 0) - { - uval = (uint32_t)decode_sign(S, uval); - } - if (D != 0) - { - assert(c.isSubrange | c.isFullRange); - if (c.isSubrange) - sum = c.sumInUnsignedRange(sum, (int)uval); - else - sum += (int)uval; - uval = (uint32_t)sum; - } - return getPopValue(this, uval); - - case cmk_pop_BHS0: - assert(S == 0 && D == 0); - uval = coding::parse(rp, B, H); - return getPopValue(this, uval); - - case cmk_pop_BYTE1: - assert(c.spec == BYTE1_spec); - assert(B == 1 && H == 256 && S == 0 && D == 0); - return getPopValue(this, *rp++ & 0xFF); - - default: - break; - } - assert(false); - return 0; + if (rp >= rplimit) + { + // Advance to next coding segment. + if (rp > rplimit || cm == nullptr || cm->next == nullptr) + { + // Must perform this check and throw an exception on bad input. + unpack_abort(ERB); + return 0; + } + cm->next->reset(this); + return getInt(); + } + + CODING_PRIVATE(c.spec); + uint32_t uval; + enum + { + B5 = 5, + B3 = 3, + H128 = 128, + H64 = 64, + H4 = 4 + }; + switch (cmk) + { + case cmk_BHS: + assert(D == 0); + uval = coding::parse(rp, B, H); + if (S == 0) + return (int)uval; + return decode_sign(S, uval); + + case cmk_BHS0: + assert(S == 0 && D == 0); + uval = coding::parse(rp, B, H); + return (int)uval; + + case cmk_BHS1: + assert(S == 1 && D == 0); + uval = coding::parse(rp, B, H); + return DECODE_SIGN_S1(uval); + + case cmk_BYTE1: + assert(c.spec == BYTE1_spec); + assert(B == 1 && H == 256 && S == 0 && D == 0); + return *rp++ & 0xFF; + + case cmk_CHAR3: + assert(c.spec == CHAR3_spec); + assert(B == B3 && H == H128 && S == 0 && D == 0); + return coding::parse_lgH(rp, B3, H128, 7); + + case cmk_UNSIGNED5: + assert(c.spec == UNSIGNED5_spec); + assert(B == B5 && H == H64 && S == 0 && D == 0); + return coding::parse_lgH(rp, B5, H64, 6); + + case cmk_BHSD1: + assert(D == 1); + uval = coding::parse(rp, B, H); + if (S != 0) + uval = (uint32_t)decode_sign(S, uval); + return getDeltaValue(this, uval, (bool)c.isSubrange); + + case cmk_BHS1D1full: + assert(S == 1 && D == 1 && c.isFullRange); + uval = coding::parse(rp, B, H); + uval = (uint32_t)DECODE_SIGN_S1(uval); + return getDeltaValue(this, uval, false); + + case cmk_BHS1D1sub: + assert(S == 1 && D == 1 && c.isSubrange); + uval = coding::parse(rp, B, H); + uval = (uint32_t)DECODE_SIGN_S1(uval); + return getDeltaValue(this, uval, true); + + case cmk_DELTA5: + assert(c.spec == DELTA5_spec); + assert(B == B5 && H == H64 && S == 1 && D == 1 && c.isFullRange); + uval = coding::parse_lgH(rp, B5, H64, 6); + sum += DECODE_SIGN_S1(uval); + return sum; + + case cmk_BCI5: + assert(c.spec == BCI5_spec); + assert(B == B5 && H == H4 && S == 0 && D == 0); + return coding::parse_lgH(rp, B5, H4, 2); + + case cmk_BRANCH5: + assert(c.spec == BRANCH5_spec); + assert(B == B5 && H == H4 && S == 2 && D == 0); + uval = coding::parse_lgH(rp, B5, H4, 2); + return decode_sign(S, uval); + + case cmk_pop: + uval = coding::parse(rp, B, H); + if (S != 0) + { + uval = (uint32_t)decode_sign(S, uval); + } + if (D != 0) + { + assert(c.isSubrange | c.isFullRange); + if (c.isSubrange) + sum = c.sumInUnsignedRange(sum, (int)uval); + else + sum += (int)uval; + uval = (uint32_t)sum; + } + return getPopValue(this, uval); + + case cmk_pop_BHS0: + assert(S == 0 && D == 0); + uval = coding::parse(rp, B, H); + return getPopValue(this, uval); + + case cmk_pop_BYTE1: + assert(c.spec == BYTE1_spec); + assert(B == 1 && H == 256 && S == 0 && D == 0); + return getPopValue(this, *rp++ & 0xFF); + + default: + break; + } + assert(false); + return 0; } static int moreCentral(int x, int y) { // used to find end of Pop.{F} - // Suggested implementation from the Pack200 specification: - uint32_t kx = (x >> 31) ^ (x << 1); - uint32_t ky = (y >> 31) ^ (y << 1); - return (kx < ky ? x : y); + // Suggested implementation from the Pack200 specification: + uint32_t kx = (x >> 31) ^ (x << 1); + uint32_t ky = (y >> 31) ^ (y << 1); + return (kx < ky ? x : y); } // static maybe_inline // int moreCentral2(int x, int y, int min) { @@ -641,7 +641,7 @@ static const byte *no_meta[] = {nullptr}; #define NO_META (*(byte **)no_meta) enum { - POP_FAVORED_N = -2 + POP_FAVORED_N = -2 }; // mode bits @@ -650,395 +650,395 @@ enum // This function knows all about meta-coding. void coding_method::init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, - coding *defc, int N, intlist *valueSink) + coding *defc, int N, intlist *valueSink) { - assert(N != 0); - - assert(u != nullptr); // must be pre-initialized - // if (u == nullptr) u = unpacker::current(); // expensive - - int op = (meta_rp == nullptr) ? _meta_default : (*meta_rp++ & 0xFF); - coding *foundc = nullptr; - coding *to_free = nullptr; - - if (op == _meta_default) - { - foundc = defc; - // and fall through - } - else if (op >= _meta_canon_min && op <= _meta_canon_max) - { - foundc = coding::findByIndex(op); - // and fall through - } - else if (op == _meta_arb) - { - int args = (*meta_rp++ & 0xFF); - // args = (D:[0..1] + 2*S[0..2] + 8*(B:[1..5]-1)) - int D = ((args >> 0) & 1); - int S = ((args >> 1) & 3); - int B = ((args >> 3) & -1) + 1; - // & (H[1..256]-1) - int H = (*meta_rp++ & 0xFF) + 1; - foundc = coding::findBySpec(B, H, S, D); - to_free = foundc; // findBySpec may dynamically allocate - if (foundc == nullptr) - { - unpack_abort("illegal arbitrary coding"); - return; - } - // and fall through - } - else if (op >= _meta_run && op < _meta_pop) - { - int args = (op - _meta_run); - // args: KX:[0..3] + 4*(KBFlag:[0..1]) + 8*(ABDef:[0..2]) - int KX = ((args >> 0) & 3); - int KBFlag = ((args >> 2) & 1); - int ABDef = ((args >> 3) & -1); - assert(ABDef <= 2); - // & KB: one of [0..255] if KBFlag=1 - int KB = (!KBFlag ? 3 : (*meta_rp++ & 0xFF)); - int K = (KB + 1) << (KX * 4); - int N2 = (N >= 0) ? N - K : N; - if (N == 0 || (N2 <= 0 && N2 != N)) - { - unpack_abort("illegal run encoding"); - } - if ((mode & DISABLE_RUN) != 0) - { - unpack_abort("illegal nested run encoding"); - } - - // & Enc{ ACode } if ADef=0 (ABDef != 1) - // No direct nesting of 'run' in ACode, but in BCode it's OK. - int disRun = mode | DISABLE_RUN; - if (ABDef == 1) - { - this->init(band_rp, band_limit, NO_META, disRun, defc, K, valueSink); - } - else - { - this->init(band_rp, band_limit, meta_rp, disRun, defc, K, valueSink); - } - - // & Enc{ BCode } if BDef=0 (ABDef != 2) - coding_method *tail = U_NEW(coding_method, 1); - if (!tail) - return; - tail->u = u; - - // The 'run' codings may be nested indirectly via 'pop' codings. - // This means that this->next may already be filled in, if - // ACode was of type 'pop' with a 'run' token coding. - // No problem: Just chain the upcoming BCode onto the end. - for (coding_method *self = this;; self = self->next) - { - if (self->next == nullptr) - { - self->next = tail; - break; - } - } - - if (ABDef == 2) - { - tail->init(band_rp, band_limit, NO_META, mode, defc, N2, valueSink); - } - else - { - tail->init(band_rp, band_limit, meta_rp, mode, defc, N2, valueSink); - } - // Note: The preceding calls to init should be tail-recursive. - - return; // done; no falling through - } - else if (op >= _meta_pop && op < _meta_limit) - { - int args = (op - _meta_pop); - // args: (FDef:[0..1]) + 2*UDef:[0..1] + 4*(TDefL:[0..11]) - int FDef = ((args >> 0) & 1); - int UDef = ((args >> 1) & 1); - int TDefL = ((args >> 2) & -1); - assert(TDefL <= 11); - int TDef = (TDefL > 0); - int TL = (TDefL <= 6) ? (2 << TDefL) : (256 - (4 << (11 - TDefL))); - int TH = (256 - TL); - if (N <= 0) - { - unpack_abort("illegal pop encoding"); - } - if ((mode & DISABLE_POP) != 0) - { - unpack_abort("illegal nested pop encoding"); - } - - // No indirect nesting of 'pop', but 'run' is OK. - int disPop = DISABLE_POP; - - // & Enc{ FCode } if FDef=0 - int FN = POP_FAVORED_N; - assert(valueSink == nullptr); - intlist fValueSink; - fValueSink.init(); - coding_method fval; - BYTES_OF(fval).clear(); - fval.u = u; - if (FDef != 0) - { - fval.init(band_rp, band_limit, NO_META, disPop, defc, FN, &fValueSink); - } - else - { - fval.init(band_rp, band_limit, meta_rp, disPop, defc, FN, &fValueSink); - } - bytes fvbuf; - fValues = (u->saveTo(fvbuf, fValueSink.b), (int *)fvbuf.ptr); - fVlength = fValueSink.length(); // i.e., the parameter K - fValueSink.free(); - - // Skip the first {F} run in all subsequent passes. - // The next call to this->init(...) will set vs0.rp to point after the {F}. - - // & Enc{ TCode } if TDef=0 (TDefL==0) - if (TDef != 0) - { - coding *tcode = coding::findBySpec(1, 256); // BYTE1 - // find the most narrowly sufficient code: - for (int B = 2; B <= B_MAX; B++) - { - if (fVlength <= tcode->umax) - break; // found it - tcode->free(); - tcode = coding::findBySpec(B, TH); - if (!tcode) - return; - } - if (!(fVlength <= tcode->umax)) - { - unpack_abort("pop.L value too small"); - } - this->init(band_rp, band_limit, NO_META, disPop, tcode, N, nullptr); - tcode->free(); - } - else - { - this->init(band_rp, band_limit, meta_rp, disPop, defc, N, nullptr); - } - - // Count the number of zero tokens right now. - // Also verify that they are in bounds. - int UN = 0; // one {U} for each zero in {T} - value_stream vs = vs0; - for (int i = 0; i < N; i++) - { - uint32_t val = vs.getInt(); - if (val == 0) - UN += 1; - if (!(val <= (uint32_t)fVlength)) - { - unpack_abort("pop token out of range"); - } - } - vs.done(); - - // & Enc{ UCode } if UDef=0 - if (UN != 0) - { - uValues = U_NEW(coding_method, 1); - if (uValues == nullptr) - return; - uValues->u = u; - if (UDef != 0) - { - uValues->init(band_rp, band_limit, NO_META, disPop, defc, UN, nullptr); - } - else - { - uValues->init(band_rp, band_limit, meta_rp, disPop, defc, UN, nullptr); - } - } - else - { - if (UDef == 0) - { - int uop = (*meta_rp++ & 0xFF); - if (uop > _meta_canon_max) - // %%% Spec. requires the more strict (uop != _meta_default). - unpack_abort("bad meta-coding for empty pop/U"); - } - } - - // Bug fix for 6259542 - // Last of all, adjust vs0.cmk to the 'pop' flavor - for (coding_method *self = this; self != nullptr; self = self->next) - { - coding_method_kind cmk2 = cmk_pop; - switch (self->vs0.cmk) - { - case cmk_BHS0: - cmk2 = cmk_pop_BHS0; - break; - case cmk_BYTE1: - cmk2 = cmk_pop_BYTE1; - break; - default: - break; - } - self->vs0.cmk = cmk2; - if (self != this) - { - assert(self->fValues == nullptr); // no double init - self->fValues = this->fValues; - self->fVlength = this->fVlength; - assert(self->uValues == nullptr); // must stay nullptr - } - } - - return; // done; no falling through - } - else - { - unpack_abort("bad meta-coding"); - } - - // Common code here skips a series of values with one coding. - assert(foundc != nullptr); - - assert(vs0.cmk == cmk_ERROR); // no garbage, please - assert(vs0.rp == nullptr); // no garbage, please - assert(vs0.rplimit == nullptr); // no garbage, please - assert(vs0.sum == 0); // no garbage, please - - vs0.init(band_rp, band_limit, foundc); - - // Done with foundc. Free if necessary. - if (to_free != nullptr) - { - to_free->free(); - to_free = nullptr; - } - foundc = nullptr; - - coding &c = vs0.c; - CODING_PRIVATE(c.spec); - // assert sane N - assert((uint32_t)N < INT_MAX_VALUE || N == POP_FAVORED_N); - - // Look at the values, or at least skip over them quickly. - if (valueSink == nullptr) - { - // Skip and ignore values in the first pass. - c.parseMultiple(band_rp, N, band_limit, B, H); - } - else if (N >= 0) - { - // Pop coding, {F} sequence, initial run of values... - assert((mode & DISABLE_POP) != 0); - value_stream vs = vs0; - for (int n = 0; n < N; n++) - { - int val = vs.getInt(); - valueSink->add(val); - } - band_rp = vs.rp; - } - else - { - // Pop coding, {F} sequence, final run of values... - assert((mode & DISABLE_POP) != 0); - assert(N == POP_FAVORED_N); - int min = INT_MIN_VALUE; // farthest from the center - // min2 is based on the buggy specification of centrality in version 150.7 - // no known implementations transmit this value, but just in case... - // int min2 = INT_MIN_VALUE; - int last = 0; - // if there were initial runs, find the potential sentinels in them: - for (int i = 0; i < valueSink->length(); i++) - { - last = valueSink->get(i); - min = moreCentral(min, last); - // min2 = moreCentral2(min2, last, min); - } - value_stream vs = vs0; - for (;;) - { - int val = vs.getInt(); - if (valueSink->length() > 0 && (val == last || val == min)) //|| val == min2 - break; - valueSink->add(val); - last = val; - min = moreCentral(min, last); - // min2 = moreCentral2(min2, last, min); - } - band_rp = vs.rp; - } - - // Get an accurate upper limit now. - vs0.rplimit = band_rp; - vs0.cm = this; - - return; // success + assert(N != 0); + + assert(u != nullptr); // must be pre-initialized + // if (u == nullptr) u = unpacker::current(); // expensive + + int op = (meta_rp == nullptr) ? _meta_default : (*meta_rp++ & 0xFF); + coding *foundc = nullptr; + coding *to_free = nullptr; + + if (op == _meta_default) + { + foundc = defc; + // and fall through + } + else if (op >= _meta_canon_min && op <= _meta_canon_max) + { + foundc = coding::findByIndex(op); + // and fall through + } + else if (op == _meta_arb) + { + int args = (*meta_rp++ & 0xFF); + // args = (D:[0..1] + 2*S[0..2] + 8*(B:[1..5]-1)) + int D = ((args >> 0) & 1); + int S = ((args >> 1) & 3); + int B = ((args >> 3) & -1) + 1; + // & (H[1..256]-1) + int H = (*meta_rp++ & 0xFF) + 1; + foundc = coding::findBySpec(B, H, S, D); + to_free = foundc; // findBySpec may dynamically allocate + if (foundc == nullptr) + { + unpack_abort("illegal arbitrary coding"); + return; + } + // and fall through + } + else if (op >= _meta_run && op < _meta_pop) + { + int args = (op - _meta_run); + // args: KX:[0..3] + 4*(KBFlag:[0..1]) + 8*(ABDef:[0..2]) + int KX = ((args >> 0) & 3); + int KBFlag = ((args >> 2) & 1); + int ABDef = ((args >> 3) & -1); + assert(ABDef <= 2); + // & KB: one of [0..255] if KBFlag=1 + int KB = (!KBFlag ? 3 : (*meta_rp++ & 0xFF)); + int K = (KB + 1) << (KX * 4); + int N2 = (N >= 0) ? N - K : N; + if (N == 0 || (N2 <= 0 && N2 != N)) + { + unpack_abort("illegal run encoding"); + } + if ((mode & DISABLE_RUN) != 0) + { + unpack_abort("illegal nested run encoding"); + } + + // & Enc{ ACode } if ADef=0 (ABDef != 1) + // No direct nesting of 'run' in ACode, but in BCode it's OK. + int disRun = mode | DISABLE_RUN; + if (ABDef == 1) + { + this->init(band_rp, band_limit, NO_META, disRun, defc, K, valueSink); + } + else + { + this->init(band_rp, band_limit, meta_rp, disRun, defc, K, valueSink); + } + + // & Enc{ BCode } if BDef=0 (ABDef != 2) + coding_method *tail = U_NEW(coding_method, 1); + if (!tail) + return; + tail->u = u; + + // The 'run' codings may be nested indirectly via 'pop' codings. + // This means that this->next may already be filled in, if + // ACode was of type 'pop' with a 'run' token coding. + // No problem: Just chain the upcoming BCode onto the end. + for (coding_method *self = this;; self = self->next) + { + if (self->next == nullptr) + { + self->next = tail; + break; + } + } + + if (ABDef == 2) + { + tail->init(band_rp, band_limit, NO_META, mode, defc, N2, valueSink); + } + else + { + tail->init(band_rp, band_limit, meta_rp, mode, defc, N2, valueSink); + } + // Note: The preceding calls to init should be tail-recursive. + + return; // done; no falling through + } + else if (op >= _meta_pop && op < _meta_limit) + { + int args = (op - _meta_pop); + // args: (FDef:[0..1]) + 2*UDef:[0..1] + 4*(TDefL:[0..11]) + int FDef = ((args >> 0) & 1); + int UDef = ((args >> 1) & 1); + int TDefL = ((args >> 2) & -1); + assert(TDefL <= 11); + int TDef = (TDefL > 0); + int TL = (TDefL <= 6) ? (2 << TDefL) : (256 - (4 << (11 - TDefL))); + int TH = (256 - TL); + if (N <= 0) + { + unpack_abort("illegal pop encoding"); + } + if ((mode & DISABLE_POP) != 0) + { + unpack_abort("illegal nested pop encoding"); + } + + // No indirect nesting of 'pop', but 'run' is OK. + int disPop = DISABLE_POP; + + // & Enc{ FCode } if FDef=0 + int FN = POP_FAVORED_N; + assert(valueSink == nullptr); + intlist fValueSink; + fValueSink.init(); + coding_method fval; + BYTES_OF(fval).clear(); + fval.u = u; + if (FDef != 0) + { + fval.init(band_rp, band_limit, NO_META, disPop, defc, FN, &fValueSink); + } + else + { + fval.init(band_rp, band_limit, meta_rp, disPop, defc, FN, &fValueSink); + } + bytes fvbuf; + fValues = (u->saveTo(fvbuf, fValueSink.b), (int *)fvbuf.ptr); + fVlength = fValueSink.length(); // i.e., the parameter K + fValueSink.free(); + + // Skip the first {F} run in all subsequent passes. + // The next call to this->init(...) will set vs0.rp to point after the {F}. + + // & Enc{ TCode } if TDef=0 (TDefL==0) + if (TDef != 0) + { + coding *tcode = coding::findBySpec(1, 256); // BYTE1 + // find the most narrowly sufficient code: + for (int B = 2; B <= B_MAX; B++) + { + if (fVlength <= tcode->umax) + break; // found it + tcode->free(); + tcode = coding::findBySpec(B, TH); + if (!tcode) + return; + } + if (!(fVlength <= tcode->umax)) + { + unpack_abort("pop.L value too small"); + } + this->init(band_rp, band_limit, NO_META, disPop, tcode, N, nullptr); + tcode->free(); + } + else + { + this->init(band_rp, band_limit, meta_rp, disPop, defc, N, nullptr); + } + + // Count the number of zero tokens right now. + // Also verify that they are in bounds. + int UN = 0; // one {U} for each zero in {T} + value_stream vs = vs0; + for (int i = 0; i < N; i++) + { + uint32_t val = vs.getInt(); + if (val == 0) + UN += 1; + if (!(val <= (uint32_t)fVlength)) + { + unpack_abort("pop token out of range"); + } + } + vs.done(); + + // & Enc{ UCode } if UDef=0 + if (UN != 0) + { + uValues = U_NEW(coding_method, 1); + if (uValues == nullptr) + return; + uValues->u = u; + if (UDef != 0) + { + uValues->init(band_rp, band_limit, NO_META, disPop, defc, UN, nullptr); + } + else + { + uValues->init(band_rp, band_limit, meta_rp, disPop, defc, UN, nullptr); + } + } + else + { + if (UDef == 0) + { + int uop = (*meta_rp++ & 0xFF); + if (uop > _meta_canon_max) + // %%% Spec. requires the more strict (uop != _meta_default). + unpack_abort("bad meta-coding for empty pop/U"); + } + } + + // Bug fix for 6259542 + // Last of all, adjust vs0.cmk to the 'pop' flavor + for (coding_method *self = this; self != nullptr; self = self->next) + { + coding_method_kind cmk2 = cmk_pop; + switch (self->vs0.cmk) + { + case cmk_BHS0: + cmk2 = cmk_pop_BHS0; + break; + case cmk_BYTE1: + cmk2 = cmk_pop_BYTE1; + break; + default: + break; + } + self->vs0.cmk = cmk2; + if (self != this) + { + assert(self->fValues == nullptr); // no double init + self->fValues = this->fValues; + self->fVlength = this->fVlength; + assert(self->uValues == nullptr); // must stay nullptr + } + } + + return; // done; no falling through + } + else + { + unpack_abort("bad meta-coding"); + } + + // Common code here skips a series of values with one coding. + assert(foundc != nullptr); + + assert(vs0.cmk == cmk_ERROR); // no garbage, please + assert(vs0.rp == nullptr); // no garbage, please + assert(vs0.rplimit == nullptr); // no garbage, please + assert(vs0.sum == 0); // no garbage, please + + vs0.init(band_rp, band_limit, foundc); + + // Done with foundc. Free if necessary. + if (to_free != nullptr) + { + to_free->free(); + to_free = nullptr; + } + foundc = nullptr; + + coding &c = vs0.c; + CODING_PRIVATE(c.spec); + // assert sane N + assert((uint32_t)N < INT_MAX_VALUE || N == POP_FAVORED_N); + + // Look at the values, or at least skip over them quickly. + if (valueSink == nullptr) + { + // Skip and ignore values in the first pass. + c.parseMultiple(band_rp, N, band_limit, B, H); + } + else if (N >= 0) + { + // Pop coding, {F} sequence, initial run of values... + assert((mode & DISABLE_POP) != 0); + value_stream vs = vs0; + for (int n = 0; n < N; n++) + { + int val = vs.getInt(); + valueSink->add(val); + } + band_rp = vs.rp; + } + else + { + // Pop coding, {F} sequence, final run of values... + assert((mode & DISABLE_POP) != 0); + assert(N == POP_FAVORED_N); + int min = INT_MIN_VALUE; // farthest from the center + // min2 is based on the buggy specification of centrality in version 150.7 + // no known implementations transmit this value, but just in case... + // int min2 = INT_MIN_VALUE; + int last = 0; + // if there were initial runs, find the potential sentinels in them: + for (int i = 0; i < valueSink->length(); i++) + { + last = valueSink->get(i); + min = moreCentral(min, last); + // min2 = moreCentral2(min2, last, min); + } + value_stream vs = vs0; + for (;;) + { + int val = vs.getInt(); + if (valueSink->length() > 0 && (val == last || val == min)) //|| val == min2 + break; + valueSink->add(val); + last = val; + min = moreCentral(min, last); + // min2 = moreCentral2(min2, last, min); + } + band_rp = vs.rp; + } + + // Get an accurate upper limit now. + vs0.rplimit = band_rp; + vs0.cm = this; + + return; // success } coding basic_codings[] = { - // This one is not a usable irregular coding, but is used by cp_Utf8_chars. - CODING_INIT(3, 128, 0, 0), - - // Fixed-length codings: - CODING_INIT(1, 256, 0, 0), CODING_INIT(1, 256, 1, 0), CODING_INIT(1, 256, 0, 1), - CODING_INIT(1, 256, 1, 1), CODING_INIT(2, 256, 0, 0), CODING_INIT(2, 256, 1, 0), - CODING_INIT(2, 256, 0, 1), CODING_INIT(2, 256, 1, 1), CODING_INIT(3, 256, 0, 0), - CODING_INIT(3, 256, 1, 0), CODING_INIT(3, 256, 0, 1), CODING_INIT(3, 256, 1, 1), - CODING_INIT(4, 256, 0, 0), CODING_INIT(4, 256, 1, 0), CODING_INIT(4, 256, 0, 1), - CODING_INIT(4, 256, 1, 1), - - // Full-range variable-length codings: - CODING_INIT(5, 4, 0, 0), CODING_INIT(5, 4, 1, 0), CODING_INIT(5, 4, 2, 0), - CODING_INIT(5, 16, 0, 0), CODING_INIT(5, 16, 1, 0), CODING_INIT(5, 16, 2, 0), - CODING_INIT(5, 32, 0, 0), CODING_INIT(5, 32, 1, 0), CODING_INIT(5, 32, 2, 0), - CODING_INIT(5, 64, 0, 0), CODING_INIT(5, 64, 1, 0), CODING_INIT(5, 64, 2, 0), - CODING_INIT(5, 128, 0, 0), CODING_INIT(5, 128, 1, 0), CODING_INIT(5, 128, 2, 0), - CODING_INIT(5, 4, 0, 1), CODING_INIT(5, 4, 1, 1), CODING_INIT(5, 4, 2, 1), - CODING_INIT(5, 16, 0, 1), CODING_INIT(5, 16, 1, 1), CODING_INIT(5, 16, 2, 1), - CODING_INIT(5, 32, 0, 1), CODING_INIT(5, 32, 1, 1), CODING_INIT(5, 32, 2, 1), - CODING_INIT(5, 64, 0, 1), CODING_INIT(5, 64, 1, 1), CODING_INIT(5, 64, 2, 1), - CODING_INIT(5, 128, 0, 1), CODING_INIT(5, 128, 1, 1), CODING_INIT(5, 128, 2, 1), - - // Variable length subrange codings: - CODING_INIT(2, 192, 0, 0), CODING_INIT(2, 224, 0, 0), CODING_INIT(2, 240, 0, 0), - CODING_INIT(2, 248, 0, 0), CODING_INIT(2, 252, 0, 0), CODING_INIT(2, 8, 0, 1), - CODING_INIT(2, 8, 1, 1), CODING_INIT(2, 16, 0, 1), CODING_INIT(2, 16, 1, 1), - CODING_INIT(2, 32, 0, 1), CODING_INIT(2, 32, 1, 1), CODING_INIT(2, 64, 0, 1), - CODING_INIT(2, 64, 1, 1), CODING_INIT(2, 128, 0, 1), CODING_INIT(2, 128, 1, 1), - CODING_INIT(2, 192, 0, 1), CODING_INIT(2, 192, 1, 1), CODING_INIT(2, 224, 0, 1), - CODING_INIT(2, 224, 1, 1), CODING_INIT(2, 240, 0, 1), CODING_INIT(2, 240, 1, 1), - CODING_INIT(2, 248, 0, 1), CODING_INIT(2, 248, 1, 1), CODING_INIT(3, 192, 0, 0), - CODING_INIT(3, 224, 0, 0), CODING_INIT(3, 240, 0, 0), CODING_INIT(3, 248, 0, 0), - CODING_INIT(3, 252, 0, 0), CODING_INIT(3, 8, 0, 1), CODING_INIT(3, 8, 1, 1), - CODING_INIT(3, 16, 0, 1), CODING_INIT(3, 16, 1, 1), CODING_INIT(3, 32, 0, 1), - CODING_INIT(3, 32, 1, 1), CODING_INIT(3, 64, 0, 1), CODING_INIT(3, 64, 1, 1), - CODING_INIT(3, 128, 0, 1), CODING_INIT(3, 128, 1, 1), CODING_INIT(3, 192, 0, 1), - CODING_INIT(3, 192, 1, 1), CODING_INIT(3, 224, 0, 1), CODING_INIT(3, 224, 1, 1), - CODING_INIT(3, 240, 0, 1), CODING_INIT(3, 240, 1, 1), CODING_INIT(3, 248, 0, 1), - CODING_INIT(3, 248, 1, 1), CODING_INIT(4, 192, 0, 0), CODING_INIT(4, 224, 0, 0), - CODING_INIT(4, 240, 0, 0), CODING_INIT(4, 248, 0, 0), CODING_INIT(4, 252, 0, 0), - CODING_INIT(4, 8, 0, 1), CODING_INIT(4, 8, 1, 1), CODING_INIT(4, 16, 0, 1), - CODING_INIT(4, 16, 1, 1), CODING_INIT(4, 32, 0, 1), CODING_INIT(4, 32, 1, 1), - CODING_INIT(4, 64, 0, 1), CODING_INIT(4, 64, 1, 1), CODING_INIT(4, 128, 0, 1), - CODING_INIT(4, 128, 1, 1), CODING_INIT(4, 192, 0, 1), CODING_INIT(4, 192, 1, 1), - CODING_INIT(4, 224, 0, 1), CODING_INIT(4, 224, 1, 1), CODING_INIT(4, 240, 0, 1), - CODING_INIT(4, 240, 1, 1), CODING_INIT(4, 248, 0, 1), CODING_INIT(4, 248, 1, 1), - CODING_INIT(0, 0, 0, 0)}; + // This one is not a usable irregular coding, but is used by cp_Utf8_chars. + CODING_INIT(3, 128, 0, 0), + + // Fixed-length codings: + CODING_INIT(1, 256, 0, 0), CODING_INIT(1, 256, 1, 0), CODING_INIT(1, 256, 0, 1), + CODING_INIT(1, 256, 1, 1), CODING_INIT(2, 256, 0, 0), CODING_INIT(2, 256, 1, 0), + CODING_INIT(2, 256, 0, 1), CODING_INIT(2, 256, 1, 1), CODING_INIT(3, 256, 0, 0), + CODING_INIT(3, 256, 1, 0), CODING_INIT(3, 256, 0, 1), CODING_INIT(3, 256, 1, 1), + CODING_INIT(4, 256, 0, 0), CODING_INIT(4, 256, 1, 0), CODING_INIT(4, 256, 0, 1), + CODING_INIT(4, 256, 1, 1), + + // Full-range variable-length codings: + CODING_INIT(5, 4, 0, 0), CODING_INIT(5, 4, 1, 0), CODING_INIT(5, 4, 2, 0), + CODING_INIT(5, 16, 0, 0), CODING_INIT(5, 16, 1, 0), CODING_INIT(5, 16, 2, 0), + CODING_INIT(5, 32, 0, 0), CODING_INIT(5, 32, 1, 0), CODING_INIT(5, 32, 2, 0), + CODING_INIT(5, 64, 0, 0), CODING_INIT(5, 64, 1, 0), CODING_INIT(5, 64, 2, 0), + CODING_INIT(5, 128, 0, 0), CODING_INIT(5, 128, 1, 0), CODING_INIT(5, 128, 2, 0), + CODING_INIT(5, 4, 0, 1), CODING_INIT(5, 4, 1, 1), CODING_INIT(5, 4, 2, 1), + CODING_INIT(5, 16, 0, 1), CODING_INIT(5, 16, 1, 1), CODING_INIT(5, 16, 2, 1), + CODING_INIT(5, 32, 0, 1), CODING_INIT(5, 32, 1, 1), CODING_INIT(5, 32, 2, 1), + CODING_INIT(5, 64, 0, 1), CODING_INIT(5, 64, 1, 1), CODING_INIT(5, 64, 2, 1), + CODING_INIT(5, 128, 0, 1), CODING_INIT(5, 128, 1, 1), CODING_INIT(5, 128, 2, 1), + + // Variable length subrange codings: + CODING_INIT(2, 192, 0, 0), CODING_INIT(2, 224, 0, 0), CODING_INIT(2, 240, 0, 0), + CODING_INIT(2, 248, 0, 0), CODING_INIT(2, 252, 0, 0), CODING_INIT(2, 8, 0, 1), + CODING_INIT(2, 8, 1, 1), CODING_INIT(2, 16, 0, 1), CODING_INIT(2, 16, 1, 1), + CODING_INIT(2, 32, 0, 1), CODING_INIT(2, 32, 1, 1), CODING_INIT(2, 64, 0, 1), + CODING_INIT(2, 64, 1, 1), CODING_INIT(2, 128, 0, 1), CODING_INIT(2, 128, 1, 1), + CODING_INIT(2, 192, 0, 1), CODING_INIT(2, 192, 1, 1), CODING_INIT(2, 224, 0, 1), + CODING_INIT(2, 224, 1, 1), CODING_INIT(2, 240, 0, 1), CODING_INIT(2, 240, 1, 1), + CODING_INIT(2, 248, 0, 1), CODING_INIT(2, 248, 1, 1), CODING_INIT(3, 192, 0, 0), + CODING_INIT(3, 224, 0, 0), CODING_INIT(3, 240, 0, 0), CODING_INIT(3, 248, 0, 0), + CODING_INIT(3, 252, 0, 0), CODING_INIT(3, 8, 0, 1), CODING_INIT(3, 8, 1, 1), + CODING_INIT(3, 16, 0, 1), CODING_INIT(3, 16, 1, 1), CODING_INIT(3, 32, 0, 1), + CODING_INIT(3, 32, 1, 1), CODING_INIT(3, 64, 0, 1), CODING_INIT(3, 64, 1, 1), + CODING_INIT(3, 128, 0, 1), CODING_INIT(3, 128, 1, 1), CODING_INIT(3, 192, 0, 1), + CODING_INIT(3, 192, 1, 1), CODING_INIT(3, 224, 0, 1), CODING_INIT(3, 224, 1, 1), + CODING_INIT(3, 240, 0, 1), CODING_INIT(3, 240, 1, 1), CODING_INIT(3, 248, 0, 1), + CODING_INIT(3, 248, 1, 1), CODING_INIT(4, 192, 0, 0), CODING_INIT(4, 224, 0, 0), + CODING_INIT(4, 240, 0, 0), CODING_INIT(4, 248, 0, 0), CODING_INIT(4, 252, 0, 0), + CODING_INIT(4, 8, 0, 1), CODING_INIT(4, 8, 1, 1), CODING_INIT(4, 16, 0, 1), + CODING_INIT(4, 16, 1, 1), CODING_INIT(4, 32, 0, 1), CODING_INIT(4, 32, 1, 1), + CODING_INIT(4, 64, 0, 1), CODING_INIT(4, 64, 1, 1), CODING_INIT(4, 128, 0, 1), + CODING_INIT(4, 128, 1, 1), CODING_INIT(4, 192, 0, 1), CODING_INIT(4, 192, 1, 1), + CODING_INIT(4, 224, 0, 1), CODING_INIT(4, 224, 1, 1), CODING_INIT(4, 240, 0, 1), + CODING_INIT(4, 240, 1, 1), CODING_INIT(4, 248, 0, 1), CODING_INIT(4, 248, 1, 1), + CODING_INIT(0, 0, 0, 0)}; #define BASIC_INDEX_LIMIT (int)(sizeof(basic_codings) / sizeof(basic_codings[0]) - 1) coding *coding::findByIndex(int idx) { - int index_limit = BASIC_INDEX_LIMIT; - assert(_meta_canon_min == 1 && _meta_canon_max + 1 == index_limit); + int index_limit = BASIC_INDEX_LIMIT; + assert(_meta_canon_min == 1 && _meta_canon_max + 1 == index_limit); - if (idx >= _meta_canon_min && idx <= _meta_canon_max) - return basic_codings[idx].init(); - else - return nullptr; + if (idx >= _meta_canon_min && idx <= _meta_canon_max) + return basic_codings[idx].init(); + else + return nullptr; } diff --git a/libraries/pack200/src/coding.h b/libraries/pack200/src/coding.h index f9bd6ca2..bfdd252e 100644 --- a/libraries/pack200/src/coding.h +++ b/libraries/pack200/src/coding.h @@ -35,9 +35,9 @@ struct unpacker; #define CODING_D(x) ((x) >> 0 & 0xF) #define CODING_INIT(B, H, S, D) \ - { \ - CODING_SPEC(B, H, S, D), 0, 0, 0, 0, 0, 0, 0, 0 \ - } + { \ + CODING_SPEC(B, H, S, D), 0, 0, 0, 0, 0, 0, 0, 0 \ + } // For debugging purposes, some compilers do not like this and will complain. // #define long do_not_use_C_long_types_use_jlong_or_int @@ -45,126 +45,126 @@ struct unpacker; struct coding { - int spec; // B,H,S,D - - // Handy values derived from the spec: - int B() - { - return CODING_B(spec); - } - int H() - { - return CODING_H(spec); - } - int S() - { - return CODING_S(spec); - } - int D() - { - return CODING_D(spec); - } - int L() - { - return 256 - CODING_H(spec); - } - int min, max; - int umin, umax; - char isSigned, isSubrange, isFullRange, isMalloc; - - coding *init(); // returns self or nullptr if error - coding *initFrom(int spec_) - { - assert(this->spec == 0); - this->spec = spec_; - return init(); - } - - static coding *findBySpec(int spec); - static coding *findBySpec(int B, int H, int S = 0, int D = 0); - static coding *findByIndex(int irregularCodingIndex); - - static uint32_t parse(byte *&rp, int B, int H); - static uint32_t parse_lgH(byte *&rp, int B, int H, int lgH); - static void parseMultiple(byte *&rp, int N, byte *limit, int B, int H); - - uint32_t parse(byte *&rp) - { - return parse(rp, CODING_B(spec), CODING_H(spec)); - } - void parseMultiple(byte *&rp, int N, byte *limit) - { - parseMultiple(rp, N, limit, CODING_B(spec), CODING_H(spec)); - } - - bool canRepresent(int x) - { - return (x >= min && x <= max); - } - bool canRepresentUnsigned(int x) - { - return (x >= umin && x <= umax); - } - - int sumInUnsignedRange(int x, int y); - - int readFrom(byte *&rpVar, int *dbase); - void readArrayFrom(byte *&rpVar, int *dbase, int length, int *values); - void skipArrayFrom(byte *&rpVar, int length) - { - readArrayFrom(rpVar, (int *)NULL, length, (int *)NULL); - } - - void free(); // free self if isMalloc + int spec; // B,H,S,D + + // Handy values derived from the spec: + int B() + { + return CODING_B(spec); + } + int H() + { + return CODING_H(spec); + } + int S() + { + return CODING_S(spec); + } + int D() + { + return CODING_D(spec); + } + int L() + { + return 256 - CODING_H(spec); + } + int min, max; + int umin, umax; + char isSigned, isSubrange, isFullRange, isMalloc; + + coding *init(); // returns self or nullptr if error + coding *initFrom(int spec_) + { + assert(this->spec == 0); + this->spec = spec_; + return init(); + } + + static coding *findBySpec(int spec); + static coding *findBySpec(int B, int H, int S = 0, int D = 0); + static coding *findByIndex(int irregularCodingIndex); + + static uint32_t parse(byte *&rp, int B, int H); + static uint32_t parse_lgH(byte *&rp, int B, int H, int lgH); + static void parseMultiple(byte *&rp, int N, byte *limit, int B, int H); + + uint32_t parse(byte *&rp) + { + return parse(rp, CODING_B(spec), CODING_H(spec)); + } + void parseMultiple(byte *&rp, int N, byte *limit) + { + parseMultiple(rp, N, limit, CODING_B(spec), CODING_H(spec)); + } + + bool canRepresent(int x) + { + return (x >= min && x <= max); + } + bool canRepresentUnsigned(int x) + { + return (x >= umin && x <= umax); + } + + int sumInUnsignedRange(int x, int y); + + int readFrom(byte *&rpVar, int *dbase); + void readArrayFrom(byte *&rpVar, int *dbase, int length, int *values); + void skipArrayFrom(byte *&rpVar, int length) + { + readArrayFrom(rpVar, (int *)NULL, length, (int *)NULL); + } + + void free(); // free self if isMalloc }; enum coding_method_kind { - cmk_ERROR, - cmk_BHS, - cmk_BHS0, - cmk_BHS1, - cmk_BHSD1, - cmk_BHS1D1full, // isFullRange - cmk_BHS1D1sub, // isSubRange - - // special cases hand-optimized (~50% of all decoded values) - cmk_BYTE1, //(1,256) 6% - cmk_CHAR3, //(3,128) 7% - cmk_UNSIGNED5, //(5,64) 13% - cmk_DELTA5, //(5,64,1,1) 5% - cmk_BCI5, //(5,4) 18% - cmk_BRANCH5, //(5,4,2) 4% - // cmk_UNSIGNED5H16, //(5,16) 5% - // cmk_UNSIGNED2H4, //(2,4) 6% - // cmk_DELTA4H8, //(4,8,1,1) 10% - // cmk_DELTA3H16, //(3,16,1,1) 9% - cmk_BHS_LIMIT, - cmk_pop, - cmk_pop_BHS0, - cmk_pop_BYTE1, - cmk_pop_LIMIT, - cmk_LIMIT + cmk_ERROR, + cmk_BHS, + cmk_BHS0, + cmk_BHS1, + cmk_BHSD1, + cmk_BHS1D1full, // isFullRange + cmk_BHS1D1sub, // isSubRange + + // special cases hand-optimized (~50% of all decoded values) + cmk_BYTE1, //(1,256) 6% + cmk_CHAR3, //(3,128) 7% + cmk_UNSIGNED5, //(5,64) 13% + cmk_DELTA5, //(5,64,1,1) 5% + cmk_BCI5, //(5,4) 18% + cmk_BRANCH5, //(5,4,2) 4% + // cmk_UNSIGNED5H16, //(5,16) 5% + // cmk_UNSIGNED2H4, //(2,4) 6% + // cmk_DELTA4H8, //(4,8,1,1) 10% + // cmk_DELTA3H16, //(3,16,1,1) 9% + cmk_BHS_LIMIT, + cmk_pop, + cmk_pop_BHS0, + cmk_pop_BYTE1, + cmk_pop_LIMIT, + cmk_LIMIT }; enum { - BYTE1_spec = CODING_SPEC(1, 256, 0, 0), - CHAR3_spec = CODING_SPEC(3, 128, 0, 0), - UNSIGNED4_spec = CODING_SPEC(4, 256, 0, 0), - UNSIGNED5_spec = CODING_SPEC(5, 64, 0, 0), - SIGNED5_spec = CODING_SPEC(5, 64, 1, 0), - DELTA5_spec = CODING_SPEC(5, 64, 1, 1), - UDELTA5_spec = CODING_SPEC(5, 64, 0, 1), - MDELTA5_spec = CODING_SPEC(5, 64, 2, 1), - BCI5_spec = CODING_SPEC(5, 4, 0, 0), - BRANCH5_spec = CODING_SPEC(5, 4, 2, 0) + BYTE1_spec = CODING_SPEC(1, 256, 0, 0), + CHAR3_spec = CODING_SPEC(3, 128, 0, 0), + UNSIGNED4_spec = CODING_SPEC(4, 256, 0, 0), + UNSIGNED5_spec = CODING_SPEC(5, 64, 0, 0), + SIGNED5_spec = CODING_SPEC(5, 64, 1, 0), + DELTA5_spec = CODING_SPEC(5, 64, 1, 1), + UDELTA5_spec = CODING_SPEC(5, 64, 0, 1), + MDELTA5_spec = CODING_SPEC(5, 64, 2, 1), + BCI5_spec = CODING_SPEC(5, 4, 0, 0), + BRANCH5_spec = CODING_SPEC(5, 4, 2, 0) }; enum { - B_MAX = 5, - C_SLOP = B_MAX * 10 + B_MAX = 5, + C_SLOP = B_MAX * 10 }; struct coding_method; @@ -172,76 +172,76 @@ struct coding_method; // iterator under the control of a meta-coding struct value_stream { - // current coding of values or values - coding c; // B,H,S,D,etc. - coding_method_kind cmk; // type of decoding needed - byte *rp; // read pointer - byte *rplimit; // final value of read pointer - int sum; // partial sum of all values so far (D=1 only) - coding_method *cm; // coding method that defines this stream - - void init(byte *band_rp, byte *band_limit, coding *defc); - void init(byte *band_rp, byte *band_limit, int spec) - { - init(band_rp, band_limit, coding::findBySpec(spec)); - } - - void setCoding(coding *c); - void setCoding(int spec) - { - setCoding(coding::findBySpec(spec)); - } - - // Parse and decode a single value. - int getInt(); - - // Parse and decode a single byte, with no error checks. - int getByte() - { - assert(cmk == cmk_BYTE1); - assert(rp < rplimit); - return *rp++ & 0xFF; - } - - // Used only for asserts. - bool hasValue(); - - void done() - { - assert(!hasValue()); - } - - // Sometimes a value stream has an auxiliary (but there are never two). - value_stream *helper() - { - assert(hasHelper()); - return this + 1; - } - bool hasHelper(); + // current coding of values or values + coding c; // B,H,S,D,etc. + coding_method_kind cmk; // type of decoding needed + byte *rp; // read pointer + byte *rplimit; // final value of read pointer + int sum; // partial sum of all values so far (D=1 only) + coding_method *cm; // coding method that defines this stream + + void init(byte *band_rp, byte *band_limit, coding *defc); + void init(byte *band_rp, byte *band_limit, int spec) + { + init(band_rp, band_limit, coding::findBySpec(spec)); + } + + void setCoding(coding *c); + void setCoding(int spec) + { + setCoding(coding::findBySpec(spec)); + } + + // Parse and decode a single value. + int getInt(); + + // Parse and decode a single byte, with no error checks. + int getByte() + { + assert(cmk == cmk_BYTE1); + assert(rp < rplimit); + return *rp++ & 0xFF; + } + + // Used only for asserts. + bool hasValue(); + + void done() + { + assert(!hasValue()); + } + + // Sometimes a value stream has an auxiliary (but there are never two). + value_stream *helper() + { + assert(hasHelper()); + return this + 1; + } + bool hasHelper(); }; struct coding_method { - value_stream vs0; // initial state snapshot (vs.meta==this) + value_stream vs0; // initial state snapshot (vs.meta==this) - coding_method *next; // what to do when we run out of bytes + coding_method *next; // what to do when we run out of bytes - // these fields are used for pop codes only: - int *fValues; // favored value array - int fVlength; // maximum favored value token - coding_method *uValues; // unfavored value stream + // these fields are used for pop codes only: + int *fValues; // favored value array + int fVlength; // maximum favored value token + coding_method *uValues; // unfavored value stream - // pointer to outer unpacker, for error checks etc. - unpacker *u; + // pointer to outer unpacker, for error checks etc. + unpacker *u; - // Initialize a value stream. - void reset(value_stream *state); + // Initialize a value stream. + void reset(value_stream *state); - // Parse a band header, size a band, and initialize for further action. - // band_rp advances (but not past band_limit), and meta_rp advances. - // The mode gives context, such as "inside a pop". - // The defc and N are the incoming parameters to a meta-coding. - // The value sink is used to collect output values, when desired. - void init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, coding *defc, int N, - intlist *valueSink); + // Parse a band header, size a band, and initialize for further action. + // band_rp advances (but not past band_limit), and meta_rp advances. + // The mode gives context, such as "inside a pop". + // The defc and N are the incoming parameters to a meta-coding. + // The value sink is used to collect output values, when desired. + void init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, coding *defc, int N, + intlist *valueSink); }; diff --git a/libraries/pack200/src/constants.h b/libraries/pack200/src/constants.h index 2cc14b7d..f1baf42a 100644 --- a/libraries/pack200/src/constants.h +++ b/libraries/pack200/src/constants.h @@ -55,388 +55,388 @@ enum { - CONSTANT_None, - CONSTANT_Utf8, - CONSTANT_unused2, /* unused, was Unicode */ - CONSTANT_Integer, - CONSTANT_Float, - CONSTANT_Long, - CONSTANT_Double, - CONSTANT_Class, - CONSTANT_String, - CONSTANT_Fieldref, - CONSTANT_Methodref, - CONSTANT_InterfaceMethodref, - CONSTANT_NameandType, - CONSTANT_Signature = 13, - CONSTANT_All = 14, - CONSTANT_Limit = 15, - CONSTANT_NONE = 0, - CONSTANT_Literal = 20, // pseudo-tag for debugging - CONSTANT_Member = 21, // pseudo-tag for debugging - SUBINDEX_BIT = 64, // combined with CONSTANT_xxx for ixTag - ACC_STATIC = 0x0008, - ACC_IC_LONG_FORM = (1 << 16), // for ic_flags - CLASS_ATTR_SourceFile = 17, - CLASS_ATTR_EnclosingMethod = 18, - CLASS_ATTR_InnerClasses = 23, - CLASS_ATTR_ClassFile_version = 24, - FIELD_ATTR_ConstantValue = 17, - METHOD_ATTR_Code = 17, - METHOD_ATTR_Exceptions = 18, - METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23, - METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24, - METHOD_ATTR_AnnotationDefault = 25, - CODE_ATTR_StackMapTable = 0, - CODE_ATTR_LineNumberTable = 1, - CODE_ATTR_LocalVariableTable = 2, - CODE_ATTR_LocalVariableTypeTable = 3, - // X_ATTR_Synthetic = 12, // ACC_SYNTHETIC; not predefined - X_ATTR_Signature = 19, - X_ATTR_Deprecated = 20, - X_ATTR_RuntimeVisibleAnnotations = 21, - X_ATTR_RuntimeInvisibleAnnotations = 22, - X_ATTR_OVERFLOW = 16, - X_ATTR_LIMIT_NO_FLAGS_HI = 32, - X_ATTR_LIMIT_FLAGS_HI = 63, + CONSTANT_None, + CONSTANT_Utf8, + CONSTANT_unused2, /* unused, was Unicode */ + CONSTANT_Integer, + CONSTANT_Float, + CONSTANT_Long, + CONSTANT_Double, + CONSTANT_Class, + CONSTANT_String, + CONSTANT_Fieldref, + CONSTANT_Methodref, + CONSTANT_InterfaceMethodref, + CONSTANT_NameandType, + CONSTANT_Signature = 13, + CONSTANT_All = 14, + CONSTANT_Limit = 15, + CONSTANT_NONE = 0, + CONSTANT_Literal = 20, // pseudo-tag for debugging + CONSTANT_Member = 21, // pseudo-tag for debugging + SUBINDEX_BIT = 64, // combined with CONSTANT_xxx for ixTag + ACC_STATIC = 0x0008, + ACC_IC_LONG_FORM = (1 << 16), // for ic_flags + CLASS_ATTR_SourceFile = 17, + CLASS_ATTR_EnclosingMethod = 18, + CLASS_ATTR_InnerClasses = 23, + CLASS_ATTR_ClassFile_version = 24, + FIELD_ATTR_ConstantValue = 17, + METHOD_ATTR_Code = 17, + METHOD_ATTR_Exceptions = 18, + METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23, + METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24, + METHOD_ATTR_AnnotationDefault = 25, + CODE_ATTR_StackMapTable = 0, + CODE_ATTR_LineNumberTable = 1, + CODE_ATTR_LocalVariableTable = 2, + CODE_ATTR_LocalVariableTypeTable = 3, + // X_ATTR_Synthetic = 12, // ACC_SYNTHETIC; not predefined + X_ATTR_Signature = 19, + X_ATTR_Deprecated = 20, + X_ATTR_RuntimeVisibleAnnotations = 21, + X_ATTR_RuntimeInvisibleAnnotations = 22, + X_ATTR_OVERFLOW = 16, + X_ATTR_LIMIT_NO_FLAGS_HI = 32, + X_ATTR_LIMIT_FLAGS_HI = 63, #define O_ATTR_DO(F) \ - F(X_ATTR_OVERFLOW, 01) \ - /*(end)*/ + F(X_ATTR_OVERFLOW, 01) \ + /*(end)*/ #define X_ATTR_DO(F) \ - O_ATTR_DO(F) F(X_ATTR_Signature, Signature) F(X_ATTR_Deprecated, Deprecated) \ - F(X_ATTR_RuntimeVisibleAnnotations, RuntimeVisibleAnnotations) \ - F(X_ATTR_RuntimeInvisibleAnnotations, RuntimeInvisibleAnnotations) \ - /*F(X_ATTR_Synthetic,Synthetic)*/ \ - /*(end)*/ + O_ATTR_DO(F) F(X_ATTR_Signature, Signature) F(X_ATTR_Deprecated, Deprecated) \ + F(X_ATTR_RuntimeVisibleAnnotations, RuntimeVisibleAnnotations) \ + F(X_ATTR_RuntimeInvisibleAnnotations, RuntimeInvisibleAnnotations) \ + /*F(X_ATTR_Synthetic,Synthetic)*/ \ + /*(end)*/ #define CLASS_ATTR_DO(F) \ - F(CLASS_ATTR_SourceFile, SourceFile) F(CLASS_ATTR_InnerClasses, InnerClasses) \ - F(CLASS_ATTR_EnclosingMethod, EnclosingMethod) F(CLASS_ATTR_ClassFile_version, 02) \ - /*(end)*/ + F(CLASS_ATTR_SourceFile, SourceFile) F(CLASS_ATTR_InnerClasses, InnerClasses) \ + F(CLASS_ATTR_EnclosingMethod, EnclosingMethod) F(CLASS_ATTR_ClassFile_version, 02) \ + /*(end)*/ #define FIELD_ATTR_DO(F) \ - F(FIELD_ATTR_ConstantValue, ConstantValue) \ - /*(end)*/ + F(FIELD_ATTR_ConstantValue, ConstantValue) \ + /*(end)*/ #define METHOD_ATTR_DO(F) \ - F(METHOD_ATTR_Code, Code) F(METHOD_ATTR_Exceptions, Exceptions) \ - F(METHOD_ATTR_RuntimeVisibleParameterAnnotations, RuntimeVisibleParameterAnnotations) \ - F(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, \ - RuntimeInvisibleParameterAnnotations) \ - F(METHOD_ATTR_AnnotationDefault, AnnotationDefault) \ - /*(end)*/ + F(METHOD_ATTR_Code, Code) F(METHOD_ATTR_Exceptions, Exceptions) \ + F(METHOD_ATTR_RuntimeVisibleParameterAnnotations, RuntimeVisibleParameterAnnotations) \ + F(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, \ + RuntimeInvisibleParameterAnnotations) \ + F(METHOD_ATTR_AnnotationDefault, AnnotationDefault) \ + /*(end)*/ #define CODE_ATTR_DO(F) \ - F(CODE_ATTR_StackMapTable, StackMapTable) F(CODE_ATTR_LineNumberTable, LineNumberTable) \ - F(CODE_ATTR_LocalVariableTable, LocalVariableTable) \ - F(CODE_ATTR_LocalVariableTypeTable, LocalVariableTypeTable) \ - /*(end)*/ + F(CODE_ATTR_StackMapTable, StackMapTable) F(CODE_ATTR_LineNumberTable, LineNumberTable) \ + F(CODE_ATTR_LocalVariableTable, LocalVariableTable) \ + F(CODE_ATTR_LocalVariableTypeTable, LocalVariableTypeTable) \ + /*(end)*/ #define ALL_ATTR_DO(F) \ - X_ATTR_DO(F) CLASS_ATTR_DO(F) FIELD_ATTR_DO(F) METHOD_ATTR_DO(F) CODE_ATTR_DO(F) \ - /*(end)*/ + X_ATTR_DO(F) CLASS_ATTR_DO(F) FIELD_ATTR_DO(F) METHOD_ATTR_DO(F) CODE_ATTR_DO(F) \ + /*(end)*/ - // attribute "context types" - ATTR_CONTEXT_CLASS = 0, - ATTR_CONTEXT_FIELD = 1, - ATTR_CONTEXT_METHOD = 2, - ATTR_CONTEXT_CODE = 3, - ATTR_CONTEXT_LIMIT = 4, + // attribute "context types" + ATTR_CONTEXT_CLASS = 0, + ATTR_CONTEXT_FIELD = 1, + ATTR_CONTEXT_METHOD = 2, + ATTR_CONTEXT_CODE = 3, + ATTR_CONTEXT_LIMIT = 4, - // constants for parsed layouts (stored in band::le_kind) - EK_NONE = 0, // not a layout element - EK_INT = 'I', // B H I SH etc., also FH etc. - EK_BCI = 'P', // PH etc. - EK_BCID = 'Q', // POH etc. - EK_BCO = 'O', // OH etc. - EK_REPL = 'N', // NH[...] etc. - EK_REF = 'R', // RUH, RUNH, KQH, etc. - EK_UN = 'T', // TB(...)[...] etc. - EK_CASE = 'K', // (...)[...] etc. - EK_CALL = '(', // (0), (1), etc. - EK_CBLE = '[', // [...][...] etc. - NO_BAND_INDEX = -1, + // constants for parsed layouts (stored in band::le_kind) + EK_NONE = 0, // not a layout element + EK_INT = 'I', // B H I SH etc., also FH etc. + EK_BCI = 'P', // PH etc. + EK_BCID = 'Q', // POH etc. + EK_BCO = 'O', // OH etc. + EK_REPL = 'N', // NH[...] etc. + EK_REF = 'R', // RUH, RUNH, KQH, etc. + EK_UN = 'T', // TB(...)[...] etc. + EK_CASE = 'K', // (...)[...] etc. + EK_CALL = '(', // (0), (1), etc. + EK_CBLE = '[', // [...][...] etc. + NO_BAND_INDEX = -1, - // File option bits, from LSB in ascending bit position. - FO_DEFLATE_HINT = 1 << 0, - FO_IS_CLASS_STUB = 1 << 1, + // File option bits, from LSB in ascending bit position. + FO_DEFLATE_HINT = 1 << 0, + FO_IS_CLASS_STUB = 1 << 1, - // Archive option bits, from LSB in ascending bit position: - AO_HAVE_SPECIAL_FORMATS = 1 << 0, - AO_HAVE_CP_NUMBERS = 1 << 1, - AO_HAVE_ALL_CODE_FLAGS = 1 << 2, - AO_3_UNUSED_MBZ = 1 << 3, - AO_HAVE_FILE_HEADERS = 1 << 4, - AO_DEFLATE_HINT = 1 << 5, - AO_HAVE_FILE_MODTIME = 1 << 6, - AO_HAVE_FILE_OPTIONS = 1 << 7, - AO_HAVE_FILE_SIZE_HI = 1 << 8, - AO_HAVE_CLASS_FLAGS_HI = 1 << 9, - AO_HAVE_FIELD_FLAGS_HI = 1 << 10, - AO_HAVE_METHOD_FLAGS_HI = 1 << 11, - AO_HAVE_CODE_FLAGS_HI = 1 << 12, + // Archive option bits, from LSB in ascending bit position: + AO_HAVE_SPECIAL_FORMATS = 1 << 0, + AO_HAVE_CP_NUMBERS = 1 << 1, + AO_HAVE_ALL_CODE_FLAGS = 1 << 2, + AO_3_UNUSED_MBZ = 1 << 3, + AO_HAVE_FILE_HEADERS = 1 << 4, + AO_DEFLATE_HINT = 1 << 5, + AO_HAVE_FILE_MODTIME = 1 << 6, + AO_HAVE_FILE_OPTIONS = 1 << 7, + AO_HAVE_FILE_SIZE_HI = 1 << 8, + AO_HAVE_CLASS_FLAGS_HI = 1 << 9, + AO_HAVE_FIELD_FLAGS_HI = 1 << 10, + AO_HAVE_METHOD_FLAGS_HI = 1 << 11, + AO_HAVE_CODE_FLAGS_HI = 1 << 12, #define ARCHIVE_BIT_DO(F) \ - F(AO_HAVE_SPECIAL_FORMATS) F(AO_HAVE_CP_NUMBERS) F(AO_HAVE_ALL_CODE_FLAGS) \ - /*F(AO_3_UNUSED_MBZ)*/ \ - F(AO_HAVE_FILE_HEADERS) F(AO_DEFLATE_HINT) F(AO_HAVE_FILE_MODTIME) \ - F(AO_HAVE_FILE_OPTIONS) F(AO_HAVE_FILE_SIZE_HI) F(AO_HAVE_CLASS_FLAGS_HI) \ - F(AO_HAVE_FIELD_FLAGS_HI) F(AO_HAVE_METHOD_FLAGS_HI) F(AO_HAVE_CODE_FLAGS_HI) \ - /*(end)*/ + F(AO_HAVE_SPECIAL_FORMATS) F(AO_HAVE_CP_NUMBERS) F(AO_HAVE_ALL_CODE_FLAGS) \ + /*F(AO_3_UNUSED_MBZ)*/ \ + F(AO_HAVE_FILE_HEADERS) F(AO_DEFLATE_HINT) F(AO_HAVE_FILE_MODTIME) \ + F(AO_HAVE_FILE_OPTIONS) F(AO_HAVE_FILE_SIZE_HI) F(AO_HAVE_CLASS_FLAGS_HI) \ + F(AO_HAVE_FIELD_FLAGS_HI) F(AO_HAVE_METHOD_FLAGS_HI) F(AO_HAVE_CODE_FLAGS_HI) \ + /*(end)*/ - // Constants for decoding attribute definition header bytes. - ADH_CONTEXT_MASK = 0x3, // (hdr & ADH_CONTEXT_MASK) - ADH_BIT_SHIFT = 0x2, // (hdr >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB = 1, // (hdr >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB + // Constants for decoding attribute definition header bytes. + ADH_CONTEXT_MASK = 0x3, // (hdr & ADH_CONTEXT_MASK) + ADH_BIT_SHIFT = 0x2, // (hdr >> ADH_BIT_SHIFT) + ADH_BIT_IS_LSB = 1, // (hdr >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB #define ADH_BYTE(context, index) ((((index) + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT) + (context)) #define ADH_BYTE_CONTEXT(adhb) ((adhb) & ADH_CONTEXT_MASK) #define ADH_BYTE_INDEX(adhb) (((adhb) >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB) - NO_MODTIME = 0, // nullptr modtime value + NO_MODTIME = 0, // nullptr modtime value - // meta-coding - _meta_default = 0, - _meta_canon_min = 1, - _meta_canon_max = 115, - _meta_arb = 116, - _meta_run = 117, - _meta_pop = 141, - _meta_limit = 189, - _meta_error = 255, - _xxx_1_end + // meta-coding + _meta_default = 0, + _meta_canon_min = 1, + _meta_canon_max = 115, + _meta_arb = 116, + _meta_run = 117, + _meta_pop = 141, + _meta_limit = 189, + _meta_error = 255, + _xxx_1_end }; // Bytecodes. enum { - bc_nop = 0, // 0x00 - bc_aconst_null = 1, // 0x01 - bc_iconst_m1 = 2, // 0x02 - bc_iconst_0 = 3, // 0x03 - bc_iconst_1 = 4, // 0x04 - bc_iconst_2 = 5, // 0x05 - bc_iconst_3 = 6, // 0x06 - bc_iconst_4 = 7, // 0x07 - bc_iconst_5 = 8, // 0x08 - bc_lconst_0 = 9, // 0x09 - bc_lconst_1 = 10, // 0x0a - bc_fconst_0 = 11, // 0x0b - bc_fconst_1 = 12, // 0x0c - bc_fconst_2 = 13, // 0x0d - bc_dconst_0 = 14, // 0x0e - bc_dconst_1 = 15, // 0x0f - bc_bipush = 16, // 0x10 - bc_sipush = 17, // 0x11 - bc_ldc = 18, // 0x12 - bc_ldc_w = 19, // 0x13 - bc_ldc2_w = 20, // 0x14 - bc_iload = 21, // 0x15 - bc_lload = 22, // 0x16 - bc_fload = 23, // 0x17 - bc_dload = 24, // 0x18 - bc_aload = 25, // 0x19 - bc_iload_0 = 26, // 0x1a - bc_iload_1 = 27, // 0x1b - bc_iload_2 = 28, // 0x1c - bc_iload_3 = 29, // 0x1d - bc_lload_0 = 30, // 0x1e - bc_lload_1 = 31, // 0x1f - bc_lload_2 = 32, // 0x20 - bc_lload_3 = 33, // 0x21 - bc_fload_0 = 34, // 0x22 - bc_fload_1 = 35, // 0x23 - bc_fload_2 = 36, // 0x24 - bc_fload_3 = 37, // 0x25 - bc_dload_0 = 38, // 0x26 - bc_dload_1 = 39, // 0x27 - bc_dload_2 = 40, // 0x28 - bc_dload_3 = 41, // 0x29 - bc_aload_0 = 42, // 0x2a - bc_aload_1 = 43, // 0x2b - bc_aload_2 = 44, // 0x2c - bc_aload_3 = 45, // 0x2d - bc_iaload = 46, // 0x2e - bc_laload = 47, // 0x2f - bc_faload = 48, // 0x30 - bc_daload = 49, // 0x31 - bc_aaload = 50, // 0x32 - bc_baload = 51, // 0x33 - bc_caload = 52, // 0x34 - bc_saload = 53, // 0x35 - bc_istore = 54, // 0x36 - bc_lstore = 55, // 0x37 - bc_fstore = 56, // 0x38 - bc_dstore = 57, // 0x39 - bc_astore = 58, // 0x3a - bc_istore_0 = 59, // 0x3b - bc_istore_1 = 60, // 0x3c - bc_istore_2 = 61, // 0x3d - bc_istore_3 = 62, // 0x3e - bc_lstore_0 = 63, // 0x3f - bc_lstore_1 = 64, // 0x40 - bc_lstore_2 = 65, // 0x41 - bc_lstore_3 = 66, // 0x42 - bc_fstore_0 = 67, // 0x43 - bc_fstore_1 = 68, // 0x44 - bc_fstore_2 = 69, // 0x45 - bc_fstore_3 = 70, // 0x46 - bc_dstore_0 = 71, // 0x47 - bc_dstore_1 = 72, // 0x48 - bc_dstore_2 = 73, // 0x49 - bc_dstore_3 = 74, // 0x4a - bc_astore_0 = 75, // 0x4b - bc_astore_1 = 76, // 0x4c - bc_astore_2 = 77, // 0x4d - bc_astore_3 = 78, // 0x4e - bc_iastore = 79, // 0x4f - bc_lastore = 80, // 0x50 - bc_fastore = 81, // 0x51 - bc_dastore = 82, // 0x52 - bc_aastore = 83, // 0x53 - bc_bastore = 84, // 0x54 - bc_castore = 85, // 0x55 - bc_sastore = 86, // 0x56 - bc_pop = 87, // 0x57 - bc_pop2 = 88, // 0x58 - bc_dup = 89, // 0x59 - bc_dup_x1 = 90, // 0x5a - bc_dup_x2 = 91, // 0x5b - bc_dup2 = 92, // 0x5c - bc_dup2_x1 = 93, // 0x5d - bc_dup2_x2 = 94, // 0x5e - bc_swap = 95, // 0x5f - bc_iadd = 96, // 0x60 - bc_ladd = 97, // 0x61 - bc_fadd = 98, // 0x62 - bc_dadd = 99, // 0x63 - bc_isub = 100, // 0x64 - bc_lsub = 101, // 0x65 - bc_fsub = 102, // 0x66 - bc_dsub = 103, // 0x67 - bc_imul = 104, // 0x68 - bc_lmul = 105, // 0x69 - bc_fmul = 106, // 0x6a - bc_dmul = 107, // 0x6b - bc_idiv = 108, // 0x6c - bc_ldiv = 109, // 0x6d - bc_fdiv = 110, // 0x6e - bc_ddiv = 111, // 0x6f - bc_irem = 112, // 0x70 - bc_lrem = 113, // 0x71 - bc_frem = 114, // 0x72 - bc_drem = 115, // 0x73 - bc_ineg = 116, // 0x74 - bc_lneg = 117, // 0x75 - bc_fneg = 118, // 0x76 - bc_dneg = 119, // 0x77 - bc_ishl = 120, // 0x78 - bc_lshl = 121, // 0x79 - bc_ishr = 122, // 0x7a - bc_lshr = 123, // 0x7b - bc_iushr = 124, // 0x7c - bc_lushr = 125, // 0x7d - bc_iand = 126, // 0x7e - bc_land = 127, // 0x7f - bc_ior = 128, // 0x80 - bc_lor = 129, // 0x81 - bc_ixor = 130, // 0x82 - bc_lxor = 131, // 0x83 - bc_iinc = 132, // 0x84 - bc_i2l = 133, // 0x85 - bc_i2f = 134, // 0x86 - bc_i2d = 135, // 0x87 - bc_l2i = 136, // 0x88 - bc_l2f = 137, // 0x89 - bc_l2d = 138, // 0x8a - bc_f2i = 139, // 0x8b - bc_f2l = 140, // 0x8c - bc_f2d = 141, // 0x8d - bc_d2i = 142, // 0x8e - bc_d2l = 143, // 0x8f - bc_d2f = 144, // 0x90 - bc_i2b = 145, // 0x91 - bc_i2c = 146, // 0x92 - bc_i2s = 147, // 0x93 - bc_lcmp = 148, // 0x94 - bc_fcmpl = 149, // 0x95 - bc_fcmpg = 150, // 0x96 - bc_dcmpl = 151, // 0x97 - bc_dcmpg = 152, // 0x98 - bc_ifeq = 153, // 0x99 - bc_ifne = 154, // 0x9a - bc_iflt = 155, // 0x9b - bc_ifge = 156, // 0x9c - bc_ifgt = 157, // 0x9d - bc_ifle = 158, // 0x9e - bc_if_icmpeq = 159, // 0x9f - bc_if_icmpne = 160, // 0xa0 - bc_if_icmplt = 161, // 0xa1 - bc_if_icmpge = 162, // 0xa2 - bc_if_icmpgt = 163, // 0xa3 - bc_if_icmple = 164, // 0xa4 - bc_if_acmpeq = 165, // 0xa5 - bc_if_acmpne = 166, // 0xa6 - bc_goto = 167, // 0xa7 - bc_jsr = 168, // 0xa8 - bc_ret = 169, // 0xa9 - bc_tableswitch = 170, // 0xaa - bc_lookupswitch = 171, // 0xab - bc_ireturn = 172, // 0xac - bc_lreturn = 173, // 0xad - bc_freturn = 174, // 0xae - bc_dreturn = 175, // 0xaf - bc_areturn = 176, // 0xb0 - bc_return = 177, // 0xb1 - bc_getstatic = 178, // 0xb2 - bc_putstatic = 179, // 0xb3 - bc_getfield = 180, // 0xb4 - bc_putfield = 181, // 0xb5 - bc_invokevirtual = 182, // 0xb6 - bc_invokespecial = 183, // 0xb7 - bc_invokestatic = 184, // 0xb8 - bc_invokeinterface = 185, // 0xb9 - bc_xxxunusedxxx = 186, // 0xba - bc_new = 187, // 0xbb - bc_newarray = 188, // 0xbc - bc_anewarray = 189, // 0xbd - bc_arraylength = 190, // 0xbe - bc_athrow = 191, // 0xbf - bc_checkcast = 192, // 0xc0 - bc_instanceof = 193, // 0xc1 - bc_monitorenter = 194, // 0xc2 - bc_monitorexit = 195, // 0xc3 - bc_wide = 196, // 0xc4 - bc_multianewarray = 197, // 0xc5 - bc_ifnull = 198, // 0xc6 - bc_ifnonnull = 199, // 0xc7 - bc_goto_w = 200, // 0xc8 - bc_jsr_w = 201, // 0xc9 - bc_bytecode_limit = 202 // 0xca + bc_nop = 0, // 0x00 + bc_aconst_null = 1, // 0x01 + bc_iconst_m1 = 2, // 0x02 + bc_iconst_0 = 3, // 0x03 + bc_iconst_1 = 4, // 0x04 + bc_iconst_2 = 5, // 0x05 + bc_iconst_3 = 6, // 0x06 + bc_iconst_4 = 7, // 0x07 + bc_iconst_5 = 8, // 0x08 + bc_lconst_0 = 9, // 0x09 + bc_lconst_1 = 10, // 0x0a + bc_fconst_0 = 11, // 0x0b + bc_fconst_1 = 12, // 0x0c + bc_fconst_2 = 13, // 0x0d + bc_dconst_0 = 14, // 0x0e + bc_dconst_1 = 15, // 0x0f + bc_bipush = 16, // 0x10 + bc_sipush = 17, // 0x11 + bc_ldc = 18, // 0x12 + bc_ldc_w = 19, // 0x13 + bc_ldc2_w = 20, // 0x14 + bc_iload = 21, // 0x15 + bc_lload = 22, // 0x16 + bc_fload = 23, // 0x17 + bc_dload = 24, // 0x18 + bc_aload = 25, // 0x19 + bc_iload_0 = 26, // 0x1a + bc_iload_1 = 27, // 0x1b + bc_iload_2 = 28, // 0x1c + bc_iload_3 = 29, // 0x1d + bc_lload_0 = 30, // 0x1e + bc_lload_1 = 31, // 0x1f + bc_lload_2 = 32, // 0x20 + bc_lload_3 = 33, // 0x21 + bc_fload_0 = 34, // 0x22 + bc_fload_1 = 35, // 0x23 + bc_fload_2 = 36, // 0x24 + bc_fload_3 = 37, // 0x25 + bc_dload_0 = 38, // 0x26 + bc_dload_1 = 39, // 0x27 + bc_dload_2 = 40, // 0x28 + bc_dload_3 = 41, // 0x29 + bc_aload_0 = 42, // 0x2a + bc_aload_1 = 43, // 0x2b + bc_aload_2 = 44, // 0x2c + bc_aload_3 = 45, // 0x2d + bc_iaload = 46, // 0x2e + bc_laload = 47, // 0x2f + bc_faload = 48, // 0x30 + bc_daload = 49, // 0x31 + bc_aaload = 50, // 0x32 + bc_baload = 51, // 0x33 + bc_caload = 52, // 0x34 + bc_saload = 53, // 0x35 + bc_istore = 54, // 0x36 + bc_lstore = 55, // 0x37 + bc_fstore = 56, // 0x38 + bc_dstore = 57, // 0x39 + bc_astore = 58, // 0x3a + bc_istore_0 = 59, // 0x3b + bc_istore_1 = 60, // 0x3c + bc_istore_2 = 61, // 0x3d + bc_istore_3 = 62, // 0x3e + bc_lstore_0 = 63, // 0x3f + bc_lstore_1 = 64, // 0x40 + bc_lstore_2 = 65, // 0x41 + bc_lstore_3 = 66, // 0x42 + bc_fstore_0 = 67, // 0x43 + bc_fstore_1 = 68, // 0x44 + bc_fstore_2 = 69, // 0x45 + bc_fstore_3 = 70, // 0x46 + bc_dstore_0 = 71, // 0x47 + bc_dstore_1 = 72, // 0x48 + bc_dstore_2 = 73, // 0x49 + bc_dstore_3 = 74, // 0x4a + bc_astore_0 = 75, // 0x4b + bc_astore_1 = 76, // 0x4c + bc_astore_2 = 77, // 0x4d + bc_astore_3 = 78, // 0x4e + bc_iastore = 79, // 0x4f + bc_lastore = 80, // 0x50 + bc_fastore = 81, // 0x51 + bc_dastore = 82, // 0x52 + bc_aastore = 83, // 0x53 + bc_bastore = 84, // 0x54 + bc_castore = 85, // 0x55 + bc_sastore = 86, // 0x56 + bc_pop = 87, // 0x57 + bc_pop2 = 88, // 0x58 + bc_dup = 89, // 0x59 + bc_dup_x1 = 90, // 0x5a + bc_dup_x2 = 91, // 0x5b + bc_dup2 = 92, // 0x5c + bc_dup2_x1 = 93, // 0x5d + bc_dup2_x2 = 94, // 0x5e + bc_swap = 95, // 0x5f + bc_iadd = 96, // 0x60 + bc_ladd = 97, // 0x61 + bc_fadd = 98, // 0x62 + bc_dadd = 99, // 0x63 + bc_isub = 100, // 0x64 + bc_lsub = 101, // 0x65 + bc_fsub = 102, // 0x66 + bc_dsub = 103, // 0x67 + bc_imul = 104, // 0x68 + bc_lmul = 105, // 0x69 + bc_fmul = 106, // 0x6a + bc_dmul = 107, // 0x6b + bc_idiv = 108, // 0x6c + bc_ldiv = 109, // 0x6d + bc_fdiv = 110, // 0x6e + bc_ddiv = 111, // 0x6f + bc_irem = 112, // 0x70 + bc_lrem = 113, // 0x71 + bc_frem = 114, // 0x72 + bc_drem = 115, // 0x73 + bc_ineg = 116, // 0x74 + bc_lneg = 117, // 0x75 + bc_fneg = 118, // 0x76 + bc_dneg = 119, // 0x77 + bc_ishl = 120, // 0x78 + bc_lshl = 121, // 0x79 + bc_ishr = 122, // 0x7a + bc_lshr = 123, // 0x7b + bc_iushr = 124, // 0x7c + bc_lushr = 125, // 0x7d + bc_iand = 126, // 0x7e + bc_land = 127, // 0x7f + bc_ior = 128, // 0x80 + bc_lor = 129, // 0x81 + bc_ixor = 130, // 0x82 + bc_lxor = 131, // 0x83 + bc_iinc = 132, // 0x84 + bc_i2l = 133, // 0x85 + bc_i2f = 134, // 0x86 + bc_i2d = 135, // 0x87 + bc_l2i = 136, // 0x88 + bc_l2f = 137, // 0x89 + bc_l2d = 138, // 0x8a + bc_f2i = 139, // 0x8b + bc_f2l = 140, // 0x8c + bc_f2d = 141, // 0x8d + bc_d2i = 142, // 0x8e + bc_d2l = 143, // 0x8f + bc_d2f = 144, // 0x90 + bc_i2b = 145, // 0x91 + bc_i2c = 146, // 0x92 + bc_i2s = 147, // 0x93 + bc_lcmp = 148, // 0x94 + bc_fcmpl = 149, // 0x95 + bc_fcmpg = 150, // 0x96 + bc_dcmpl = 151, // 0x97 + bc_dcmpg = 152, // 0x98 + bc_ifeq = 153, // 0x99 + bc_ifne = 154, // 0x9a + bc_iflt = 155, // 0x9b + bc_ifge = 156, // 0x9c + bc_ifgt = 157, // 0x9d + bc_ifle = 158, // 0x9e + bc_if_icmpeq = 159, // 0x9f + bc_if_icmpne = 160, // 0xa0 + bc_if_icmplt = 161, // 0xa1 + bc_if_icmpge = 162, // 0xa2 + bc_if_icmpgt = 163, // 0xa3 + bc_if_icmple = 164, // 0xa4 + bc_if_acmpeq = 165, // 0xa5 + bc_if_acmpne = 166, // 0xa6 + bc_goto = 167, // 0xa7 + bc_jsr = 168, // 0xa8 + bc_ret = 169, // 0xa9 + bc_tableswitch = 170, // 0xaa + bc_lookupswitch = 171, // 0xab + bc_ireturn = 172, // 0xac + bc_lreturn = 173, // 0xad + bc_freturn = 174, // 0xae + bc_dreturn = 175, // 0xaf + bc_areturn = 176, // 0xb0 + bc_return = 177, // 0xb1 + bc_getstatic = 178, // 0xb2 + bc_putstatic = 179, // 0xb3 + bc_getfield = 180, // 0xb4 + bc_putfield = 181, // 0xb5 + bc_invokevirtual = 182, // 0xb6 + bc_invokespecial = 183, // 0xb7 + bc_invokestatic = 184, // 0xb8 + bc_invokeinterface = 185, // 0xb9 + bc_xxxunusedxxx = 186, // 0xba + bc_new = 187, // 0xbb + bc_newarray = 188, // 0xbc + bc_anewarray = 189, // 0xbd + bc_arraylength = 190, // 0xbe + bc_athrow = 191, // 0xbf + bc_checkcast = 192, // 0xc0 + bc_instanceof = 193, // 0xc1 + bc_monitorenter = 194, // 0xc2 + bc_monitorexit = 195, // 0xc3 + bc_wide = 196, // 0xc4 + bc_multianewarray = 197, // 0xc5 + bc_ifnull = 198, // 0xc6 + bc_ifnonnull = 199, // 0xc7 + bc_goto_w = 200, // 0xc8 + bc_jsr_w = 201, // 0xc9 + bc_bytecode_limit = 202 // 0xca }; enum { - bc_end_marker = 255, - bc_byte_escape = 254, - bc_ref_escape = 253, - _first_linker_op = bc_getstatic, - _last_linker_op = bc_invokestatic, - _num_linker_ops = (_last_linker_op - _first_linker_op) + 1, - _self_linker_op = bc_bytecode_limit, - _self_linker_aload_flag = 1 * _num_linker_ops, - _self_linker_super_flag = 2 * _num_linker_ops, - _self_linker_limit = _self_linker_op + 4 * _num_linker_ops, - _invokeinit_op = _self_linker_limit, - _invokeinit_self_option = 0, - _invokeinit_super_option = 1, - _invokeinit_new_option = 2, - _invokeinit_limit = _invokeinit_op + 3, - _xldc_op = _invokeinit_limit, - bc_aldc = bc_ldc, - bc_cldc = _xldc_op + 0, - bc_ildc = _xldc_op + 1, - bc_fldc = _xldc_op + 2, - bc_aldc_w = bc_ldc_w, - bc_cldc_w = _xldc_op + 3, - bc_ildc_w = _xldc_op + 4, - bc_fldc_w = _xldc_op + 5, - bc_lldc2_w = bc_ldc2_w, - bc_dldc2_w = _xldc_op + 6, - _xldc_limit = _xldc_op + 7, - _xxx_3_end + bc_end_marker = 255, + bc_byte_escape = 254, + bc_ref_escape = 253, + _first_linker_op = bc_getstatic, + _last_linker_op = bc_invokestatic, + _num_linker_ops = (_last_linker_op - _first_linker_op) + 1, + _self_linker_op = bc_bytecode_limit, + _self_linker_aload_flag = 1 * _num_linker_ops, + _self_linker_super_flag = 2 * _num_linker_ops, + _self_linker_limit = _self_linker_op + 4 * _num_linker_ops, + _invokeinit_op = _self_linker_limit, + _invokeinit_self_option = 0, + _invokeinit_super_option = 1, + _invokeinit_new_option = 2, + _invokeinit_limit = _invokeinit_op + 3, + _xldc_op = _invokeinit_limit, + bc_aldc = bc_ldc, + bc_cldc = _xldc_op + 0, + bc_ildc = _xldc_op + 1, + bc_fldc = _xldc_op + 2, + bc_aldc_w = bc_ldc_w, + bc_cldc_w = _xldc_op + 3, + bc_ildc_w = _xldc_op + 4, + bc_fldc_w = _xldc_op + 5, + bc_lldc2_w = bc_ldc2_w, + bc_dldc2_w = _xldc_op + 6, + _xldc_limit = _xldc_op + 7, + _xxx_3_end }; diff --git a/libraries/pack200/src/unpack.cpp b/libraries/pack200/src/unpack.cpp index 55d253b2..9c4c633c 100644 --- a/libraries/pack200/src/unpack.cpp +++ b/libraries/pack200/src/unpack.cpp @@ -34,13 +34,7 @@ * _LP64 can be explicitly set (used on Linux). * Solaris compilers will define __sparcv9 or __x86_64 on 64bit compilations. */ -#if defined(_LP64) || defined(__sparcv9) || defined(__x86_64) -#define LONG_LONG_FORMAT "%ld" -#define LONG_LONG_HEX_FORMAT "%lx" -#else -#define LONG_LONG_FORMAT "%lld" -#define LONG_LONG_HEX_FORMAT "%016llx" -#endif +#include #include @@ -67,248 +61,248 @@ // tags, in canonical order: static const byte TAGS_IN_ORDER[] = { - CONSTANT_Utf8, CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, - CONSTANT_Double, CONSTANT_String, CONSTANT_Class, CONSTANT_Signature, - CONSTANT_NameandType, CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref}; + CONSTANT_Utf8, CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, + CONSTANT_Double, CONSTANT_String, CONSTANT_Class, CONSTANT_Signature, + CONSTANT_NameandType, CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref}; #define N_TAGS_IN_ORDER (sizeof TAGS_IN_ORDER) // REQUESTED must be -2 for u2 and REQUESTED_LDC must be -1 for u1 enum { - NOT_REQUESTED = 0, - REQUESTED = -2, - REQUESTED_LDC = -1 + NOT_REQUESTED = 0, + REQUESTED = -2, + REQUESTED_LDC = -1 }; #define NO_INORD ((uint32_t) - 1) struct entry { - byte tag; - unsigned short nrefs; // pack w/ tag - - int outputIndex; - uint32_t inord; // &cp.entries[cp.tag_base[this->tag]+this->inord] == this - - entry **refs; - - // put last to pack best - union - { - bytes b; - int i; - int64_t l; - } value; - - void requestOutputIndex(constant_pool &cp, int req = REQUESTED); - int getOutputIndex() - { - assert(outputIndex > NOT_REQUESTED); - return outputIndex; - } - - entry *ref(int refnum) - { - assert((uint32_t)refnum < nrefs); - return refs[refnum]; - } - - const char *utf8String() - { - assert(tagMatches(CONSTANT_Utf8)); - assert(value.b.len == strlen((const char *)value.b.ptr)); - return (const char *)value.b.ptr; - } - - entry *className() - { - assert(tagMatches(CONSTANT_Class)); - return ref(0); - } - - entry *memberClass() - { - assert(tagMatches(CONSTANT_Member)); - return ref(0); - } - - entry *memberDescr() - { - assert(tagMatches(CONSTANT_Member)); - return ref(1); - } - - entry *descrName() - { - assert(tagMatches(CONSTANT_NameandType)); - return ref(0); - } - - entry *descrType() - { - assert(tagMatches(CONSTANT_NameandType)); - return ref(1); - } - - int typeSize(); - - bytes &asUtf8(); - int asInteger() - { - assert(tag == CONSTANT_Integer); - return value.i; - } - - bool isUtf8(bytes &b) - { - return tagMatches(CONSTANT_Utf8) && value.b.equals(b); - } - - bool isDoubleWord() - { - return tag == CONSTANT_Double || tag == CONSTANT_Long; - } - - bool tagMatches(byte tag2) - { - return (tag2 == tag) || (tag2 == CONSTANT_Utf8 && tag == CONSTANT_Signature) || - (tag2 == CONSTANT_Literal && tag >= CONSTANT_Integer && tag <= CONSTANT_String && - tag != CONSTANT_Class) || - (tag2 == CONSTANT_Member && tag >= CONSTANT_Fieldref && - tag <= CONSTANT_InterfaceMethodref); - } + byte tag; + unsigned short nrefs; // pack w/ tag + + int outputIndex; + uint32_t inord; // &cp.entries[cp.tag_base[this->tag]+this->inord] == this + + entry **refs; + + // put last to pack best + union + { + bytes b; + int i; + int64_t l; + } value; + + void requestOutputIndex(constant_pool &cp, int req = REQUESTED); + int getOutputIndex() + { + assert(outputIndex > NOT_REQUESTED); + return outputIndex; + } + + entry *ref(int refnum) + { + assert((uint32_t)refnum < nrefs); + return refs[refnum]; + } + + const char *utf8String() + { + assert(tagMatches(CONSTANT_Utf8)); + assert(value.b.len == strlen((const char *)value.b.ptr)); + return (const char *)value.b.ptr; + } + + entry *className() + { + assert(tagMatches(CONSTANT_Class)); + return ref(0); + } + + entry *memberClass() + { + assert(tagMatches(CONSTANT_Member)); + return ref(0); + } + + entry *memberDescr() + { + assert(tagMatches(CONSTANT_Member)); + return ref(1); + } + + entry *descrName() + { + assert(tagMatches(CONSTANT_NameandType)); + return ref(0); + } + + entry *descrType() + { + assert(tagMatches(CONSTANT_NameandType)); + return ref(1); + } + + int typeSize(); + + bytes &asUtf8(); + int asInteger() + { + assert(tag == CONSTANT_Integer); + return value.i; + } + + bool isUtf8(bytes &b) + { + return tagMatches(CONSTANT_Utf8) && value.b.equals(b); + } + + bool isDoubleWord() + { + return tag == CONSTANT_Double || tag == CONSTANT_Long; + } + + bool tagMatches(byte tag2) + { + return (tag2 == tag) || (tag2 == CONSTANT_Utf8 && tag == CONSTANT_Signature) || + (tag2 == CONSTANT_Literal && tag >= CONSTANT_Integer && tag <= CONSTANT_String && + tag != CONSTANT_Class) || + (tag2 == CONSTANT_Member && tag >= CONSTANT_Fieldref && + tag <= CONSTANT_InterfaceMethodref); + } }; entry *cpindex::get(uint32_t i) { - if (i >= len) - return nullptr; - else if (base1 != nullptr) - // primary index - return &base1[i]; - else - // secondary index - return base2[i]; + if (i >= len) + return nullptr; + else if (base1 != nullptr) + // primary index + return &base1[i]; + else + // secondary index + return base2[i]; } inline bytes &entry::asUtf8() { - assert(tagMatches(CONSTANT_Utf8)); - return value.b; + assert(tagMatches(CONSTANT_Utf8)); + return value.b; } int entry::typeSize() { - assert(tagMatches(CONSTANT_Utf8)); - const char *sigp = (char *)value.b.ptr; - switch (*sigp) - { - case '(': - sigp++; - break; // skip opening '(' - case 'D': - case 'J': - return 2; // double field - default: - return 1; // field - } - int siglen = 0; - for (;;) - { - int ch = *sigp++; - switch (ch) - { - case 'D': - case 'J': - siglen += 1; - break; - case '[': - // Skip rest of array info. - while (ch == '[') - { - ch = *sigp++; - } - if (ch != 'L') - break; - // else fall through - case 'L': - sigp = strchr(sigp, ';'); - if (sigp == nullptr) - { - unpack_abort("bad data"); - return 0; - } - sigp += 1; - break; - case ')': // closing ')' - return siglen; - } - siglen += 1; - } + assert(tagMatches(CONSTANT_Utf8)); + const char *sigp = (char *)value.b.ptr; + switch (*sigp) + { + case '(': + sigp++; + break; // skip opening '(' + case 'D': + case 'J': + return 2; // double field + default: + return 1; // field + } + int siglen = 0; + for (;;) + { + int ch = *sigp++; + switch (ch) + { + case 'D': + case 'J': + siglen += 1; + break; + case '[': + // Skip rest of array info. + while (ch == '[') + { + ch = *sigp++; + } + if (ch != 'L') + break; + // else fall through + case 'L': + sigp = strchr(sigp, ';'); + if (sigp == nullptr) + { + unpack_abort("bad data"); + return 0; + } + sigp += 1; + break; + case ')': // closing ')' + return siglen; + } + siglen += 1; + } } inline cpindex *constant_pool::getFieldIndex(entry *classRef) { - assert(classRef->tagMatches(CONSTANT_Class)); - assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); - return &member_indexes[classRef->inord * 2 + 0]; + assert(classRef->tagMatches(CONSTANT_Class)); + assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); + return &member_indexes[classRef->inord * 2 + 0]; } inline cpindex *constant_pool::getMethodIndex(entry *classRef) { - assert(classRef->tagMatches(CONSTANT_Class)); - assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); - return &member_indexes[classRef->inord * 2 + 1]; + assert(classRef->tagMatches(CONSTANT_Class)); + assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); + return &member_indexes[classRef->inord * 2 + 1]; } struct inner_class { - entry *inner; - entry *outer; - entry *name; - int flags; - inner_class *next_sibling; - bool requested; + entry *inner; + entry *outer; + entry *name; + int flags; + inner_class *next_sibling; + bool requested; }; // Here is where everything gets deallocated: void unpacker::free() { - int i; - if (jarout != nullptr) - jarout->reset(); - if (gzin != nullptr) - { - gzin->free(); - gzin = nullptr; - } - if (free_input) - input.free(); - /* - * free everybody ever allocated with U_NEW or (recently) with T_NEW - */ - assert(smallbuf.base() == nullptr || mallocs.contains(smallbuf.base())); - assert(tsmallbuf.base() == nullptr || tmallocs.contains(tsmallbuf.base())); - mallocs.freeAll(); - tmallocs.freeAll(); - smallbuf.init(); - tsmallbuf.init(); - bcimap.free(); - class_fixup_type.free(); - class_fixup_offset.free(); - class_fixup_ref.free(); - code_fixup_type.free(); - code_fixup_offset.free(); - code_fixup_source.free(); - requested_ics.free(); - cur_classfile_head.free(); - cur_classfile_tail.free(); - for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) - attr_defs[i].free(); - - // free CP state - cp.outputEntries.free(); - for (i = 0; i < CONSTANT_Limit; i++) - cp.tag_extras[i].free(); + int i; + if (jarout != nullptr) + jarout->reset(); + if (gzin != nullptr) + { + gzin->free(); + gzin = nullptr; + } + if (free_input) + input.free(); + /* + * free everybody ever allocated with U_NEW or (recently) with T_NEW + */ + assert(smallbuf.base() == nullptr || mallocs.contains(smallbuf.base())); + assert(tsmallbuf.base() == nullptr || tmallocs.contains(tsmallbuf.base())); + mallocs.freeAll(); + tmallocs.freeAll(); + smallbuf.init(); + tsmallbuf.init(); + bcimap.free(); + class_fixup_type.free(); + class_fixup_offset.free(); + class_fixup_ref.free(); + code_fixup_type.free(); + code_fixup_offset.free(); + code_fixup_source.free(); + requested_ics.free(); + cur_classfile_head.free(); + cur_classfile_tail.free(); + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + attr_defs[i].free(); + + // free CP state + cp.outputEntries.free(); + for (i = 0; i < CONSTANT_Limit; i++) + cp.tag_extras[i].free(); } // input handling @@ -318,1933 +312,1933 @@ void unpacker::free() // unless rplimit hits input.limit(). bool unpacker::ensure_input(int64_t more) { - uint64_t want = more - input_remaining(); - if ((int64_t)want <= 0) - return true; // it's already in the buffer - if (rplimit == input.limit()) - return true; // not expecting any more - - if (read_input_fn == nullptr) - { - // assume it is already all there - bytes_read += input.limit() - rplimit; - rplimit = input.limit(); - return true; - } - - uint64_t remaining = (input.limit() - rplimit); // how much left to read? - byte *rpgoal = (want >= remaining) ? input.limit() : rplimit + (size_t)want; - enum - { - CHUNK_SIZE = (1 << 14) - }; - uint64_t fetch = want; - if (fetch < CHUNK_SIZE) - fetch = CHUNK_SIZE; - if (fetch > remaining * 3 / 4) - fetch = remaining; - // Try to fetch at least "more" bytes. - while ((int64_t)fetch > 0) - { - int64_t nr = (*read_input_fn)(this, rplimit, fetch, remaining); - if (nr <= 0) - { - return (rplimit >= rpgoal); - } - remaining -= nr; - rplimit += nr; - fetch -= nr; - bytes_read += nr; - assert(remaining == (uint64_t)(input.limit() - rplimit)); - } - return true; + uint64_t want = more - input_remaining(); + if ((int64_t)want <= 0) + return true; // it's already in the buffer + if (rplimit == input.limit()) + return true; // not expecting any more + + if (read_input_fn == nullptr) + { + // assume it is already all there + bytes_read += input.limit() - rplimit; + rplimit = input.limit(); + return true; + } + + uint64_t remaining = (input.limit() - rplimit); // how much left to read? + byte *rpgoal = (want >= remaining) ? input.limit() : rplimit + (size_t)want; + enum + { + CHUNK_SIZE = (1 << 14) + }; + uint64_t fetch = want; + if (fetch < CHUNK_SIZE) + fetch = CHUNK_SIZE; + if (fetch > remaining * 3 / 4) + fetch = remaining; + // Try to fetch at least "more" bytes. + while ((int64_t)fetch > 0) + { + int64_t nr = (*read_input_fn)(this, rplimit, fetch, remaining); + if (nr <= 0) + { + return (rplimit >= rpgoal); + } + remaining -= nr; + rplimit += nr; + fetch -= nr; + bytes_read += nr; + assert(remaining == (uint64_t)(input.limit() - rplimit)); + } + return true; } // output handling fillbytes *unpacker::close_output(fillbytes *which) { - assert(wp != nullptr); - if (which == nullptr) - { - if (wpbase == cur_classfile_head.base()) - { - which = &cur_classfile_head; - } - else - { - which = &cur_classfile_tail; - } - } - assert(wpbase == which->base()); - assert(wplimit == which->end()); - which->setLimit(wp); - wp = nullptr; - wplimit = nullptr; - // wpbase = nullptr; - return which; + assert(wp != nullptr); + if (which == nullptr) + { + if (wpbase == cur_classfile_head.base()) + { + which = &cur_classfile_head; + } + else + { + which = &cur_classfile_tail; + } + } + assert(wpbase == which->base()); + assert(wplimit == which->end()); + which->setLimit(wp); + wp = nullptr; + wplimit = nullptr; + // wpbase = nullptr; + return which; } // maybe_inline void unpacker::ensure_put_space(size_t size) { - if (wp + size <= wplimit) - return; - // Determine which segment needs expanding. - fillbytes *which = close_output(); - byte *wp0 = which->grow(size); - wpbase = which->base(); - wplimit = which->end(); - wp = wp0; + if (wp + size <= wplimit) + return; + // Determine which segment needs expanding. + fillbytes *which = close_output(); + byte *wp0 = which->grow(size); + wpbase = which->base(); + wplimit = which->end(); + wp = wp0; } byte *unpacker::put_space(size_t size) { - byte *wp0 = wp; - byte *wp1 = wp0 + size; - if (wp1 > wplimit) - { - ensure_put_space(size); - wp0 = wp; - wp1 = wp0 + size; - } - wp = wp1; - return wp0; + byte *wp0 = wp; + byte *wp1 = wp0 + size; + if (wp1 > wplimit) + { + ensure_put_space(size); + wp0 = wp; + wp1 = wp0 + size; + } + wp = wp1; + return wp0; } void unpacker::putu2_at(byte *wp, int n) { - if (n != (unsigned short)n) - { - unpack_abort(ERROR_OVERFLOW); - return; - } - wp[0] = (n) >> 8; - wp[1] = (n) >> 0; + if (n != (unsigned short)n) + { + unpack_abort(ERROR_OVERFLOW); + return; + } + wp[0] = (n) >> 8; + wp[1] = (n) >> 0; } void unpacker::putu4_at(byte *wp, int n) { - wp[0] = (n) >> 24; - wp[1] = (n) >> 16; - wp[2] = (n) >> 8; - wp[3] = (n) >> 0; + wp[0] = (n) >> 24; + wp[1] = (n) >> 16; + wp[2] = (n) >> 8; + wp[3] = (n) >> 0; } void unpacker::putu8_at(byte *wp, int64_t n) { - putu4_at(wp + 0, (int)((uint64_t)n >> 32)); - putu4_at(wp + 4, (int)((uint64_t)n >> 0)); + putu4_at(wp + 0, (int)((uint64_t)n >> 32)); + putu4_at(wp + 4, (int)((uint64_t)n >> 0)); } void unpacker::putu2(int n) { - putu2_at(put_space(2), n); + putu2_at(put_space(2), n); } void unpacker::putu4(int n) { - putu4_at(put_space(4), n); + putu4_at(put_space(4), n); } void unpacker::putu8(int64_t n) { - putu8_at(put_space(8), n); + putu8_at(put_space(8), n); } int unpacker::putref_index(entry *e, int size) { - if (e == nullptr) - return 0; - else if (e->outputIndex > NOT_REQUESTED) - return e->outputIndex; - else if (e->tag == CONSTANT_Signature) - return putref_index(e->ref(0), size); - else - { - e->requestOutputIndex(cp, -size); - // Later on we'll fix the bits. - class_fixup_type.addByte(size); - class_fixup_offset.add((int)wpoffset()); - class_fixup_ref.add(e); - return 0; - } + if (e == nullptr) + return 0; + else if (e->outputIndex > NOT_REQUESTED) + return e->outputIndex; + else if (e->tag == CONSTANT_Signature) + return putref_index(e->ref(0), size); + else + { + e->requestOutputIndex(cp, -size); + // Later on we'll fix the bits. + class_fixup_type.addByte(size); + class_fixup_offset.add((int)wpoffset()); + class_fixup_ref.add(e); + return 0; + } } void unpacker::putref(entry *e) { - int oidx = putref_index(e, 2); - putu2_at(put_space(2), oidx); + int oidx = putref_index(e, 2); + putu2_at(put_space(2), oidx); } void unpacker::putu1ref(entry *e) { - int oidx = putref_index(e, 1); - putu1_at(put_space(1), oidx); + int oidx = putref_index(e, 1); + putu1_at(put_space(1), oidx); } // Allocation of small and large blocks. enum { - CHUNK = (1 << 14), - SMALL = (1 << 9) + CHUNK = (1 << 14), + SMALL = (1 << 9) }; // Call malloc. Try to combine small blocks and free much later. void *unpacker::alloc_heap(size_t size, bool smallOK, bool temp) { - if (!smallOK || size > SMALL) - { - void *res = must_malloc((int)size); - (temp ? &tmallocs : &mallocs)->add(res); - return res; - } - fillbytes &xsmallbuf = *(temp ? &tsmallbuf : &smallbuf); - if (!xsmallbuf.canAppend(size + 1)) - { - xsmallbuf.init(CHUNK); - (temp ? &tmallocs : &mallocs)->add(xsmallbuf.base()); - } - int growBy = (int)size; - growBy += -growBy & 7; // round up mod 8 - return xsmallbuf.grow(growBy); + if (!smallOK || size > SMALL) + { + void *res = must_malloc((int)size); + (temp ? &tmallocs : &mallocs)->add(res); + return res; + } + fillbytes &xsmallbuf = *(temp ? &tsmallbuf : &smallbuf); + if (!xsmallbuf.canAppend(size + 1)) + { + xsmallbuf.init(CHUNK); + (temp ? &tmallocs : &mallocs)->add(xsmallbuf.base()); + } + int growBy = (int)size; + growBy += -growBy & 7; // round up mod 8 + return xsmallbuf.grow(growBy); } void unpacker::saveTo(bytes &b, byte *ptr, size_t len) { - b.ptr = U_NEW(byte, add_size(len, 1)); - b.len = len; - b.copyFrom(ptr, len); + b.ptr = U_NEW(byte, add_size(len, 1)); + b.len = len; + b.copyFrom(ptr, len); } // Read up through band_headers. // Do the archive_size dance to set the size of the input mega-buffer. void unpacker::read_file_header() { - // Read file header to determine file type and total size. - enum - { - MAGIC_BYTES = 4, - AH_LENGTH_0 = 3, // minver, majver, options are outside of archive_size - AH_LENGTH_0_MAX = AH_LENGTH_0 + 1, // options might have 2 bytes - AH_LENGTH = 26, // maximum archive header length (w/ all fields) - // Length contributions from optional header fields: - AH_FILE_HEADER_LEN = 5, // sizehi/lo/next/modtime/files - AH_ARCHIVE_SIZE_LEN = 2, // sizehi/lo only; part of AH_FILE_HEADER_LEN - AH_CP_NUMBER_LEN = 4, // int/float/long/double - AH_SPECIAL_FORMAT_LEN = 2, // layouts/band-headers - AH_LENGTH_MIN = - AH_LENGTH - (AH_FILE_HEADER_LEN + AH_SPECIAL_FORMAT_LEN + AH_CP_NUMBER_LEN), - ARCHIVE_SIZE_MIN = AH_LENGTH_MIN - (AH_LENGTH_0 + AH_ARCHIVE_SIZE_LEN), - FIRST_READ = MAGIC_BYTES + AH_LENGTH_MIN - }; - - assert(AH_LENGTH_MIN == 15); // # of UNSIGNED5 fields required after archive_magic - assert(ARCHIVE_SIZE_MIN == 10); // # of UNSIGNED5 fields required after archive_size - // An absolute minimum nullptr archive is magic[4], {minver,majver,options}[3], - // archive_size[0], cp_counts[8], class_counts[4], for a total of 19 bytes. - // (Note that archive_size is optional; it may be 0..10 bytes in length.) - // The first read must capture everything up through the options field. - // This happens to work even if {minver,majver,options} is a pathological - // 15 bytes long. Legal pack files limit those three fields to 1+1+2 bytes. - assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0 * B_MAX); - - // Up through archive_size, the largest possible archive header is - // magic[4], {minver,majver,options}[4], archive_size[10]. - // (Note only the low 12 bits of options are allowed to be non-zero.) - // In order to parse archive_size, we need at least this many bytes - // in the first read. Of course, if archive_size_hi is more than - // a byte, we probably will fail to allocate the buffer, since it - // will be many gigabytes long. This is a practical, not an - // architectural limit to Pack200 archive sizes. - assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0_MAX + 2 * B_MAX); - - bool foreign_buf = (read_input_fn == nullptr); - byte initbuf[(int)FIRST_READ + (int)C_SLOP + 200]; // 200 is for JAR I/O - if (foreign_buf) - { - // inbytes is all there is - input.set(inbytes); - rp = input.base(); - rplimit = input.limit(); - } - else - { - // inbytes, if not empty, contains some read-ahead we must use first - // ensure_input will take care of copying it into initbuf, - // then querying read_input_fn for any additional data needed. - // However, the caller must assume that we use up all of inbytes. - // There is no way to tell the caller that we used only part of them. - // Therefore, the caller must use only a bare minimum of read-ahead. - if (inbytes.len > FIRST_READ) - { - unpack_abort("too much read-ahead"); - } - input.set(initbuf, sizeof(initbuf)); - input.b.clear(); - input.b.copyFrom(inbytes); - rplimit = rp = input.base(); - rplimit += inbytes.len; - bytes_read += inbytes.len; - } - // Read only 19 bytes, which is certain to contain #archive_options fields, - // but is certain not to overflow past the archive_header. - input.b.len = FIRST_READ; - if (!ensure_input(FIRST_READ)) - unpack_abort("EOF reading archive magic number"); - - if (rp[0] == 'P' && rp[1] == 'K') - { - // In the Unix-style program, we simply simulate a copy command. - // Copy until EOF; assume the JAR file is the last segment. - fprintf(stderr, "Copy-mode.\n"); - for (;;) - { - jarout->write_data(rp, (int)input_remaining()); - if (foreign_buf) - break; // one-time use of a passed in buffer - if (input.size() < CHUNK) - { - // Get some breathing room. - input.set(U_NEW(byte, (size_t)CHUNK + C_SLOP), (size_t)CHUNK); - } - rp = rplimit = input.base(); - if (!ensure_input(1)) - break; - } - jarout->closeJarFile(false); - return; - } - - // Read the magic number. - magic = 0; - for (int i1 = 0; i1 < (int)sizeof(magic); i1++) - { - magic <<= 8; - magic += (*rp++ & 0xFF); - } - - // Read the first 3 values from the header. - value_stream hdr; - int hdrVals = 0; - int hdrValsSkipped = 0; // debug only - hdr.init(rp, rplimit, UNSIGNED5_spec); - minver = hdr.getInt(); - majver = hdr.getInt(); - hdrVals += 2; - - if (magic != (int)JAVA_PACKAGE_MAGIC || - (majver != JAVA5_PACKAGE_MAJOR_VERSION && majver != JAVA6_PACKAGE_MAJOR_VERSION) || - (minver != JAVA5_PACKAGE_MINOR_VERSION && minver != JAVA6_PACKAGE_MINOR_VERSION)) - { - char message[200]; - sprintf(message, "@" ERROR_FORMAT ": magic/ver = " - "%08X/%d.%d should be %08X/%d.%d OR %08X/%d.%d\n", - magic, majver, minver, JAVA_PACKAGE_MAGIC, JAVA5_PACKAGE_MAJOR_VERSION, - JAVA5_PACKAGE_MINOR_VERSION, JAVA_PACKAGE_MAGIC, JAVA6_PACKAGE_MAJOR_VERSION, - JAVA6_PACKAGE_MINOR_VERSION); - unpack_abort(message); - } - - archive_options = hdr.getInt(); - hdrVals += 1; - assert(hdrVals == AH_LENGTH_0); // first three fields only + // Read file header to determine file type and total size. + enum + { + MAGIC_BYTES = 4, + AH_LENGTH_0 = 3, // minver, majver, options are outside of archive_size + AH_LENGTH_0_MAX = AH_LENGTH_0 + 1, // options might have 2 bytes + AH_LENGTH = 26, // maximum archive header length (w/ all fields) + // Length contributions from optional header fields: + AH_FILE_HEADER_LEN = 5, // sizehi/lo/next/modtime/files + AH_ARCHIVE_SIZE_LEN = 2, // sizehi/lo only; part of AH_FILE_HEADER_LEN + AH_CP_NUMBER_LEN = 4, // int/float/long/double + AH_SPECIAL_FORMAT_LEN = 2, // layouts/band-headers + AH_LENGTH_MIN = + AH_LENGTH - (AH_FILE_HEADER_LEN + AH_SPECIAL_FORMAT_LEN + AH_CP_NUMBER_LEN), + ARCHIVE_SIZE_MIN = AH_LENGTH_MIN - (AH_LENGTH_0 + AH_ARCHIVE_SIZE_LEN), + FIRST_READ = MAGIC_BYTES + AH_LENGTH_MIN + }; + + assert(AH_LENGTH_MIN == 15); // # of UNSIGNED5 fields required after archive_magic + assert(ARCHIVE_SIZE_MIN == 10); // # of UNSIGNED5 fields required after archive_size + // An absolute minimum nullptr archive is magic[4], {minver,majver,options}[3], + // archive_size[0], cp_counts[8], class_counts[4], for a total of 19 bytes. + // (Note that archive_size is optional; it may be 0..10 bytes in length.) + // The first read must capture everything up through the options field. + // This happens to work even if {minver,majver,options} is a pathological + // 15 bytes long. Legal pack files limit those three fields to 1+1+2 bytes. + assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0 * B_MAX); + + // Up through archive_size, the largest possible archive header is + // magic[4], {minver,majver,options}[4], archive_size[10]. + // (Note only the low 12 bits of options are allowed to be non-zero.) + // In order to parse archive_size, we need at least this many bytes + // in the first read. Of course, if archive_size_hi is more than + // a byte, we probably will fail to allocate the buffer, since it + // will be many gigabytes long. This is a practical, not an + // architectural limit to Pack200 archive sizes. + assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0_MAX + 2 * B_MAX); + + bool foreign_buf = (read_input_fn == nullptr); + byte initbuf[(int)FIRST_READ + (int)C_SLOP + 200]; // 200 is for JAR I/O + if (foreign_buf) + { + // inbytes is all there is + input.set(inbytes); + rp = input.base(); + rplimit = input.limit(); + } + else + { + // inbytes, if not empty, contains some read-ahead we must use first + // ensure_input will take care of copying it into initbuf, + // then querying read_input_fn for any additional data needed. + // However, the caller must assume that we use up all of inbytes. + // There is no way to tell the caller that we used only part of them. + // Therefore, the caller must use only a bare minimum of read-ahead. + if (inbytes.len > FIRST_READ) + { + unpack_abort("too much read-ahead"); + } + input.set(initbuf, sizeof(initbuf)); + input.b.clear(); + input.b.copyFrom(inbytes); + rplimit = rp = input.base(); + rplimit += inbytes.len; + bytes_read += inbytes.len; + } + // Read only 19 bytes, which is certain to contain #archive_options fields, + // but is certain not to overflow past the archive_header. + input.b.len = FIRST_READ; + if (!ensure_input(FIRST_READ)) + unpack_abort("EOF reading archive magic number"); + + if (rp[0] == 'P' && rp[1] == 'K') + { + // In the Unix-style program, we simply simulate a copy command. + // Copy until EOF; assume the JAR file is the last segment. + fprintf(stderr, "Copy-mode.\n"); + for (;;) + { + jarout->write_data(rp, (int)input_remaining()); + if (foreign_buf) + break; // one-time use of a passed in buffer + if (input.size() < CHUNK) + { + // Get some breathing room. + input.set(U_NEW(byte, (size_t)CHUNK + C_SLOP), (size_t)CHUNK); + } + rp = rplimit = input.base(); + if (!ensure_input(1)) + break; + } + jarout->closeJarFile(false); + return; + } + + // Read the magic number. + magic = 0; + for (int i1 = 0; i1 < (int)sizeof(magic); i1++) + { + magic <<= 8; + magic += (*rp++ & 0xFF); + } + + // Read the first 3 values from the header. + value_stream hdr; + int hdrVals = 0; + int hdrValsSkipped = 0; // debug only + hdr.init(rp, rplimit, UNSIGNED5_spec); + minver = hdr.getInt(); + majver = hdr.getInt(); + hdrVals += 2; + + if (magic != (int)JAVA_PACKAGE_MAGIC || + (majver != JAVA5_PACKAGE_MAJOR_VERSION && majver != JAVA6_PACKAGE_MAJOR_VERSION) || + (minver != JAVA5_PACKAGE_MINOR_VERSION && minver != JAVA6_PACKAGE_MINOR_VERSION)) + { + char message[200]; + sprintf(message, "@" ERROR_FORMAT ": magic/ver = " + "%08X/%d.%d should be %08X/%d.%d OR %08X/%d.%d\n", + magic, majver, minver, JAVA_PACKAGE_MAGIC, JAVA5_PACKAGE_MAJOR_VERSION, + JAVA5_PACKAGE_MINOR_VERSION, JAVA_PACKAGE_MAGIC, JAVA6_PACKAGE_MAJOR_VERSION, + JAVA6_PACKAGE_MINOR_VERSION); + unpack_abort(message); + } + + archive_options = hdr.getInt(); + hdrVals += 1; + assert(hdrVals == AH_LENGTH_0); // first three fields only #define ORBIT(bit) | (bit) - int OPTION_LIMIT = (0 ARCHIVE_BIT_DO(ORBIT)); + int OPTION_LIMIT = (0 ARCHIVE_BIT_DO(ORBIT)); #undef ORBIT - if ((archive_options & ~OPTION_LIMIT) != 0) - { - fprintf(stderr, "Warning: Illegal archive options 0x%x\n", archive_options); - unpack_abort("illegal archive options"); - return; - } - - if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) - { - uint32_t hi = hdr.getInt(); - uint32_t lo = hdr.getInt(); - uint64_t x = band::makeLong(hi, lo); - archive_size = (size_t)x; - if (archive_size != x) - { - // Silly size specified; force overflow. - archive_size = PSIZE_MAX + 1; - } - hdrVals += 2; - } - else - { - hdrValsSkipped += 2; - } - - // Now we can size the whole archive. - // Read everything else into a mega-buffer. - rp = hdr.rp; - int header_size_0 = (int)(rp - input.base()); // used-up header (4byte + 3int) - int header_size_1 = (int)(rplimit - rp); // buffered unused initial fragment - int header_size = header_size_0 + header_size_1; - unsized_bytes_read = header_size_0; - if (foreign_buf) - { - if (archive_size > (size_t)header_size_1) - { - unpack_abort("EOF reading fixed input buffer"); - return; - } - } - else if (archive_size != 0) - { - if (archive_size < ARCHIVE_SIZE_MIN) - { - unpack_abort("impossible archive size"); // bad input data - return; - } - if (archive_size < (size_t)header_size_1) - { - unpack_abort("too much read-ahead"); // somehow we pre-fetched too much? - return; - } - input.set(U_NEW(byte, add_size(header_size_0, archive_size, C_SLOP)), - (size_t)header_size_0 + archive_size); - assert(input.limit()[0] == 0); - // Move all the bytes we read initially into the real buffer. - input.b.copyFrom(initbuf, header_size); - rp = input.b.ptr + header_size_0; - rplimit = input.b.ptr + header_size; - } - else - { - // It's more complicated and painful. - // A zero archive_size means that we must read until EOF. - input.init(CHUNK * 2); - input.b.len = input.allocated; - rp = rplimit = input.base(); - // Set up input buffer as if we already read the header: - input.b.copyFrom(initbuf, header_size); - rplimit += header_size; - while (ensure_input(input.limit() - rp)) - { - size_t dataSoFar = input_remaining(); - size_t nextSize = add_size(dataSoFar, CHUNK); - input.ensureSize(nextSize); - input.b.len = input.allocated; - rp = rplimit = input.base(); - rplimit += dataSoFar; - } - size_t dataSize = (rplimit - input.base()); - input.b.len = dataSize; - input.grow(C_SLOP); - free_input = true; // free it later - input.b.len = dataSize; - assert(input.limit()[0] == 0); - rp = rplimit = input.base(); - rplimit += dataSize; - rp += header_size_0; // already scanned these bytes... - } - live_input = true; // mark as "do not reuse" - - // read the rest of the header fields - ensure_input((AH_LENGTH - AH_LENGTH_0) * B_MAX); - hdr.rp = rp; - hdr.rplimit = rplimit; - - if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) - { - archive_next_count = hdr.getInt(); - if (archive_next_count < 0) - unpack_abort("bad archive_next_count"); - archive_modtime = hdr.getInt(); - file_count = hdr.getInt(); - if (file_count < 0) - unpack_abort("bad file_count"); - hdrVals += 3; - } - else - { - hdrValsSkipped += 3; - } - - if ((archive_options & AO_HAVE_SPECIAL_FORMATS) != 0) - { - band_headers_size = hdr.getInt(); - if (band_headers_size < 0) - unpack_abort("bad band_headers_size"); - attr_definition_count = hdr.getInt(); - if (attr_definition_count < 0) - unpack_abort("bad attr_definition_count"); - hdrVals += 2; - } - else - { - hdrValsSkipped += 2; - } - - int cp_counts[N_TAGS_IN_ORDER]; - for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) - { - if (!(archive_options & AO_HAVE_CP_NUMBERS)) - { - switch (TAGS_IN_ORDER[k]) - { - case CONSTANT_Integer: - case CONSTANT_Float: - case CONSTANT_Long: - case CONSTANT_Double: - cp_counts[k] = 0; - hdrValsSkipped += 1; - continue; - } - } - cp_counts[k] = hdr.getInt(); - if (cp_counts[k] < 0) - unpack_abort("bad cp_counts"); - hdrVals += 1; - } - - ic_count = hdr.getInt(); - if (ic_count < 0) - unpack_abort("bad ic_count"); - - default_class_minver = hdr.getInt(); - default_class_majver = hdr.getInt(); - - class_count = hdr.getInt(); - if (class_count < 0) - unpack_abort("bad class_count"); - - hdrVals += 4; - - // done with archive_header - hdrVals += hdrValsSkipped; - assert(hdrVals == AH_LENGTH); - - rp = hdr.rp; - if (rp > rplimit) - unpack_abort("EOF reading archive header"); - - // Now size the CP. - cp.init(this, cp_counts); - - default_file_modtime = archive_modtime; - if (default_file_modtime == 0 && !(archive_options & AO_HAVE_FILE_MODTIME)) - default_file_modtime = DEFAULT_ARCHIVE_MODTIME; // taken from driver - if ((archive_options & AO_DEFLATE_HINT) != 0) - default_file_options |= FO_DEFLATE_HINT; - - // meta-bytes, if any, immediately follow archive header - // band_headers.readData(band_headers_size); - ensure_input(band_headers_size); - if (input_remaining() < (size_t)band_headers_size) - { - unpack_abort("EOF reading band headers"); - return; - } - bytes band_headers; - // The "1+" allows an initial byte to be pushed on the front. - band_headers.set(1 + U_NEW(byte, 1 + band_headers_size + C_SLOP), band_headers_size); - - // Start scanning band headers here: - band_headers.copyFrom(rp, band_headers.len); - rp += band_headers.len; - assert(rp <= rplimit); - meta_rp = band_headers.ptr; - // Put evil meta-codes at the end of the band headers, - // so we are sure to throw an error if we run off the end. - bytes::of(band_headers.limit(), C_SLOP).clear(_meta_error); + if ((archive_options & ~OPTION_LIMIT) != 0) + { + fprintf(stderr, "Warning: Illegal archive options 0x%x\n", archive_options); + unpack_abort("illegal archive options"); + return; + } + + if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) + { + uint32_t hi = hdr.getInt(); + uint32_t lo = hdr.getInt(); + uint64_t x = band::makeLong(hi, lo); + archive_size = (size_t)x; + if (archive_size != x) + { + // Silly size specified; force overflow. + archive_size = PSIZE_MAX + 1; + } + hdrVals += 2; + } + else + { + hdrValsSkipped += 2; + } + + // Now we can size the whole archive. + // Read everything else into a mega-buffer. + rp = hdr.rp; + int header_size_0 = (int)(rp - input.base()); // used-up header (4byte + 3int) + int header_size_1 = (int)(rplimit - rp); // buffered unused initial fragment + int header_size = header_size_0 + header_size_1; + unsized_bytes_read = header_size_0; + if (foreign_buf) + { + if (archive_size > (size_t)header_size_1) + { + unpack_abort("EOF reading fixed input buffer"); + return; + } + } + else if (archive_size != 0) + { + if (archive_size < ARCHIVE_SIZE_MIN) + { + unpack_abort("impossible archive size"); // bad input data + return; + } + if (archive_size < (size_t)header_size_1) + { + unpack_abort("too much read-ahead"); // somehow we pre-fetched too much? + return; + } + input.set(U_NEW(byte, add_size(header_size_0, archive_size, C_SLOP)), + (size_t)header_size_0 + archive_size); + assert(input.limit()[0] == 0); + // Move all the bytes we read initially into the real buffer. + input.b.copyFrom(initbuf, header_size); + rp = input.b.ptr + header_size_0; + rplimit = input.b.ptr + header_size; + } + else + { + // It's more complicated and painful. + // A zero archive_size means that we must read until EOF. + input.init(CHUNK * 2); + input.b.len = input.allocated; + rp = rplimit = input.base(); + // Set up input buffer as if we already read the header: + input.b.copyFrom(initbuf, header_size); + rplimit += header_size; + while (ensure_input(input.limit() - rp)) + { + size_t dataSoFar = input_remaining(); + size_t nextSize = add_size(dataSoFar, CHUNK); + input.ensureSize(nextSize); + input.b.len = input.allocated; + rp = rplimit = input.base(); + rplimit += dataSoFar; + } + size_t dataSize = (rplimit - input.base()); + input.b.len = dataSize; + input.grow(C_SLOP); + free_input = true; // free it later + input.b.len = dataSize; + assert(input.limit()[0] == 0); + rp = rplimit = input.base(); + rplimit += dataSize; + rp += header_size_0; // already scanned these bytes... + } + live_input = true; // mark as "do not reuse" + + // read the rest of the header fields + ensure_input((AH_LENGTH - AH_LENGTH_0) * B_MAX); + hdr.rp = rp; + hdr.rplimit = rplimit; + + if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) + { + archive_next_count = hdr.getInt(); + if (archive_next_count < 0) + unpack_abort("bad archive_next_count"); + archive_modtime = hdr.getInt(); + file_count = hdr.getInt(); + if (file_count < 0) + unpack_abort("bad file_count"); + hdrVals += 3; + } + else + { + hdrValsSkipped += 3; + } + + if ((archive_options & AO_HAVE_SPECIAL_FORMATS) != 0) + { + band_headers_size = hdr.getInt(); + if (band_headers_size < 0) + unpack_abort("bad band_headers_size"); + attr_definition_count = hdr.getInt(); + if (attr_definition_count < 0) + unpack_abort("bad attr_definition_count"); + hdrVals += 2; + } + else + { + hdrValsSkipped += 2; + } + + int cp_counts[N_TAGS_IN_ORDER]; + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + if (!(archive_options & AO_HAVE_CP_NUMBERS)) + { + switch (TAGS_IN_ORDER[k]) + { + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_Long: + case CONSTANT_Double: + cp_counts[k] = 0; + hdrValsSkipped += 1; + continue; + } + } + cp_counts[k] = hdr.getInt(); + if (cp_counts[k] < 0) + unpack_abort("bad cp_counts"); + hdrVals += 1; + } + + ic_count = hdr.getInt(); + if (ic_count < 0) + unpack_abort("bad ic_count"); + + default_class_minver = hdr.getInt(); + default_class_majver = hdr.getInt(); + + class_count = hdr.getInt(); + if (class_count < 0) + unpack_abort("bad class_count"); + + hdrVals += 4; + + // done with archive_header + hdrVals += hdrValsSkipped; + assert(hdrVals == AH_LENGTH); + + rp = hdr.rp; + if (rp > rplimit) + unpack_abort("EOF reading archive header"); + + // Now size the CP. + cp.init(this, cp_counts); + + default_file_modtime = archive_modtime; + if (default_file_modtime == 0 && !(archive_options & AO_HAVE_FILE_MODTIME)) + default_file_modtime = DEFAULT_ARCHIVE_MODTIME; // taken from driver + if ((archive_options & AO_DEFLATE_HINT) != 0) + default_file_options |= FO_DEFLATE_HINT; + + // meta-bytes, if any, immediately follow archive header + // band_headers.readData(band_headers_size); + ensure_input(band_headers_size); + if (input_remaining() < (size_t)band_headers_size) + { + unpack_abort("EOF reading band headers"); + return; + } + bytes band_headers; + // The "1+" allows an initial byte to be pushed on the front. + band_headers.set(1 + U_NEW(byte, 1 + band_headers_size + C_SLOP), band_headers_size); + + // Start scanning band headers here: + band_headers.copyFrom(rp, band_headers.len); + rp += band_headers.len; + assert(rp <= rplimit); + meta_rp = band_headers.ptr; + // Put evil meta-codes at the end of the band headers, + // so we are sure to throw an error if we run off the end. + bytes::of(band_headers.limit(), C_SLOP).clear(_meta_error); } void unpacker::finish() { - if (verbose >= 1) - { - fprintf(stderr, "A total of " LONG_LONG_FORMAT " bytes were read in %d segment(s).\n", - (bytes_read_before_reset + bytes_read), segments_read_before_reset + 1); - fprintf(stderr, "A total of " LONG_LONG_FORMAT " file content bytes were written.\n", - (bytes_written_before_reset + bytes_written)); - fprintf(stderr, - "A total of %d files (of which %d are classes) were written to output.\n", - files_written_before_reset + files_written, - classes_written_before_reset + classes_written); - } - if (jarout != nullptr) - jarout->closeJarFile(true); + if (verbose >= 1) + { + fprintf(stderr, "A total of %" PRIu64 " bytes were read in %d segment(s).\n", + (bytes_read_before_reset + bytes_read), segments_read_before_reset + 1); + fprintf(stderr, "A total of %" PRIu64 " file content bytes were written.\n", + (bytes_written_before_reset + bytes_written)); + fprintf(stderr, + "A total of %d files (of which %d are classes) were written to output.\n", + files_written_before_reset + files_written, + classes_written_before_reset + classes_written); + } + if (jarout != nullptr) + jarout->closeJarFile(true); } // Cf. PackageReader.readConstantPoolCounts void constant_pool::init(unpacker *u_, int counts[NUM_COUNTS]) { - this->u = u_; - - // Fill-pointer for CP. - int next_entry = 0; - - // Size the constant pool: - for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) - { - byte tag = TAGS_IN_ORDER[k]; - int len = counts[k]; - tag_count[tag] = len; - tag_base[tag] = next_entry; - next_entry += len; - // Detect and defend against constant pool size overflow. - // (Pack200 forbids the sum of CP counts to exceed 2^29-1.) - enum - { - CP_SIZE_LIMIT = (1 << 29), - IMPLICIT_ENTRY_COUNT = 1 // empty Utf8 string - }; - if (len >= (1 << 29) || len < 0 || next_entry >= CP_SIZE_LIMIT + IMPLICIT_ENTRY_COUNT) - { - unpack_abort("archive too large: constant pool limit exceeded"); - } - } - - // Close off the end of the CP: - nentries = next_entry; - - // place a limit on future CP growth: - int generous = 0; - generous = add_size(generous, u->ic_count); // implicit name - generous = add_size(generous, u->ic_count); // outer - generous = add_size(generous, u->ic_count); // outer.utf8 - generous = add_size(generous, 40); // WKUs, misc - generous = add_size(generous, u->class_count); // implicit SourceFile strings - maxentries = add_size(nentries, generous); - - // Note that this CP does not include "empty" entries - // for longs and doubles. Those are introduced when - // the entries are renumbered for classfile output. - - entries = U_NEW(entry, maxentries); - - first_extra_entry = &entries[nentries]; - - // Initialize the standard indexes. - tag_count[CONSTANT_All] = nentries; - tag_base[CONSTANT_All] = 0; - for (int tag = 0; tag < CONSTANT_Limit; tag++) - { - entry *cpMap = &entries[tag_base[tag]]; - tag_index[tag].init(tag_count[tag], cpMap, tag); - } - - // Initialize hashTab to a generous power-of-two size. - uint32_t pow2 = 1; - uint32_t target = maxentries + maxentries / 2; // 60% full - while (pow2 < target) - pow2 <<= 1; - hashTab = U_NEW(entry *, hashTabLength = pow2); + this->u = u_; + + // Fill-pointer for CP. + int next_entry = 0; + + // Size the constant pool: + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + byte tag = TAGS_IN_ORDER[k]; + int len = counts[k]; + tag_count[tag] = len; + tag_base[tag] = next_entry; + next_entry += len; + // Detect and defend against constant pool size overflow. + // (Pack200 forbids the sum of CP counts to exceed 2^29-1.) + enum + { + CP_SIZE_LIMIT = (1 << 29), + IMPLICIT_ENTRY_COUNT = 1 // empty Utf8 string + }; + if (len >= (1 << 29) || len < 0 || next_entry >= CP_SIZE_LIMIT + IMPLICIT_ENTRY_COUNT) + { + unpack_abort("archive too large: constant pool limit exceeded"); + } + } + + // Close off the end of the CP: + nentries = next_entry; + + // place a limit on future CP growth: + int generous = 0; + generous = add_size(generous, u->ic_count); // implicit name + generous = add_size(generous, u->ic_count); // outer + generous = add_size(generous, u->ic_count); // outer.utf8 + generous = add_size(generous, 40); // WKUs, misc + generous = add_size(generous, u->class_count); // implicit SourceFile strings + maxentries = add_size(nentries, generous); + + // Note that this CP does not include "empty" entries + // for longs and doubles. Those are introduced when + // the entries are renumbered for classfile output. + + entries = U_NEW(entry, maxentries); + + first_extra_entry = &entries[nentries]; + + // Initialize the standard indexes. + tag_count[CONSTANT_All] = nentries; + tag_base[CONSTANT_All] = 0; + for (int tag = 0; tag < CONSTANT_Limit; tag++) + { + entry *cpMap = &entries[tag_base[tag]]; + tag_index[tag].init(tag_count[tag], cpMap, tag); + } + + // Initialize hashTab to a generous power-of-two size. + uint32_t pow2 = 1; + uint32_t target = maxentries + maxentries / 2; // 60% full + while (pow2 < target) + pow2 <<= 1; + hashTab = U_NEW(entry *, hashTabLength = pow2); } static byte *store_Utf8_char(byte *cp, unsigned short ch) { - if (ch >= 0x001 && ch <= 0x007F) - { - *cp++ = (byte)ch; - } - else if (ch <= 0x07FF) - { - *cp++ = (byte)(0xC0 | ((ch >> 6) & 0x1F)); - *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); - } - else - { - *cp++ = (byte)(0xE0 | ((ch >> 12) & 0x0F)); - *cp++ = (byte)(0x80 | ((ch >> 6) & 0x3F)); - *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); - } - return cp; + if (ch >= 0x001 && ch <= 0x007F) + { + *cp++ = (byte)ch; + } + else if (ch <= 0x07FF) + { + *cp++ = (byte)(0xC0 | ((ch >> 6) & 0x1F)); + *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); + } + else + { + *cp++ = (byte)(0xE0 | ((ch >> 12) & 0x0F)); + *cp++ = (byte)(0x80 | ((ch >> 6) & 0x3F)); + *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); + } + return cp; } static byte *skip_Utf8_chars(byte *cp, int len) { - for (;; cp++) - { - int ch = *cp & 0xFF; - if ((ch & 0xC0) != 0x80) - { - if (len-- == 0) - return cp; - if (ch < 0x80 && len == 0) - return cp + 1; - } - } + for (;; cp++) + { + int ch = *cp & 0xFF; + if ((ch & 0xC0) != 0x80) + { + if (len-- == 0) + return cp; + if (ch < 0x80 && len == 0) + return cp + 1; + } + } } static int compare_Utf8_chars(bytes &b1, bytes &b2) { - int l1 = (int)b1.len; - int l2 = (int)b2.len; - int l0 = (l1 < l2) ? l1 : l2; - byte *p1 = b1.ptr; - byte *p2 = b2.ptr; - int c0 = 0; - for (int i = 0; i < l0; i++) - { - int c1 = p1[i] & 0xFF; - int c2 = p2[i] & 0xFF; - if (c1 != c2) - { - // Before returning the obvious answer, - // check to see if c1 or c2 is part of a 0x0000, - // which encodes as {0xC0,0x80}. The 0x0000 is the - // lowest-sorting Java char value, and yet it encodes - // as if it were the first char after 0x7F, which causes - // strings containing nulls to sort too high. All other - // comparisons are consistent between Utf8 and Java chars. - if (c1 == 0xC0 && (p1[i + 1] & 0xFF) == 0x80) - c1 = 0; - if (c2 == 0xC0 && (p2[i + 1] & 0xFF) == 0x80) - c2 = 0; - if (c0 == 0xC0) - { - assert(((c1 | c2) & 0xC0) == 0x80); // c1 & c2 are extension chars - if (c1 == 0x80) - c1 = 0; // will sort below c2 - if (c2 == 0x80) - c2 = 0; // will sort below c1 - } - return c1 - c2; - } - c0 = c1; // save away previous char - } - // common prefix is identical; return length difference if any - return l1 - l2; + int l1 = (int)b1.len; + int l2 = (int)b2.len; + int l0 = (l1 < l2) ? l1 : l2; + byte *p1 = b1.ptr; + byte *p2 = b2.ptr; + int c0 = 0; + for (int i = 0; i < l0; i++) + { + int c1 = p1[i] & 0xFF; + int c2 = p2[i] & 0xFF; + if (c1 != c2) + { + // Before returning the obvious answer, + // check to see if c1 or c2 is part of a 0x0000, + // which encodes as {0xC0,0x80}. The 0x0000 is the + // lowest-sorting Java char value, and yet it encodes + // as if it were the first char after 0x7F, which causes + // strings containing nulls to sort too high. All other + // comparisons are consistent between Utf8 and Java chars. + if (c1 == 0xC0 && (p1[i + 1] & 0xFF) == 0x80) + c1 = 0; + if (c2 == 0xC0 && (p2[i + 1] & 0xFF) == 0x80) + c2 = 0; + if (c0 == 0xC0) + { + assert(((c1 | c2) & 0xC0) == 0x80); // c1 & c2 are extension chars + if (c1 == 0x80) + c1 = 0; // will sort below c2 + if (c2 == 0x80) + c2 = 0; // will sort below c1 + } + return c1 - c2; + } + c0 = c1; // save away previous char + } + // common prefix is identical; return length difference if any + return l1 - l2; } // Cf. PackageReader.readUtf8Bands void unpacker::read_Utf8_values(entry *cpMap, int len) { - // Implicit first Utf8 string is the empty string. - enum - { - // certain bands begin with implicit zeroes - PREFIX_SKIP_2 = 2, - SUFFIX_SKIP_1 = 1 - }; - - int i; - - // First band: Read lengths of shared prefixes. - if (len > PREFIX_SKIP_2) - cp_Utf8_prefix.readData(len - PREFIX_SKIP_2); - - // Second band: Read lengths of unshared suffixes: - if (len > SUFFIX_SKIP_1) - cp_Utf8_suffix.readData(len - SUFFIX_SKIP_1); - - bytes *allsuffixes = T_NEW(bytes, len); - - int nbigsuf = 0; - fillbytes charbuf; // buffer to allocate small strings - charbuf.init(); - - // Third band: Read the char values in the unshared suffixes: - cp_Utf8_chars.readData(cp_Utf8_suffix.getIntTotal()); - for (i = 0; i < len; i++) - { - int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); - if (suffix < 0) - { - unpack_abort("bad utf8 suffix"); - } - if (suffix == 0 && i >= SUFFIX_SKIP_1) - { - // chars are packed in cp_Utf8_big_chars - nbigsuf += 1; - continue; - } - bytes &chars = allsuffixes[i]; - uint32_t size3 = suffix * 3; // max Utf8 length - bool isMalloc = (suffix > SMALL); - if (isMalloc) - { - chars.malloc(size3); - } - else - { - if (!charbuf.canAppend(size3 + 1)) - { - assert(charbuf.allocated == 0 || tmallocs.contains(charbuf.base())); - charbuf.init(CHUNK); // Reset to new buffer. - tmallocs.add(charbuf.base()); - } - chars.set(charbuf.grow(size3 + 1), size3); - } - - byte *chp = chars.ptr; - for (int j = 0; j < suffix; j++) - { - unsigned short ch = cp_Utf8_chars.getInt(); - chp = store_Utf8_char(chp, ch); - } - // shrink to fit: - if (isMalloc) - { - chars.realloc(chp - chars.ptr); - tmallocs.add(chars.ptr); // free it later - } - else - { - int shrink = (int)(chars.limit() - chp); - chars.len -= shrink; - charbuf.b.len -= shrink; // ungrow to reclaim buffer space - // Note that we did not reclaim the final '\0'. - assert(chars.limit() == charbuf.limit() - 1); - assert(strlen((char *)chars.ptr) == chars.len); - } - } - // cp_Utf8_chars.done(); - - // Fourth band: Go back and size the specially packed strings. - int maxlen = 0; - cp_Utf8_big_suffix.readData(nbigsuf); - cp_Utf8_suffix.rewind(); - for (i = 0; i < len; i++) - { - int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); - int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); - if (prefix < 0 || prefix + suffix < 0) - { - unpack_abort("bad utf8 prefix"); - } - bytes &chars = allsuffixes[i]; - if (suffix == 0 && i >= SUFFIX_SKIP_1) - { - suffix = cp_Utf8_big_suffix.getInt(); - assert(chars.ptr == nullptr); - chars.len = suffix; // just a momentary hack - } - else - { - assert(chars.ptr != nullptr); - } - if (maxlen < prefix + suffix) - { - maxlen = prefix + suffix; - } - } - // cp_Utf8_suffix.done(); // will use allsuffixes[i].len (ptr!=nullptr) - // cp_Utf8_big_suffix.done(); // will use allsuffixes[i].len - - // Fifth band(s): Get the specially packed characters. - cp_Utf8_big_suffix.rewind(); - for (i = 0; i < len; i++) - { - bytes &chars = allsuffixes[i]; - if (chars.ptr != nullptr) - continue; // already input - int suffix = (int)chars.len; // pick up the hack - uint32_t size3 = suffix * 3; - if (suffix == 0) - continue; // done with empty string - chars.malloc(size3); - byte *chp = chars.ptr; - band saved_band = cp_Utf8_big_chars; - cp_Utf8_big_chars.readData(suffix); - for (int j = 0; j < suffix; j++) - { - unsigned short ch = cp_Utf8_big_chars.getInt(); - chp = store_Utf8_char(chp, ch); - } - chars.realloc(chp - chars.ptr); - tmallocs.add(chars.ptr); // free it later - // cp_Utf8_big_chars.done(); - cp_Utf8_big_chars = saved_band; // reset the band for the next string - } - cp_Utf8_big_chars.readData(0); // zero chars - // cp_Utf8_big_chars.done(); - - // Finally, sew together all the prefixes and suffixes. - bytes bigbuf; - bigbuf.malloc(maxlen * 3 + 1); // max Utf8 length, plus slop for nullptr - int prevlen = 0; // previous string length (in chars) - tmallocs.add(bigbuf.ptr); // free after this block - cp_Utf8_prefix.rewind(); - for (i = 0; i < len; i++) - { - bytes &chars = allsuffixes[i]; - int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); - int suffix = (int)chars.len; - byte *fillp; - // by induction, the buffer is already filled with the prefix - // make sure the prefix value is not corrupted, though: - if (prefix > prevlen) - { - unpack_abort("utf8 prefix overflow"); - return; - } - fillp = skip_Utf8_chars(bigbuf.ptr, prefix); - // copy the suffix into the same buffer: - fillp = chars.writeTo(fillp); - assert(bigbuf.inBounds(fillp)); - *fillp = 0; // bigbuf must contain a well-formed Utf8 string - int length = (int)(fillp - bigbuf.ptr); - bytes &value = cpMap[i].value.b; - value.set(U_NEW(byte, add_size(length, 1)), length); - value.copyFrom(bigbuf.ptr, length); - // Index all Utf8 strings - entry *&htref = cp.hashTabRef(CONSTANT_Utf8, value); - if (htref == nullptr) - { - // Note that if two identical strings are transmitted, - // the first is taken to be the canonical one. - htref = &cpMap[i]; - } - prevlen = prefix + suffix; - } - // cp_Utf8_prefix.done(); - - // Free intermediate buffers. - free_temps(); + // Implicit first Utf8 string is the empty string. + enum + { + // certain bands begin with implicit zeroes + PREFIX_SKIP_2 = 2, + SUFFIX_SKIP_1 = 1 + }; + + int i; + + // First band: Read lengths of shared prefixes. + if (len > PREFIX_SKIP_2) + cp_Utf8_prefix.readData(len - PREFIX_SKIP_2); + + // Second band: Read lengths of unshared suffixes: + if (len > SUFFIX_SKIP_1) + cp_Utf8_suffix.readData(len - SUFFIX_SKIP_1); + + bytes *allsuffixes = T_NEW(bytes, len); + + int nbigsuf = 0; + fillbytes charbuf; // buffer to allocate small strings + charbuf.init(); + + // Third band: Read the char values in the unshared suffixes: + cp_Utf8_chars.readData(cp_Utf8_suffix.getIntTotal()); + for (i = 0; i < len; i++) + { + int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); + if (suffix < 0) + { + unpack_abort("bad utf8 suffix"); + } + if (suffix == 0 && i >= SUFFIX_SKIP_1) + { + // chars are packed in cp_Utf8_big_chars + nbigsuf += 1; + continue; + } + bytes &chars = allsuffixes[i]; + uint32_t size3 = suffix * 3; // max Utf8 length + bool isMalloc = (suffix > SMALL); + if (isMalloc) + { + chars.malloc(size3); + } + else + { + if (!charbuf.canAppend(size3 + 1)) + { + assert(charbuf.allocated == 0 || tmallocs.contains(charbuf.base())); + charbuf.init(CHUNK); // Reset to new buffer. + tmallocs.add(charbuf.base()); + } + chars.set(charbuf.grow(size3 + 1), size3); + } + + byte *chp = chars.ptr; + for (int j = 0; j < suffix; j++) + { + unsigned short ch = cp_Utf8_chars.getInt(); + chp = store_Utf8_char(chp, ch); + } + // shrink to fit: + if (isMalloc) + { + chars.realloc(chp - chars.ptr); + tmallocs.add(chars.ptr); // free it later + } + else + { + int shrink = (int)(chars.limit() - chp); + chars.len -= shrink; + charbuf.b.len -= shrink; // ungrow to reclaim buffer space + // Note that we did not reclaim the final '\0'. + assert(chars.limit() == charbuf.limit() - 1); + assert(strlen((char *)chars.ptr) == chars.len); + } + } + // cp_Utf8_chars.done(); + + // Fourth band: Go back and size the specially packed strings. + int maxlen = 0; + cp_Utf8_big_suffix.readData(nbigsuf); + cp_Utf8_suffix.rewind(); + for (i = 0; i < len; i++) + { + int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); + int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); + if (prefix < 0 || prefix + suffix < 0) + { + unpack_abort("bad utf8 prefix"); + } + bytes &chars = allsuffixes[i]; + if (suffix == 0 && i >= SUFFIX_SKIP_1) + { + suffix = cp_Utf8_big_suffix.getInt(); + assert(chars.ptr == nullptr); + chars.len = suffix; // just a momentary hack + } + else + { + assert(chars.ptr != nullptr); + } + if (maxlen < prefix + suffix) + { + maxlen = prefix + suffix; + } + } + // cp_Utf8_suffix.done(); // will use allsuffixes[i].len (ptr!=nullptr) + // cp_Utf8_big_suffix.done(); // will use allsuffixes[i].len + + // Fifth band(s): Get the specially packed characters. + cp_Utf8_big_suffix.rewind(); + for (i = 0; i < len; i++) + { + bytes &chars = allsuffixes[i]; + if (chars.ptr != nullptr) + continue; // already input + int suffix = (int)chars.len; // pick up the hack + uint32_t size3 = suffix * 3; + if (suffix == 0) + continue; // done with empty string + chars.malloc(size3); + byte *chp = chars.ptr; + band saved_band = cp_Utf8_big_chars; + cp_Utf8_big_chars.readData(suffix); + for (int j = 0; j < suffix; j++) + { + unsigned short ch = cp_Utf8_big_chars.getInt(); + chp = store_Utf8_char(chp, ch); + } + chars.realloc(chp - chars.ptr); + tmallocs.add(chars.ptr); // free it later + // cp_Utf8_big_chars.done(); + cp_Utf8_big_chars = saved_band; // reset the band for the next string + } + cp_Utf8_big_chars.readData(0); // zero chars + // cp_Utf8_big_chars.done(); + + // Finally, sew together all the prefixes and suffixes. + bytes bigbuf; + bigbuf.malloc(maxlen * 3 + 1); // max Utf8 length, plus slop for nullptr + int prevlen = 0; // previous string length (in chars) + tmallocs.add(bigbuf.ptr); // free after this block + cp_Utf8_prefix.rewind(); + for (i = 0; i < len; i++) + { + bytes &chars = allsuffixes[i]; + int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); + int suffix = (int)chars.len; + byte *fillp; + // by induction, the buffer is already filled with the prefix + // make sure the prefix value is not corrupted, though: + if (prefix > prevlen) + { + unpack_abort("utf8 prefix overflow"); + return; + } + fillp = skip_Utf8_chars(bigbuf.ptr, prefix); + // copy the suffix into the same buffer: + fillp = chars.writeTo(fillp); + assert(bigbuf.inBounds(fillp)); + *fillp = 0; // bigbuf must contain a well-formed Utf8 string + int length = (int)(fillp - bigbuf.ptr); + bytes &value = cpMap[i].value.b; + value.set(U_NEW(byte, add_size(length, 1)), length); + value.copyFrom(bigbuf.ptr, length); + // Index all Utf8 strings + entry *&htref = cp.hashTabRef(CONSTANT_Utf8, value); + if (htref == nullptr) + { + // Note that if two identical strings are transmitted, + // the first is taken to be the canonical one. + htref = &cpMap[i]; + } + prevlen = prefix + suffix; + } + // cp_Utf8_prefix.done(); + + // Free intermediate buffers. + free_temps(); } void unpacker::read_single_words(band &cp_band, entry *cpMap, int len) { - cp_band.readData(len); - for (int i = 0; i < len; i++) - { - cpMap[i].value.i = cp_band.getInt(); // coding handles signs OK - } + cp_band.readData(len); + for (int i = 0; i < len; i++) + { + cpMap[i].value.i = cp_band.getInt(); // coding handles signs OK + } } void unpacker::read_double_words(band &cp_bands, entry *cpMap, int len) { - band &cp_band_hi = cp_bands; - band &cp_band_lo = cp_bands.nextBand(); - cp_band_hi.readData(len); - cp_band_lo.readData(len); - for (int i = 0; i < len; i++) - { - cpMap[i].value.l = cp_band_hi.getLong(cp_band_lo, true); - } - // cp_band_hi.done(); - // cp_band_lo.done(); + band &cp_band_hi = cp_bands; + band &cp_band_lo = cp_bands.nextBand(); + cp_band_hi.readData(len); + cp_band_lo.readData(len); + for (int i = 0; i < len; i++) + { + cpMap[i].value.l = cp_band_hi.getLong(cp_band_lo, true); + } + // cp_band_hi.done(); + // cp_band_lo.done(); } void unpacker::read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len) { - assert(refTag == CONSTANT_Utf8); - cp_band.setIndexByTag(refTag); - cp_band.readData(len); - int indexTag = (cp_band.bn == e_cp_Class) ? CONSTANT_Class : 0; - for (int i = 0; i < len; i++) - { - entry &e = cpMap[i]; - e.refs = U_NEW(entry *, e.nrefs = 1); - entry *utf = cp_band.getRef(); - e.refs[0] = utf; - e.value.b = utf->value.b; // copy value of Utf8 string to self - if (indexTag != 0) - { - // Maintain cross-reference: - entry *&htref = cp.hashTabRef(indexTag, e.value.b); - if (htref == nullptr) - { - // Note that if two identical classes are transmitted, - // the first is taken to be the canonical one. - htref = &e; - } - } - } - // cp_band.done(); + assert(refTag == CONSTANT_Utf8); + cp_band.setIndexByTag(refTag); + cp_band.readData(len); + int indexTag = (cp_band.bn == e_cp_Class) ? CONSTANT_Class : 0; + for (int i = 0; i < len; i++) + { + entry &e = cpMap[i]; + e.refs = U_NEW(entry *, e.nrefs = 1); + entry *utf = cp_band.getRef(); + e.refs[0] = utf; + e.value.b = utf->value.b; // copy value of Utf8 string to self + if (indexTag != 0) + { + // Maintain cross-reference: + entry *&htref = cp.hashTabRef(indexTag, e.value.b); + if (htref == nullptr) + { + // Note that if two identical classes are transmitted, + // the first is taken to be the canonical one. + htref = &e; + } + } + } + // cp_band.done(); } void unpacker::read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, - int len) + int len) { - band &cp_band1 = cp_band; - band &cp_band2 = cp_band.nextBand(); - cp_band1.setIndexByTag(ref1Tag); - cp_band2.setIndexByTag(ref2Tag); - cp_band1.readData(len); - cp_band2.readData(len); - for (int i = 0; i < len; i++) - { - entry &e = cpMap[i]; - e.refs = U_NEW(entry *, e.nrefs = 2); - e.refs[0] = cp_band1.getRef(); - e.refs[1] = cp_band2.getRef(); - } - // cp_band1.done(); - // cp_band2.done(); + band &cp_band1 = cp_band; + band &cp_band2 = cp_band.nextBand(); + cp_band1.setIndexByTag(ref1Tag); + cp_band2.setIndexByTag(ref2Tag); + cp_band1.readData(len); + cp_band2.readData(len); + for (int i = 0; i < len; i++) + { + entry &e = cpMap[i]; + e.refs = U_NEW(entry *, e.nrefs = 2); + e.refs[0] = cp_band1.getRef(); + e.refs[1] = cp_band2.getRef(); + } + // cp_band1.done(); + // cp_band2.done(); } // Cf. PackageReader.readSignatureBands void unpacker::read_signature_values(entry *cpMap, int len) { - cp_Signature_form.setIndexByTag(CONSTANT_Utf8); - cp_Signature_form.readData(len); - int ncTotal = 0; - int i; - for (i = 0; i < len; i++) - { - entry &e = cpMap[i]; - entry &form = *cp_Signature_form.getRef(); - int nc = 0; - - for (const char *ncp = form.utf8String(); *ncp; ncp++) - { - if (*ncp == 'L') - nc++; - } - - ncTotal += nc; - e.refs = U_NEW(entry *, cpMap[i].nrefs = 1 + nc); - e.refs[0] = &form; - } - // cp_Signature_form.done(); - cp_Signature_classes.setIndexByTag(CONSTANT_Class); - cp_Signature_classes.readData(ncTotal); - for (i = 0; i < len; i++) - { - entry &e = cpMap[i]; - for (int j = 1; j < e.nrefs; j++) - { - e.refs[j] = cp_Signature_classes.getRef(); - } - } - // cp_Signature_classes.done(); + cp_Signature_form.setIndexByTag(CONSTANT_Utf8); + cp_Signature_form.readData(len); + int ncTotal = 0; + int i; + for (i = 0; i < len; i++) + { + entry &e = cpMap[i]; + entry &form = *cp_Signature_form.getRef(); + int nc = 0; + + for (const char *ncp = form.utf8String(); *ncp; ncp++) + { + if (*ncp == 'L') + nc++; + } + + ncTotal += nc; + e.refs = U_NEW(entry *, cpMap[i].nrefs = 1 + nc); + e.refs[0] = &form; + } + // cp_Signature_form.done(); + cp_Signature_classes.setIndexByTag(CONSTANT_Class); + cp_Signature_classes.readData(ncTotal); + for (i = 0; i < len; i++) + { + entry &e = cpMap[i]; + for (int j = 1; j < e.nrefs; j++) + { + e.refs[j] = cp_Signature_classes.getRef(); + } + } + // cp_Signature_classes.done(); } // Cf. PackageReader.readConstantPool void unpacker::read_cp() { - int i; - - for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) - { - byte tag = TAGS_IN_ORDER[k]; - int len = cp.tag_count[tag]; - int base = cp.tag_base[tag]; - - entry *cpMap = &cp.entries[base]; - for (i = 0; i < len; i++) - { - cpMap[i].tag = tag; - cpMap[i].inord = i; - } - - switch (tag) - { - case CONSTANT_Utf8: - read_Utf8_values(cpMap, len); - break; - case CONSTANT_Integer: - read_single_words(cp_Int, cpMap, len); - break; - case CONSTANT_Float: - read_single_words(cp_Float, cpMap, len); - break; - case CONSTANT_Long: - read_double_words(cp_Long_hi /*& cp_Long_lo*/, cpMap, len); - break; - case CONSTANT_Double: - read_double_words(cp_Double_hi /*& cp_Double_lo*/, cpMap, len); - break; - case CONSTANT_String: - read_single_refs(cp_String, CONSTANT_Utf8, cpMap, len); - break; - case CONSTANT_Class: - read_single_refs(cp_Class, CONSTANT_Utf8, cpMap, len); - break; - case CONSTANT_Signature: - read_signature_values(cpMap, len); - break; - case CONSTANT_NameandType: - read_double_refs(cp_Descr_name /*& cp_Descr_type*/, CONSTANT_Utf8, - CONSTANT_Signature, cpMap, len); - break; - case CONSTANT_Fieldref: - read_double_refs(cp_Field_class /*& cp_Field_desc*/, CONSTANT_Class, - CONSTANT_NameandType, cpMap, len); - break; - case CONSTANT_Methodref: - read_double_refs(cp_Method_class /*& cp_Method_desc*/, CONSTANT_Class, - CONSTANT_NameandType, cpMap, len); - break; - case CONSTANT_InterfaceMethodref: - read_double_refs(cp_Imethod_class /*& cp_Imethod_desc*/, CONSTANT_Class, - CONSTANT_NameandType, cpMap, len); - break; - default: - assert(false); - break; - } - } - - cp.expandSignatures(); - cp.initMemberIndexes(); + int i; + + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + byte tag = TAGS_IN_ORDER[k]; + int len = cp.tag_count[tag]; + int base = cp.tag_base[tag]; + + entry *cpMap = &cp.entries[base]; + for (i = 0; i < len; i++) + { + cpMap[i].tag = tag; + cpMap[i].inord = i; + } + + switch (tag) + { + case CONSTANT_Utf8: + read_Utf8_values(cpMap, len); + break; + case CONSTANT_Integer: + read_single_words(cp_Int, cpMap, len); + break; + case CONSTANT_Float: + read_single_words(cp_Float, cpMap, len); + break; + case CONSTANT_Long: + read_double_words(cp_Long_hi /*& cp_Long_lo*/, cpMap, len); + break; + case CONSTANT_Double: + read_double_words(cp_Double_hi /*& cp_Double_lo*/, cpMap, len); + break; + case CONSTANT_String: + read_single_refs(cp_String, CONSTANT_Utf8, cpMap, len); + break; + case CONSTANT_Class: + read_single_refs(cp_Class, CONSTANT_Utf8, cpMap, len); + break; + case CONSTANT_Signature: + read_signature_values(cpMap, len); + break; + case CONSTANT_NameandType: + read_double_refs(cp_Descr_name /*& cp_Descr_type*/, CONSTANT_Utf8, + CONSTANT_Signature, cpMap, len); + break; + case CONSTANT_Fieldref: + read_double_refs(cp_Field_class /*& cp_Field_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + case CONSTANT_Methodref: + read_double_refs(cp_Method_class /*& cp_Method_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + case CONSTANT_InterfaceMethodref: + read_double_refs(cp_Imethod_class /*& cp_Imethod_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + default: + assert(false); + break; + } + } + + cp.expandSignatures(); + cp.initMemberIndexes(); #define SNAME(n, s) #s "\0" - const char *symNames = (ALL_ATTR_DO(SNAME) ""); + const char *symNames = (ALL_ATTR_DO(SNAME) ""); #undef SNAME - for (int sn = 0; sn < constant_pool::s_LIMIT; sn++) - { - assert(symNames[0] >= '0' && symNames[0] <= 'Z'); // sanity - bytes name; - name.set(symNames); - if (name.len > 0 && name.ptr[0] != '0') - { - cp.sym[sn] = cp.ensureUtf8(name); - } - symNames += name.len + 1; // skip trailing nullptr to next name - } - - band::initIndexes(this); + for (int sn = 0; sn < constant_pool::s_LIMIT; sn++) + { + assert(symNames[0] >= '0' && symNames[0] <= 'Z'); // sanity + bytes name; + name.set(symNames); + if (name.len > 0 && name.ptr[0] != '0') + { + cp.sym[sn] = cp.ensureUtf8(name); + } + symNames += name.len + 1; // skip trailing nullptr to next name + } + + band::initIndexes(this); } static band *no_bands[] = {nullptr}; // shared empty body inline band &unpacker::attr_definitions::fixed_band(int e_class_xxx) { - return u->all_bands[xxx_flags_hi_bn + (e_class_xxx - e_class_flags_hi)]; + return u->all_bands[xxx_flags_hi_bn + (e_class_xxx - e_class_flags_hi)]; } inline band &unpacker::attr_definitions::xxx_flags_hi() { - return fixed_band(e_class_flags_hi); + return fixed_band(e_class_flags_hi); } inline band &unpacker::attr_definitions::xxx_flags_lo() { - return fixed_band(e_class_flags_lo); + return fixed_band(e_class_flags_lo); } inline band &unpacker::attr_definitions::xxx_attr_count() { - return fixed_band(e_class_attr_count); + return fixed_band(e_class_attr_count); } inline band &unpacker::attr_definitions::xxx_attr_indexes() { - return fixed_band(e_class_attr_indexes); + return fixed_band(e_class_attr_indexes); } inline band &unpacker::attr_definitions::xxx_attr_calls() { - return fixed_band(e_class_attr_calls); + return fixed_band(e_class_attr_calls); } inline unpacker::layout_definition * unpacker::attr_definitions::defineLayout(int idx, entry *nameEntry, const char *layout) { - const char *name = nameEntry->value.b.strval(); - layout_definition *lo = defineLayout(idx, name, layout); - lo->nameEntry = nameEntry; - return lo; + const char *name = nameEntry->value.b.strval(); + layout_definition *lo = defineLayout(idx, name, layout); + lo->nameEntry = nameEntry; + return lo; } unpacker::layout_definition *unpacker::attr_definitions::defineLayout(int idx, const char *name, - const char *layout) + const char *layout) { - assert(flag_limit != 0); // must be set up already - if (idx >= 0) - { - // Fixed attr. - if (idx >= (int)flag_limit) - unpack_abort("attribute index too large"); - if (isRedefined(idx)) - unpack_abort("redefined attribute index"); - redef |= ((uint64_t)1 << idx); - } - else - { - idx = flag_limit + overflow_count.length(); - overflow_count.add(0); // make a new counter - } - layout_definition *lo = U_NEW(layout_definition, 1); - lo->idx = idx; - lo->name = name; - lo->layout = layout; - for (int adds = (idx + 1) - layouts.length(); adds > 0; adds--) - { - layouts.add(nullptr); - } - layouts.get(idx) = lo; - return lo; + assert(flag_limit != 0); // must be set up already + if (idx >= 0) + { + // Fixed attr. + if (idx >= (int)flag_limit) + unpack_abort("attribute index too large"); + if (isRedefined(idx)) + unpack_abort("redefined attribute index"); + redef |= ((uint64_t)1 << idx); + } + else + { + idx = flag_limit + overflow_count.length(); + overflow_count.add(0); // make a new counter + } + layout_definition *lo = U_NEW(layout_definition, 1); + lo->idx = idx; + lo->name = name; + lo->layout = layout; + for (int adds = (idx + 1) - layouts.length(); adds > 0; adds--) + { + layouts.add(nullptr); + } + layouts.get(idx) = lo; + return lo; } band **unpacker::attr_definitions::buildBands(unpacker::layout_definition *lo) { - int i; - if (lo->elems != nullptr) - return lo->bands(); - if (lo->layout[0] == '\0') - { - lo->elems = no_bands; - } - else - { - // Create bands for this attribute by parsing the layout. - bool hasCallables = lo->hasCallables(); - bands_made = 0x10000; // base number for bands made - const char *lp = lo->layout; - lp = parseLayout(lp, lo->elems, -1); - if (lp[0] != '\0' || band_stack.length() > 0) - { - unpack_abort("garbage at end of layout"); - } - band_stack.popTo(0); - - // Fix up callables to point at their callees. - band **bands = lo->elems; - assert(bands == lo->bands()); - int num_callables = 0; - if (hasCallables) - { - while (bands[num_callables] != nullptr) - { - if (bands[num_callables]->le_kind != EK_CBLE) - { - unpack_abort("garbage mixed with callables"); - break; - } - num_callables += 1; - } - } - for (i = 0; i < calls_to_link.length(); i++) - { - band &call = *(band *)calls_to_link.get(i); - assert(call.le_kind == EK_CALL); - // Determine the callee. - int call_num = call.le_len; - if (call_num < 0 || call_num >= num_callables) - { - unpack_abort("bad call in layout"); - break; - } - band &cble = *bands[call_num]; - // Link the call to it. - call.le_body[0] = &cble; - // Distinguish backward calls and callables: - assert(cble.le_kind == EK_CBLE); - // FIXME: hit this one - // assert(cble.le_len == call_num); - cble.le_back |= call.le_back; - } - calls_to_link.popTo(0); - } - return lo->elems; + int i; + if (lo->elems != nullptr) + return lo->bands(); + if (lo->layout[0] == '\0') + { + lo->elems = no_bands; + } + else + { + // Create bands for this attribute by parsing the layout. + bool hasCallables = lo->hasCallables(); + bands_made = 0x10000; // base number for bands made + const char *lp = lo->layout; + lp = parseLayout(lp, lo->elems, -1); + if (lp[0] != '\0' || band_stack.length() > 0) + { + unpack_abort("garbage at end of layout"); + } + band_stack.popTo(0); + + // Fix up callables to point at their callees. + band **bands = lo->elems; + assert(bands == lo->bands()); + int num_callables = 0; + if (hasCallables) + { + while (bands[num_callables] != nullptr) + { + if (bands[num_callables]->le_kind != EK_CBLE) + { + unpack_abort("garbage mixed with callables"); + break; + } + num_callables += 1; + } + } + for (i = 0; i < calls_to_link.length(); i++) + { + band &call = *(band *)calls_to_link.get(i); + assert(call.le_kind == EK_CALL); + // Determine the callee. + int call_num = call.le_len; + if (call_num < 0 || call_num >= num_callables) + { + unpack_abort("bad call in layout"); + break; + } + band &cble = *bands[call_num]; + // Link the call to it. + call.le_body[0] = &cble; + // Distinguish backward calls and callables: + assert(cble.le_kind == EK_CBLE); + // FIXME: hit this one + // assert(cble.le_len == call_num); + cble.le_back |= call.le_back; + } + calls_to_link.popTo(0); + } + return lo->elems; } /* attribute layout language parser attribute_layout: - ( layout_element )* | ( callable )+ + ( layout_element )* | ( callable )+ layout_element: - ( integral | replication | union | call | reference ) + ( integral | replication | union | call | reference ) callable: - '[' body ']' + '[' body ']' body: - ( layout_element )+ + ( layout_element )+ integral: - ( unsigned_int | signed_int | bc_index | bc_offset | flag ) + ( unsigned_int | signed_int | bc_index | bc_offset | flag ) unsigned_int: - uint_type + uint_type signed_int: - 'S' uint_type + 'S' uint_type any_int: - ( unsigned_int | signed_int ) + ( unsigned_int | signed_int ) bc_index: - ( 'P' uint_type | 'PO' uint_type ) + ( 'P' uint_type | 'PO' uint_type ) bc_offset: - 'O' any_int + 'O' any_int flag: - 'F' uint_type + 'F' uint_type uint_type: - ( 'B' | 'H' | 'I' | 'V' ) + ( 'B' | 'H' | 'I' | 'V' ) replication: - 'N' uint_type '[' body ']' + 'N' uint_type '[' body ']' union: - 'T' any_int (union_case)* '(' ')' '[' (body)? ']' + 'T' any_int (union_case)* '(' ')' '[' (body)? ']' union_case: - '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']' + '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']' union_case_tag: - ( numeral | numeral '-' numeral ) + ( numeral | numeral '-' numeral ) call: - '(' numeral ')' + '(' numeral ')' reference: - reference_type ( 'N' )? uint_type + reference_type ( 'N' )? uint_type reference_type: - ( constant_ref | schema_ref | utf8_ref | untyped_ref ) + ( constant_ref | schema_ref | utf8_ref | untyped_ref ) constant_ref: - ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' ) + ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' ) schema_ref: - ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' ) + ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' ) utf8_ref: - 'RU' + 'RU' untyped_ref: - 'RQ' + 'RQ' numeral: - '(' ('-')? (digit)+ ')' + '(' ('-')? (digit)+ ')' digit: - ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ) + ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ) */ const char *unpacker::attr_definitions::parseIntLayout(const char *lp, band *&res, byte le_kind, - bool can_be_signed) + bool can_be_signed) { - band *b = U_NEW(band, 1); - char le = *lp++; - int spec = UNSIGNED5_spec; - if (le == 'S' && can_be_signed) - { - // Note: This is the last use of sign. There is no 'EF_SIGN'. - spec = SIGNED5_spec; - le = *lp++; - } - else if (le == 'B') - { - spec = BYTE1_spec; // unsigned byte - } - b->init(u, bands_made++, spec); - b->le_kind = le_kind; - int le_len = 0; - switch (le) - { - case 'B': - le_len = 1; - break; - case 'H': - le_len = 2; - break; - case 'I': - le_len = 4; - break; - case 'V': - le_len = 0; - break; - default: - unpack_abort("bad layout element"); - } - b->le_len = le_len; - band_stack.add(b); - res = b; - return lp; + band *b = U_NEW(band, 1); + char le = *lp++; + int spec = UNSIGNED5_spec; + if (le == 'S' && can_be_signed) + { + // Note: This is the last use of sign. There is no 'EF_SIGN'. + spec = SIGNED5_spec; + le = *lp++; + } + else if (le == 'B') + { + spec = BYTE1_spec; // unsigned byte + } + b->init(u, bands_made++, spec); + b->le_kind = le_kind; + int le_len = 0; + switch (le) + { + case 'B': + le_len = 1; + break; + case 'H': + le_len = 2; + break; + case 'I': + le_len = 4; + break; + case 'V': + le_len = 0; + break; + default: + unpack_abort("bad layout element"); + } + b->le_len = le_len; + band_stack.add(b); + res = b; + return lp; } const char *unpacker::attr_definitions::parseNumeral(const char *lp, int &res) { - bool sgn = false; - if (*lp == '0') - { - res = 0; - return lp + 1; - } // special case '0' - if (*lp == '-') - { - sgn = true; - lp++; - } - const char *dp = lp; - int con = 0; - while (*dp >= '0' && *dp <= '9') - { - int con0 = con; - con *= 10; - con += (*dp++) - '0'; - if (con <= con0) - { - con = -1; - break; - } // numeral overflow - } - if (lp == dp) - { - unpack_abort("missing numeral in layout"); - } - lp = dp; - if (con < 0 && !(sgn && con == -con)) - { - // (Portability note: Misses the error if int is not 32 bits.) - unpack_abort("numeral overflow"); - } - if (sgn) - con = -con; - res = con; - return lp; + bool sgn = false; + if (*lp == '0') + { + res = 0; + return lp + 1; + } // special case '0' + if (*lp == '-') + { + sgn = true; + lp++; + } + const char *dp = lp; + int con = 0; + while (*dp >= '0' && *dp <= '9') + { + int con0 = con; + con *= 10; + con += (*dp++) - '0'; + if (con <= con0) + { + con = -1; + break; + } // numeral overflow + } + if (lp == dp) + { + unpack_abort("missing numeral in layout"); + } + lp = dp; + if (con < 0 && !(sgn && con == -con)) + { + // (Portability note: Misses the error if int is not 32 bits.) + unpack_abort("numeral overflow"); + } + if (sgn) + con = -con; + res = con; + return lp; } band **unpacker::attr_definitions::popBody(int bs_base) { - // Return everything that was pushed, as a nullptr-terminated pointer array. - int bs_limit = band_stack.length(); - if (bs_base == bs_limit) - { - return no_bands; - } - else - { - int nb = bs_limit - bs_base; - band **res = U_NEW(band *, add_size(nb, 1)); - for (int i = 0; i < nb; i++) - { - band *b = (band *)band_stack.get(bs_base + i); - res[i] = b; - } - band_stack.popTo(bs_base); - return res; - } + // Return everything that was pushed, as a nullptr-terminated pointer array. + int bs_limit = band_stack.length(); + if (bs_base == bs_limit) + { + return no_bands; + } + else + { + int nb = bs_limit - bs_base; + band **res = U_NEW(band *, add_size(nb, 1)); + for (int i = 0; i < nb; i++) + { + band *b = (band *)band_stack.get(bs_base + i); + res[i] = b; + } + band_stack.popTo(bs_base); + return res; + } } const char *unpacker::attr_definitions::parseLayout(const char *lp, band **&res, int curCble) { - int bs_base = band_stack.length(); - bool top_level = (bs_base == 0); - band *b; - enum - { - can_be_signed = true - }; // optional arg to parseIntLayout - - for (bool done = false; !done;) - { - switch (*lp++) - { - case 'B': - case 'H': - case 'I': - case 'V': // unsigned_int - case 'S': // signed_int - --lp; // reparse - case 'F': - lp = parseIntLayout(lp, b, EK_INT); - break; - case 'P': - { - int le_bci = EK_BCI; - if (*lp == 'O') - { - ++lp; - le_bci = EK_BCID; - } - assert(*lp != 'S'); // no PSH, etc. - lp = parseIntLayout(lp, b, EK_INT); - b->le_bci = le_bci; - if (le_bci == EK_BCI) - b->defc = coding::findBySpec(BCI5_spec); - else - b->defc = coding::findBySpec(BRANCH5_spec); - } - break; - case 'O': - lp = parseIntLayout(lp, b, EK_INT, can_be_signed); - b->le_bci = EK_BCO; - b->defc = coding::findBySpec(BRANCH5_spec); - break; - case 'N': // replication: 'N' uint32_t '[' elem ... ']' - lp = parseIntLayout(lp, b, EK_REPL); - assert(*lp == '['); - ++lp; - lp = parseLayout(lp, b->le_body, curCble); - break; - case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' - lp = parseIntLayout(lp, b, EK_UN, can_be_signed); - { - int union_base = band_stack.length(); - for (;;) - { // for each case - band &k_case = *U_NEW(band, 1); - band_stack.add(&k_case); - k_case.le_kind = EK_CASE; - k_case.bn = bands_made++; - if (*lp++ != '(') - { - unpack_abort("bad union case"); - return ""; - } - if (*lp++ != ')') - { - --lp; // reparse - // Read some case values. (Use band_stack for temp. storage.) - int case_base = band_stack.length(); - for (;;) - { - int caseval = 0; - lp = parseNumeral(lp, caseval); - band_stack.add((void *)(size_t)caseval); - if (*lp == '-') - { - // new in version 160, allow (1-5) for (1,2,3,4,5) - if (u->majver < JAVA6_PACKAGE_MAJOR_VERSION) - { - unpack_abort( - "bad range in union case label (old archive format)"); - return ""; - } - int caselimit = caseval; - lp++; - lp = parseNumeral(lp, caselimit); - if (caseval >= caselimit || - (uint32_t)(caselimit - caseval) > 0x10000) - { - // Note: 0x10000 is arbitrary implementation restriction. - // We can remove it later if it's important to. - unpack_abort("bad range in union case label"); - } - for (;;) - { - ++caseval; - band_stack.add((void *)(size_t)caseval); - if (caseval == caselimit) - break; - } - } - if (*lp != ',') - break; - lp++; - } - if (*lp++ != ')') - { - unpack_abort("bad case label"); - } - // save away the case labels - int ntags = band_stack.length() - case_base; - int *tags = U_NEW(int, add_size(ntags, 1)); - k_case.le_casetags = tags; - *tags++ = ntags; - for (int i = 0; i < ntags; i++) - { - *tags++ = ptrlowbits(band_stack.get(case_base + i)); - } - band_stack.popTo(case_base); - } - // Got le_casetags. Now grab the body. - assert(*lp == '['); - ++lp; - lp = parseLayout(lp, k_case.le_body, curCble); - if (k_case.le_casetags == nullptr) - break; // done - } - b->le_body = popBody(union_base); - } - break; - case '(': // call: '(' -?NN* ')' - { - band &call = *U_NEW(band, 1); - band_stack.add(&call); - call.le_kind = EK_CALL; - call.bn = bands_made++; - call.le_body = U_NEW(band *, 2); // fill in later - int call_num = 0; - lp = parseNumeral(lp, call_num); - call.le_back = (call_num <= 0); - call_num += curCble; // numeral is self-relative offset - call.le_len = call_num; // use le_len as scratch - calls_to_link.add(&call); - if (*lp++ != ')') - { - unpack_abort("bad call label"); - } - } - break; - case 'K': // reference_type: constant_ref - case 'R': // reference_type: schema_ref - { - int ixTag = CONSTANT_None; - if (lp[-1] == 'K') - { - switch (*lp++) - { - case 'I': - ixTag = CONSTANT_Integer; - break; - case 'J': - ixTag = CONSTANT_Long; - break; - case 'F': - ixTag = CONSTANT_Float; - break; - case 'D': - ixTag = CONSTANT_Double; - break; - case 'S': - ixTag = CONSTANT_String; - break; - case 'Q': - ixTag = CONSTANT_Literal; - break; - } - } - else - { - switch (*lp++) - { - case 'C': - ixTag = CONSTANT_Class; - break; - case 'S': - ixTag = CONSTANT_Signature; - break; - case 'D': - ixTag = CONSTANT_NameandType; - break; - case 'F': - ixTag = CONSTANT_Fieldref; - break; - case 'M': - ixTag = CONSTANT_Methodref; - break; - case 'I': - ixTag = CONSTANT_InterfaceMethodref; - break; - case 'U': - ixTag = CONSTANT_Utf8; - break; // utf8_ref - case 'Q': - ixTag = CONSTANT_All; - break; // untyped_ref - } - } - if (ixTag == CONSTANT_None) - { - unpack_abort("bad reference layout"); - break; - } - bool nullOK = false; - if (*lp == 'N') - { - nullOK = true; - lp++; - } - lp = parseIntLayout(lp, b, EK_REF); - b->defc = coding::findBySpec(UNSIGNED5_spec); - b->initRef(ixTag, nullOK); - } - break; - case '[': - { - // [callable1][callable2]... - if (!top_level) - { - unpack_abort("bad nested callable"); - break; - } - curCble += 1; - band &cble = *U_NEW(band, 1); - band_stack.add(&cble); - cble.le_kind = EK_CBLE; - cble.bn = bands_made++; - lp = parseLayout(lp, cble.le_body, curCble); - } - break; - case ']': - // Hit a closing brace. This ends whatever body we were in. - done = true; - break; - case '\0': - // Hit a nullptr. Also ends the (top-level) body. - --lp; // back up, so caller can see the nullptr also - done = true; - break; - default: - unpack_abort("bad layout"); - } - } - - // Return the accumulated bands: - res = popBody(bs_base); - return lp; + int bs_base = band_stack.length(); + bool top_level = (bs_base == 0); + band *b; + enum + { + can_be_signed = true + }; // optional arg to parseIntLayout + + for (bool done = false; !done;) + { + switch (*lp++) + { + case 'B': + case 'H': + case 'I': + case 'V': // unsigned_int + case 'S': // signed_int + --lp; // reparse + case 'F': + lp = parseIntLayout(lp, b, EK_INT); + break; + case 'P': + { + int le_bci = EK_BCI; + if (*lp == 'O') + { + ++lp; + le_bci = EK_BCID; + } + assert(*lp != 'S'); // no PSH, etc. + lp = parseIntLayout(lp, b, EK_INT); + b->le_bci = le_bci; + if (le_bci == EK_BCI) + b->defc = coding::findBySpec(BCI5_spec); + else + b->defc = coding::findBySpec(BRANCH5_spec); + } + break; + case 'O': + lp = parseIntLayout(lp, b, EK_INT, can_be_signed); + b->le_bci = EK_BCO; + b->defc = coding::findBySpec(BRANCH5_spec); + break; + case 'N': // replication: 'N' uint32_t '[' elem ... ']' + lp = parseIntLayout(lp, b, EK_REPL); + assert(*lp == '['); + ++lp; + lp = parseLayout(lp, b->le_body, curCble); + break; + case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' + lp = parseIntLayout(lp, b, EK_UN, can_be_signed); + { + int union_base = band_stack.length(); + for (;;) + { // for each case + band &k_case = *U_NEW(band, 1); + band_stack.add(&k_case); + k_case.le_kind = EK_CASE; + k_case.bn = bands_made++; + if (*lp++ != '(') + { + unpack_abort("bad union case"); + return ""; + } + if (*lp++ != ')') + { + --lp; // reparse + // Read some case values. (Use band_stack for temp. storage.) + int case_base = band_stack.length(); + for (;;) + { + int caseval = 0; + lp = parseNumeral(lp, caseval); + band_stack.add((void *)(size_t)caseval); + if (*lp == '-') + { + // new in version 160, allow (1-5) for (1,2,3,4,5) + if (u->majver < JAVA6_PACKAGE_MAJOR_VERSION) + { + unpack_abort( + "bad range in union case label (old archive format)"); + return ""; + } + int caselimit = caseval; + lp++; + lp = parseNumeral(lp, caselimit); + if (caseval >= caselimit || + (uint32_t)(caselimit - caseval) > 0x10000) + { + // Note: 0x10000 is arbitrary implementation restriction. + // We can remove it later if it's important to. + unpack_abort("bad range in union case label"); + } + for (;;) + { + ++caseval; + band_stack.add((void *)(size_t)caseval); + if (caseval == caselimit) + break; + } + } + if (*lp != ',') + break; + lp++; + } + if (*lp++ != ')') + { + unpack_abort("bad case label"); + } + // save away the case labels + int ntags = band_stack.length() - case_base; + int *tags = U_NEW(int, add_size(ntags, 1)); + k_case.le_casetags = tags; + *tags++ = ntags; + for (int i = 0; i < ntags; i++) + { + *tags++ = ptrlowbits(band_stack.get(case_base + i)); + } + band_stack.popTo(case_base); + } + // Got le_casetags. Now grab the body. + assert(*lp == '['); + ++lp; + lp = parseLayout(lp, k_case.le_body, curCble); + if (k_case.le_casetags == nullptr) + break; // done + } + b->le_body = popBody(union_base); + } + break; + case '(': // call: '(' -?NN* ')' + { + band &call = *U_NEW(band, 1); + band_stack.add(&call); + call.le_kind = EK_CALL; + call.bn = bands_made++; + call.le_body = U_NEW(band *, 2); // fill in later + int call_num = 0; + lp = parseNumeral(lp, call_num); + call.le_back = (call_num <= 0); + call_num += curCble; // numeral is self-relative offset + call.le_len = call_num; // use le_len as scratch + calls_to_link.add(&call); + if (*lp++ != ')') + { + unpack_abort("bad call label"); + } + } + break; + case 'K': // reference_type: constant_ref + case 'R': // reference_type: schema_ref + { + int ixTag = CONSTANT_None; + if (lp[-1] == 'K') + { + switch (*lp++) + { + case 'I': + ixTag = CONSTANT_Integer; + break; + case 'J': + ixTag = CONSTANT_Long; + break; + case 'F': + ixTag = CONSTANT_Float; + break; + case 'D': + ixTag = CONSTANT_Double; + break; + case 'S': + ixTag = CONSTANT_String; + break; + case 'Q': + ixTag = CONSTANT_Literal; + break; + } + } + else + { + switch (*lp++) + { + case 'C': + ixTag = CONSTANT_Class; + break; + case 'S': + ixTag = CONSTANT_Signature; + break; + case 'D': + ixTag = CONSTANT_NameandType; + break; + case 'F': + ixTag = CONSTANT_Fieldref; + break; + case 'M': + ixTag = CONSTANT_Methodref; + break; + case 'I': + ixTag = CONSTANT_InterfaceMethodref; + break; + case 'U': + ixTag = CONSTANT_Utf8; + break; // utf8_ref + case 'Q': + ixTag = CONSTANT_All; + break; // untyped_ref + } + } + if (ixTag == CONSTANT_None) + { + unpack_abort("bad reference layout"); + break; + } + bool nullOK = false; + if (*lp == 'N') + { + nullOK = true; + lp++; + } + lp = parseIntLayout(lp, b, EK_REF); + b->defc = coding::findBySpec(UNSIGNED5_spec); + b->initRef(ixTag, nullOK); + } + break; + case '[': + { + // [callable1][callable2]... + if (!top_level) + { + unpack_abort("bad nested callable"); + break; + } + curCble += 1; + band &cble = *U_NEW(band, 1); + band_stack.add(&cble); + cble.le_kind = EK_CBLE; + cble.bn = bands_made++; + lp = parseLayout(lp, cble.le_body, curCble); + } + break; + case ']': + // Hit a closing brace. This ends whatever body we were in. + done = true; + break; + case '\0': + // Hit a nullptr. Also ends the (top-level) body. + --lp; // back up, so caller can see the nullptr also + done = true; + break; + default: + unpack_abort("bad layout"); + } + } + + // Return the accumulated bands: + res = popBody(bs_base); + return lp; } void unpacker::read_attr_defs() { - int i; - - // Tell each AD which attrc it is and where its fixed flags are: - attr_defs[ATTR_CONTEXT_CLASS].attrc = ATTR_CONTEXT_CLASS; - attr_defs[ATTR_CONTEXT_CLASS].xxx_flags_hi_bn = e_class_flags_hi; - attr_defs[ATTR_CONTEXT_FIELD].attrc = ATTR_CONTEXT_FIELD; - attr_defs[ATTR_CONTEXT_FIELD].xxx_flags_hi_bn = e_field_flags_hi; - attr_defs[ATTR_CONTEXT_METHOD].attrc = ATTR_CONTEXT_METHOD; - attr_defs[ATTR_CONTEXT_METHOD].xxx_flags_hi_bn = e_method_flags_hi; - attr_defs[ATTR_CONTEXT_CODE].attrc = ATTR_CONTEXT_CODE; - attr_defs[ATTR_CONTEXT_CODE].xxx_flags_hi_bn = e_code_flags_hi; - - // Decide whether bands for the optional high flag words are present. - attr_defs[ATTR_CONTEXT_CLASS] - .setHaveLongFlags((archive_options & AO_HAVE_CLASS_FLAGS_HI) != 0); - attr_defs[ATTR_CONTEXT_FIELD] - .setHaveLongFlags((archive_options & AO_HAVE_FIELD_FLAGS_HI) != 0); - attr_defs[ATTR_CONTEXT_METHOD] - .setHaveLongFlags((archive_options & AO_HAVE_METHOD_FLAGS_HI) != 0); - attr_defs[ATTR_CONTEXT_CODE] - .setHaveLongFlags((archive_options & AO_HAVE_CODE_FLAGS_HI) != 0); - - // Set up built-in attrs. - // (The simple ones are hard-coded. The metadata layouts are not.) - const char *md_layout = ( + int i; + + // Tell each AD which attrc it is and where its fixed flags are: + attr_defs[ATTR_CONTEXT_CLASS].attrc = ATTR_CONTEXT_CLASS; + attr_defs[ATTR_CONTEXT_CLASS].xxx_flags_hi_bn = e_class_flags_hi; + attr_defs[ATTR_CONTEXT_FIELD].attrc = ATTR_CONTEXT_FIELD; + attr_defs[ATTR_CONTEXT_FIELD].xxx_flags_hi_bn = e_field_flags_hi; + attr_defs[ATTR_CONTEXT_METHOD].attrc = ATTR_CONTEXT_METHOD; + attr_defs[ATTR_CONTEXT_METHOD].xxx_flags_hi_bn = e_method_flags_hi; + attr_defs[ATTR_CONTEXT_CODE].attrc = ATTR_CONTEXT_CODE; + attr_defs[ATTR_CONTEXT_CODE].xxx_flags_hi_bn = e_code_flags_hi; + + // Decide whether bands for the optional high flag words are present. + attr_defs[ATTR_CONTEXT_CLASS] + .setHaveLongFlags((archive_options & AO_HAVE_CLASS_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_FIELD] + .setHaveLongFlags((archive_options & AO_HAVE_FIELD_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_METHOD] + .setHaveLongFlags((archive_options & AO_HAVE_METHOD_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_CODE] + .setHaveLongFlags((archive_options & AO_HAVE_CODE_FLAGS_HI) != 0); + + // Set up built-in attrs. + // (The simple ones are hard-coded. The metadata layouts are not.) + const char *md_layout = ( // parameter annotations: #define MDL0 "[NB[(1)]]" - MDL0 + MDL0 // annotations: #define MDL1 \ - "[NH[(1)]]" \ - "[RSHNH[RUH(1)]]" - MDL1 - // member_value: - "[TB" - "(66,67,73,83,90)[KIH]" - "(68)[KDH]" - "(70)[KFH]" - "(74)[KJH]" - "(99)[RSH]" - "(101)[RSHRUH]" - "(115)[RUH]" - "(91)[NH[(0)]]" - "(64)[" - // nested annotation: - "RSH" - "NH[RUH(0)]" - "]" - "()[]" - "]"); - - const char *md_layout_P = md_layout; - const char *md_layout_A = md_layout + strlen(MDL0); - const char *md_layout_V = md_layout + strlen(MDL0 MDL1); - assert(0 == strncmp(&md_layout_A[-3], ")]][", 4)); - assert(0 == strncmp(&md_layout_V[-3], ")]][", 4)); - - for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) - { - attr_definitions &ad = attr_defs[i]; - ad.defineLayout(X_ATTR_RuntimeVisibleAnnotations, "RuntimeVisibleAnnotations", - md_layout_A); - ad.defineLayout(X_ATTR_RuntimeInvisibleAnnotations, "RuntimeInvisibleAnnotations", - md_layout_A); - if (i != ATTR_CONTEXT_METHOD) - continue; - ad.defineLayout(METHOD_ATTR_RuntimeVisibleParameterAnnotations, - "RuntimeVisibleParameterAnnotations", md_layout_P); - ad.defineLayout(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, - "RuntimeInvisibleParameterAnnotations", md_layout_P); - ad.defineLayout(METHOD_ATTR_AnnotationDefault, "AnnotationDefault", md_layout_V); - } - - attr_definition_headers.readData(attr_definition_count); - attr_definition_name.readData(attr_definition_count); - attr_definition_layout.readData(attr_definition_count); + "[NH[(1)]]" \ + "[RSHNH[RUH(1)]]" + MDL1 + // member_value: + "[TB" + "(66,67,73,83,90)[KIH]" + "(68)[KDH]" + "(70)[KFH]" + "(74)[KJH]" + "(99)[RSH]" + "(101)[RSHRUH]" + "(115)[RUH]" + "(91)[NH[(0)]]" + "(64)[" + // nested annotation: + "RSH" + "NH[RUH(0)]" + "]" + "()[]" + "]"); + + const char *md_layout_P = md_layout; + const char *md_layout_A = md_layout + strlen(MDL0); + const char *md_layout_V = md_layout + strlen(MDL0 MDL1); + assert(0 == strncmp(&md_layout_A[-3], ")]][", 4)); + assert(0 == strncmp(&md_layout_V[-3], ")]][", 4)); + + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + { + attr_definitions &ad = attr_defs[i]; + ad.defineLayout(X_ATTR_RuntimeVisibleAnnotations, "RuntimeVisibleAnnotations", + md_layout_A); + ad.defineLayout(X_ATTR_RuntimeInvisibleAnnotations, "RuntimeInvisibleAnnotations", + md_layout_A); + if (i != ATTR_CONTEXT_METHOD) + continue; + ad.defineLayout(METHOD_ATTR_RuntimeVisibleParameterAnnotations, + "RuntimeVisibleParameterAnnotations", md_layout_P); + ad.defineLayout(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, + "RuntimeInvisibleParameterAnnotations", md_layout_P); + ad.defineLayout(METHOD_ATTR_AnnotationDefault, "AnnotationDefault", md_layout_V); + } + + attr_definition_headers.readData(attr_definition_count); + attr_definition_name.readData(attr_definition_count); + attr_definition_layout.readData(attr_definition_count); // Initialize correct predef bits, to distinguish predefs from new defs. #define ORBIT(n, s) | ((uint64_t)1 << n) - attr_defs[ATTR_CONTEXT_CLASS].predef = (0 X_ATTR_DO(ORBIT) CLASS_ATTR_DO(ORBIT)); - attr_defs[ATTR_CONTEXT_FIELD].predef = (0 X_ATTR_DO(ORBIT) FIELD_ATTR_DO(ORBIT)); - attr_defs[ATTR_CONTEXT_METHOD].predef = (0 X_ATTR_DO(ORBIT) METHOD_ATTR_DO(ORBIT)); - attr_defs[ATTR_CONTEXT_CODE].predef = (0 O_ATTR_DO(ORBIT) CODE_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_CLASS].predef = (0 X_ATTR_DO(ORBIT) CLASS_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_FIELD].predef = (0 X_ATTR_DO(ORBIT) FIELD_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_METHOD].predef = (0 X_ATTR_DO(ORBIT) METHOD_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_CODE].predef = (0 O_ATTR_DO(ORBIT) CODE_ATTR_DO(ORBIT)); #undef ORBIT - // Clear out the redef bits, folding them back into predef. - for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) - { - attr_defs[i].predef |= attr_defs[i].redef; - attr_defs[i].redef = 0; - } - - // Now read the transmitted locally defined attrs. - // This will set redef bits again. - for (i = 0; i < attr_definition_count; i++) - { - int header = attr_definition_headers.getByte(); - int attrc = ADH_BYTE_CONTEXT(header); - int idx = ADH_BYTE_INDEX(header); - entry *name = attr_definition_name.getRef(); - entry *layout = attr_definition_layout.getRef(); - attr_defs[attrc].defineLayout(idx, name, layout->value.b.strval()); - } + // Clear out the redef bits, folding them back into predef. + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + { + attr_defs[i].predef |= attr_defs[i].redef; + attr_defs[i].redef = 0; + } + + // Now read the transmitted locally defined attrs. + // This will set redef bits again. + for (i = 0; i < attr_definition_count; i++) + { + int header = attr_definition_headers.getByte(); + int attrc = ADH_BYTE_CONTEXT(header); + int idx = ADH_BYTE_INDEX(header); + entry *name = attr_definition_name.getRef(); + entry *layout = attr_definition_layout.getRef(); + attr_defs[attrc].defineLayout(idx, name, layout->value.b.strval()); + } } #define NO_ENTRY_YET ((entry *)-1) static bool isDigitString(bytes &x, int beg, int end) { - if (beg == end) - return false; // nullptr string - byte *xptr = x.ptr; - for (int i = beg; i < end; i++) - { - char ch = xptr[i]; - if (!(ch >= '0' && ch <= '9')) - return false; - } - return true; + if (beg == end) + return false; // nullptr string + byte *xptr = x.ptr; + for (int i = beg; i < end; i++) + { + char ch = xptr[i]; + if (!(ch >= '0' && ch <= '9')) + return false; + } + return true; } enum { // constants for parsing class names - SLASH_MIN = '.', - SLASH_MAX = '/', - DOLLAR_MIN = 0, - DOLLAR_MAX = '-'}; + SLASH_MIN = '.', + SLASH_MAX = '/', + DOLLAR_MIN = 0, + DOLLAR_MAX = '-'}; static int lastIndexOf(int chmin, int chmax, bytes &x, int pos) { - byte *ptr = x.ptr; - for (byte *cp = ptr + pos; --cp >= ptr;) - { - assert(x.inBounds(cp)); - if (*cp >= chmin && *cp <= chmax) - return (int)(cp - ptr); - } - return -1; + byte *ptr = x.ptr; + for (byte *cp = ptr + pos; --cp >= ptr;) + { + assert(x.inBounds(cp)); + if (*cp >= chmin && *cp <= chmax) + return (int)(cp - ptr); + } + return -1; } inner_class *constant_pool::getIC(entry *inner) { - if (inner == nullptr) - return nullptr; - assert(inner->tag == CONSTANT_Class); - if (inner->inord == NO_INORD) - return nullptr; - inner_class *ic = ic_index[inner->inord]; - assert(ic == nullptr || ic->inner == inner); - return ic; + if (inner == nullptr) + return nullptr; + assert(inner->tag == CONSTANT_Class); + if (inner->inord == NO_INORD) + return nullptr; + inner_class *ic = ic_index[inner->inord]; + assert(ic == nullptr || ic->inner == inner); + return ic; } inner_class *constant_pool::getFirstChildIC(entry *outer) { - if (outer == nullptr) - return nullptr; - assert(outer->tag == CONSTANT_Class); - if (outer->inord == NO_INORD) - return nullptr; - inner_class *ic = ic_child_index[outer->inord]; - assert(ic == nullptr || ic->outer == outer); - return ic; + if (outer == nullptr) + return nullptr; + assert(outer->tag == CONSTANT_Class); + if (outer->inord == NO_INORD) + return nullptr; + inner_class *ic = ic_child_index[outer->inord]; + assert(ic == nullptr || ic->outer == outer); + return ic; } inner_class *constant_pool::getNextChildIC(inner_class *child) { - inner_class *ic = child->next_sibling; - assert(ic == nullptr || ic->outer == child->outer); - return ic; + inner_class *ic = child->next_sibling; + assert(ic == nullptr || ic->outer == child->outer); + return ic; } void unpacker::read_ics() { - int i; - int index_size = cp.tag_count[CONSTANT_Class]; - inner_class **ic_index = U_NEW(inner_class *, index_size); - inner_class **ic_child_index = U_NEW(inner_class *, index_size); - cp.ic_index = ic_index; - cp.ic_child_index = ic_child_index; - ics = U_NEW(inner_class, ic_count); - ic_this_class.readData(ic_count); - ic_flags.readData(ic_count); - // Scan flags to get count of long-form bands. - int long_forms = 0; - for (i = 0; i < ic_count; i++) - { - int flags = ic_flags.getInt(); // may be long form! - if ((flags & ACC_IC_LONG_FORM) != 0) - { - long_forms += 1; - ics[i].name = NO_ENTRY_YET; - } - flags &= ~ACC_IC_LONG_FORM; - entry *inner = ic_this_class.getRef(); - uint32_t inord = inner->inord; - assert(inord < (uint32_t)cp.tag_count[CONSTANT_Class]); - if (ic_index[inord] != nullptr) - { - unpack_abort("identical inner class"); - break; - } - ic_index[inord] = &ics[i]; - ics[i].inner = inner; - ics[i].flags = flags; - assert(cp.getIC(inner) == &ics[i]); - } - // ic_this_class.done(); - // ic_flags.done(); - ic_outer_class.readData(long_forms); - ic_name.readData(long_forms); - for (i = 0; i < ic_count; i++) - { - if (ics[i].name == NO_ENTRY_YET) - { - // Long form. - ics[i].outer = ic_outer_class.getRefN(); - ics[i].name = ic_name.getRefN(); - } - else - { - // Fill in outer and name based on inner. - bytes &n = ics[i].inner->value.b; - bytes pkgOuter; - bytes number; - bytes name; - // Parse n into pkgOuter and name (and number). - int dollar1, dollar2; // pointers to $ in the pattern - // parse n = (/)*($)?($)? - int nlen = (int)n.len; - int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, nlen) + 1; - dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, nlen); - if (dollar2 < 0) - { - unpack_abort(); - } - assert(dollar2 >= pkglen); - if (isDigitString(n, dollar2 + 1, nlen)) - { - // n = (/)*$ - number = n.slice(dollar2 + 1, nlen); - name.set(nullptr, 0); - dollar1 = dollar2; - } - else if (pkglen < (dollar1 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2 - 1)) && - isDigitString(n, dollar1 + 1, dollar2)) - { - // n = (/)*$$ - number = n.slice(dollar1 + 1, dollar2); - name = n.slice(dollar2 + 1, nlen); - } - else - { - // n = (/)*$ - dollar1 = dollar2; - number.set(nullptr, 0); - name = n.slice(dollar2 + 1, nlen); - } - if (number.ptr == nullptr) - pkgOuter = n.slice(0, dollar1); - else - pkgOuter.set(nullptr, 0); - - if (pkgOuter.ptr != nullptr) - ics[i].outer = cp.ensureClass(pkgOuter); - - if (name.ptr != nullptr) - ics[i].name = cp.ensureUtf8(name); - } - - // update child/sibling list - if (ics[i].outer != nullptr) - { - uint32_t outord = ics[i].outer->inord; - if (outord != NO_INORD) - { - assert(outord < (uint32_t)cp.tag_count[CONSTANT_Class]); - ics[i].next_sibling = ic_child_index[outord]; - ic_child_index[outord] = &ics[i]; - } - } - } - // ic_outer_class.done(); - // ic_name.done(); + int i; + int index_size = cp.tag_count[CONSTANT_Class]; + inner_class **ic_index = U_NEW(inner_class *, index_size); + inner_class **ic_child_index = U_NEW(inner_class *, index_size); + cp.ic_index = ic_index; + cp.ic_child_index = ic_child_index; + ics = U_NEW(inner_class, ic_count); + ic_this_class.readData(ic_count); + ic_flags.readData(ic_count); + // Scan flags to get count of long-form bands. + int long_forms = 0; + for (i = 0; i < ic_count; i++) + { + int flags = ic_flags.getInt(); // may be long form! + if ((flags & ACC_IC_LONG_FORM) != 0) + { + long_forms += 1; + ics[i].name = NO_ENTRY_YET; + } + flags &= ~ACC_IC_LONG_FORM; + entry *inner = ic_this_class.getRef(); + uint32_t inord = inner->inord; + assert(inord < (uint32_t)cp.tag_count[CONSTANT_Class]); + if (ic_index[inord] != nullptr) + { + unpack_abort("identical inner class"); + break; + } + ic_index[inord] = &ics[i]; + ics[i].inner = inner; + ics[i].flags = flags; + assert(cp.getIC(inner) == &ics[i]); + } + // ic_this_class.done(); + // ic_flags.done(); + ic_outer_class.readData(long_forms); + ic_name.readData(long_forms); + for (i = 0; i < ic_count; i++) + { + if (ics[i].name == NO_ENTRY_YET) + { + // Long form. + ics[i].outer = ic_outer_class.getRefN(); + ics[i].name = ic_name.getRefN(); + } + else + { + // Fill in outer and name based on inner. + bytes &n = ics[i].inner->value.b; + bytes pkgOuter; + bytes number; + bytes name; + // Parse n into pkgOuter and name (and number). + int dollar1, dollar2; // pointers to $ in the pattern + // parse n = (/)*($)?($)? + int nlen = (int)n.len; + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, nlen) + 1; + dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, nlen); + if (dollar2 < 0) + { + unpack_abort(); + } + assert(dollar2 >= pkglen); + if (isDigitString(n, dollar2 + 1, nlen)) + { + // n = (/)*$ + number = n.slice(dollar2 + 1, nlen); + name.set(nullptr, 0); + dollar1 = dollar2; + } + else if (pkglen < (dollar1 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2 - 1)) && + isDigitString(n, dollar1 + 1, dollar2)) + { + // n = (/)*$$ + number = n.slice(dollar1 + 1, dollar2); + name = n.slice(dollar2 + 1, nlen); + } + else + { + // n = (/)*$ + dollar1 = dollar2; + number.set(nullptr, 0); + name = n.slice(dollar2 + 1, nlen); + } + if (number.ptr == nullptr) + pkgOuter = n.slice(0, dollar1); + else + pkgOuter.set(nullptr, 0); + + if (pkgOuter.ptr != nullptr) + ics[i].outer = cp.ensureClass(pkgOuter); + + if (name.ptr != nullptr) + ics[i].name = cp.ensureUtf8(name); + } + + // update child/sibling list + if (ics[i].outer != nullptr) + { + uint32_t outord = ics[i].outer->inord; + if (outord != NO_INORD) + { + assert(outord < (uint32_t)cp.tag_count[CONSTANT_Class]); + ics[i].next_sibling = ic_child_index[outord]; + ic_child_index[outord] = &ics[i]; + } + } + } + // ic_outer_class.done(); + // ic_name.done(); } void unpacker::read_classes() { - class_this.readData(class_count); - class_super.readData(class_count); - class_interface_count.readData(class_count); - class_interface.readData(class_interface_count.getIntTotal()); + class_this.readData(class_count); + class_super.readData(class_count); + class_interface_count.readData(class_count); + class_interface.readData(class_interface_count.getIntTotal()); #if 0 int i; @@ -2256,1218 +2250,1220 @@ void unpacker::read_classes() class_super.rewind(); #endif - // Members. - class_field_count.readData(class_count); - class_method_count.readData(class_count); + // Members. + class_field_count.readData(class_count); + class_method_count.readData(class_count); - int field_count = class_field_count.getIntTotal(); - int method_count = class_method_count.getIntTotal(); + int field_count = class_field_count.getIntTotal(); + int method_count = class_method_count.getIntTotal(); - field_descr.readData(field_count); - read_attrs(ATTR_CONTEXT_FIELD, field_count); - method_descr.readData(method_count); - read_attrs(ATTR_CONTEXT_METHOD, method_count); - read_attrs(ATTR_CONTEXT_CLASS, class_count); - read_code_headers(); + field_descr.readData(field_count); + read_attrs(ATTR_CONTEXT_FIELD, field_count); + method_descr.readData(method_count); + read_attrs(ATTR_CONTEXT_METHOD, method_count); + read_attrs(ATTR_CONTEXT_CLASS, class_count); + read_code_headers(); } int unpacker::attr_definitions::predefCount(uint32_t idx) { - return isPredefined(idx) ? flag_count[idx] : 0; + return isPredefined(idx) ? flag_count[idx] : 0; } void unpacker::read_attrs(int attrc, int obj_count) { - attr_definitions &ad = attr_defs[attrc]; - assert(ad.attrc == attrc); - - int i, idx, count; - - bool haveLongFlags = ad.haveLongFlags(); - - band &xxx_flags_hi = ad.xxx_flags_hi(); - if (haveLongFlags) - xxx_flags_hi.readData(obj_count); - - band &xxx_flags_lo = ad.xxx_flags_lo(); - xxx_flags_lo.readData(obj_count); - - // pre-scan flags, counting occurrences of each index bit - uint64_t indexMask = ad.flagIndexMask(); // which flag bits are index bits? - for (i = 0; i < obj_count; i++) - { - uint64_t indexBits = xxx_flags_hi.getLong(xxx_flags_lo, haveLongFlags); - if ((indexBits & ~indexMask) > (ushort) - 1) - { - unpack_abort("undefined attribute flag bit"); - return; - } - indexBits &= indexMask; // ignore classfile flag bits - for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) - { - ad.flag_count[idx] += (int)(indexBits & 1); - } - } - // we'll scan these again later for output: - xxx_flags_lo.rewind(); - xxx_flags_hi.rewind(); - - band &xxx_attr_count = ad.xxx_attr_count(); - // There is one count element for each 1<<16 bit set in flags: - xxx_attr_count.readData(ad.predefCount(X_ATTR_OVERFLOW)); - - band &xxx_attr_indexes = ad.xxx_attr_indexes(); - int overflowIndexCount = xxx_attr_count.getIntTotal(); - xxx_attr_indexes.readData(overflowIndexCount); - // pre-scan attr indexes, counting occurrences of each value - for (i = 0; i < overflowIndexCount; i++) - { - idx = xxx_attr_indexes.getInt(); - if (!ad.isIndex(idx)) - { - unpack_abort("attribute index out of bounds"); - return; - } - ad.getCount(idx) += 1; - } - xxx_attr_indexes.rewind(); // we'll scan it again later for output - - // We will need a backward call count for each used backward callable. - int backwardCounts = 0; - for (idx = 0; idx < ad.layouts.length(); idx++) - { - layout_definition *lo = ad.getLayout(idx); - if (lo != nullptr && ad.getCount(idx) != 0) - { - // Build the bands lazily, only when they are used. - band **bands = ad.buildBands(lo); - if (lo->hasCallables()) - { - for (i = 0; bands[i] != nullptr; i++) - { - if (bands[i]->le_back) - { - assert(bands[i]->le_kind == EK_CBLE); - backwardCounts += 1; - } - } - } - } - } - ad.xxx_attr_calls().readData(backwardCounts); - - // Read built-in bands. - // Mostly, these are hand-coded equivalents to readBandData(). - switch (attrc) - { - case ATTR_CONTEXT_CLASS: - - count = ad.predefCount(CLASS_ATTR_SourceFile); - class_SourceFile_RUN.readData(count); - - count = ad.predefCount(CLASS_ATTR_EnclosingMethod); - class_EnclosingMethod_RC.readData(count); - class_EnclosingMethod_RDN.readData(count); - - count = ad.predefCount(X_ATTR_Signature); - class_Signature_RS.readData(count); - - ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); - ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); - - count = ad.predefCount(CLASS_ATTR_InnerClasses); - class_InnerClasses_N.readData(count); - - count = class_InnerClasses_N.getIntTotal(); - class_InnerClasses_RC.readData(count); - class_InnerClasses_F.readData(count); - - // Drop remaining columns wherever flags are zero: - count -= class_InnerClasses_F.getIntCount(0); - class_InnerClasses_outer_RCN.readData(count); - class_InnerClasses_name_RUN.readData(count); - - count = ad.predefCount(CLASS_ATTR_ClassFile_version); - class_ClassFile_version_minor_H.readData(count); - class_ClassFile_version_major_H.readData(count); - break; - - case ATTR_CONTEXT_FIELD: - - count = ad.predefCount(FIELD_ATTR_ConstantValue); - field_ConstantValue_KQ.readData(count); - - count = ad.predefCount(X_ATTR_Signature); - field_Signature_RS.readData(count); - - ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); - ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); - break; - - case ATTR_CONTEXT_METHOD: - - code_count = ad.predefCount(METHOD_ATTR_Code); - // Code attrs are handled very specially below... - - count = ad.predefCount(METHOD_ATTR_Exceptions); - method_Exceptions_N.readData(count); - count = method_Exceptions_N.getIntTotal(); - method_Exceptions_RC.readData(count); - - count = ad.predefCount(X_ATTR_Signature); - method_Signature_RS.readData(count); - - ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); - ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); - ad.readBandData(METHOD_ATTR_RuntimeVisibleParameterAnnotations); - ad.readBandData(METHOD_ATTR_RuntimeInvisibleParameterAnnotations); - ad.readBandData(METHOD_ATTR_AnnotationDefault); - break; - - case ATTR_CONTEXT_CODE: - // (keep this code aligned with its brother in unpacker::write_attrs) - count = ad.predefCount(CODE_ATTR_StackMapTable); - // disable this feature in old archives! - if (count != 0 && majver < JAVA6_PACKAGE_MAJOR_VERSION) - { - unpack_abort("undefined StackMapTable attribute (old archive format)"); - return; - } - code_StackMapTable_N.readData(count); - count = code_StackMapTable_N.getIntTotal(); - code_StackMapTable_frame_T.readData(count); - // the rest of it depends in a complicated way on frame tags - { - int fat_frame_count = 0; - int offset_count = 0; - int type_count = 0; - for (int k = 0; k < count; k++) - { - int tag = code_StackMapTable_frame_T.getByte(); - if (tag <= 127) - { - // (64-127) [(2)] - if (tag >= 64) - type_count++; - } - else if (tag <= 251) - { - // (247) [(1)(2)] - // (248-251) [(1)] - if (tag >= 247) - offset_count++; - if (tag == 247) - type_count++; - } - else if (tag <= 254) - { - // (252) [(1)(2)] - // (253) [(1)(2)(2)] - // (254) [(1)(2)(2)(2)] - offset_count++; - type_count += (tag - 251); - } - else - { - // (255) [(1)NH[(2)]NH[(2)]] - fat_frame_count++; - } - } - - // done pre-scanning frame tags: - code_StackMapTable_frame_T.rewind(); - - // deal completely with fat frames: - offset_count += fat_frame_count; - code_StackMapTable_local_N.readData(fat_frame_count); - type_count += code_StackMapTable_local_N.getIntTotal(); - code_StackMapTable_stack_N.readData(fat_frame_count); - type_count += code_StackMapTable_stack_N.getIntTotal(); - // read the rest: - code_StackMapTable_offset.readData(offset_count); - code_StackMapTable_T.readData(type_count); - // (7) [RCH] - count = code_StackMapTable_T.getIntCount(7); - code_StackMapTable_RC.readData(count); - // (8) [PH] - count = code_StackMapTable_T.getIntCount(8); - code_StackMapTable_P.readData(count); - } - - count = ad.predefCount(CODE_ATTR_LineNumberTable); - code_LineNumberTable_N.readData(count); - count = code_LineNumberTable_N.getIntTotal(); - code_LineNumberTable_bci_P.readData(count); - code_LineNumberTable_line.readData(count); - - count = ad.predefCount(CODE_ATTR_LocalVariableTable); - code_LocalVariableTable_N.readData(count); - count = code_LocalVariableTable_N.getIntTotal(); - code_LocalVariableTable_bci_P.readData(count); - code_LocalVariableTable_span_O.readData(count); - code_LocalVariableTable_name_RU.readData(count); - code_LocalVariableTable_type_RS.readData(count); - code_LocalVariableTable_slot.readData(count); - - count = ad.predefCount(CODE_ATTR_LocalVariableTypeTable); - code_LocalVariableTypeTable_N.readData(count); - count = code_LocalVariableTypeTable_N.getIntTotal(); - code_LocalVariableTypeTable_bci_P.readData(count); - code_LocalVariableTypeTable_span_O.readData(count); - code_LocalVariableTypeTable_name_RU.readData(count); - code_LocalVariableTypeTable_type_RS.readData(count); - code_LocalVariableTypeTable_slot.readData(count); - break; - } - - // Read compressor-defined bands. - for (idx = 0; idx < ad.layouts.length(); idx++) - { - if (ad.getLayout(idx) == nullptr) - continue; // none at this fixed index <32 - if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) - continue; // already handled - if (ad.getCount(idx) == 0) - continue; // no attributes of this type (then why transmit layouts?) - ad.readBandData(idx); - } + attr_definitions &ad = attr_defs[attrc]; + assert(ad.attrc == attrc); + + int i, idx, count; + + bool haveLongFlags = ad.haveLongFlags(); + + band &xxx_flags_hi = ad.xxx_flags_hi(); + if (haveLongFlags) + xxx_flags_hi.readData(obj_count); + + band &xxx_flags_lo = ad.xxx_flags_lo(); + xxx_flags_lo.readData(obj_count); + + // pre-scan flags, counting occurrences of each index bit + uint64_t indexMask = ad.flagIndexMask(); // which flag bits are index bits? + for (i = 0; i < obj_count; i++) + { + uint64_t indexBits = xxx_flags_hi.getLong(xxx_flags_lo, haveLongFlags); + if ((indexBits & ~indexMask) > (ushort) - 1) + { + unpack_abort("undefined attribute flag bit"); + return; + } + indexBits &= indexMask; // ignore classfile flag bits + for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) + { + ad.flag_count[idx] += (int)(indexBits & 1); + } + } + // we'll scan these again later for output: + xxx_flags_lo.rewind(); + xxx_flags_hi.rewind(); + + band &xxx_attr_count = ad.xxx_attr_count(); + // There is one count element for each 1<<16 bit set in flags: + xxx_attr_count.readData(ad.predefCount(X_ATTR_OVERFLOW)); + + band &xxx_attr_indexes = ad.xxx_attr_indexes(); + int overflowIndexCount = xxx_attr_count.getIntTotal(); + xxx_attr_indexes.readData(overflowIndexCount); + // pre-scan attr indexes, counting occurrences of each value + for (i = 0; i < overflowIndexCount; i++) + { + idx = xxx_attr_indexes.getInt(); + if (!ad.isIndex(idx)) + { + unpack_abort("attribute index out of bounds"); + return; + } + ad.getCount(idx) += 1; + } + xxx_attr_indexes.rewind(); // we'll scan it again later for output + + // We will need a backward call count for each used backward callable. + int backwardCounts = 0; + for (idx = 0; idx < ad.layouts.length(); idx++) + { + layout_definition *lo = ad.getLayout(idx); + if (lo != nullptr && ad.getCount(idx) != 0) + { + // Build the bands lazily, only when they are used. + band **bands = ad.buildBands(lo); + if (lo->hasCallables()) + { + for (i = 0; bands[i] != nullptr; i++) + { + if (bands[i]->le_back) + { + assert(bands[i]->le_kind == EK_CBLE); + backwardCounts += 1; + } + } + } + } + } + ad.xxx_attr_calls().readData(backwardCounts); + + // Read built-in bands. + // Mostly, these are hand-coded equivalents to readBandData(). + switch (attrc) + { + case ATTR_CONTEXT_CLASS: + + count = ad.predefCount(CLASS_ATTR_SourceFile); + class_SourceFile_RUN.readData(count); + + count = ad.predefCount(CLASS_ATTR_EnclosingMethod); + class_EnclosingMethod_RC.readData(count); + class_EnclosingMethod_RDN.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + class_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + + count = ad.predefCount(CLASS_ATTR_InnerClasses); + class_InnerClasses_N.readData(count); + + count = class_InnerClasses_N.getIntTotal(); + class_InnerClasses_RC.readData(count); + class_InnerClasses_F.readData(count); + + // Drop remaining columns wherever flags are zero: + count -= class_InnerClasses_F.getIntCount(0); + class_InnerClasses_outer_RCN.readData(count); + class_InnerClasses_name_RUN.readData(count); + + count = ad.predefCount(CLASS_ATTR_ClassFile_version); + class_ClassFile_version_minor_H.readData(count); + class_ClassFile_version_major_H.readData(count); + break; + + case ATTR_CONTEXT_FIELD: + + count = ad.predefCount(FIELD_ATTR_ConstantValue); + field_ConstantValue_KQ.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + field_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + break; + + case ATTR_CONTEXT_METHOD: + + code_count = ad.predefCount(METHOD_ATTR_Code); + // Code attrs are handled very specially below... + + count = ad.predefCount(METHOD_ATTR_Exceptions); + method_Exceptions_N.readData(count); + count = method_Exceptions_N.getIntTotal(); + method_Exceptions_RC.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + method_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + ad.readBandData(METHOD_ATTR_RuntimeVisibleParameterAnnotations); + ad.readBandData(METHOD_ATTR_RuntimeInvisibleParameterAnnotations); + ad.readBandData(METHOD_ATTR_AnnotationDefault); + break; + + case ATTR_CONTEXT_CODE: + // (keep this code aligned with its brother in unpacker::write_attrs) + count = ad.predefCount(CODE_ATTR_StackMapTable); + // disable this feature in old archives! + if (count != 0 && majver < JAVA6_PACKAGE_MAJOR_VERSION) + { + unpack_abort("undefined StackMapTable attribute (old archive format)"); + return; + } + code_StackMapTable_N.readData(count); + count = code_StackMapTable_N.getIntTotal(); + code_StackMapTable_frame_T.readData(count); + // the rest of it depends in a complicated way on frame tags + { + int fat_frame_count = 0; + int offset_count = 0; + int type_count = 0; + for (int k = 0; k < count; k++) + { + int tag = code_StackMapTable_frame_T.getByte(); + if (tag <= 127) + { + // (64-127) [(2)] + if (tag >= 64) + type_count++; + } + else if (tag <= 251) + { + // (247) [(1)(2)] + // (248-251) [(1)] + if (tag >= 247) + offset_count++; + if (tag == 247) + type_count++; + } + else if (tag <= 254) + { + // (252) [(1)(2)] + // (253) [(1)(2)(2)] + // (254) [(1)(2)(2)(2)] + offset_count++; + type_count += (tag - 251); + } + else + { + // (255) [(1)NH[(2)]NH[(2)]] + fat_frame_count++; + } + } + + // done pre-scanning frame tags: + code_StackMapTable_frame_T.rewind(); + + // deal completely with fat frames: + offset_count += fat_frame_count; + code_StackMapTable_local_N.readData(fat_frame_count); + type_count += code_StackMapTable_local_N.getIntTotal(); + code_StackMapTable_stack_N.readData(fat_frame_count); + type_count += code_StackMapTable_stack_N.getIntTotal(); + // read the rest: + code_StackMapTable_offset.readData(offset_count); + code_StackMapTable_T.readData(type_count); + // (7) [RCH] + count = code_StackMapTable_T.getIntCount(7); + code_StackMapTable_RC.readData(count); + // (8) [PH] + count = code_StackMapTable_T.getIntCount(8); + code_StackMapTable_P.readData(count); + } + + count = ad.predefCount(CODE_ATTR_LineNumberTable); + code_LineNumberTable_N.readData(count); + count = code_LineNumberTable_N.getIntTotal(); + code_LineNumberTable_bci_P.readData(count); + code_LineNumberTable_line.readData(count); + + count = ad.predefCount(CODE_ATTR_LocalVariableTable); + code_LocalVariableTable_N.readData(count); + count = code_LocalVariableTable_N.getIntTotal(); + code_LocalVariableTable_bci_P.readData(count); + code_LocalVariableTable_span_O.readData(count); + code_LocalVariableTable_name_RU.readData(count); + code_LocalVariableTable_type_RS.readData(count); + code_LocalVariableTable_slot.readData(count); + + count = ad.predefCount(CODE_ATTR_LocalVariableTypeTable); + code_LocalVariableTypeTable_N.readData(count); + count = code_LocalVariableTypeTable_N.getIntTotal(); + code_LocalVariableTypeTable_bci_P.readData(count); + code_LocalVariableTypeTable_span_O.readData(count); + code_LocalVariableTypeTable_name_RU.readData(count); + code_LocalVariableTypeTable_type_RS.readData(count); + code_LocalVariableTypeTable_slot.readData(count); + break; + } + + // Read compressor-defined bands. + for (idx = 0; idx < ad.layouts.length(); idx++) + { + if (ad.getLayout(idx) == nullptr) + continue; // none at this fixed index <32 + if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) + continue; // already handled + if (ad.getCount(idx) == 0) + continue; // no attributes of this type (then why transmit layouts?) + ad.readBandData(idx); + } } void unpacker::attr_definitions::readBandData(int idx) { - int j; - uint32_t count = getCount(idx); - if (count == 0) - return; - layout_definition *lo = getLayout(idx); - bool hasCallables = lo->hasCallables(); - band **bands = lo->bands(); - if (!hasCallables) - { - // Read through the rest of the bands in a regular way. - readBandData(bands, count); - } - else - { - // Deal with the callables. - // First set up the forward entry count for each callable. - // This is stored on band::length of the callable. - bands[0]->expectMoreLength(count); - for (j = 0; bands[j] != nullptr; j++) - { - band &j_cble = *bands[j]; - assert(j_cble.le_kind == EK_CBLE); - if (j_cble.le_back) - { - // Add in the predicted effects of backward calls, too. - int back_calls = xxx_attr_calls().getInt(); - j_cble.expectMoreLength(back_calls); - // In a moment, more forward calls may increment j_cble.length. - } - } - // Now consult whichever callables have non-zero entry counts. - readBandData(bands, (uint32_t) - 1); - } + int j; + uint32_t count = getCount(idx); + if (count == 0) + return; + layout_definition *lo = getLayout(idx); + bool hasCallables = lo->hasCallables(); + band **bands = lo->bands(); + if (!hasCallables) + { + // Read through the rest of the bands in a regular way. + readBandData(bands, count); + } + else + { + // Deal with the callables. + // First set up the forward entry count for each callable. + // This is stored on band::length of the callable. + bands[0]->expectMoreLength(count); + for (j = 0; bands[j] != nullptr; j++) + { + band &j_cble = *bands[j]; + assert(j_cble.le_kind == EK_CBLE); + if (j_cble.le_back) + { + // Add in the predicted effects of backward calls, too. + int back_calls = xxx_attr_calls().getInt(); + j_cble.expectMoreLength(back_calls); + // In a moment, more forward calls may increment j_cble.length. + } + } + // Now consult whichever callables have non-zero entry counts. + readBandData(bands, (uint32_t) - 1); + } } // Recursive helper to the previous function: void unpacker::attr_definitions::readBandData(band **body, uint32_t count) { - int j, k; - for (j = 0; body[j] != nullptr; j++) - { - band &b = *body[j]; - if (b.defc != nullptr) - { - // It has data, so read it. - b.readData(count); - } - switch (b.le_kind) - { - case EK_REPL: - { - int reps = b.getIntTotal(); - readBandData(b.le_body, reps); - } - break; - case EK_UN: - { - int remaining = count; - for (k = 0; b.le_body[k] != nullptr; k++) - { - band &k_case = *b.le_body[k]; - int k_count = 0; - if (k_case.le_casetags == nullptr) - { - k_count = remaining; // last (empty) case - } - else - { - int *tags = k_case.le_casetags; - int ntags = *tags++; // 1st element is length (why not?) - while (ntags-- > 0) - { - int tag = *tags++; - k_count += b.getIntCount(tag); - } - } - readBandData(k_case.le_body, k_count); - remaining -= k_count; - } - assert(remaining == 0); - } - break; - case EK_CALL: - // Push the count forward, if it is not a backward call. - if (!b.le_back) - { - band &cble = *b.le_body[0]; - assert(cble.le_kind == EK_CBLE); - cble.expectMoreLength(count); - } - break; - case EK_CBLE: - assert((int)count == -1); // incoming count is meaningless - k = b.length; - assert(k >= 0); - // This is intended and required for non production mode. - assert((b.length = -1)); // make it unable to accept more calls now. - readBandData(b.le_body, k); - break; - } - } + int j, k; + for (j = 0; body[j] != nullptr; j++) + { + band &b = *body[j]; + if (b.defc != nullptr) + { + // It has data, so read it. + b.readData(count); + } + switch (b.le_kind) + { + case EK_REPL: + { + int reps = b.getIntTotal(); + readBandData(b.le_body, reps); + } + break; + case EK_UN: + { + int remaining = count; + for (k = 0; b.le_body[k] != nullptr; k++) + { + band &k_case = *b.le_body[k]; + int k_count = 0; + if (k_case.le_casetags == nullptr) + { + k_count = remaining; // last (empty) case + } + else + { + int *tags = k_case.le_casetags; + int ntags = *tags++; // 1st element is length (why not?) + while (ntags-- > 0) + { + int tag = *tags++; + k_count += b.getIntCount(tag); + } + } + readBandData(k_case.le_body, k_count); + remaining -= k_count; + } + assert(remaining == 0); + } + break; + case EK_CALL: + // Push the count forward, if it is not a backward call. + if (!b.le_back) + { + band &cble = *b.le_body[0]; + assert(cble.le_kind == EK_CBLE); + cble.expectMoreLength(count); + } + break; + case EK_CBLE: + assert((int)count == -1); // incoming count is meaningless + k = b.length; + assert(k >= 0); + // This is intended and required for non production mode. + assert((b.length = -1)); // make it unable to accept more calls now. + readBandData(b.le_body, k); + break; + } + } } static inline band **findMatchingCase(int matchTag, band **cases) { - for (int k = 0; cases[k] != nullptr; k++) - { - band &k_case = *cases[k]; - if (k_case.le_casetags != nullptr) - { - // If it has tags, it must match a tag. - int *tags = k_case.le_casetags; - int ntags = *tags++; // 1st element is length - for (; ntags > 0; ntags--) - { - int tag = *tags++; - if (tag == matchTag) - break; - } - if (ntags == 0) - continue; // does not match - } - return k_case.le_body; - } - return nullptr; + for (int k = 0; cases[k] != nullptr; k++) + { + band &k_case = *cases[k]; + if (k_case.le_casetags != nullptr) + { + // If it has tags, it must match a tag. + int *tags = k_case.le_casetags; + int ntags = *tags++; // 1st element is length + for (; ntags > 0; ntags--) + { + int tag = *tags++; + if (tag == matchTag) + break; + } + if (ntags == 0) + continue; // does not match + } + return k_case.le_body; + } + return nullptr; } // write attribute band data: void unpacker::putlayout(band **body) { - int i; - int prevBII = -1; - int prevBCI = -1; - if (body == NULL) - { - unpack_abort("putlayout: unexpected NULL for body"); - return; - } - for (i = 0; body[i] != nullptr; i++) - { - band &b = *body[i]; - byte le_kind = b.le_kind; - - // Handle scalar part, if any. - int x = 0; - entry *e = nullptr; - if (b.defc != nullptr) - { - // It has data, so unparse an element. - if (b.ixTag != CONSTANT_None) - { - assert(le_kind == EK_REF); - if (b.ixTag == CONSTANT_Literal) - e = b.getRefUsing(cp.getKQIndex()); - else - e = b.getRefN(); - switch (b.le_len) - { - case 0: - break; - case 1: - putu1ref(e); - break; - case 2: - putref(e); - break; - case 4: - putu2(0); - putref(e); - break; - default: - assert(false); - } - } - else - { - assert(le_kind == EK_INT || le_kind == EK_REPL || le_kind == EK_UN); - x = b.getInt(); - - assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); - switch (b.le_bci) - { - case EK_BCI: // PH: transmit R(bci), store bci - x = to_bci(prevBII = x); - prevBCI = x; - break; - case EK_BCID: // POH: transmit D(R(bci)), store bci - x = to_bci(prevBII += x); - prevBCI = x; - break; - case EK_BCO: // OH: transmit D(R(bci)), store D(bci) - x = to_bci(prevBII += x) - prevBCI; - prevBCI += x; - break; - } - assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); - - switch (b.le_len) - { - case 0: - break; - case 1: - putu1(x); - break; - case 2: - putu2(x); - break; - case 4: - putu4(x); - break; - default: - assert(false); - } - } - } - - // Handle subparts, if any. - switch (le_kind) - { - case EK_REPL: - // x is the repeat count - while (x-- > 0) - { - putlayout(b.le_body); - } - break; - case EK_UN: - // x is the tag - putlayout(findMatchingCase(x, b.le_body)); - break; - case EK_CALL: - { - band &cble = *b.le_body[0]; - assert(cble.le_kind == EK_CBLE); - // FIXME: hit this one - // assert(cble.le_len == b.le_len); - putlayout(cble.le_body); - } - break; - - case EK_CBLE: - case EK_CASE: - assert(false); // should not reach here - } - } + int i; + int prevBII = -1; + int prevBCI = -1; + if (body == NULL) + { + unpack_abort("putlayout: unexpected NULL for body"); + return; + } + for (i = 0; body[i] != nullptr; i++) + { + band &b = *body[i]; + byte le_kind = b.le_kind; + + // Handle scalar part, if any. + int x = 0; + entry *e = nullptr; + if (b.defc != nullptr) + { + // It has data, so unparse an element. + if (b.ixTag != CONSTANT_None) + { + assert(le_kind == EK_REF); + if (b.ixTag == CONSTANT_Literal) + e = b.getRefUsing(cp.getKQIndex()); + else + e = b.getRefN(); + switch (b.le_len) + { + case 0: + break; + case 1: + putu1ref(e); + break; + case 2: + putref(e); + break; + case 4: + putu2(0); + putref(e); + break; + default: + assert(false); + } + } + else + { + assert(le_kind == EK_INT || le_kind == EK_REPL || le_kind == EK_UN); + x = b.getInt(); + + assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); + switch (b.le_bci) + { + case EK_BCI: // PH: transmit R(bci), store bci + x = to_bci(prevBII = x); + prevBCI = x; + break; + case EK_BCID: // POH: transmit D(R(bci)), store bci + x = to_bci(prevBII += x); + prevBCI = x; + break; + case EK_BCO: // OH: transmit D(R(bci)), store D(bci) + x = to_bci(prevBII += x) - prevBCI; + prevBCI += x; + break; + } + assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); + + switch (b.le_len) + { + case 0: + break; + case 1: + putu1(x); + break; + case 2: + putu2(x); + break; + case 4: + putu4(x); + break; + default: + assert(false); + } + } + } + + // Handle subparts, if any. + switch (le_kind) + { + case EK_REPL: + // x is the repeat count + while (x-- > 0) + { + putlayout(b.le_body); + } + break; + case EK_UN: + // x is the tag + putlayout(findMatchingCase(x, b.le_body)); + break; + case EK_CALL: + { + band &cble = *b.le_body[0]; + assert(cble.le_kind == EK_CBLE); + // FIXME: hit this one + // assert(cble.le_len == b.le_len); + putlayout(cble.le_body); + } + break; + + case EK_CBLE: + case EK_CASE: + assert(false); // should not reach here + } + } } void unpacker::read_files() { - file_name.readData(file_count); - if ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0) - file_size_hi.readData(file_count); - file_size_lo.readData(file_count); - if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) - file_modtime.readData(file_count); - int allFiles = file_count + class_count; - if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) - { - file_options.readData(file_count); - // FO_IS_CLASS_STUB might be set, causing overlap between classes and files - for (int i = 0; i < file_count; i++) - { - if ((file_options.getInt() & FO_IS_CLASS_STUB) != 0) - { - allFiles -= 1; // this one counts as both class and file - } - } - file_options.rewind(); - } - assert((default_file_options & FO_IS_CLASS_STUB) == 0); - files_remaining = allFiles; + file_name.readData(file_count); + if ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0) + file_size_hi.readData(file_count); + file_size_lo.readData(file_count); + if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) + file_modtime.readData(file_count); + int allFiles = file_count + class_count; + if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) + { + file_options.readData(file_count); + // FO_IS_CLASS_STUB might be set, causing overlap between classes and files + for (int i = 0; i < file_count; i++) + { + if ((file_options.getInt() & FO_IS_CLASS_STUB) != 0) + { + allFiles -= 1; // this one counts as both class and file + } + } + file_options.rewind(); + } + assert((default_file_options & FO_IS_CLASS_STUB) == 0); + files_remaining = allFiles; } void unpacker::get_code_header(int &max_stack, int &max_na_locals, int &handler_count, - int &cflags) + int &cflags) { - int sc = code_headers.getByte(); - if (sc == 0) - { - max_stack = max_na_locals = handler_count = cflags = -1; - return; - } - // Short code header is the usual case: - int nh; - int mod; - if (sc < 1 + 12 * 12) - { - sc -= 1; - nh = 0; - mod = 12; - } - else if (sc < 1 + 12 * 12 + 8 * 8) - { - sc -= 1 + 12 * 12; - nh = 1; - mod = 8; - } - else - { - assert(sc < 1 + 12 * 12 + 8 * 8 + 7 * 7); - sc -= 1 + 12 * 12 + 8 * 8; - nh = 2; - mod = 7; - } - max_stack = sc % mod; - max_na_locals = sc / mod; // caller must add static, siglen - handler_count = nh; - if ((archive_options & AO_HAVE_ALL_CODE_FLAGS) != 0) - cflags = -1; - else - cflags = 0; // this one has no attributes + int sc = code_headers.getByte(); + if (sc == 0) + { + max_stack = max_na_locals = handler_count = cflags = -1; + return; + } + // Short code header is the usual case: + int nh; + int mod; + if (sc < 1 + 12 * 12) + { + sc -= 1; + nh = 0; + mod = 12; + } + else if (sc < 1 + 12 * 12 + 8 * 8) + { + sc -= 1 + 12 * 12; + nh = 1; + mod = 8; + } + else + { + assert(sc < 1 + 12 * 12 + 8 * 8 + 7 * 7); + sc -= 1 + 12 * 12 + 8 * 8; + nh = 2; + mod = 7; + } + max_stack = sc % mod; + max_na_locals = sc / mod; // caller must add static, siglen + handler_count = nh; + if ((archive_options & AO_HAVE_ALL_CODE_FLAGS) != 0) + cflags = -1; + else + cflags = 0; // this one has no attributes } // Cf. PackageReader.readCodeHeaders void unpacker::read_code_headers() { - code_headers.readData(code_count); - int totalHandlerCount = 0; - int totalFlagsCount = 0; - for (int i = 0; i < code_count; i++) - { - int max_stack, max_locals, handler_count, cflags; - get_code_header(max_stack, max_locals, handler_count, cflags); - if (max_stack < 0) - code_max_stack.expectMoreLength(1); - if (max_locals < 0) - code_max_na_locals.expectMoreLength(1); - if (handler_count < 0) - code_handler_count.expectMoreLength(1); - else - totalHandlerCount += handler_count; - if (cflags < 0) - totalFlagsCount += 1; - } - code_headers.rewind(); // replay later during writing - - code_max_stack.readData(); - code_max_na_locals.readData(); - code_handler_count.readData(); - totalHandlerCount += code_handler_count.getIntTotal(); - - // Read handler specifications. - // Cf. PackageReader.readCodeHandlers. - code_handler_start_P.readData(totalHandlerCount); - code_handler_end_PO.readData(totalHandlerCount); - code_handler_catch_PO.readData(totalHandlerCount); - code_handler_class_RCN.readData(totalHandlerCount); - - read_attrs(ATTR_CONTEXT_CODE, totalFlagsCount); + code_headers.readData(code_count); + int totalHandlerCount = 0; + int totalFlagsCount = 0; + for (int i = 0; i < code_count; i++) + { + int max_stack, max_locals, handler_count, cflags; + get_code_header(max_stack, max_locals, handler_count, cflags); + if (max_stack < 0) + code_max_stack.expectMoreLength(1); + if (max_locals < 0) + code_max_na_locals.expectMoreLength(1); + if (handler_count < 0) + code_handler_count.expectMoreLength(1); + else + totalHandlerCount += handler_count; + if (cflags < 0) + totalFlagsCount += 1; + } + code_headers.rewind(); // replay later during writing + + code_max_stack.readData(); + code_max_na_locals.readData(); + code_handler_count.readData(); + totalHandlerCount += code_handler_count.getIntTotal(); + + // Read handler specifications. + // Cf. PackageReader.readCodeHandlers. + code_handler_start_P.readData(totalHandlerCount); + code_handler_end_PO.readData(totalHandlerCount); + code_handler_catch_PO.readData(totalHandlerCount); + code_handler_class_RCN.readData(totalHandlerCount); + + read_attrs(ATTR_CONTEXT_CODE, totalFlagsCount); } static inline bool is_in_range(uint32_t n, uint32_t min, uint32_t max) { - return n - min <= max - min; // unsigned arithmetic! + return n - min <= max - min; // unsigned arithmetic! } static inline bool is_field_op(int bc) { - return is_in_range(bc, bc_getstatic, bc_putfield); + return is_in_range(bc, bc_getstatic, bc_putfield); } static inline bool is_invoke_init_op(int bc) { - return is_in_range(bc, _invokeinit_op, _invokeinit_limit - 1); + return is_in_range(bc, _invokeinit_op, _invokeinit_limit - 1); } static inline bool is_self_linker_op(int bc) { - return is_in_range(bc, _self_linker_op, _self_linker_limit - 1); + return is_in_range(bc, _self_linker_op, _self_linker_limit - 1); } static bool is_branch_op(int bc) { - return is_in_range(bc, bc_ifeq, bc_jsr) || is_in_range(bc, bc_ifnull, bc_jsr_w); + return is_in_range(bc, bc_ifeq, bc_jsr) || is_in_range(bc, bc_ifnull, bc_jsr_w); } static bool is_local_slot_op(int bc) { - return is_in_range(bc, bc_iload, bc_aload) || is_in_range(bc, bc_istore, bc_astore) || - bc == bc_iinc || bc == bc_ret; + return is_in_range(bc, bc_iload, bc_aload) || is_in_range(bc, bc_istore, bc_astore) || + bc == bc_iinc || bc == bc_ret; } band *unpacker::ref_band_for_op(int bc) { - switch (bc) - { - case bc_ildc: - case bc_ildc_w: - return &bc_intref; - case bc_fldc: - case bc_fldc_w: - return &bc_floatref; - case bc_lldc2_w: - return &bc_longref; - case bc_dldc2_w: - return &bc_doubleref; - case bc_aldc: - case bc_aldc_w: - return &bc_stringref; - case bc_cldc: - case bc_cldc_w: - return &bc_classref; - - case bc_getstatic: - case bc_putstatic: - case bc_getfield: - case bc_putfield: - return &bc_fieldref; - - case bc_invokevirtual: - case bc_invokespecial: - case bc_invokestatic: - return &bc_methodref; - case bc_invokeinterface: - return &bc_imethodref; - - case bc_new: - case bc_anewarray: - case bc_checkcast: - case bc_instanceof: - case bc_multianewarray: - return &bc_classref; - } - return nullptr; + switch (bc) + { + case bc_ildc: + case bc_ildc_w: + return &bc_intref; + case bc_fldc: + case bc_fldc_w: + return &bc_floatref; + case bc_lldc2_w: + return &bc_longref; + case bc_dldc2_w: + return &bc_doubleref; + case bc_aldc: + case bc_aldc_w: + return &bc_stringref; + case bc_cldc: + case bc_cldc_w: + return &bc_classref; + + case bc_getstatic: + case bc_putstatic: + case bc_getfield: + case bc_putfield: + return &bc_fieldref; + + case bc_invokevirtual: + case bc_invokespecial: + case bc_invokestatic: + return &bc_methodref; + case bc_invokeinterface: + return &bc_imethodref; + + case bc_new: + case bc_anewarray: + case bc_checkcast: + case bc_instanceof: + case bc_multianewarray: + return &bc_classref; + } + return nullptr; } band *unpacker::ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar) { - if (!is_self_linker_op(bc)) - return nullptr; - int idx = (bc - _self_linker_op); - bool isSuper = (idx >= _self_linker_super_flag); - if (isSuper) - idx -= _self_linker_super_flag; - bool isAload = (idx >= _self_linker_aload_flag); - if (isAload) - idx -= _self_linker_aload_flag; - int origBC = _first_linker_op + idx; - bool isField = is_field_op(origBC); - isAloadVar = isAload; - origBCVar = _first_linker_op + idx; - if (!isSuper) - return isField ? &bc_thisfield : &bc_thismethod; - else - return isField ? &bc_superfield : &bc_supermethod; + if (!is_self_linker_op(bc)) + return nullptr; + int idx = (bc - _self_linker_op); + bool isSuper = (idx >= _self_linker_super_flag); + if (isSuper) + idx -= _self_linker_super_flag; + bool isAload = (idx >= _self_linker_aload_flag); + if (isAload) + idx -= _self_linker_aload_flag; + int origBC = _first_linker_op + idx; + bool isField = is_field_op(origBC); + isAloadVar = isAload; + origBCVar = _first_linker_op + idx; + if (!isSuper) + return isField ? &bc_thisfield : &bc_thismethod; + else + return isField ? &bc_superfield : &bc_supermethod; } // Cf. PackageReader.readByteCodes inline // called exactly once => inline - void + void unpacker::read_bcs() { - // read from bc_codes and bc_case_count - fillbytes all_switch_ops; - all_switch_ops.init(); - - // Read directly from rp/rplimit. - // Do this later: bc_codes.readData(...) - byte *rp0 = rp; - - band *bc_which; - byte *opptr = rp; - byte *oplimit = rplimit; - - bool isAload; // passed by ref and then ignored - int junkBC; // passed by ref and then ignored - for (int k = 0; k < code_count; k++) - { - // Scan one method: - for (;;) - { - if (opptr + 2 > oplimit) - { - rp = opptr; - ensure_input(2); - oplimit = rplimit; - rp = rp0; // back up - } - if (opptr == oplimit) - { - unpack_abort(); - } - int bc = *opptr++ & 0xFF; - bool isWide = false; - if (bc == bc_wide) - { - if (opptr == oplimit) - { - unpack_abort(); - } - bc = *opptr++ & 0xFF; - isWide = true; - } - // Adjust expectations of various band sizes. - switch (bc) - { - case bc_tableswitch: - case bc_lookupswitch: - all_switch_ops.addByte(bc); - break; - case bc_iinc: - bc_local.expectMoreLength(1); - bc_which = isWide ? &bc_short : &bc_byte; - bc_which->expectMoreLength(1); - break; - case bc_sipush: - bc_short.expectMoreLength(1); - break; - case bc_bipush: - bc_byte.expectMoreLength(1); - break; - case bc_newarray: - bc_byte.expectMoreLength(1); - break; - case bc_multianewarray: - assert(ref_band_for_op(bc) == &bc_classref); - bc_classref.expectMoreLength(1); - bc_byte.expectMoreLength(1); - break; - case bc_ref_escape: - bc_escrefsize.expectMoreLength(1); - bc_escref.expectMoreLength(1); - break; - case bc_byte_escape: - bc_escsize.expectMoreLength(1); - // bc_escbyte will have to be counted too - break; - default: - if (is_invoke_init_op(bc)) - { - bc_initref.expectMoreLength(1); - break; - } - bc_which = ref_band_for_self_op(bc, isAload, junkBC); - if (bc_which != nullptr) - { - bc_which->expectMoreLength(1); - break; - } - if (is_branch_op(bc)) - { - bc_label.expectMoreLength(1); - break; - } - bc_which = ref_band_for_op(bc); - if (bc_which != nullptr) - { - bc_which->expectMoreLength(1); - assert(bc != bc_multianewarray); // handled elsewhere - break; - } - if (is_local_slot_op(bc)) - { - bc_local.expectMoreLength(1); - break; - } - break; - case bc_end_marker: - // Increment k and test against code_count. - goto doneScanningMethod; - } - } - doneScanningMethod: - { - } - } - - // Go through the formality, so we can use it in a regular fashion later: - assert(rp == rp0); - bc_codes.readData((int)(opptr - rp)); - - int i = 0; - - // To size instruction bands correctly, we need info on switches: - bc_case_count.readData((int)all_switch_ops.size()); - for (i = 0; i < (int)all_switch_ops.size(); i++) - { - int caseCount = bc_case_count.getInt(); - int bc = all_switch_ops.getByte(i); - bc_label.expectMoreLength(1 + caseCount); // default label + cases - bc_case_value.expectMoreLength(bc == bc_tableswitch ? 1 : caseCount); - } - bc_case_count.rewind(); // uses again for output - - all_switch_ops.free(); - - for (i = e_bc_case_value; i <= e_bc_escsize; i++) - { - all_bands[i].readData(); - } - - // The bc_escbyte band is counted by the immediately previous band. - bc_escbyte.readData(bc_escsize.getIntTotal()); + // read from bc_codes and bc_case_count + fillbytes all_switch_ops; + all_switch_ops.init(); + + // Read directly from rp/rplimit. + // Do this later: bc_codes.readData(...) + byte *rp0 = rp; + + band *bc_which; + byte *opptr = rp; + byte *oplimit = rplimit; + + bool isAload; // passed by ref and then ignored + int junkBC; // passed by ref and then ignored + for (int k = 0; k < code_count; k++) + { + // Scan one method: + for (;;) + { + if (opptr + 2 > oplimit) + { + rp = opptr; + ensure_input(2); + oplimit = rplimit; + rp = rp0; // back up + } + if (opptr == oplimit) + { + unpack_abort(); + } + int bc = *opptr++ & 0xFF; + bool isWide = false; + if (bc == bc_wide) + { + if (opptr == oplimit) + { + unpack_abort(); + } + bc = *opptr++ & 0xFF; + isWide = true; + } + // Adjust expectations of various band sizes. + switch (bc) + { + case bc_tableswitch: + case bc_lookupswitch: + all_switch_ops.addByte(bc); + break; + case bc_iinc: + bc_local.expectMoreLength(1); + bc_which = isWide ? &bc_short : &bc_byte; + bc_which->expectMoreLength(1); + break; + case bc_sipush: + bc_short.expectMoreLength(1); + break; + case bc_bipush: + bc_byte.expectMoreLength(1); + break; + case bc_newarray: + bc_byte.expectMoreLength(1); + break; + case bc_multianewarray: + assert(ref_band_for_op(bc) == &bc_classref); + bc_classref.expectMoreLength(1); + bc_byte.expectMoreLength(1); + break; + case bc_ref_escape: + bc_escrefsize.expectMoreLength(1); + bc_escref.expectMoreLength(1); + break; + case bc_byte_escape: + bc_escsize.expectMoreLength(1); + // bc_escbyte will have to be counted too + break; + default: + if (is_invoke_init_op(bc)) + { + bc_initref.expectMoreLength(1); + break; + } + bc_which = ref_band_for_self_op(bc, isAload, junkBC); + if (bc_which != nullptr) + { + bc_which->expectMoreLength(1); + break; + } + if (is_branch_op(bc)) + { + bc_label.expectMoreLength(1); + break; + } + bc_which = ref_band_for_op(bc); + if (bc_which != nullptr) + { + bc_which->expectMoreLength(1); + assert(bc != bc_multianewarray); // handled elsewhere + break; + } + if (is_local_slot_op(bc)) + { + bc_local.expectMoreLength(1); + break; + } + break; + case bc_end_marker: + // Increment k and test against code_count. + goto doneScanningMethod; + } + } + doneScanningMethod: + { + } + } + + // Go through the formality, so we can use it in a regular fashion later: + assert(rp == rp0); + bc_codes.readData((int)(opptr - rp)); + + int i = 0; + + // To size instruction bands correctly, we need info on switches: + bc_case_count.readData((int)all_switch_ops.size()); + for (i = 0; i < (int)all_switch_ops.size(); i++) + { + int caseCount = bc_case_count.getInt(); + int bc = all_switch_ops.getByte(i); + bc_label.expectMoreLength(1 + caseCount); // default label + cases + bc_case_value.expectMoreLength(bc == bc_tableswitch ? 1 : caseCount); + } + bc_case_count.rewind(); // uses again for output + + all_switch_ops.free(); + + for (i = e_bc_case_value; i <= e_bc_escsize; i++) + { + all_bands[i].readData(); + } + + // The bc_escbyte band is counted by the immediately previous band. + bc_escbyte.readData(bc_escsize.getIntTotal()); } void unpacker::read_bands() { - read_file_header(); - - if (cp.nentries == 0) - { - // read_file_header failed to read a CP, because it copied a JAR. - return; - } - - // Do this after the file header has been read: - check_options(); - - read_cp(); - read_attr_defs(); - read_ics(); - read_classes(); - read_bcs(); - read_files(); + read_file_header(); + + if (cp.nentries == 0) + { + // read_file_header failed to read a CP, because it copied a JAR. + return; + } + + // Do this after the file header has been read: + check_options(); + + read_cp(); + read_attr_defs(); + read_ics(); + read_classes(); + read_bcs(); + read_files(); } /// CP routines entry *&constant_pool::hashTabRef(byte tag, bytes &b) { - uint32_t hash = tag + (int)b.len; - for (int i = 0; i < (int)b.len; i++) - { - hash = hash * 31 + (0xFF & b.ptr[i]); - } - entry **ht = hashTab; - int hlen = hashTabLength; - assert((hlen & (hlen - 1)) == 0); // must be power of 2 - uint32_t hash1 = hash & (hlen - 1); // == hash % hlen - uint32_t hash2 = 0; // lazily computed (requires mod op.) - int probes = 0; - while (ht[hash1] != nullptr) - { - entry &e = *ht[hash1]; - if (e.value.b.equals(b) && e.tag == tag) - break; - if (hash2 == 0) - // Note: hash2 must be relatively prime to hlen, hence the "|1". - hash2 = (((hash % 499) & (hlen - 1)) | 1); - hash1 += hash2; - if (hash1 >= (uint32_t)hlen) - hash1 -= hlen; - assert(hash1 < (uint32_t)hlen); - assert(++probes < hlen); - } - return ht[hash1]; + uint32_t hash = tag + (int)b.len; + for (int i = 0; i < (int)b.len; i++) + { + hash = hash * 31 + (0xFF & b.ptr[i]); + } + entry **ht = hashTab; + int hlen = hashTabLength; + assert((hlen & (hlen - 1)) == 0); // must be power of 2 + uint32_t hash1 = hash & (hlen - 1); // == hash % hlen + uint32_t hash2 = 0; // lazily computed (requires mod op.) +#ifndef NDEBUG + int probes = 0; +#endif + while (ht[hash1] != nullptr) + { + entry &e = *ht[hash1]; + if (e.value.b.equals(b) && e.tag == tag) + break; + if (hash2 == 0) + // Note: hash2 must be relatively prime to hlen, hence the "|1". + hash2 = (((hash % 499) & (hlen - 1)) | 1); + hash1 += hash2; + if (hash1 >= (uint32_t)hlen) + hash1 -= hlen; + assert(hash1 < (uint32_t)hlen); + assert(++probes < hlen); + } + return ht[hash1]; } static void insert_extra(entry *e, ptrlist &extras) { - // This ordering helps implement the Pack200 requirement - // of a predictable CP order in the class files produced. - e->inord = NO_INORD; // mark as an "extra" - extras.add(e); - // Note: We will sort the list (by string-name) later. + // This ordering helps implement the Pack200 requirement + // of a predictable CP order in the class files produced. + e->inord = NO_INORD; // mark as an "extra" + extras.add(e); + // Note: We will sort the list (by string-name) later. } entry *constant_pool::ensureUtf8(bytes &b) { - entry *&ix = hashTabRef(CONSTANT_Utf8, b); - if (ix != nullptr) - return ix; - // Make one. - if (nentries == maxentries) - { - unpack_abort("cp utf8 overflow"); - return &entries[tag_base[CONSTANT_Utf8]]; // return something - } - entry &e = entries[nentries++]; - e.tag = CONSTANT_Utf8; - u->saveTo(e.value.b, b); - assert(&e >= first_extra_entry); - insert_extra(&e, tag_extras[CONSTANT_Utf8]); - return ix = &e; + entry *&ix = hashTabRef(CONSTANT_Utf8, b); + if (ix != nullptr) + return ix; + // Make one. + if (nentries == maxentries) + { + unpack_abort("cp utf8 overflow"); + return &entries[tag_base[CONSTANT_Utf8]]; // return something + } + entry &e = entries[nentries++]; + e.tag = CONSTANT_Utf8; + u->saveTo(e.value.b, b); + assert(&e >= first_extra_entry); + insert_extra(&e, tag_extras[CONSTANT_Utf8]); + return ix = &e; } entry *constant_pool::ensureClass(bytes &b) { - entry *&ix = hashTabRef(CONSTANT_Class, b); - if (ix != nullptr) - return ix; - // Make one. - if (nentries == maxentries) - { - unpack_abort("cp class overflow"); - return &entries[tag_base[CONSTANT_Class]]; // return something - } - entry &e = entries[nentries++]; - e.tag = CONSTANT_Class; - e.nrefs = 1; - e.refs = U_NEW(entry *, 1); - ix = &e; // hold my spot in the index - entry *utf = ensureUtf8(b); - e.refs[0] = utf; - e.value.b = utf->value.b; - assert(&e >= first_extra_entry); - insert_extra(&e, tag_extras[CONSTANT_Class]); - return &e; + entry *&ix = hashTabRef(CONSTANT_Class, b); + if (ix != nullptr) + return ix; + // Make one. + if (nentries == maxentries) + { + unpack_abort("cp class overflow"); + return &entries[tag_base[CONSTANT_Class]]; // return something + } + entry &e = entries[nentries++]; + e.tag = CONSTANT_Class; + e.nrefs = 1; + e.refs = U_NEW(entry *, 1); + ix = &e; // hold my spot in the index + entry *utf = ensureUtf8(b); + e.refs[0] = utf; + e.value.b = utf->value.b; + assert(&e >= first_extra_entry); + insert_extra(&e, tag_extras[CONSTANT_Class]); + return &e; } void constant_pool::expandSignatures() { - int i; - int nsigs = 0; - int nreused = 0; - int first_sig = tag_base[CONSTANT_Signature]; - int sig_limit = tag_count[CONSTANT_Signature] + first_sig; - fillbytes buf; - buf.init(1 << 10); - for (i = first_sig; i < sig_limit; i++) - { - entry &e = entries[i]; - assert(e.tag == CONSTANT_Signature); - int refnum = 0; - bytes form = e.refs[refnum++]->asUtf8(); - buf.empty(); - for (int j = 0; j < (int)form.len; j++) - { - int c = form.ptr[j]; - buf.addByte(c); - if (c == 'L') - { - entry *cls = e.refs[refnum++]; - buf.append(cls->className()->asUtf8()); - } - } - assert(refnum == e.nrefs); - bytes &sig = buf.b; - - // try to find a pre-existing Utf8: - entry *&e2 = hashTabRef(CONSTANT_Utf8, sig); - if (e2 != nullptr) - { - assert(e2->isUtf8(sig)); - e.value.b = e2->value.b; - e.refs[0] = e2; - e.nrefs = 1; - nreused++; - } - else - { - // there is no other replacement; reuse this CP entry as a Utf8 - u->saveTo(e.value.b, sig); - e.tag = CONSTANT_Utf8; - e.nrefs = 0; - e2 = &e; - } - nsigs++; - } - buf.free(); - - // go expunge all references to remaining signatures: - for (i = 0; i < (int)nentries; i++) - { - entry &e = entries[i]; - for (int j = 0; j < e.nrefs; j++) - { - entry *&e2 = e.refs[j]; - if (e2 != nullptr && e2->tag == CONSTANT_Signature) - e2 = e2->refs[0]; - } - } + int i; + int nsigs = 0; + int nreused = 0; + int first_sig = tag_base[CONSTANT_Signature]; + int sig_limit = tag_count[CONSTANT_Signature] + first_sig; + fillbytes buf; + buf.init(1 << 10); + for (i = first_sig; i < sig_limit; i++) + { + entry &e = entries[i]; + assert(e.tag == CONSTANT_Signature); + int refnum = 0; + bytes form = e.refs[refnum++]->asUtf8(); + buf.empty(); + for (int j = 0; j < (int)form.len; j++) + { + int c = form.ptr[j]; + buf.addByte(c); + if (c == 'L') + { + entry *cls = e.refs[refnum++]; + buf.append(cls->className()->asUtf8()); + } + } + assert(refnum == e.nrefs); + bytes &sig = buf.b; + + // try to find a pre-existing Utf8: + entry *&e2 = hashTabRef(CONSTANT_Utf8, sig); + if (e2 != nullptr) + { + assert(e2->isUtf8(sig)); + e.value.b = e2->value.b; + e.refs[0] = e2; + e.nrefs = 1; + nreused++; + } + else + { + // there is no other replacement; reuse this CP entry as a Utf8 + u->saveTo(e.value.b, sig); + e.tag = CONSTANT_Utf8; + e.nrefs = 0; + e2 = &e; + } + nsigs++; + } + buf.free(); + + // go expunge all references to remaining signatures: + for (i = 0; i < (int)nentries; i++) + { + entry &e = entries[i]; + for (int j = 0; j < e.nrefs; j++) + { + entry *&e2 = e.refs[j]; + if (e2 != nullptr && e2->tag == CONSTANT_Signature) + e2 = e2->refs[0]; + } + } } void constant_pool::initMemberIndexes() { - // This function does NOT refer to any class schema. - // It is totally internal to the cpool. - int i, j; - - // Get the pre-existing indexes: - int nclasses = tag_count[CONSTANT_Class]; - // entry *classes = tag_base[CONSTANT_Class] + entries; // UNUSED - int nfields = tag_count[CONSTANT_Fieldref]; - entry *fields = tag_base[CONSTANT_Fieldref] + entries; - int nmethods = tag_count[CONSTANT_Methodref]; - entry *methods = tag_base[CONSTANT_Methodref] + entries; - - int *field_counts = T_NEW(int, nclasses); - int *method_counts = T_NEW(int, nclasses); - cpindex *all_indexes = U_NEW(cpindex, nclasses * 2); - entry **field_ix = U_NEW(entry *, add_size(nfields, nclasses)); - entry **method_ix = U_NEW(entry *, add_size(nmethods, nclasses)); - - for (j = 0; j < nfields; j++) - { - entry &f = fields[j]; - i = f.memberClass()->inord; - assert(i < nclasses); - field_counts[i]++; - } - for (j = 0; j < nmethods; j++) - { - entry &m = methods[j]; - i = m.memberClass()->inord; - assert(i < nclasses); - method_counts[i]++; - } - - int fbase = 0, mbase = 0; - for (i = 0; i < nclasses; i++) - { - int fc = field_counts[i]; - int mc = method_counts[i]; - all_indexes[i * 2 + 0].init(fc, field_ix + fbase, CONSTANT_Fieldref + SUBINDEX_BIT); - all_indexes[i * 2 + 1].init(mc, method_ix + mbase, CONSTANT_Methodref + SUBINDEX_BIT); - // reuse field_counts and member_counts as fill pointers: - field_counts[i] = fbase; - method_counts[i] = mbase; - fbase += fc + 1; - mbase += mc + 1; - // (the +1 leaves a space between every subarray) - } - assert(fbase == nfields + nclasses); - assert(mbase == nmethods + nclasses); - - for (j = 0; j < nfields; j++) - { - entry &f = fields[j]; - i = f.memberClass()->inord; - field_ix[field_counts[i]++] = &f; - } - for (j = 0; j < nmethods; j++) - { - entry &m = methods[j]; - i = m.memberClass()->inord; - method_ix[method_counts[i]++] = &m; - } - - member_indexes = all_indexes; - - // Free intermediate buffers. - u->free_temps(); + // This function does NOT refer to any class schema. + // It is totally internal to the cpool. + int i, j; + + // Get the pre-existing indexes: + int nclasses = tag_count[CONSTANT_Class]; + // entry *classes = tag_base[CONSTANT_Class] + entries; // UNUSED + int nfields = tag_count[CONSTANT_Fieldref]; + entry *fields = tag_base[CONSTANT_Fieldref] + entries; + int nmethods = tag_count[CONSTANT_Methodref]; + entry *methods = tag_base[CONSTANT_Methodref] + entries; + + int *field_counts = T_NEW(int, nclasses); + int *method_counts = T_NEW(int, nclasses); + cpindex *all_indexes = U_NEW(cpindex, nclasses * 2); + entry **field_ix = U_NEW(entry *, add_size(nfields, nclasses)); + entry **method_ix = U_NEW(entry *, add_size(nmethods, nclasses)); + + for (j = 0; j < nfields; j++) + { + entry &f = fields[j]; + i = f.memberClass()->inord; + assert(i < nclasses); + field_counts[i]++; + } + for (j = 0; j < nmethods; j++) + { + entry &m = methods[j]; + i = m.memberClass()->inord; + assert(i < nclasses); + method_counts[i]++; + } + + int fbase = 0, mbase = 0; + for (i = 0; i < nclasses; i++) + { + int fc = field_counts[i]; + int mc = method_counts[i]; + all_indexes[i * 2 + 0].init(fc, field_ix + fbase, CONSTANT_Fieldref + SUBINDEX_BIT); + all_indexes[i * 2 + 1].init(mc, method_ix + mbase, CONSTANT_Methodref + SUBINDEX_BIT); + // reuse field_counts and member_counts as fill pointers: + field_counts[i] = fbase; + method_counts[i] = mbase; + fbase += fc + 1; + mbase += mc + 1; + // (the +1 leaves a space between every subarray) + } + assert(fbase == nfields + nclasses); + assert(mbase == nmethods + nclasses); + + for (j = 0; j < nfields; j++) + { + entry &f = fields[j]; + i = f.memberClass()->inord; + field_ix[field_counts[i]++] = &f; + } + for (j = 0; j < nmethods; j++) + { + entry &m = methods[j]; + i = m.memberClass()->inord; + method_ix[method_counts[i]++] = &m; + } + + member_indexes = all_indexes; + + // Free intermediate buffers. + u->free_temps(); } void entry::requestOutputIndex(constant_pool &cp, int req) { - assert(outputIndex <= NOT_REQUESTED); // must not have assigned indexes yet - if (tag == CONSTANT_Signature) - { - ref(0)->requestOutputIndex(cp, req); - return; - } - assert(req == REQUESTED || req == REQUESTED_LDC); - if (outputIndex != NOT_REQUESTED) - { - if (req == REQUESTED_LDC) - outputIndex = req; // this kind has precedence - return; - } - outputIndex = req; - // assert(!cp.outputEntries.contains(this)); - assert(tag != CONSTANT_Signature); - cp.outputEntries.add(this); - for (int j = 0; j < nrefs; j++) - { - ref(j)->requestOutputIndex(cp); - } + assert(outputIndex <= NOT_REQUESTED); // must not have assigned indexes yet + if (tag == CONSTANT_Signature) + { + ref(0)->requestOutputIndex(cp, req); + return; + } + assert(req == REQUESTED || req == REQUESTED_LDC); + if (outputIndex != NOT_REQUESTED) + { + if (req == REQUESTED_LDC) + outputIndex = req; // this kind has precedence + return; + } + outputIndex = req; + // assert(!cp.outputEntries.contains(this)); + assert(tag != CONSTANT_Signature); + cp.outputEntries.add(this); + for (int j = 0; j < nrefs; j++) + { + ref(j)->requestOutputIndex(cp); + } } void constant_pool::resetOutputIndexes() { - int i; - int noes = outputEntries.length(); - entry **oes = (entry **)outputEntries.base(); - for (i = 0; i < noes; i++) - { - entry &e = *oes[i]; - e.outputIndex = NOT_REQUESTED; - } - outputIndexLimit = 0; - outputEntries.empty(); + int i; + int noes = outputEntries.length(); + entry **oes = (entry **)outputEntries.base(); + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + e.outputIndex = NOT_REQUESTED; + } + outputIndexLimit = 0; + outputEntries.empty(); } static const byte TAG_ORDER[CONSTANT_Limit] = {0, 1, 0, 2, 3, 4, 5, 7, 6, 10, 11, 12, 9, 8}; extern "C" int outputEntry_cmp(const void *e1p, const void *e2p) { - // Sort entries according to the Pack200 rules for deterministic - // constant pool ordering. - // - // The four sort keys as follows, in order of decreasing importance: - // 1. ldc first, then non-ldc guys - // 2. normal cp_All entries by input order (i.e., address order) - // 3. after that, extra entries by lexical order (as in tag_extras[*]) - entry &e1 = *(entry *)*(void **)e1p; - entry &e2 = *(entry *)*(void **)e2p; - int oi1 = e1.outputIndex; - int oi2 = e2.outputIndex; - assert(oi1 == REQUESTED || oi1 == REQUESTED_LDC); - assert(oi2 == REQUESTED || oi2 == REQUESTED_LDC); - if (oi1 != oi2) - { - if (oi1 == REQUESTED_LDC) - return 0 - 1; - if (oi2 == REQUESTED_LDC) - return 1 - 0; - // Else fall through; neither is an ldc request. - } - if (e1.inord != NO_INORD || e2.inord != NO_INORD) - { - // One or both is normal. Use input order. - if (&e1 > &e2) - return 1 - 0; - if (&e1 < &e2) - return 0 - 1; - return 0; // equal pointers - } - // Both are extras. Sort by tag and then by value. - if (e1.tag != e2.tag) - { - return TAG_ORDER[e1.tag] - TAG_ORDER[e2.tag]; - } - // If the tags are the same, use string comparison. - return compare_Utf8_chars(e1.value.b, e2.value.b); + // Sort entries according to the Pack200 rules for deterministic + // constant pool ordering. + // + // The four sort keys as follows, in order of decreasing importance: + // 1. ldc first, then non-ldc guys + // 2. normal cp_All entries by input order (i.e., address order) + // 3. after that, extra entries by lexical order (as in tag_extras[*]) + entry &e1 = *(entry *)*(void **)e1p; + entry &e2 = *(entry *)*(void **)e2p; + int oi1 = e1.outputIndex; + int oi2 = e2.outputIndex; + assert(oi1 == REQUESTED || oi1 == REQUESTED_LDC); + assert(oi2 == REQUESTED || oi2 == REQUESTED_LDC); + if (oi1 != oi2) + { + if (oi1 == REQUESTED_LDC) + return 0 - 1; + if (oi2 == REQUESTED_LDC) + return 1 - 0; + // Else fall through; neither is an ldc request. + } + if (e1.inord != NO_INORD || e2.inord != NO_INORD) + { + // One or both is normal. Use input order. + if (&e1 > &e2) + return 1 - 0; + if (&e1 < &e2) + return 0 - 1; + return 0; // equal pointers + } + // Both are extras. Sort by tag and then by value. + if (e1.tag != e2.tag) + { + return TAG_ORDER[e1.tag] - TAG_ORDER[e2.tag]; + } + // If the tags are the same, use string comparison. + return compare_Utf8_chars(e1.value.b, e2.value.b); } void constant_pool::computeOutputIndexes() { - int i; - - int noes = outputEntries.length(); - entry **oes = (entry **)outputEntries.base(); - - // Sort the output constant pool into the order required by Pack200. - PTRLIST_QSORT(outputEntries, outputEntry_cmp); - - // Allocate a new index for each entry that needs one. - // We do this in two passes, one for LDC entries and one for the rest. - int nextIndex = 1; // always skip index #0 in output cpool - for (i = 0; i < noes; i++) - { - entry &e = *oes[i]; - assert(e.outputIndex == REQUESTED || e.outputIndex == REQUESTED_LDC); - e.outputIndex = nextIndex++; - if (e.isDoubleWord()) - nextIndex++; // do not use the next index - } - outputIndexLimit = nextIndex; + int i; + + int noes = outputEntries.length(); + entry **oes = (entry **)outputEntries.base(); + + // Sort the output constant pool into the order required by Pack200. + PTRLIST_QSORT(outputEntries, outputEntry_cmp); + + // Allocate a new index for each entry that needs one. + // We do this in two passes, one for LDC entries and one for the rest. + int nextIndex = 1; // always skip index #0 in output cpool + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + assert(e.outputIndex == REQUESTED || e.outputIndex == REQUESTED_LDC); + e.outputIndex = nextIndex++; + if (e.isDoubleWord()) + nextIndex++; // do not use the next index + } + outputIndexLimit = nextIndex; } // Unpacker Start @@ -3477,64 +3473,63 @@ void constant_pool::computeOutputIndexes() // Do not reset any unpack options. void unpacker::reset() { - bytes_read_before_reset += bytes_read; - bytes_written_before_reset += bytes_written; - files_written_before_reset += files_written; - classes_written_before_reset += classes_written; - segments_read_before_reset += 1; - if (verbose >= 2) - { - fprintf(stderr, "After segment %d, " LONG_LONG_FORMAT - " bytes read and " LONG_LONG_FORMAT " bytes written.\n", - segments_read_before_reset - 1, bytes_read_before_reset, - bytes_written_before_reset); - fprintf(stderr, - "After segment %d, %d files (of which %d are classes) written to output.\n", - segments_read_before_reset - 1, files_written_before_reset, - classes_written_before_reset); - if (archive_next_count != 0) - { - fprintf(stderr, "After segment %d, %d segment%s remaining (estimated).\n", - segments_read_before_reset - 1, archive_next_count, - archive_next_count == 1 ? "" : "s"); - } - } - - unpacker save_u = (*this); // save bytewise image - infileptr = nullptr; // make asserts happy - jarout = nullptr; // do not close the output jar - gzin = nullptr; // do not close the input gzip stream - this->free(); - this->init(read_input_fn); - - // restore selected interface state: - infileptr = save_u.infileptr; - inbytes = save_u.inbytes; - jarout = save_u.jarout; - gzin = save_u.gzin; - verbose = save_u.verbose; - deflate_hint_or_zero = save_u.deflate_hint_or_zero; - modification_time_or_zero = save_u.modification_time_or_zero; - bytes_read_before_reset = save_u.bytes_read_before_reset; - bytes_written_before_reset = save_u.bytes_written_before_reset; - files_written_before_reset = save_u.files_written_before_reset; - classes_written_before_reset = save_u.classes_written_before_reset; - segments_read_before_reset = save_u.segments_read_before_reset; - // Note: If we use strip_names, watch out: They get nuked here. + bytes_read_before_reset += bytes_read; + bytes_written_before_reset += bytes_written; + files_written_before_reset += files_written; + classes_written_before_reset += classes_written; + segments_read_before_reset += 1; + if (verbose >= 2) + { + fprintf(stderr, "After segment %d, %" PRIu64 " bytes read and %" PRIu64 " bytes written.\n", + segments_read_before_reset - 1, bytes_read_before_reset, + bytes_written_before_reset); + fprintf(stderr, + "After segment %d, %d files (of which %d are classes) written to output.\n", + segments_read_before_reset - 1, files_written_before_reset, + classes_written_before_reset); + if (archive_next_count != 0) + { + fprintf(stderr, "After segment %d, %d segment%s remaining (estimated).\n", + segments_read_before_reset - 1, archive_next_count, + archive_next_count == 1 ? "" : "s"); + } + } + + unpacker save_u = (*this); // save bytewise image + infileptr = nullptr; // make asserts happy + jarout = nullptr; // do not close the output jar + gzin = nullptr; // do not close the input gzip stream + this->free(); + this->init(read_input_fn); + + // restore selected interface state: + infileptr = save_u.infileptr; + inbytes = save_u.inbytes; + jarout = save_u.jarout; + gzin = save_u.gzin; + verbose = save_u.verbose; + deflate_hint_or_zero = save_u.deflate_hint_or_zero; + modification_time_or_zero = save_u.modification_time_or_zero; + bytes_read_before_reset = save_u.bytes_read_before_reset; + bytes_written_before_reset = save_u.bytes_written_before_reset; + files_written_before_reset = save_u.files_written_before_reset; + classes_written_before_reset = save_u.classes_written_before_reset; + segments_read_before_reset = save_u.segments_read_before_reset; + // Note: If we use strip_names, watch out: They get nuked here. } void unpacker::init(read_input_fn_t input_fn) { - int i; - BYTES_OF(*this).clear(); - this->u = this; // self-reference for U_NEW macro - read_input_fn = input_fn; - all_bands = band::makeBands(this); - // Make a default jar buffer; caller may safely overwrite it. - jarout = U_NEW(jar, 1); - jarout->init(this); - for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) - attr_defs[i].u = u; // set up outer ptr + int i; + BYTES_OF(*this).clear(); + this->u = this; // self-reference for U_NEW macro + read_input_fn = input_fn; + all_bands = band::makeBands(this); + // Make a default jar buffer; caller may safely overwrite it. + jarout = U_NEW(jar, 1); + jarout->init(this); + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + attr_defs[i].u = u; // set up outer ptr } // Usage: unpack a byte buffer @@ -3543,1251 +3538,1253 @@ void unpacker::init(read_input_fn_t input_fn) // If nullptr, the callback is used to fill an internal buffer. void unpacker::start(void *packptr, size_t len) { - if (packptr != nullptr && len != 0) - { - inbytes.set((byte *)packptr, len); - } - read_bands(); + if (packptr != nullptr && len != 0) + { + inbytes.set((byte *)packptr, len); + } + read_bands(); } void unpacker::check_options() { - if (deflate_hint_or_zero != 0) - { - bool force_deflate_hint = (deflate_hint_or_zero > 0); - if (force_deflate_hint) - default_file_options |= FO_DEFLATE_HINT; - else - default_file_options &= ~FO_DEFLATE_HINT; - // Turn off per-file deflate hint by force. - suppress_file_options |= FO_DEFLATE_HINT; - } - if (modification_time_or_zero != 0) - { - default_file_modtime = modification_time_or_zero; - // Turn off per-file modtime by force. - archive_options &= ~AO_HAVE_FILE_MODTIME; - } + if (deflate_hint_or_zero != 0) + { + bool force_deflate_hint = (deflate_hint_or_zero > 0); + if (force_deflate_hint) + default_file_options |= FO_DEFLATE_HINT; + else + default_file_options &= ~FO_DEFLATE_HINT; + // Turn off per-file deflate hint by force. + suppress_file_options |= FO_DEFLATE_HINT; + } + if (modification_time_or_zero != 0) + { + default_file_modtime = modification_time_or_zero; + // Turn off per-file modtime by force. + archive_options &= ~AO_HAVE_FILE_MODTIME; + } } // classfile writing void unpacker::reset_cur_classfile() { - // set defaults - cur_class_minver = default_class_minver; - cur_class_majver = default_class_majver; - - // reset constant pool state - cp.resetOutputIndexes(); - - // reset fixups - class_fixup_type.empty(); - class_fixup_offset.empty(); - class_fixup_ref.empty(); - requested_ics.empty(); + // set defaults + cur_class_minver = default_class_minver; + cur_class_majver = default_class_majver; + + // reset constant pool state + cp.resetOutputIndexes(); + + // reset fixups + class_fixup_type.empty(); + class_fixup_offset.empty(); + class_fixup_ref.empty(); + requested_ics.empty(); } cpindex *constant_pool::getKQIndex() { - char ch = '?'; - if (u->cur_descr != nullptr) - { - entry *type = u->cur_descr->descrType(); - ch = type->value.b.ptr[0]; - } - byte tag = CONSTANT_Integer; - switch (ch) - { - case 'L': - tag = CONSTANT_String; - break; - case 'I': - tag = CONSTANT_Integer; - break; - case 'J': - tag = CONSTANT_Long; - break; - case 'F': - tag = CONSTANT_Float; - break; - case 'D': - tag = CONSTANT_Double; - break; - case 'B': - case 'S': - case 'C': - case 'Z': - tag = CONSTANT_Integer; - break; - default: - unpack_abort("bad KQ reference"); - break; - } - return getIndex(tag); + char ch = '?'; + if (u->cur_descr != nullptr) + { + entry *type = u->cur_descr->descrType(); + ch = type->value.b.ptr[0]; + } + byte tag = CONSTANT_Integer; + switch (ch) + { + case 'L': + tag = CONSTANT_String; + break; + case 'I': + tag = CONSTANT_Integer; + break; + case 'J': + tag = CONSTANT_Long; + break; + case 'F': + tag = CONSTANT_Float; + break; + case 'D': + tag = CONSTANT_Double; + break; + case 'B': + case 'S': + case 'C': + case 'Z': + tag = CONSTANT_Integer; + break; + default: + unpack_abort("bad KQ reference"); + break; + } + return getIndex(tag); } uint32_t unpacker::to_bci(uint32_t bii) { - uint32_t len = bcimap.length(); - uint32_t *map = (uint32_t *)bcimap.base(); - assert(len > 0); // must be initialized before using to_bci - if (bii < len) - return map[bii]; - // Else it's a fractional or out-of-range BCI. - uint32_t key = bii - len; - for (int i = len;; i--) - { - if (map[i - 1] - (i - 1) <= key) - break; - else - --bii; - } - return bii; + uint32_t len = bcimap.length(); + uint32_t *map = (uint32_t *)bcimap.base(); + assert(len > 0); // must be initialized before using to_bci + if (bii < len) + return map[bii]; + // Else it's a fractional or out-of-range BCI. + uint32_t key = bii - len; + for (int i = len;; i--) + { + if (map[i - 1] - (i - 1) <= key) + break; + else + --bii; + } + return bii; } void unpacker::put_stackmap_type() { - int tag = code_StackMapTable_T.getByte(); - putu1(tag); - switch (tag) - { - case 7: // (7) [RCH] - putref(code_StackMapTable_RC.getRef()); - break; - case 8: // (8) [PH] - putu2(to_bci(code_StackMapTable_P.getInt())); - break; - } + int tag = code_StackMapTable_T.getByte(); + putu1(tag); + switch (tag) + { + case 7: // (7) [RCH] + putref(code_StackMapTable_RC.getRef()); + break; + case 8: // (8) [PH] + putu2(to_bci(code_StackMapTable_P.getInt())); + break; + } } // Functions for writing code. void unpacker::put_label(int curIP, int size) { - code_fixup_type.addByte(size); - code_fixup_offset.add((int)put_empty(size)); - code_fixup_source.add(curIP); + code_fixup_type.addByte(size); + code_fixup_offset.add((int)put_empty(size)); + code_fixup_source.add(curIP); } inline // called exactly once => inline - void + void unpacker::write_bc_ops() { - bcimap.empty(); - code_fixup_type.empty(); - code_fixup_offset.empty(); - code_fixup_source.empty(); - - band *bc_which; - - byte *opptr = bc_codes.curRP(); - // No need for oplimit, since the codes are pre-counted. - - size_t codeBase = wpoffset(); - - bool isAload; // copy-out result - int origBC; - - entry *thisClass = cur_class; - entry *superClass = cur_super; - entry *newClass = nullptr; // class of last _new opcode - - // overwrite any prior index on these bands; it changes w/ current class: - bc_thisfield.setIndex(cp.getFieldIndex(thisClass)); - bc_thismethod.setIndex(cp.getMethodIndex(thisClass)); - if (superClass != nullptr) - { - bc_superfield.setIndex(cp.getFieldIndex(superClass)); - bc_supermethod.setIndex(cp.getMethodIndex(superClass)); - } - - for (int curIP = 0;; curIP++) - { - int curPC = (int)(wpoffset() - codeBase); - bcimap.add(curPC); - ensure_put_space(10); // covers most instrs w/o further bounds check - int bc = *opptr++ & 0xFF; - - putu1_fast(bc); - // Note: See '--wp' below for pseudo-bytecodes like bc_end_marker. - - bool isWide = false; - if (bc == bc_wide) - { - bc = *opptr++ & 0xFF; - putu1_fast(bc); - isWide = true; - } - switch (bc) - { - case bc_end_marker: - --wp; // not really part of the code - assert(opptr <= bc_codes.maxRP()); - bc_codes.curRP() = opptr; // advance over this in bc_codes - goto doneScanningMethod; - case bc_tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) - case bc_lookupswitch: // apc: (df, nc, nc*(case, label)) - { - int caseCount = bc_case_count.getInt(); - while (((wpoffset() - codeBase) % 4) != 0) - putu1_fast(0); - ensure_put_space(30 + caseCount * 8); - put_label(curIP, 4); // int df = bc_label.getInt(); - if (bc == bc_tableswitch) - { - int lo = bc_case_value.getInt(); - int hi = lo + caseCount - 1; - putu4(lo); - putu4(hi); - for (int j = 0; j < caseCount; j++) - { - put_label(curIP, 4); // int lVal = bc_label.getInt(); - // int cVal = lo + j; - } - } - else - { - putu4(caseCount); - for (int j = 0; j < caseCount; j++) - { - int cVal = bc_case_value.getInt(); - putu4(cVal); - put_label(curIP, 4); // int lVal = bc_label.getInt(); - } - } - assert((int)to_bci(curIP) == curPC); - continue; - } - case bc_iinc: - { - int local = bc_local.getInt(); - int delta = (isWide ? bc_short : bc_byte).getInt(); - if (isWide) - { - putu2(local); - putu2(delta); - } - else - { - putu1_fast(local); - putu1_fast(delta); - } - continue; - } - case bc_sipush: - { - int val = bc_short.getInt(); - putu2(val); - continue; - } - case bc_bipush: - case bc_newarray: - { - int val = bc_byte.getByte(); - putu1_fast(val); - continue; - } - case bc_ref_escape: - { - // Note that insnMap has one entry for this. - --wp; // not really part of the code - int size = bc_escrefsize.getInt(); - entry *ref = bc_escref.getRefN(); - switch (size) - { - case 1: - putu1ref(ref); - break; - case 2: - putref(ref); - break; - default: - assert(false); - } - continue; - } - case bc_byte_escape: - { - // Note that insnMap has one entry for all these bytes. - --wp; // not really part of the code - int size = bc_escsize.getInt(); - ensure_put_space(size); - for (int j = 0; j < size; j++) - putu1_fast(bc_escbyte.getByte()); - continue; - } - default: - if (is_invoke_init_op(bc)) - { - origBC = bc_invokespecial; - entry *classRef; - switch (bc - _invokeinit_op) - { - case _invokeinit_self_option: - classRef = thisClass; - break; - case _invokeinit_super_option: - classRef = superClass; - break; - default: - assert(bc == _invokeinit_op + _invokeinit_new_option); - case _invokeinit_new_option: - classRef = newClass; - break; - } - wp[-1] = origBC; // overwrite with origBC - int coding = bc_initref.getInt(); - // Find the nth overloading of in classRef. - entry *ref = nullptr; - cpindex *ix = (classRef == nullptr) ? nullptr : cp.getMethodIndex(classRef); - for (int j = 0, which_init = 0;; j++) - { - ref = (ix == nullptr) ? nullptr : ix->get(j); - if (ref == nullptr) - break; // oops, bad input - assert(ref->tag == CONSTANT_Methodref); - if (ref->memberDescr()->descrName() == cp.sym[constant_pool::s_lt_init_gt]) - { - if (which_init++ == coding) - break; - } - } - putref(ref); - continue; - } - bc_which = ref_band_for_self_op(bc, isAload, origBC); - if (bc_which != nullptr) - { - if (!isAload) - { - wp[-1] = origBC; // overwrite with origBC - } - else - { - wp[-1] = bc_aload_0; // overwrite with _aload_0 - // Note: insnMap keeps the _aload_0 separate. - bcimap.add(++curPC); - ++curIP; - putu1_fast(origBC); - } - entry *ref = bc_which->getRef(); - putref(ref); - continue; - } - if (is_branch_op(bc)) - { - // int lVal = bc_label.getInt(); - if (bc < bc_goto_w) - { - put_label(curIP, 2); // putu2(lVal & 0xFFFF); - } - else - { - assert(bc <= bc_jsr_w); - put_label(curIP, 4); // putu4(lVal); - } - assert((int)to_bci(curIP) == curPC); - continue; - } - bc_which = ref_band_for_op(bc); - if (bc_which != nullptr) - { - entry *ref = bc_which->getRefCommon(bc_which->ix, bc_which->nullOK); - if (ref == nullptr && bc_which == &bc_classref) - { - // Shorthand for class self-references. - ref = thisClass; - } - origBC = bc; - switch (bc) - { - case bc_ildc: - case bc_cldc: - case bc_fldc: - case bc_aldc: - origBC = bc_ldc; - break; - case bc_ildc_w: - case bc_cldc_w: - case bc_fldc_w: - case bc_aldc_w: - origBC = bc_ldc_w; - break; - case bc_lldc2_w: - case bc_dldc2_w: - origBC = bc_ldc2_w; - break; - case bc_new: - newClass = ref; - break; - } - wp[-1] = origBC; // overwrite with origBC - if (origBC == bc_ldc) - { - putu1ref(ref); - } - else - { - putref(ref); - } - if (origBC == bc_multianewarray) - { - // Copy the trailing byte also. - int val = bc_byte.getByte(); - putu1_fast(val); - } - else if (origBC == bc_invokeinterface) - { - int argSize = ref->memberDescr()->descrType()->typeSize(); - putu1_fast(1 + argSize); - putu1_fast(0); - } - continue; - } - if (is_local_slot_op(bc)) - { - int local = bc_local.getInt(); - if (isWide) - { - putu2(local); - if (bc == bc_iinc) - { - int iVal = bc_short.getInt(); - putu2(iVal); - } - } - else - { - putu1_fast(local); - if (bc == bc_iinc) - { - int iVal = bc_byte.getByte(); - putu1_fast(iVal); - } - } - continue; - } - // Random bytecode. Just copy it. - assert(bc < bc_bytecode_limit); - } - } + bcimap.empty(); + code_fixup_type.empty(); + code_fixup_offset.empty(); + code_fixup_source.empty(); + + band *bc_which; + + byte *opptr = bc_codes.curRP(); + // No need for oplimit, since the codes are pre-counted. + + size_t codeBase = wpoffset(); + + bool isAload; // copy-out result + int origBC; + + entry *thisClass = cur_class; + entry *superClass = cur_super; + entry *newClass = nullptr; // class of last _new opcode + + // overwrite any prior index on these bands; it changes w/ current class: + bc_thisfield.setIndex(cp.getFieldIndex(thisClass)); + bc_thismethod.setIndex(cp.getMethodIndex(thisClass)); + if (superClass != nullptr) + { + bc_superfield.setIndex(cp.getFieldIndex(superClass)); + bc_supermethod.setIndex(cp.getMethodIndex(superClass)); + } + + for (int curIP = 0;; curIP++) + { + int curPC = (int)(wpoffset() - codeBase); + bcimap.add(curPC); + ensure_put_space(10); // covers most instrs w/o further bounds check + int bc = *opptr++ & 0xFF; + + putu1_fast(bc); + // Note: See '--wp' below for pseudo-bytecodes like bc_end_marker. + + bool isWide = false; + if (bc == bc_wide) + { + bc = *opptr++ & 0xFF; + putu1_fast(bc); + isWide = true; + } + switch (bc) + { + case bc_end_marker: + --wp; // not really part of the code + assert(opptr <= bc_codes.maxRP()); + bc_codes.curRP() = opptr; // advance over this in bc_codes + goto doneScanningMethod; + case bc_tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) + case bc_lookupswitch: // apc: (df, nc, nc*(case, label)) + { + int caseCount = bc_case_count.getInt(); + while (((wpoffset() - codeBase) % 4) != 0) + putu1_fast(0); + ensure_put_space(30 + caseCount * 8); + put_label(curIP, 4); // int df = bc_label.getInt(); + if (bc == bc_tableswitch) + { + int lo = bc_case_value.getInt(); + int hi = lo + caseCount - 1; + putu4(lo); + putu4(hi); + for (int j = 0; j < caseCount; j++) + { + put_label(curIP, 4); // int lVal = bc_label.getInt(); + // int cVal = lo + j; + } + } + else + { + putu4(caseCount); + for (int j = 0; j < caseCount; j++) + { + int cVal = bc_case_value.getInt(); + putu4(cVal); + put_label(curIP, 4); // int lVal = bc_label.getInt(); + } + } + assert((int)to_bci(curIP) == curPC); + continue; + } + case bc_iinc: + { + int local = bc_local.getInt(); + int delta = (isWide ? bc_short : bc_byte).getInt(); + if (isWide) + { + putu2(local); + putu2(delta); + } + else + { + putu1_fast(local); + putu1_fast(delta); + } + continue; + } + case bc_sipush: + { + int val = bc_short.getInt(); + putu2(val); + continue; + } + case bc_bipush: + case bc_newarray: + { + int val = bc_byte.getByte(); + putu1_fast(val); + continue; + } + case bc_ref_escape: + { + // Note that insnMap has one entry for this. + --wp; // not really part of the code + int size = bc_escrefsize.getInt(); + entry *ref = bc_escref.getRefN(); + switch (size) + { + case 1: + putu1ref(ref); + break; + case 2: + putref(ref); + break; + default: + assert(false); + } + continue; + } + case bc_byte_escape: + { + // Note that insnMap has one entry for all these bytes. + --wp; // not really part of the code + int size = bc_escsize.getInt(); + ensure_put_space(size); + for (int j = 0; j < size; j++) + putu1_fast(bc_escbyte.getByte()); + continue; + } + default: + if (is_invoke_init_op(bc)) + { + origBC = bc_invokespecial; + entry *classRef; + switch (bc - _invokeinit_op) + { + case _invokeinit_self_option: + classRef = thisClass; + break; + case _invokeinit_super_option: + classRef = superClass; + break; + default: + assert(bc == _invokeinit_op + _invokeinit_new_option); + case _invokeinit_new_option: + classRef = newClass; + break; + } + wp[-1] = origBC; // overwrite with origBC + int coding = bc_initref.getInt(); + // Find the nth overloading of in classRef. + entry *ref = nullptr; + cpindex *ix = (classRef == nullptr) ? nullptr : cp.getMethodIndex(classRef); + for (int j = 0, which_init = 0;; j++) + { + ref = (ix == nullptr) ? nullptr : ix->get(j); + if (ref == nullptr) + break; // oops, bad input + assert(ref->tag == CONSTANT_Methodref); + if (ref->memberDescr()->descrName() == cp.sym[constant_pool::s_lt_init_gt]) + { + if (which_init++ == coding) + break; + } + } + putref(ref); + continue; + } + bc_which = ref_band_for_self_op(bc, isAload, origBC); + if (bc_which != nullptr) + { + if (!isAload) + { + wp[-1] = origBC; // overwrite with origBC + } + else + { + wp[-1] = bc_aload_0; // overwrite with _aload_0 + // Note: insnMap keeps the _aload_0 separate. + bcimap.add(++curPC); + ++curIP; + putu1_fast(origBC); + } + entry *ref = bc_which->getRef(); + putref(ref); + continue; + } + if (is_branch_op(bc)) + { + // int lVal = bc_label.getInt(); + if (bc < bc_goto_w) + { + put_label(curIP, 2); // putu2(lVal & 0xFFFF); + } + else + { + assert(bc <= bc_jsr_w); + put_label(curIP, 4); // putu4(lVal); + } + assert((int)to_bci(curIP) == curPC); + continue; + } + bc_which = ref_band_for_op(bc); + if (bc_which != nullptr) + { + entry *ref = bc_which->getRefCommon(bc_which->ix, bc_which->nullOK); + if (ref == nullptr && bc_which == &bc_classref) + { + // Shorthand for class self-references. + ref = thisClass; + } + origBC = bc; + switch (bc) + { + case bc_ildc: + case bc_cldc: + case bc_fldc: + case bc_aldc: + origBC = bc_ldc; + break; + case bc_ildc_w: + case bc_cldc_w: + case bc_fldc_w: + case bc_aldc_w: + origBC = bc_ldc_w; + break; + case bc_lldc2_w: + case bc_dldc2_w: + origBC = bc_ldc2_w; + break; + case bc_new: + newClass = ref; + break; + } + wp[-1] = origBC; // overwrite with origBC + if (origBC == bc_ldc) + { + putu1ref(ref); + } + else + { + putref(ref); + } + if (origBC == bc_multianewarray) + { + // Copy the trailing byte also. + int val = bc_byte.getByte(); + putu1_fast(val); + } + else if (origBC == bc_invokeinterface) + { + int argSize = ref->memberDescr()->descrType()->typeSize(); + putu1_fast(1 + argSize); + putu1_fast(0); + } + continue; + } + if (is_local_slot_op(bc)) + { + int local = bc_local.getInt(); + if (isWide) + { + putu2(local); + if (bc == bc_iinc) + { + int iVal = bc_short.getInt(); + putu2(iVal); + } + } + else + { + putu1_fast(local); + if (bc == bc_iinc) + { + int iVal = bc_byte.getByte(); + putu1_fast(iVal); + } + } + continue; + } + // Random bytecode. Just copy it. + assert(bc < bc_bytecode_limit); + } + } doneScanningMethod: { } - // bcimap.add(curPC); // PC limit is already also in map, from bc_end_marker - - // Armed with a bcimap, we can now fix up all the labels. - for (int i = 0; i < (int)code_fixup_type.size(); i++) - { - int type = code_fixup_type.getByte(i); - byte *bp = wp_at(code_fixup_offset.get(i)); - int curIP = code_fixup_source.get(i); - int destIP = curIP + bc_label.getInt(); - int span = to_bci(destIP) - to_bci(curIP); - switch (type) - { - case 2: - putu2_at(bp, (ushort)span); - break; - case 4: - putu4_at(bp, span); - break; - default: - assert(false); - } - } + // bcimap.add(curPC); // PC limit is already also in map, from bc_end_marker + + // Armed with a bcimap, we can now fix up all the labels. + for (int i = 0; i < (int)code_fixup_type.size(); i++) + { + int type = code_fixup_type.getByte(i); + byte *bp = wp_at(code_fixup_offset.get(i)); + int curIP = code_fixup_source.get(i); + int destIP = curIP + bc_label.getInt(); + int span = to_bci(destIP) - to_bci(curIP); + switch (type) + { + case 2: + putu2_at(bp, (ushort)span); + break; + case 4: + putu4_at(bp, span); + break; + default: + assert(false); + } + } } inline // called exactly once => inline - void + void unpacker::write_code() { - int j; - - int max_stack, max_locals, handler_count, cflags; - get_code_header(max_stack, max_locals, handler_count, cflags); - - if (max_stack < 0) - max_stack = code_max_stack.getInt(); - if (max_locals < 0) - max_locals = code_max_na_locals.getInt(); - if (handler_count < 0) - handler_count = code_handler_count.getInt(); - - int siglen = cur_descr->descrType()->typeSize(); - if ((cur_descr_flags & ACC_STATIC) == 0) - siglen++; - max_locals += siglen; - - putu2(max_stack); - putu2(max_locals); - size_t bcbase = put_empty(4); - - // Write the bytecodes themselves. - write_bc_ops(); - - byte *bcbasewp = wp_at(bcbase); - putu4_at(bcbasewp, (int)(wp - (bcbasewp + 4))); // size of code attr - - putu2(handler_count); - for (j = 0; j < handler_count; j++) - { - int bii = code_handler_start_P.getInt(); - putu2(to_bci(bii)); - bii += code_handler_end_PO.getInt(); - putu2(to_bci(bii)); - bii += code_handler_catch_PO.getInt(); - putu2(to_bci(bii)); - putref(code_handler_class_RCN.getRefN()); - } - - uint64_t indexBits = cflags; - if (cflags < 0) - { - bool haveLongFlags = attr_defs[ATTR_CONTEXT_CODE].haveLongFlags(); - indexBits = code_flags_hi.getLong(code_flags_lo, haveLongFlags); - } - write_attrs(ATTR_CONTEXT_CODE, indexBits); + int j; + + int max_stack, max_locals, handler_count, cflags; + get_code_header(max_stack, max_locals, handler_count, cflags); + + if (max_stack < 0) + max_stack = code_max_stack.getInt(); + if (max_locals < 0) + max_locals = code_max_na_locals.getInt(); + if (handler_count < 0) + handler_count = code_handler_count.getInt(); + + int siglen = cur_descr->descrType()->typeSize(); + if ((cur_descr_flags & ACC_STATIC) == 0) + siglen++; + max_locals += siglen; + + putu2(max_stack); + putu2(max_locals); + size_t bcbase = put_empty(4); + + // Write the bytecodes themselves. + write_bc_ops(); + + byte *bcbasewp = wp_at(bcbase); + putu4_at(bcbasewp, (int)(wp - (bcbasewp + 4))); // size of code attr + + putu2(handler_count); + for (j = 0; j < handler_count; j++) + { + int bii = code_handler_start_P.getInt(); + putu2(to_bci(bii)); + bii += code_handler_end_PO.getInt(); + putu2(to_bci(bii)); + bii += code_handler_catch_PO.getInt(); + putu2(to_bci(bii)); + putref(code_handler_class_RCN.getRefN()); + } + + uint64_t indexBits = cflags; + if (cflags < 0) + { + bool haveLongFlags = attr_defs[ATTR_CONTEXT_CODE].haveLongFlags(); + indexBits = code_flags_hi.getLong(code_flags_lo, haveLongFlags); + } + write_attrs(ATTR_CONTEXT_CODE, indexBits); } int unpacker::write_attrs(int attrc, uint64_t indexBits) { - if (indexBits == 0) - { - // Quick short-circuit. - putu2(0); - return 0; - } - - attr_definitions &ad = attr_defs[attrc]; - - int i, j, j2, idx, count; - - int oiCount = 0; - if (ad.isPredefined(X_ATTR_OVERFLOW) && (indexBits & ((uint64_t)1 << X_ATTR_OVERFLOW)) != 0) - { - indexBits -= ((uint64_t)1 << X_ATTR_OVERFLOW); - oiCount = ad.xxx_attr_count().getInt(); - } - - int bitIndexes[X_ATTR_LIMIT_FLAGS_HI]; - int biCount = 0; - - // Fill bitIndexes with index bits, in order. - for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) - { - if ((indexBits & 1) != 0) - bitIndexes[biCount++] = idx; - } - assert(biCount <= (int)lengthof(bitIndexes)); - - // Write a provisional attribute count, perhaps to be corrected later. - int naOffset = (int)wpoffset(); - int na0 = biCount + oiCount; - putu2(na0); - - int na = 0; - for (i = 0; i < na0; i++) - { - if (i < biCount) - idx = bitIndexes[i]; - else - idx = ad.xxx_attr_indexes().getInt(); - assert(ad.isIndex(idx)); - entry *aname = nullptr; - entry *ref; // scratch - size_t abase = put_empty(2 + 4); - if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) - { - // Switch on the attrc and idx simultaneously. - switch (ADH_BYTE(attrc, idx)) - { - - case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_OVERFLOW) : - case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_OVERFLOW) : - case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_OVERFLOW) : - case ADH_BYTE(ATTR_CONTEXT_CODE, X_ATTR_OVERFLOW) : - // no attribute at all, so back up on this one - wp = wp_at(abase); - continue; - - case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_ClassFile_version) : - cur_class_minver = class_ClassFile_version_minor_H.getInt(); - cur_class_majver = class_ClassFile_version_major_H.getInt(); - // back up; not a real attribute - wp = wp_at(abase); - continue; - - case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_InnerClasses) : - // note the existence of this attr, but save for later - if (cur_class_has_local_ics) - unpack_abort("too many InnerClasses attrs"); - cur_class_has_local_ics = true; - wp = wp_at(abase); - continue; - - case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_SourceFile) : - aname = cp.sym[constant_pool::s_SourceFile]; - ref = class_SourceFile_RUN.getRefN(); - if (ref == nullptr) - { - bytes &n = cur_class->ref(0)->value.b; - // parse n = (/)*?($)* - int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, (int)n.len) + 1; - bytes prefix = n.slice(pkglen, n.len); - for (;;) - { - // Work backwards, finding all '$', '#', etc. - int dollar = - lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, prefix, (int)prefix.len); - if (dollar < 0) - break; - prefix = prefix.slice(0, dollar); - } - const char *suffix = ".java"; - int len = (int)(prefix.len + strlen(suffix)); - bytes name; - name.set(T_NEW(byte, add_size(len, 1)), len); - name.strcat(prefix).strcat(suffix); - ref = cp.ensureUtf8(name); - } - putref(ref); - break; - - case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_EnclosingMethod) : - aname = cp.sym[constant_pool::s_EnclosingMethod]; - putref(class_EnclosingMethod_RC.getRefN()); - putref(class_EnclosingMethod_RDN.getRefN()); - break; - - case ADH_BYTE(ATTR_CONTEXT_FIELD, FIELD_ATTR_ConstantValue) : - aname = cp.sym[constant_pool::s_ConstantValue]; - putref(field_ConstantValue_KQ.getRefUsing(cp.getKQIndex())); - break; - - case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Code) : - aname = cp.sym[constant_pool::s_Code]; - write_code(); - break; - - case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Exceptions) : - aname = cp.sym[constant_pool::s_Exceptions]; - putu2(count = method_Exceptions_N.getInt()); - for (j = 0; j < count; j++) - { - putref(method_Exceptions_RC.getRefN()); - } - break; - - case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_StackMapTable) : - aname = cp.sym[constant_pool::s_StackMapTable]; - // (keep this code aligned with its brother in unpacker::read_attrs) - putu2(count = code_StackMapTable_N.getInt()); - for (j = 0; j < count; j++) - { - int tag = code_StackMapTable_frame_T.getByte(); - putu1(tag); - if (tag <= 127) - { - // (64-127) [(2)] - if (tag >= 64) - put_stackmap_type(); - } - else if (tag <= 251) - { - // (247) [(1)(2)] - // (248-251) [(1)] - if (tag >= 247) - putu2(code_StackMapTable_offset.getInt()); - if (tag == 247) - put_stackmap_type(); - } - else if (tag <= 254) - { - // (252) [(1)(2)] - // (253) [(1)(2)(2)] - // (254) [(1)(2)(2)(2)] - putu2(code_StackMapTable_offset.getInt()); - for (int k = (tag - 251); k > 0; k--) - { - put_stackmap_type(); - } - } - else - { - // (255) [(1)NH[(2)]NH[(2)]] - putu2(code_StackMapTable_offset.getInt()); - putu2(j2 = code_StackMapTable_local_N.getInt()); - while (j2-- > 0) - put_stackmap_type(); - putu2(j2 = code_StackMapTable_stack_N.getInt()); - while (j2-- > 0) - put_stackmap_type(); - } - } - break; - - case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LineNumberTable) : - aname = cp.sym[constant_pool::s_LineNumberTable]; - putu2(count = code_LineNumberTable_N.getInt()); - for (j = 0; j < count; j++) - { - putu2(to_bci(code_LineNumberTable_bci_P.getInt())); - putu2(code_LineNumberTable_line.getInt()); - } - break; - - case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTable) : - aname = cp.sym[constant_pool::s_LocalVariableTable]; - putu2(count = code_LocalVariableTable_N.getInt()); - for (j = 0; j < count; j++) - { - int bii = code_LocalVariableTable_bci_P.getInt(); - int bci = to_bci(bii); - putu2(bci); - bii += code_LocalVariableTable_span_O.getInt(); - putu2(to_bci(bii) - bci); - putref(code_LocalVariableTable_name_RU.getRefN()); - putref(code_LocalVariableTable_type_RS.getRefN()); - putu2(code_LocalVariableTable_slot.getInt()); - } - break; - - case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTypeTable) : - aname = cp.sym[constant_pool::s_LocalVariableTypeTable]; - putu2(count = code_LocalVariableTypeTable_N.getInt()); - for (j = 0; j < count; j++) - { - int bii = code_LocalVariableTypeTable_bci_P.getInt(); - int bci = to_bci(bii); - putu2(bci); - bii += code_LocalVariableTypeTable_span_O.getInt(); - putu2(to_bci(bii) - bci); - putref(code_LocalVariableTypeTable_name_RU.getRefN()); - putref(code_LocalVariableTypeTable_type_RS.getRefN()); - putu2(code_LocalVariableTypeTable_slot.getInt()); - } - break; - - case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Signature) : - aname = cp.sym[constant_pool::s_Signature]; - putref(class_Signature_RS.getRefN()); - break; - - case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Signature) : - aname = cp.sym[constant_pool::s_Signature]; - putref(field_Signature_RS.getRefN()); - break; - - case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Signature) : - aname = cp.sym[constant_pool::s_Signature]; - putref(method_Signature_RS.getRefN()); - break; - - case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Deprecated) : - case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Deprecated) : - case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Deprecated) : - aname = cp.sym[constant_pool::s_Deprecated]; - // no data - break; - } - } - - if (aname == nullptr) - { - // Unparse a compressor-defined attribute. - layout_definition *lo = ad.getLayout(idx); - if (lo == nullptr) - { - unpack_abort("bad layout index"); - break; - } - assert((int)lo->idx == idx); - aname = lo->nameEntry; - if (aname == nullptr) - { - bytes nameb; - nameb.set(lo->name); - aname = cp.ensureUtf8(nameb); - // Cache the name entry for next time. - lo->nameEntry = aname; - } - // Execute all the layout elements. - band **bands = lo->bands(); - if (lo->hasCallables()) - { - band &cble = *bands[0]; - assert(cble.le_kind == EK_CBLE); - bands = cble.le_body; - } - putlayout(bands); - } - - if (aname == nullptr) - unpack_abort("bad attribute index"); - - byte *wp1 = wp; - wp = wp_at(abase); - - // DTRT if this attr is on the strip-list. - // (Note that we emptied the data out of the band first.) - if (ad.strip_names.contains(aname)) - { - continue; - } - - // patch the name and length - putref(aname); - putu4((int)(wp1 - (wp + 4))); // put the attr size - wp = wp1; - na++; // count the attrs actually written - } - - if (na != na0) - // Refresh changed count. - putu2_at(wp_at(naOffset), na); - return na; + if (indexBits == 0) + { + // Quick short-circuit. + putu2(0); + return 0; + } + + attr_definitions &ad = attr_defs[attrc]; + + int i, j, j2, idx, count; + + int oiCount = 0; + if (ad.isPredefined(X_ATTR_OVERFLOW) && (indexBits & ((uint64_t)1 << X_ATTR_OVERFLOW)) != 0) + { + indexBits -= ((uint64_t)1 << X_ATTR_OVERFLOW); + oiCount = ad.xxx_attr_count().getInt(); + } + + int bitIndexes[X_ATTR_LIMIT_FLAGS_HI]; + int biCount = 0; + + // Fill bitIndexes with index bits, in order. + for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) + { + if ((indexBits & 1) != 0) + bitIndexes[biCount++] = idx; + } + assert(biCount <= (int)lengthof(bitIndexes)); + + // Write a provisional attribute count, perhaps to be corrected later. + int naOffset = (int)wpoffset(); + int na0 = biCount + oiCount; + putu2(na0); + + int na = 0; + for (i = 0; i < na0; i++) + { + if (i < biCount) + idx = bitIndexes[i]; + else + idx = ad.xxx_attr_indexes().getInt(); + assert(ad.isIndex(idx)); + entry *aname = nullptr; + entry *ref; // scratch + size_t abase = put_empty(2 + 4); + if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) + { + // Switch on the attrc and idx simultaneously. + switch (ADH_BYTE(attrc, idx)) + { + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_CODE, X_ATTR_OVERFLOW) : + // no attribute at all, so back up on this one + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_ClassFile_version) : + cur_class_minver = class_ClassFile_version_minor_H.getInt(); + cur_class_majver = class_ClassFile_version_major_H.getInt(); + // back up; not a real attribute + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_InnerClasses) : + // note the existence of this attr, but save for later + if (cur_class_has_local_ics) + unpack_abort("too many InnerClasses attrs"); + cur_class_has_local_ics = true; + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_SourceFile) : + aname = cp.sym[constant_pool::s_SourceFile]; + ref = class_SourceFile_RUN.getRefN(); + if (ref == nullptr) + { + bytes &n = cur_class->ref(0)->value.b; + // parse n = (/)*?($)* + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, (int)n.len) + 1; + bytes prefix = n.slice(pkglen, n.len); + for (;;) + { + // Work backwards, finding all '$', '#', etc. + int dollar = + lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, prefix, (int)prefix.len); + if (dollar < 0) + break; + prefix = prefix.slice(0, dollar); + } + const char *suffix = ".java"; + int len = (int)(prefix.len + strlen(suffix)); + bytes name; + name.set(T_NEW(byte, add_size(len, 1)), len); + name.strcat(prefix).strcat(suffix); + ref = cp.ensureUtf8(name); + } + putref(ref); + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_EnclosingMethod) : + aname = cp.sym[constant_pool::s_EnclosingMethod]; + putref(class_EnclosingMethod_RC.getRefN()); + putref(class_EnclosingMethod_RDN.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_FIELD, FIELD_ATTR_ConstantValue) : + aname = cp.sym[constant_pool::s_ConstantValue]; + putref(field_ConstantValue_KQ.getRefUsing(cp.getKQIndex())); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Code) : + aname = cp.sym[constant_pool::s_Code]; + write_code(); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Exceptions) : + aname = cp.sym[constant_pool::s_Exceptions]; + putu2(count = method_Exceptions_N.getInt()); + for (j = 0; j < count; j++) + { + putref(method_Exceptions_RC.getRefN()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_StackMapTable) : + aname = cp.sym[constant_pool::s_StackMapTable]; + // (keep this code aligned with its brother in unpacker::read_attrs) + putu2(count = code_StackMapTable_N.getInt()); + for (j = 0; j < count; j++) + { + int tag = code_StackMapTable_frame_T.getByte(); + putu1(tag); + if (tag <= 127) + { + // (64-127) [(2)] + if (tag >= 64) + put_stackmap_type(); + } + else if (tag <= 251) + { + // (247) [(1)(2)] + // (248-251) [(1)] + if (tag >= 247) + putu2(code_StackMapTable_offset.getInt()); + if (tag == 247) + put_stackmap_type(); + } + else if (tag <= 254) + { + // (252) [(1)(2)] + // (253) [(1)(2)(2)] + // (254) [(1)(2)(2)(2)] + putu2(code_StackMapTable_offset.getInt()); + for (int k = (tag - 251); k > 0; k--) + { + put_stackmap_type(); + } + } + else + { + // (255) [(1)NH[(2)]NH[(2)]] + putu2(code_StackMapTable_offset.getInt()); + putu2(j2 = code_StackMapTable_local_N.getInt()); + while (j2-- > 0) + put_stackmap_type(); + putu2(j2 = code_StackMapTable_stack_N.getInt()); + while (j2-- > 0) + put_stackmap_type(); + } + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LineNumberTable) : + aname = cp.sym[constant_pool::s_LineNumberTable]; + putu2(count = code_LineNumberTable_N.getInt()); + for (j = 0; j < count; j++) + { + putu2(to_bci(code_LineNumberTable_bci_P.getInt())); + putu2(code_LineNumberTable_line.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTable) : + aname = cp.sym[constant_pool::s_LocalVariableTable]; + putu2(count = code_LocalVariableTable_N.getInt()); + for (j = 0; j < count; j++) + { + int bii = code_LocalVariableTable_bci_P.getInt(); + int bci = to_bci(bii); + putu2(bci); + bii += code_LocalVariableTable_span_O.getInt(); + putu2(to_bci(bii) - bci); + putref(code_LocalVariableTable_name_RU.getRefN()); + putref(code_LocalVariableTable_type_RS.getRefN()); + putu2(code_LocalVariableTable_slot.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTypeTable) : + aname = cp.sym[constant_pool::s_LocalVariableTypeTable]; + putu2(count = code_LocalVariableTypeTable_N.getInt()); + for (j = 0; j < count; j++) + { + int bii = code_LocalVariableTypeTable_bci_P.getInt(); + int bci = to_bci(bii); + putu2(bci); + bii += code_LocalVariableTypeTable_span_O.getInt(); + putu2(to_bci(bii) - bci); + putref(code_LocalVariableTypeTable_name_RU.getRefN()); + putref(code_LocalVariableTypeTable_type_RS.getRefN()); + putu2(code_LocalVariableTypeTable_slot.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(class_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(field_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(method_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Deprecated) : + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Deprecated) : + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Deprecated) : + aname = cp.sym[constant_pool::s_Deprecated]; + // no data + break; + } + } + + if (aname == nullptr) + { + // Unparse a compressor-defined attribute. + layout_definition *lo = ad.getLayout(idx); + if (lo == nullptr) + { + unpack_abort("bad layout index"); + break; + } + assert((int)lo->idx == idx); + aname = lo->nameEntry; + if (aname == nullptr) + { + bytes nameb; + nameb.set(lo->name); + aname = cp.ensureUtf8(nameb); + // Cache the name entry for next time. + lo->nameEntry = aname; + } + // Execute all the layout elements. + band **bands = lo->bands(); + if (lo->hasCallables()) + { + band &cble = *bands[0]; + assert(cble.le_kind == EK_CBLE); + bands = cble.le_body; + } + putlayout(bands); + } + + if (aname == nullptr) + unpack_abort("bad attribute index"); + + byte *wp1 = wp; + wp = wp_at(abase); + + // DTRT if this attr is on the strip-list. + // (Note that we emptied the data out of the band first.) + if (ad.strip_names.contains(aname)) + { + continue; + } + + // patch the name and length + putref(aname); + putu4((int)(wp1 - (wp + 4))); // put the attr size + wp = wp1; + na++; // count the attrs actually written + } + + if (na != na0) + // Refresh changed count. + putu2_at(wp_at(naOffset), na); + return na; } void unpacker::write_members(int num, int attrc) { - attr_definitions &ad = attr_defs[attrc]; - band &member_flags_hi = ad.xxx_flags_hi(); - band &member_flags_lo = ad.xxx_flags_lo(); - band &member_descr = (&member_flags_hi)[e_field_descr - e_field_flags_hi]; - bool haveLongFlags = ad.haveLongFlags(); - - putu2(num); - uint64_t indexMask = attr_defs[attrc].flagIndexMask(); - for (int i = 0; i < num; i++) - { - uint64_t mflags = member_flags_hi.getLong(member_flags_lo, haveLongFlags); - entry *mdescr = member_descr.getRef(); - cur_descr = mdescr; - putu2(cur_descr_flags = (ushort)(mflags & ~indexMask)); - putref(mdescr->descrName()); - putref(mdescr->descrType()); - write_attrs(attrc, (mflags & indexMask)); - } - cur_descr = nullptr; + attr_definitions &ad = attr_defs[attrc]; + band &member_flags_hi = ad.xxx_flags_hi(); + band &member_flags_lo = ad.xxx_flags_lo(); + band &member_descr = (&member_flags_hi)[e_field_descr - e_field_flags_hi]; + bool haveLongFlags = ad.haveLongFlags(); + + putu2(num); + uint64_t indexMask = attr_defs[attrc].flagIndexMask(); + for (int i = 0; i < num; i++) + { + uint64_t mflags = member_flags_hi.getLong(member_flags_lo, haveLongFlags); + entry *mdescr = member_descr.getRef(); + cur_descr = mdescr; + putu2(cur_descr_flags = (ushort)(mflags & ~indexMask)); + putref(mdescr->descrName()); + putref(mdescr->descrType()); + write_attrs(attrc, (mflags & indexMask)); + } + cur_descr = nullptr; } extern "C" int raw_address_cmp(const void *p1p, const void *p2p) { - void *p1 = *(void **)p1p; - void *p2 = *(void **)p2p; - return (p1 > p2) ? 1 : (p1 < p2) ? -1 : 0; + void *p1 = *(void **)p1p; + void *p2 = *(void **)p2p; + return (p1 > p2) ? 1 : (p1 < p2) ? -1 : 0; } void unpacker::write_classfile_tail() { - cur_classfile_tail.empty(); - set_output(&cur_classfile_tail); + cur_classfile_tail.empty(); + set_output(&cur_classfile_tail); - int i, num; + int i, num; - attr_definitions &ad = attr_defs[ATTR_CONTEXT_CLASS]; + attr_definitions &ad = attr_defs[ATTR_CONTEXT_CLASS]; - bool haveLongFlags = ad.haveLongFlags(); - uint64_t kflags = class_flags_hi.getLong(class_flags_lo, haveLongFlags); - uint64_t indexMask = ad.flagIndexMask(); + bool haveLongFlags = ad.haveLongFlags(); + uint64_t kflags = class_flags_hi.getLong(class_flags_lo, haveLongFlags); + uint64_t indexMask = ad.flagIndexMask(); - cur_class = class_this.getRef(); - cur_super = class_super.getRef(); + cur_class = class_this.getRef(); + cur_super = class_super.getRef(); - if (cur_super == cur_class) - cur_super = nullptr; - // special representation for java/lang/Object + if (cur_super == cur_class) + cur_super = nullptr; + // special representation for java/lang/Object - putu2((ushort)(kflags & ~indexMask)); - putref(cur_class); - putref(cur_super); + putu2((ushort)(kflags & ~indexMask)); + putref(cur_class); + putref(cur_super); - putu2(num = class_interface_count.getInt()); - for (i = 0; i < num; i++) - { - putref(class_interface.getRef()); - } + putu2(num = class_interface_count.getInt()); + for (i = 0; i < num; i++) + { + putref(class_interface.getRef()); + } - write_members(class_field_count.getInt(), ATTR_CONTEXT_FIELD); - write_members(class_method_count.getInt(), ATTR_CONTEXT_METHOD); + write_members(class_field_count.getInt(), ATTR_CONTEXT_FIELD); + write_members(class_method_count.getInt(), ATTR_CONTEXT_METHOD); - cur_class_has_local_ics = false; // may be set true by write_attrs + cur_class_has_local_ics = false; // may be set true by write_attrs - int naOffset = (int)wpoffset(); - int na = write_attrs(ATTR_CONTEXT_CLASS, (kflags & indexMask)); + int naOffset = (int)wpoffset(); + int na = write_attrs(ATTR_CONTEXT_CLASS, (kflags & indexMask)); // at the very last, choose which inner classes (if any) pertain to k: #ifdef ASSERT - for (i = 0; i < ic_count; i++) - { - assert(!ics[i].requested); - } + for (i = 0; i < ic_count; i++) + { + assert(!ics[i].requested); + } #endif - // First, consult the global table and the local constant pool, - // and decide on the globally implied inner classes. - // (Note that we read the cpool's outputIndex fields, but we - // do not yet write them, since the local IC attribute might - // reverse a global decision to declare an IC.) - assert(requested_ics.length() == 0); // must start out empty - // Always include all members of the current class. - for (inner_class *child = cp.getFirstChildIC(cur_class); child != nullptr; - child = cp.getNextChildIC(child)) - { - child->requested = true; - requested_ics.add(child); - } - // And, for each inner class mentioned in the constant pool, - // include it and all its outers. - int noes = cp.outputEntries.length(); - entry **oes = (entry **)cp.outputEntries.base(); - for (i = 0; i < noes; i++) - { - entry &e = *oes[i]; - if (e.tag != CONSTANT_Class) - continue; // wrong sort - for (inner_class *ic = cp.getIC(&e); ic != nullptr; ic = cp.getIC(ic->outer)) - { - if (ic->requested) - break; // already processed - ic->requested = true; - requested_ics.add(ic); - } - } - int local_ics = requested_ics.length(); - // Second, consult a local attribute (if any) and adjust the global set. - inner_class *extra_ics = nullptr; - int num_extra_ics = 0; - if (cur_class_has_local_ics) - { - // adjust the set of ICs by symmetric set difference w/ the locals - num_extra_ics = class_InnerClasses_N.getInt(); - if (num_extra_ics == 0) - { - // Explicit zero count has an irregular meaning: It deletes the attr. - local_ics = 0; // (short-circuit all tests of requested bits) - } - else - { - extra_ics = T_NEW(inner_class, num_extra_ics); - // Note: extra_ics will be freed up by next call to get_next_file(). - } - } - for (i = 0; i < num_extra_ics; i++) - { - inner_class &extra_ic = extra_ics[i]; - extra_ic.inner = class_InnerClasses_RC.getRef(); - // Find the corresponding equivalent global IC: - inner_class *global_ic = cp.getIC(extra_ic.inner); - int flags = class_InnerClasses_F.getInt(); - if (flags == 0) - { - // The extra IC is simply a copy of a global IC. - if (global_ic == nullptr) - { - unpack_abort("bad reference to inner class"); - break; - } - extra_ic = (*global_ic); // fill in rest of fields - } - else - { - flags &= ~ACC_IC_LONG_FORM; // clear high bit if set to get clean zero - extra_ic.flags = flags; - extra_ic.outer = class_InnerClasses_outer_RCN.getRefN(); - extra_ic.name = class_InnerClasses_name_RUN.getRefN(); - // Detect if this is an exact copy of the global tuple. - if (global_ic != nullptr) - { - if (global_ic->flags != extra_ic.flags || global_ic->outer != extra_ic.outer || - global_ic->name != extra_ic.name) - { - global_ic = nullptr; // not really the same, so break the link - } - } - } - if (global_ic != nullptr && global_ic->requested) - { - // This local repetition reverses the globally implied request. - global_ic->requested = false; - extra_ic.requested = false; - local_ics -= 1; - } - else - { - // The global either does not exist, or is not yet requested. - extra_ic.requested = true; - local_ics += 1; - } - } - // Finally, if there are any that survived, put them into an attribute. - // (Note that a zero-count attribute is always deleted.) - // The putref calls below will tell the constant pool to add any - // necessary local CP references to support the InnerClasses attribute. - // This step must be the last round of additions to the local CP. - if (local_ics > 0) - { - // append the new attribute: - putref(cp.sym[constant_pool::s_InnerClasses]); - putu4(2 + 2 * 4 * local_ics); - putu2(local_ics); - PTRLIST_QSORT(requested_ics, raw_address_cmp); - int num_global_ics = requested_ics.length(); - for (i = -num_global_ics; i < num_extra_ics; i++) - { - inner_class *ic; - if (i < 0) - ic = (inner_class *)requested_ics.get(num_global_ics + i); - else - ic = &extra_ics[i]; - if (ic->requested) - { - putref(ic->inner); - putref(ic->outer); - putref(ic->name); - putu2(ic->flags); - } - } - putu2_at(wp_at(naOffset), ++na); // increment class attr count - } - - // Tidy up global 'requested' bits: - for (i = requested_ics.length(); --i >= 0;) - { - inner_class *ic = (inner_class *)requested_ics.get(i); - ic->requested = false; - } - requested_ics.empty(); - - close_output(); - - // rewrite CP references in the tail - cp.computeOutputIndexes(); - int nextref = 0; - for (i = 0; i < (int)class_fixup_type.size(); i++) - { - int type = class_fixup_type.getByte(i); - byte *fixp = wp_at(class_fixup_offset.get(i)); - entry *e = (entry *)class_fixup_ref.get(nextref++); - int idx = e->getOutputIndex(); - switch (type) - { - case 1: - putu1_at(fixp, idx); - break; - case 2: - putu2_at(fixp, idx); - break; - default: - assert(false); // should not reach here - } - } + // First, consult the global table and the local constant pool, + // and decide on the globally implied inner classes. + // (Note that we read the cpool's outputIndex fields, but we + // do not yet write them, since the local IC attribute might + // reverse a global decision to declare an IC.) + assert(requested_ics.length() == 0); // must start out empty + // Always include all members of the current class. + for (inner_class *child = cp.getFirstChildIC(cur_class); child != nullptr; + child = cp.getNextChildIC(child)) + { + child->requested = true; + requested_ics.add(child); + } + // And, for each inner class mentioned in the constant pool, + // include it and all its outers. + int noes = cp.outputEntries.length(); + entry **oes = (entry **)cp.outputEntries.base(); + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + if (e.tag != CONSTANT_Class) + continue; // wrong sort + for (inner_class *ic = cp.getIC(&e); ic != nullptr; ic = cp.getIC(ic->outer)) + { + if (ic->requested) + break; // already processed + ic->requested = true; + requested_ics.add(ic); + } + } + int local_ics = requested_ics.length(); + // Second, consult a local attribute (if any) and adjust the global set. + inner_class *extra_ics = nullptr; + int num_extra_ics = 0; + if (cur_class_has_local_ics) + { + // adjust the set of ICs by symmetric set difference w/ the locals + num_extra_ics = class_InnerClasses_N.getInt(); + if (num_extra_ics == 0) + { + // Explicit zero count has an irregular meaning: It deletes the attr. + local_ics = 0; // (short-circuit all tests of requested bits) + } + else + { + extra_ics = T_NEW(inner_class, num_extra_ics); + // Note: extra_ics will be freed up by next call to get_next_file(). + } + } + for (i = 0; i < num_extra_ics; i++) + { + inner_class &extra_ic = extra_ics[i]; + extra_ic.inner = class_InnerClasses_RC.getRef(); + // Find the corresponding equivalent global IC: + inner_class *global_ic = cp.getIC(extra_ic.inner); + int flags = class_InnerClasses_F.getInt(); + if (flags == 0) + { + // The extra IC is simply a copy of a global IC. + if (global_ic == nullptr) + { + unpack_abort("bad reference to inner class"); + break; + } + extra_ic = (*global_ic); // fill in rest of fields + } + else + { + flags &= ~ACC_IC_LONG_FORM; // clear high bit if set to get clean zero + extra_ic.flags = flags; + extra_ic.outer = class_InnerClasses_outer_RCN.getRefN(); + extra_ic.name = class_InnerClasses_name_RUN.getRefN(); + // Detect if this is an exact copy of the global tuple. + if (global_ic != nullptr) + { + if (global_ic->flags != extra_ic.flags || global_ic->outer != extra_ic.outer || + global_ic->name != extra_ic.name) + { + global_ic = nullptr; // not really the same, so break the link + } + } + } + if (global_ic != nullptr && global_ic->requested) + { + // This local repetition reverses the globally implied request. + global_ic->requested = false; + extra_ic.requested = false; + local_ics -= 1; + } + else + { + // The global either does not exist, or is not yet requested. + extra_ic.requested = true; + local_ics += 1; + } + } + // Finally, if there are any that survived, put them into an attribute. + // (Note that a zero-count attribute is always deleted.) + // The putref calls below will tell the constant pool to add any + // necessary local CP references to support the InnerClasses attribute. + // This step must be the last round of additions to the local CP. + if (local_ics > 0) + { + // append the new attribute: + putref(cp.sym[constant_pool::s_InnerClasses]); + putu4(2 + 2 * 4 * local_ics); + putu2(local_ics); + PTRLIST_QSORT(requested_ics, raw_address_cmp); + int num_global_ics = requested_ics.length(); + for (i = -num_global_ics; i < num_extra_ics; i++) + { + inner_class *ic; + if (i < 0) + ic = (inner_class *)requested_ics.get(num_global_ics + i); + else + ic = &extra_ics[i]; + if (ic->requested) + { + putref(ic->inner); + putref(ic->outer); + putref(ic->name); + putu2(ic->flags); + } + } + putu2_at(wp_at(naOffset), ++na); // increment class attr count + } + + // Tidy up global 'requested' bits: + for (i = requested_ics.length(); --i >= 0;) + { + inner_class *ic = (inner_class *)requested_ics.get(i); + ic->requested = false; + } + requested_ics.empty(); + + close_output(); + + // rewrite CP references in the tail + cp.computeOutputIndexes(); + int nextref = 0; + for (i = 0; i < (int)class_fixup_type.size(); i++) + { + int type = class_fixup_type.getByte(i); + byte *fixp = wp_at(class_fixup_offset.get(i)); + entry *e = (entry *)class_fixup_ref.get(nextref++); + int idx = e->getOutputIndex(); + switch (type) + { + case 1: + putu1_at(fixp, idx); + break; + case 2: + putu2_at(fixp, idx); + break; + default: + assert(false); // should not reach here + } + } } void unpacker::write_classfile_head() { - cur_classfile_head.empty(); - set_output(&cur_classfile_head); - - putu4(JAVA_MAGIC); - putu2(cur_class_minver); - putu2(cur_class_majver); - putu2(cp.outputIndexLimit); - - int checkIndex = 1; - int noes = cp.outputEntries.length(); - entry **oes = (entry **)cp.outputEntries.base(); - for (int i = 0; i < noes; i++) - { - entry &e = *oes[i]; - assert(e.getOutputIndex() == checkIndex++); - byte tag = e.tag; - assert(tag != CONSTANT_Signature); - putu1(tag); - switch (tag) - { - case CONSTANT_Utf8: - putu2((int)e.value.b.len); - put_bytes(e.value.b); - break; - case CONSTANT_Integer: - case CONSTANT_Float: - putu4(e.value.i); - break; - case CONSTANT_Long: - case CONSTANT_Double: - putu8(e.value.l); - assert(checkIndex++); - break; - case CONSTANT_Class: - case CONSTANT_String: - // just write the ref - putu2(e.refs[0]->getOutputIndex()); - break; - case CONSTANT_Fieldref: - case CONSTANT_Methodref: - case CONSTANT_InterfaceMethodref: - case CONSTANT_NameandType: - putu2(e.refs[0]->getOutputIndex()); - putu2(e.refs[1]->getOutputIndex()); - break; - default: - unpack_abort(ERROR_INTERNAL); - } - } - close_output(); + cur_classfile_head.empty(); + set_output(&cur_classfile_head); + + putu4(JAVA_MAGIC); + putu2(cur_class_minver); + putu2(cur_class_majver); + putu2(cp.outputIndexLimit); + +#ifndef NDEBUG + int checkIndex = 1; +#endif + int noes = cp.outputEntries.length(); + entry **oes = (entry **)cp.outputEntries.base(); + for (int i = 0; i < noes; i++) + { + entry &e = *oes[i]; + assert(e.getOutputIndex() == checkIndex++); + byte tag = e.tag; + assert(tag != CONSTANT_Signature); + putu1(tag); + switch (tag) + { + case CONSTANT_Utf8: + putu2((int)e.value.b.len); + put_bytes(e.value.b); + break; + case CONSTANT_Integer: + case CONSTANT_Float: + putu4(e.value.i); + break; + case CONSTANT_Long: + case CONSTANT_Double: + putu8(e.value.l); + assert(checkIndex++); + break; + case CONSTANT_Class: + case CONSTANT_String: + // just write the ref + putu2(e.refs[0]->getOutputIndex()); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameandType: + putu2(e.refs[0]->getOutputIndex()); + putu2(e.refs[1]->getOutputIndex()); + break; + default: + unpack_abort(ERROR_INTERNAL); + } + } + close_output(); } unpacker::file *unpacker::get_next_file() { - free_temps(); - if (files_remaining == 0) - { - // Leave a clue that we're exhausted. - cur_file.name = nullptr; - cur_file.size = 0; - if (archive_size != 0) - { - uint64_t predicted_size = unsized_bytes_read + archive_size; - if (predicted_size != bytes_read) - unpack_abort("archive header had incorrect size"); - } - return nullptr; - } - files_remaining -= 1; - assert(files_written < file_count || classes_written < class_count); - cur_file.name = ""; - cur_file.size = 0; - cur_file.modtime = default_file_modtime; - cur_file.options = default_file_options; - cur_file.data[0].set(nullptr, 0); - cur_file.data[1].set(nullptr, 0); - if (files_written < file_count) - { - entry *e = file_name.getRef(); - cur_file.name = e->utf8String(); - bool haveLongSize = ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0); - cur_file.size = file_size_hi.getLong(file_size_lo, haveLongSize); - if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) - cur_file.modtime += file_modtime.getInt(); // relative to archive modtime - if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) - cur_file.options |= file_options.getInt() & ~suppress_file_options; - } - else if (classes_written < class_count) - { - // there is a class for a missing file record - cur_file.options |= FO_IS_CLASS_STUB; - } - if ((cur_file.options & FO_IS_CLASS_STUB) != 0) - { - assert(classes_written < class_count); - classes_written += 1; - if (cur_file.size != 0) - { - unpack_abort("class file size transmitted"); - } - reset_cur_classfile(); - - // write the meat of the classfile: - write_classfile_tail(); - cur_file.data[1] = cur_classfile_tail.b; - - // write the CP of the classfile, second: - write_classfile_head(); - cur_file.data[0] = cur_classfile_head.b; - - cur_file.size += cur_file.data[0].len; - cur_file.size += cur_file.data[1].len; - if (cur_file.name[0] == '\0') - { - bytes &prefix = cur_class->ref(0)->value.b; - const char *suffix = ".class"; - int len = (int)(prefix.len + strlen(suffix)); - bytes name; - name.set(T_NEW(byte, add_size(len, 1)), len); - cur_file.name = name.strcat(prefix).strcat(suffix).strval(); - } - } - else - { - // If there is buffered file data, produce a pointer to it. - if (cur_file.size != (size_t)cur_file.size) - { - // Silly size specified. - unpack_abort("resource file too large"); - } - size_t rpleft = input_remaining(); - if (rpleft > 0) - { - if (rpleft > cur_file.size) - rpleft = (size_t)cur_file.size; - cur_file.data[0].set(rp, rpleft); - rp += rpleft; - } - if (rpleft < cur_file.size) - { - // Caller must read the rest. - size_t fleft = (size_t)cur_file.size - rpleft; - bytes_read += fleft; // Credit it to the overall archive size. - } - } - bytes_written += cur_file.size; - files_written += 1; - return &cur_file; + free_temps(); + if (files_remaining == 0) + { + // Leave a clue that we're exhausted. + cur_file.name = nullptr; + cur_file.size = 0; + if (archive_size != 0) + { + uint64_t predicted_size = unsized_bytes_read + archive_size; + if (predicted_size != bytes_read) + unpack_abort("archive header had incorrect size"); + } + return nullptr; + } + files_remaining -= 1; + assert(files_written < file_count || classes_written < class_count); + cur_file.name = ""; + cur_file.size = 0; + cur_file.modtime = default_file_modtime; + cur_file.options = default_file_options; + cur_file.data[0].set(nullptr, 0); + cur_file.data[1].set(nullptr, 0); + if (files_written < file_count) + { + entry *e = file_name.getRef(); + cur_file.name = e->utf8String(); + bool haveLongSize = ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0); + cur_file.size = file_size_hi.getLong(file_size_lo, haveLongSize); + if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) + cur_file.modtime += file_modtime.getInt(); // relative to archive modtime + if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) + cur_file.options |= file_options.getInt() & ~suppress_file_options; + } + else if (classes_written < class_count) + { + // there is a class for a missing file record + cur_file.options |= FO_IS_CLASS_STUB; + } + if ((cur_file.options & FO_IS_CLASS_STUB) != 0) + { + assert(classes_written < class_count); + classes_written += 1; + if (cur_file.size != 0) + { + unpack_abort("class file size transmitted"); + } + reset_cur_classfile(); + + // write the meat of the classfile: + write_classfile_tail(); + cur_file.data[1] = cur_classfile_tail.b; + + // write the CP of the classfile, second: + write_classfile_head(); + cur_file.data[0] = cur_classfile_head.b; + + cur_file.size += cur_file.data[0].len; + cur_file.size += cur_file.data[1].len; + if (cur_file.name[0] == '\0') + { + bytes &prefix = cur_class->ref(0)->value.b; + const char *suffix = ".class"; + int len = (int)(prefix.len + strlen(suffix)); + bytes name; + name.set(T_NEW(byte, add_size(len, 1)), len); + cur_file.name = name.strcat(prefix).strcat(suffix).strval(); + } + } + else + { + // If there is buffered file data, produce a pointer to it. + if (cur_file.size != (size_t)cur_file.size) + { + // Silly size specified. + unpack_abort("resource file too large"); + } + size_t rpleft = input_remaining(); + if (rpleft > 0) + { + if (rpleft > cur_file.size) + rpleft = (size_t)cur_file.size; + cur_file.data[0].set(rp, rpleft); + rp += rpleft; + } + if (rpleft < cur_file.size) + { + // Caller must read the rest. + size_t fleft = (size_t)cur_file.size - rpleft; + bytes_read += fleft; // Credit it to the overall archive size. + } + } + bytes_written += cur_file.size; + files_written += 1; + return &cur_file; } // Write a file to jarout. void unpacker::write_file_to_jar(unpacker::file *f) { - size_t htsize = f->data[0].len + f->data[1].len; - uint64_t fsize = f->size; - if (htsize == fsize) - { - jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, f->data[0], f->data[1]); - } - else - { - assert(input_remaining() == 0); - bytes part1, part2; - part1.len = f->data[0].len; - part1.set(T_NEW(byte, part1.len), part1.len); - part1.copyFrom(f->data[0]); - assert(f->data[1].len == 0); - part2.set(nullptr, 0); - size_t fleft = (size_t)fsize - part1.len; - assert(bytes_read > fleft); // part2 already credited by get_next_file - bytes_read -= fleft; - if (fleft > 0) - { - // Must read some more. - if (live_input) - { - // Stop using the input buffer. Make a new one: - if (free_input) - input.free(); - input.init(fleft > (1 << 12) ? fleft : (1 << 12)); - free_input = true; - live_input = false; - } - else - { - // Make it large enough. - assert(free_input); // must be reallocable - input.ensureSize(fleft); - } - rplimit = rp = input.base(); - input.setLimit(rp + fleft); - if (!ensure_input(fleft)) - unpack_abort("EOF reading resource file"); - part2.ptr = input_scan(); - part2.len = input_remaining(); - rplimit = rp = input.base(); - } - jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, part1, part2); - } - if (verbose >= 3) - { - fprintf(stderr, "Wrote " LONG_LONG_FORMAT " bytes to: %s\n", fsize, f->name); - } + size_t htsize = f->data[0].len + f->data[1].len; + uint64_t fsize = f->size; + if (htsize == fsize) + { + jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, f->data[0], f->data[1]); + } + else + { + assert(input_remaining() == 0); + bytes part1, part2; + part1.len = f->data[0].len; + part1.set(T_NEW(byte, part1.len), part1.len); + part1.copyFrom(f->data[0]); + assert(f->data[1].len == 0); + part2.set(nullptr, 0); + size_t fleft = (size_t)fsize - part1.len; + assert(bytes_read > fleft); // part2 already credited by get_next_file + bytes_read -= fleft; + if (fleft > 0) + { + // Must read some more. + if (live_input) + { + // Stop using the input buffer. Make a new one: + if (free_input) + input.free(); + input.init(fleft > (1 << 12) ? fleft : (1 << 12)); + free_input = true; + live_input = false; + } + else + { + // Make it large enough. + assert(free_input); // must be reallocable + input.ensureSize(fleft); + } + rplimit = rp = input.base(); + input.setLimit(rp + fleft); + if (!ensure_input(fleft)) + unpack_abort("EOF reading resource file"); + part2.ptr = input_scan(); + part2.len = input_remaining(); + rplimit = rp = input.base(); + } + jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, part1, part2); + } + if (verbose >= 3) + { + fprintf(stderr, "Wrote %" PRIu64 " bytes to: %s\n", fsize, f->name); + } } diff --git a/libraries/pack200/src/unpack.h b/libraries/pack200/src/unpack.h index 0100700d..cc5dd60a 100644 --- a/libraries/pack200/src/unpack.h +++ b/libraries/pack200/src/unpack.h @@ -23,6 +23,8 @@ * questions. */ +#pragma once + // Global Structures struct jar; struct gunzip; @@ -33,106 +35,108 @@ struct cpindex; struct inner_class; struct value_stream; +typedef int64_t (*read_input_fn_t)(unpacker *self, void *buf, int64_t minlen, int64_t maxlen); + struct cpindex { - uint32_t len; - entry *base1; // base of primary index - entry **base2; // base of secondary index - byte ixTag; // type of entries (!= CONSTANT_None), plus 64 if sub-index - enum - { - SUB_TAG = 64 - }; - - entry *get(uint32_t i); - - void init(int len_, entry *base1_, int ixTag_) - { - len = len_; - base1 = base1_; - base2 = nullptr; - ixTag = ixTag_; - } - void init(int len_, entry **base2_, int ixTag_) - { - len = len_; - base1 = nullptr; - base2 = base2_; - ixTag = ixTag_; - } + uint32_t len; + entry *base1; // base of primary index + entry **base2; // base of secondary index + byte ixTag; // type of entries (!= CONSTANT_None), plus 64 if sub-index + enum + { + SUB_TAG = 64 + }; + + entry *get(uint32_t i); + + void init(int len_, entry *base1_, int ixTag_) + { + len = len_; + base1 = base1_; + base2 = nullptr; + ixTag = ixTag_; + } + void init(int len_, entry **base2_, int ixTag_) + { + len = len_; + base1 = nullptr; + base2 = base2_; + ixTag = ixTag_; + } }; struct constant_pool { - uint32_t nentries; - entry *entries; - entry *first_extra_entry; - uint32_t maxentries; // total allocated size of entries - - // Position and size of each homogeneous subrange: - int tag_count[CONSTANT_Limit]; - int tag_base[CONSTANT_Limit]; - cpindex tag_index[CONSTANT_Limit]; - ptrlist tag_extras[CONSTANT_Limit]; - - cpindex *member_indexes; // indexed by 2*CONSTANT_Class.inord - cpindex *getFieldIndex(entry *classRef); - cpindex *getMethodIndex(entry *classRef); - - inner_class **ic_index; - inner_class **ic_child_index; - inner_class *getIC(entry *inner); - inner_class *getFirstChildIC(entry *outer); - inner_class *getNextChildIC(inner_class *child); - - int outputIndexLimit; // index limit after renumbering - ptrlist outputEntries; // list of entry* needing output idx assigned - - entry **hashTab; - uint32_t hashTabLength; - entry *&hashTabRef(byte tag, bytes &b); - entry *ensureUtf8(bytes &b); - entry *ensureClass(bytes &b); - - // Well-known Utf8 symbols. - enum - { + uint32_t nentries; + entry *entries; + entry *first_extra_entry; + uint32_t maxentries; // total allocated size of entries + + // Position and size of each homogeneous subrange: + int tag_count[CONSTANT_Limit]; + int tag_base[CONSTANT_Limit]; + cpindex tag_index[CONSTANT_Limit]; + ptrlist tag_extras[CONSTANT_Limit]; + + cpindex *member_indexes; // indexed by 2*CONSTANT_Class.inord + cpindex *getFieldIndex(entry *classRef); + cpindex *getMethodIndex(entry *classRef); + + inner_class **ic_index; + inner_class **ic_child_index; + inner_class *getIC(entry *inner); + inner_class *getFirstChildIC(entry *outer); + inner_class *getNextChildIC(inner_class *child); + + int outputIndexLimit; // index limit after renumbering + ptrlist outputEntries; // list of entry* needing output idx assigned + + entry **hashTab; + uint32_t hashTabLength; + entry *&hashTabRef(byte tag, bytes &b); + entry *ensureUtf8(bytes &b); + entry *ensureClass(bytes &b); + + // Well-known Utf8 symbols. + enum + { #define SNAME(n, s) s_##s, - ALL_ATTR_DO(SNAME) + ALL_ATTR_DO(SNAME) #undef SNAME - s_lt_init_gt, // - s_LIMIT - }; - entry *sym[s_LIMIT]; - - // read counts from hdr, allocate main arrays - enum - { - NUM_COUNTS = 12 - }; - void init(unpacker *u, int counts[NUM_COUNTS]); - - // pointer to outer unpacker, for error checks etc. - unpacker *u; - - int getCount(byte tag) - { - assert((uint32_t)tag < CONSTANT_Limit); - return tag_count[tag]; - } - cpindex *getIndex(byte tag) - { - assert((uint32_t)tag < CONSTANT_Limit); - return &tag_index[tag]; - } - cpindex *getKQIndex(); // uses cur_descr - - void expandSignatures(); - void initMemberIndexes(); - - void computeOutputOrder(); - void computeOutputIndexes(); - void resetOutputIndexes(); + s_lt_init_gt, // + s_LIMIT + }; + entry *sym[s_LIMIT]; + + // read counts from hdr, allocate main arrays + enum + { + NUM_COUNTS = 12 + }; + void init(unpacker *u, int counts[NUM_COUNTS]); + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + int getCount(byte tag) + { + assert((uint32_t)tag < CONSTANT_Limit); + return tag_count[tag]; + } + cpindex *getIndex(byte tag) + { + assert((uint32_t)tag < CONSTANT_Limit); + return &tag_index[tag]; + } + cpindex *getKQIndex(); // uses cur_descr + + void expandSignatures(); + void initMemberIndexes(); + + void computeOutputOrder(); + void computeOutputIndexes(); + void resetOutputIndexes(); }; /* @@ -141,407 +145,405 @@ struct constant_pool */ struct unpacker { - // One element of the resulting JAR. - struct file - { - const char *name; - uint64_t size; - int modtime; - int options; - bytes data[2]; - // Note: If Sum(data[*].len) < size, - // remaining bytes must be read directly from the input stream. - bool deflate_hint() - { - return ((options & FO_DEFLATE_HINT) != 0); - } - }; - - // if running Unix-style, here are the inputs and outputs - FILE *infileptr; // buffered - bytes inbytes; // direct - gunzip *gzin; // gunzip filter, if any - jar *jarout; // output JAR file - - // pointer to self, for U_NEW macro - unpacker *u; - - ptrlist mallocs; // list of guys to free when we are all done - ptrlist tmallocs; // list of guys to free on next client request - fillbytes smallbuf; // supplies small alloc requests - fillbytes tsmallbuf; // supplies temporary small alloc requests - - // option management members - int verbose; // verbose level, 0 means no output - int deflate_hint_or_zero; // ==0 means not set, otherwise -1 or 1 - int modification_time_or_zero; - - // input stream - fillbytes input; // the whole block (size is predicted, has slop too) - bool live_input; // is the data in this block live? - bool free_input; // must the input buffer be freed? - byte *rp; // read pointer (< rplimit <= input.limit()) - byte *rplimit; // how much of the input block has been read? - uint64_t bytes_read; - int unsized_bytes_read; - - // callback to read at least one byte, up to available input - typedef int64_t (*read_input_fn_t)(unpacker *self, void *buf, int64_t minlen, - int64_t maxlen); - read_input_fn_t read_input_fn; - - // archive header fields - int magic, minver, majver; - size_t archive_size; - int archive_next_count, archive_options, archive_modtime; - int band_headers_size; - int file_count, attr_definition_count, ic_count, class_count; - int default_class_minver, default_class_majver; - int default_file_options, suppress_file_options; // not header fields - int default_archive_modtime, default_file_modtime; // not header fields - int code_count; // not a header field - int files_remaining; // not a header field - - // engine state - band *all_bands; // indexed by band_number - byte *meta_rp; // read-pointer into (copy of) band_headers - constant_pool cp; // all constant pool information - inner_class *ics; // InnerClasses - - // output stream - bytes output; // output block (either classfile head or tail) - byte *wp; // write pointer (< wplimit == output.limit()) - byte *wpbase; // write pointer starting address (<= wp) - byte *wplimit; // how much of the output block has been written? - - // output state - file cur_file; - entry *cur_class; // CONSTANT_Class entry - entry *cur_super; // CONSTANT_Class entry or nullptr - entry *cur_descr; // CONSTANT_NameandType entry - int cur_descr_flags; // flags corresponding to cur_descr - int cur_class_minver, cur_class_majver; - bool cur_class_has_local_ics; - fillbytes cur_classfile_head; - fillbytes cur_classfile_tail; - int files_written; // also tells which file we're working on - int classes_written; // also tells which class we're working on - uint64_t bytes_written; - intlist bcimap; - fillbytes class_fixup_type; - intlist class_fixup_offset; - ptrlist class_fixup_ref; - fillbytes code_fixup_type; // which format of branch operand? - intlist code_fixup_offset; // location of operand needing fixup - intlist code_fixup_source; // encoded ID of branch insn - ptrlist requested_ics; // which ics need output? - - // stats pertaining to multiple segments (updated on reset) - uint64_t bytes_read_before_reset; - uint64_t bytes_written_before_reset; - int files_written_before_reset; - int classes_written_before_reset; - int segments_read_before_reset; - - // attribute state - struct layout_definition - { - uint32_t idx; // index (0..31...) which identifies this layout - const char *name; // name of layout - entry *nameEntry; - const char *layout; // string of layout (not yet parsed) - band **elems; // array of top-level layout elems (or callables) - - bool hasCallables() - { - return layout[0] == '['; - } - band **bands() - { - assert(elems != nullptr); - return elems; - } - }; - struct attr_definitions - { - unpacker *u; // pointer to self, for U_NEW macro - int xxx_flags_hi_bn; // locator for flags, count, indexes, calls bands - int attrc; // ATTR_CONTEXT_CLASS, etc. - uint32_t flag_limit; // 32 or 63, depending on archive_options bit - uint64_t predef; // mask of built-in definitions - uint64_t redef; // mask of local flag definitions or redefinitions - ptrlist layouts; // local (compressor-defined) defs, in index order - int flag_count[X_ATTR_LIMIT_FLAGS_HI]; - intlist overflow_count; - ptrlist strip_names; // what attribute names are being stripped? - ptrlist band_stack; // Temp., used during layout parsing. - ptrlist calls_to_link; // (ditto) - int bands_made; // (ditto) - - void free() - { - layouts.free(); - overflow_count.free(); - strip_names.free(); - band_stack.free(); - calls_to_link.free(); - } - - // Locate the five fixed bands. - band &xxx_flags_hi(); - band &xxx_flags_lo(); - band &xxx_attr_count(); - band &xxx_attr_indexes(); - band &xxx_attr_calls(); - band &fixed_band(int e_class_xxx); - - // Register a new layout, and make bands for it. - layout_definition *defineLayout(int idx, const char *name, const char *layout); - layout_definition *defineLayout(int idx, entry *nameEntry, const char *layout); - band **buildBands(layout_definition *lo); - - // Parse a layout string or part of one, recursively if necessary. - const char *parseLayout(const char *lp, band **&res, int curCble); - const char *parseNumeral(const char *lp, int &res); - const char *parseIntLayout(const char *lp, band *&res, byte le_kind, - bool can_be_signed = false); - band **popBody(int band_stack_base); // pops a body off band_stack - - // Read data into the bands of the idx-th layout. - void readBandData(int idx); // parse layout, make bands, read data - void readBandData(band **body, uint32_t count); // recursive helper - - layout_definition *getLayout(uint32_t idx) - { - if (idx >= (uint32_t)layouts.length()) - return nullptr; - return (layout_definition *)layouts.get(idx); - } - - void setHaveLongFlags(bool z) - { - assert(flag_limit == 0); // not set up yet - flag_limit = (z ? X_ATTR_LIMIT_FLAGS_HI : X_ATTR_LIMIT_NO_FLAGS_HI); - } - bool haveLongFlags() - { - assert(flag_limit == X_ATTR_LIMIT_NO_FLAGS_HI || - flag_limit == X_ATTR_LIMIT_FLAGS_HI); - return flag_limit == X_ATTR_LIMIT_FLAGS_HI; - } - - // Return flag_count if idx is predef and not redef, else zero. - int predefCount(uint32_t idx); - - bool isRedefined(uint32_t idx) - { - if (idx >= flag_limit) - return false; - return (bool)((redef >> idx) & 1); - } - bool isPredefined(uint32_t idx) - { - if (idx >= flag_limit) - return false; - return (bool)(((predef & ~redef) >> idx) & 1); - } - uint64_t flagIndexMask() - { - return (predef | redef); - } - bool isIndex(uint32_t idx) - { - assert(flag_limit != 0); // must be set up already - if (idx < flag_limit) - return (bool)(((predef | redef) >> idx) & 1); - else - return (idx - flag_limit < (uint32_t)overflow_count.length()); - } - int &getCount(uint32_t idx) - { - assert(isIndex(idx)); - if (idx < flag_limit) - return flag_count[idx]; - else - return overflow_count.get(idx - flag_limit); - } - }; - - attr_definitions attr_defs[ATTR_CONTEXT_LIMIT]; - - // Initialization - void init(read_input_fn_t input_fn = nullptr); - // Resets to a known sane state - void reset(); - // Deallocates all storage. - void free(); - // Deallocates temporary storage (volatile after next client call). - void free_temps() - { - tsmallbuf.init(); - tmallocs.freeAll(); - } - - // Option management methods - bool set_option(const char *option, const char *value); - const char *get_option(const char *option); - - // Fetching input. - bool ensure_input(int64_t more); - byte *input_scan() - { - return rp; - } - size_t input_remaining() - { - return rplimit - rp; - } - size_t input_consumed() - { - return rp - input.base(); - } - - // Entry points to the unpack engine - static int run(int argc, char **argv); // Unix-style entry point. - void check_options(); - void start(void *packptr = nullptr, size_t len = 0); - void write_file_to_jar(file *f); - void finish(); - - // Public post unpack methods - int get_files_remaining() - { - return files_remaining; - } - int get_segments_remaining() - { - return archive_next_count; - } - file *get_next_file(); // returns nullptr on last file - - // General purpose methods - void *alloc(size_t size) - { - return alloc_heap(size, true); - } - void *temp_alloc(size_t size) - { - return alloc_heap(size, true, true); - } - void *alloc_heap(size_t size, bool smallOK = false, bool temp = false); - void saveTo(bytes &b, const char *str) - { - saveTo(b, (byte *)str, strlen(str)); - } - void saveTo(bytes &b, bytes &data) - { - saveTo(b, data.ptr, data.len); - } - void saveTo(bytes &b, byte *ptr, size_t len); //{ b.ptr = U_NEW...} - const char *saveStr(const char *str) - { - bytes buf; - saveTo(buf, str); - return buf.strval(); - } - const char *saveIntStr(int num) - { - char buf[30]; - sprintf(buf, "%d", num); - return saveStr(buf); - } - static unpacker *current(); // find current instance - - // Output management - void set_output(fillbytes *which) - { - assert(wp == nullptr); - which->ensureSize(1 << 12); // covers the average classfile - wpbase = which->base(); - wp = which->limit(); - wplimit = which->end(); - } - fillbytes *close_output(fillbytes *which = nullptr); // inverse of set_output - - // These take an implicit parameter of wp/wplimit, and resize as necessary: - byte *put_space(size_t len); // allocates space at wp, returns pointer - size_t put_empty(size_t s) - { - byte *p = put_space(s); - return p - wpbase; - } - void ensure_put_space(size_t len); - void put_bytes(bytes &b) - { - b.writeTo(put_space(b.len)); - } - void putu1(int n) - { - putu1_at(put_space(1), n); - } - void putu1_fast(int n) - { - putu1_at(wp++, n); - } - void putu2(int n); // { putu2_at(put_space(2), n); } - void putu4(int n); // { putu4_at(put_space(4), n); } - void putu8(int64_t n); // { putu8_at(put_space(8), n); } - void putref(entry *e); // { putu2_at(put_space(2), putref_index(e, 2)); } - void putu1ref(entry *e); // { putu1_at(put_space(1), putref_index(e, 1)); } - int putref_index(entry *e, int size); // size in [1..2] - void put_label(int curIP, int size); // size in {2,4} - void putlayout(band **body); - void put_stackmap_type(); - - size_t wpoffset() - { - return (size_t)(wp - wpbase); - } // (unvariant across overflow) - byte *wp_at(size_t offset) - { - return wpbase + offset; - } - uint32_t to_bci(uint32_t bii); - void get_code_header(int &max_stack, int &max_na_locals, int &handler_count, int &cflags); - band *ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar); - band *ref_band_for_op(int bc); - - // Definitions of standard classfile int formats: - static void putu1_at(byte *wp, int n) - { - assert(n == (n & 0xFF)); - wp[0] = n; - } - static void putu2_at(byte *wp, int n); - static void putu4_at(byte *wp, int n); - static void putu8_at(byte *wp, int64_t n); - - // Private stuff - void reset_cur_classfile(); - void write_classfile_tail(); - void write_classfile_head(); - void write_code(); - void write_bc_ops(); - void write_members(int num, int attrc); // attrc=ATTR_CONTEXT_FIELD/METHOD - int write_attrs(int attrc, uint64_t indexBits); - - // The readers - void read_bands(); - void read_file_header(); - void read_cp(); - void read_cp_counts(value_stream &hdr); - void read_attr_defs(); - void read_ics(); - void read_attrs(int attrc, int obj_count); - void read_classes(); - void read_code_headers(); - void read_bcs(); - void read_bc_ops(); - void read_files(); - void read_Utf8_values(entry *cpMap, int len); - void read_single_words(band &cp_band, entry *cpMap, int len); - void read_double_words(band &cp_bands, entry *cpMap, int len); - void read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len); - void read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, int len); - void read_signature_values(entry *cpMap, int len); + // One element of the resulting JAR. + struct file + { + const char *name; + uint64_t size; + int modtime; + int options; + bytes data[2]; + // Note: If Sum(data[*].len) < size, + // remaining bytes must be read directly from the input stream. + bool deflate_hint() + { + return ((options & FO_DEFLATE_HINT) != 0); + } + }; + + // if running Unix-style, here are the inputs and outputs + FILE *infileptr; // buffered + bytes inbytes; // direct + gunzip *gzin; // gunzip filter, if any + jar *jarout; // output JAR file + + // pointer to self, for U_NEW macro + unpacker *u; + + ptrlist mallocs; // list of guys to free when we are all done + ptrlist tmallocs; // list of guys to free on next client request + fillbytes smallbuf; // supplies small alloc requests + fillbytes tsmallbuf; // supplies temporary small alloc requests + + // option management members + int verbose; // verbose level, 0 means no output + int deflate_hint_or_zero; // ==0 means not set, otherwise -1 or 1 + int modification_time_or_zero; + + // input stream + fillbytes input; // the whole block (size is predicted, has slop too) + bool live_input; // is the data in this block live? + bool free_input; // must the input buffer be freed? + byte *rp; // read pointer (< rplimit <= input.limit()) + byte *rplimit; // how much of the input block has been read? + uint64_t bytes_read; + int unsized_bytes_read; + + // callback to read at least one byte, up to available input + read_input_fn_t read_input_fn; + + // archive header fields + int magic, minver, majver; + size_t archive_size; + int archive_next_count, archive_options, archive_modtime; + int band_headers_size; + int file_count, attr_definition_count, ic_count, class_count; + int default_class_minver, default_class_majver; + int default_file_options, suppress_file_options; // not header fields + int default_archive_modtime, default_file_modtime; // not header fields + int code_count; // not a header field + int files_remaining; // not a header field + + // engine state + band *all_bands; // indexed by band_number + byte *meta_rp; // read-pointer into (copy of) band_headers + constant_pool cp; // all constant pool information + inner_class *ics; // InnerClasses + + // output stream + bytes output; // output block (either classfile head or tail) + byte *wp; // write pointer (< wplimit == output.limit()) + byte *wpbase; // write pointer starting address (<= wp) + byte *wplimit; // how much of the output block has been written? + + // output state + file cur_file; + entry *cur_class; // CONSTANT_Class entry + entry *cur_super; // CONSTANT_Class entry or nullptr + entry *cur_descr; // CONSTANT_NameandType entry + int cur_descr_flags; // flags corresponding to cur_descr + int cur_class_minver, cur_class_majver; + bool cur_class_has_local_ics; + fillbytes cur_classfile_head; + fillbytes cur_classfile_tail; + int files_written; // also tells which file we're working on + int classes_written; // also tells which class we're working on + uint64_t bytes_written; + intlist bcimap; + fillbytes class_fixup_type; + intlist class_fixup_offset; + ptrlist class_fixup_ref; + fillbytes code_fixup_type; // which format of branch operand? + intlist code_fixup_offset; // location of operand needing fixup + intlist code_fixup_source; // encoded ID of branch insn + ptrlist requested_ics; // which ics need output? + + // stats pertaining to multiple segments (updated on reset) + uint64_t bytes_read_before_reset; + uint64_t bytes_written_before_reset; + int files_written_before_reset; + int classes_written_before_reset; + int segments_read_before_reset; + + // attribute state + struct layout_definition + { + uint32_t idx; // index (0..31...) which identifies this layout + const char *name; // name of layout + entry *nameEntry; + const char *layout; // string of layout (not yet parsed) + band **elems; // array of top-level layout elems (or callables) + + bool hasCallables() + { + return layout[0] == '['; + } + band **bands() + { + assert(elems != nullptr); + return elems; + } + }; + struct attr_definitions + { + unpacker *u; // pointer to self, for U_NEW macro + int xxx_flags_hi_bn; // locator for flags, count, indexes, calls bands + int attrc; // ATTR_CONTEXT_CLASS, etc. + uint32_t flag_limit; // 32 or 63, depending on archive_options bit + uint64_t predef; // mask of built-in definitions + uint64_t redef; // mask of local flag definitions or redefinitions + ptrlist layouts; // local (compressor-defined) defs, in index order + int flag_count[X_ATTR_LIMIT_FLAGS_HI]; + intlist overflow_count; + ptrlist strip_names; // what attribute names are being stripped? + ptrlist band_stack; // Temp., used during layout parsing. + ptrlist calls_to_link; // (ditto) + int bands_made; // (ditto) + + void free() + { + layouts.free(); + overflow_count.free(); + strip_names.free(); + band_stack.free(); + calls_to_link.free(); + } + + // Locate the five fixed bands. + band &xxx_flags_hi(); + band &xxx_flags_lo(); + band &xxx_attr_count(); + band &xxx_attr_indexes(); + band &xxx_attr_calls(); + band &fixed_band(int e_class_xxx); + + // Register a new layout, and make bands for it. + layout_definition *defineLayout(int idx, const char *name, const char *layout); + layout_definition *defineLayout(int idx, entry *nameEntry, const char *layout); + band **buildBands(layout_definition *lo); + + // Parse a layout string or part of one, recursively if necessary. + const char *parseLayout(const char *lp, band **&res, int curCble); + const char *parseNumeral(const char *lp, int &res); + const char *parseIntLayout(const char *lp, band *&res, byte le_kind, + bool can_be_signed = false); + band **popBody(int band_stack_base); // pops a body off band_stack + + // Read data into the bands of the idx-th layout. + void readBandData(int idx); // parse layout, make bands, read data + void readBandData(band **body, uint32_t count); // recursive helper + + layout_definition *getLayout(uint32_t idx) + { + if (idx >= (uint32_t)layouts.length()) + return nullptr; + return (layout_definition *)layouts.get(idx); + } + + void setHaveLongFlags(bool z) + { + assert(flag_limit == 0); // not set up yet + flag_limit = (z ? X_ATTR_LIMIT_FLAGS_HI : X_ATTR_LIMIT_NO_FLAGS_HI); + } + bool haveLongFlags() + { + assert(flag_limit == X_ATTR_LIMIT_NO_FLAGS_HI || + flag_limit == X_ATTR_LIMIT_FLAGS_HI); + return flag_limit == X_ATTR_LIMIT_FLAGS_HI; + } + + // Return flag_count if idx is predef and not redef, else zero. + int predefCount(uint32_t idx); + + bool isRedefined(uint32_t idx) + { + if (idx >= flag_limit) + return false; + return (bool)((redef >> idx) & 1); + } + bool isPredefined(uint32_t idx) + { + if (idx >= flag_limit) + return false; + return (bool)(((predef & ~redef) >> idx) & 1); + } + uint64_t flagIndexMask() + { + return (predef | redef); + } + bool isIndex(uint32_t idx) + { + assert(flag_limit != 0); // must be set up already + if (idx < flag_limit) + return (bool)(((predef | redef) >> idx) & 1); + else + return (idx - flag_limit < (uint32_t)overflow_count.length()); + } + int &getCount(uint32_t idx) + { + assert(isIndex(idx)); + if (idx < flag_limit) + return flag_count[idx]; + else + return overflow_count.get(idx - flag_limit); + } + }; + + attr_definitions attr_defs[ATTR_CONTEXT_LIMIT]; + + // Initialization + void init(read_input_fn_t input_fn = nullptr); + // Resets to a known sane state + void reset(); + // Deallocates all storage. + void free(); + // Deallocates temporary storage (volatile after next client call). + void free_temps() + { + tsmallbuf.init(); + tmallocs.freeAll(); + } + + // Option management methods + bool set_option(const char *option, const char *value); + const char *get_option(const char *option); + + // Fetching input. + bool ensure_input(int64_t more); + byte *input_scan() + { + return rp; + } + size_t input_remaining() + { + return rplimit - rp; + } + size_t input_consumed() + { + return rp - input.base(); + } + + // Entry points to the unpack engine + static int run(int argc, char **argv); // Unix-style entry point. + void check_options(); + void start(void *packptr = nullptr, size_t len = 0); + void write_file_to_jar(file *f); + void finish(); + + // Public post unpack methods + int get_files_remaining() + { + return files_remaining; + } + int get_segments_remaining() + { + return archive_next_count; + } + file *get_next_file(); // returns nullptr on last file + + // General purpose methods + void *alloc(size_t size) + { + return alloc_heap(size, true); + } + void *temp_alloc(size_t size) + { + return alloc_heap(size, true, true); + } + void *alloc_heap(size_t size, bool smallOK = false, bool temp = false); + void saveTo(bytes &b, const char *str) + { + saveTo(b, (byte *)str, strlen(str)); + } + void saveTo(bytes &b, bytes &data) + { + saveTo(b, data.ptr, data.len); + } + void saveTo(bytes &b, byte *ptr, size_t len); //{ b.ptr = U_NEW...} + const char *saveStr(const char *str) + { + bytes buf; + saveTo(buf, str); + return buf.strval(); + } + const char *saveIntStr(int num) + { + char buf[30]; + sprintf(buf, "%d", num); + return saveStr(buf); + } + static unpacker *current(); // find current instance + + // Output management + void set_output(fillbytes *which) + { + assert(wp == nullptr); + which->ensureSize(1 << 12); // covers the average classfile + wpbase = which->base(); + wp = which->limit(); + wplimit = which->end(); + } + fillbytes *close_output(fillbytes *which = nullptr); // inverse of set_output + + // These take an implicit parameter of wp/wplimit, and resize as necessary: + byte *put_space(size_t len); // allocates space at wp, returns pointer + size_t put_empty(size_t s) + { + byte *p = put_space(s); + return p - wpbase; + } + void ensure_put_space(size_t len); + void put_bytes(bytes &b) + { + b.writeTo(put_space(b.len)); + } + void putu1(int n) + { + putu1_at(put_space(1), n); + } + void putu1_fast(int n) + { + putu1_at(wp++, n); + } + void putu2(int n); // { putu2_at(put_space(2), n); } + void putu4(int n); // { putu4_at(put_space(4), n); } + void putu8(int64_t n); // { putu8_at(put_space(8), n); } + void putref(entry *e); // { putu2_at(put_space(2), putref_index(e, 2)); } + void putu1ref(entry *e); // { putu1_at(put_space(1), putref_index(e, 1)); } + int putref_index(entry *e, int size); // size in [1..2] + void put_label(int curIP, int size); // size in {2,4} + void putlayout(band **body); + void put_stackmap_type(); + + size_t wpoffset() + { + return (size_t)(wp - wpbase); + } // (unvariant across overflow) + byte *wp_at(size_t offset) + { + return wpbase + offset; + } + uint32_t to_bci(uint32_t bii); + void get_code_header(int &max_stack, int &max_na_locals, int &handler_count, int &cflags); + band *ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar); + band *ref_band_for_op(int bc); + + // Definitions of standard classfile int formats: + static void putu1_at(byte *wp, int n) + { + assert(n == (n & 0xFF)); + wp[0] = n; + } + static void putu2_at(byte *wp, int n); + static void putu4_at(byte *wp, int n); + static void putu8_at(byte *wp, int64_t n); + + // Private stuff + void reset_cur_classfile(); + void write_classfile_tail(); + void write_classfile_head(); + void write_code(); + void write_bc_ops(); + void write_members(int num, int attrc); // attrc=ATTR_CONTEXT_FIELD/METHOD + int write_attrs(int attrc, uint64_t indexBits); + + // The readers + void read_bands(); + void read_file_header(); + void read_cp(); + void read_cp_counts(value_stream &hdr); + void read_attr_defs(); + void read_ics(); + void read_attrs(int attrc, int obj_count); + void read_classes(); + void read_code_headers(); + void read_bcs(); + void read_bc_ops(); + void read_files(); + void read_Utf8_values(entry *cpMap, int len); + void read_single_words(band &cp_band, entry *cpMap, int len); + void read_double_words(band &cp_bands, entry *cpMap, int len); + void read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len); + void read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, int len); + void read_signature_values(entry *cpMap, int len); }; diff --git a/libraries/pack200/src/unpack200.cpp b/libraries/pack200/src/unpack200.cpp index 22b7f3b0..e8826f28 100644 --- a/libraries/pack200/src/unpack200.cpp +++ b/libraries/pack200/src/unpack200.cpp @@ -45,118 +45,118 @@ // Callback for fetching data, Unix style. static int64_t read_input_via_stdio(unpacker *u, void *buf, int64_t minlen, int64_t maxlen) { - assert(u->infileptr != nullptr); - assert(minlen <= maxlen); // don't talk nonsense - int64_t numread = 0; - char *bufptr = (char *)buf; - while (numread < minlen) - { - // read available input, up to buf.length or maxlen - int readlen = (1 << 16); - if (readlen > (maxlen - numread)) - readlen = (int)(maxlen - numread); - int nr = 0; + assert(u->infileptr != nullptr); + assert(minlen <= maxlen); // don't talk nonsense + int64_t numread = 0; + char *bufptr = (char *)buf; + while (numread < minlen) + { + // read available input, up to buf.length or maxlen + int readlen = (1 << 16); + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + int nr = 0; - nr = (int)fread(bufptr, 1, readlen, u->infileptr); - if (nr <= 0) - { - if (errno != EINTR) - break; - nr = 0; - } - numread += nr; - bufptr += nr; - assert(numread <= maxlen); - } - return numread; + nr = (int)fread(bufptr, 1, readlen, u->infileptr); + if (nr <= 0) + { + if (errno != EINTR) + break; + nr = 0; + } + numread += nr; + bufptr += nr; + assert(numread <= maxlen); + } + return numread; } enum { - EOF_MAGIC = 0, - BAD_MAGIC = -1 + EOF_MAGIC = 0, + BAD_MAGIC = -1 }; static int read_magic(unpacker *u, char peek[], int peeklen) { - assert(peeklen == 4); // magic numbers are always 4 bytes - int64_t nr = (u->read_input_fn)(u, peek, peeklen, peeklen); - if (nr != peeklen) - { - return (nr == 0) ? EOF_MAGIC : BAD_MAGIC; - } - int magic = 0; - for (int i = 0; i < peeklen; i++) - { - magic <<= 8; - magic += peek[i] & 0xFF; - } - return magic; + assert(peeklen == 4); // magic numbers are always 4 bytes + int64_t nr = (u->read_input_fn)(u, peek, peeklen, peeklen); + if (nr != peeklen) + { + return (nr == 0) ? EOF_MAGIC : BAD_MAGIC; + } + int magic = 0; + for (int i = 0; i < peeklen; i++) + { + magic <<= 8; + magic += peek[i] & 0xFF; + } + return magic; } void unpack_200(FILE *input, FILE *output) { - unpacker u; - u.init(read_input_via_stdio); + unpacker u; + u.init(read_input_via_stdio); - // initialize jar output - // the output takes ownership of the file handle - jar jarout; - jarout.init(&u); - jarout.jarfp = output; + // initialize jar output + // the output takes ownership of the file handle + jar jarout; + jarout.init(&u); + jarout.jarfp = output; - // the input doesn't - u.infileptr = input; + // the input doesn't + u.infileptr = input; - // read the magic! - char peek[4]; - int magic; - magic = read_magic(&u, peek, (int)sizeof(peek)); + // read the magic! + char peek[4]; + int magic; + magic = read_magic(&u, peek, (int)sizeof(peek)); - // if it is a gzip encoded file, we need an extra gzip input filter - if ((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC) - { - gunzip *gzin = NEW(gunzip, 1); - gzin->init(&u); - // FIXME: why the side effects? WHY? - u.gzin->start(magic); - u.start(); - } - else - { - // otherwise, feed the bytes to the unpacker directly - u.start(peek, sizeof(peek)); - } + // if it is a gzip encoded file, we need an extra gzip input filter + if ((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC) + { + gunzip *gzin = NEW(gunzip, 1); + gzin->init(&u); + // FIXME: why the side effects? WHY? + u.gzin->start(magic); + u.start(); + } + else + { + // otherwise, feed the bytes to the unpacker directly + u.start(peek, sizeof(peek)); + } - // Note: The checks to u.aborting() are necessary to gracefully - // terminate processing when the first segment throws an error. - for (;;) - { - // Each trip through this loop unpacks one segment - // and then resets the unpacker. - for (unpacker::file *filep; (filep = u.get_next_file()) != nullptr;) - { - u.write_file_to_jar(filep); - } + // Note: The checks to u.aborting() are necessary to gracefully + // terminate processing when the first segment throws an error. + for (;;) + { + // Each trip through this loop unpacks one segment + // and then resets the unpacker. + for (unpacker::file *filep; (filep = u.get_next_file()) != nullptr;) + { + u.write_file_to_jar(filep); + } - // Peek ahead for more data. - magic = read_magic(&u, peek, (int)sizeof(peek)); - if (magic != (int)JAVA_PACKAGE_MAGIC) - { - // we do not feel strongly about this kind of thing... - /* - if (magic != EOF_MAGIC) - unpack_abort("garbage after end of pack archive"); - */ - break; // all done - } + // Peek ahead for more data. + magic = read_magic(&u, peek, (int)sizeof(peek)); + if (magic != (int)JAVA_PACKAGE_MAGIC) + { + // we do not feel strongly about this kind of thing... + /* + if (magic != EOF_MAGIC) + unpack_abort("garbage after end of pack archive"); + */ + break; // all done + } - // Release all storage from parsing the old segment. - u.reset(); - // Restart, beginning with the peek-ahead. - u.start(peek, sizeof(peek)); - } - u.finish(); - u.free(); // tidy up malloc blocks - fclose(input); + // Release all storage from parsing the old segment. + u.reset(); + // Restart, beginning with the peek-ahead. + u.start(peek, sizeof(peek)); + } + u.finish(); + u.free(); // tidy up malloc blocks + fclose(input); } diff --git a/libraries/pack200/src/utils.cpp b/libraries/pack200/src/utils.cpp index 0b7d91ca..fd6dad60 100644 --- a/libraries/pack200/src/utils.cpp +++ b/libraries/pack200/src/utils.cpp @@ -50,22 +50,22 @@ void *must_malloc(size_t size) { - size_t msize = size; - void *ptr = (msize > PSIZE_MAX) ? nullptr : malloc(msize); - if (ptr != nullptr) - { - memset(ptr, 0, size); - } - else - { - throw std::runtime_error(ERROR_ENOMEM); - } - return ptr; + size_t msize = size; + void *ptr = (msize > PSIZE_MAX) ? nullptr : malloc(msize); + if (ptr != nullptr) + { + memset(ptr, 0, size); + } + else + { + throw std::runtime_error(ERROR_ENOMEM); + } + return ptr; } void unpack_abort(const char *msg) { - if (msg == nullptr) - msg = "corrupt pack file or internal error"; - throw std::runtime_error(msg); + if (msg == nullptr) + msg = "corrupt pack file or internal error"; + throw std::runtime_error(msg); } diff --git a/libraries/pack200/src/utils.h b/libraries/pack200/src/utils.h index 5a3dc8f6..3bd2dae7 100644 --- a/libraries/pack200/src/utils.h +++ b/libraries/pack200/src/utils.h @@ -35,17 +35,17 @@ void *must_malloc(size_t size); inline size_t scale_size(size_t size, size_t scale) { - return (size > PSIZE_MAX / scale) ? OVERFLOW : size * scale; + return (size > PSIZE_MAX / scale) ? OVERFLOW : size * scale; } inline size_t add_size(size_t size1, size_t size2) { - return ((size1 | size2 | (size1 + size2)) > PSIZE_MAX) ? OVERFLOW : size1 + size2; + return ((size1 | size2 | (size1 + size2)) > PSIZE_MAX) ? OVERFLOW : size1 + size2; } inline size_t add_size(size_t size1, size_t size2, int size3) { - return add_size(add_size(size1, size2), size3); + return add_size(add_size(size1, size2), size3); } struct unpacker; diff --git a/libraries/pack200/src/zip.cpp b/libraries/pack200/src/zip.cpp index 32e8bd50..e776510b 100644 --- a/libraries/pack200/src/zip.cpp +++ b/libraries/pack200/src/zip.cpp @@ -52,7 +52,7 @@ inline uint32_t jar::get_crc32(uint32_t c, uchar *ptr, uint32_t len) { - return crc32(c, ptr, len); + return crc32(c, ptr, len); } // FIXME: this is bullshit. Do real endianness detection. @@ -68,175 +68,175 @@ inline uint32_t jar::get_crc32(uint32_t c, uchar *ptr, uint32_t len) void jar::init(unpacker *u_) { - BYTES_OF(*this).clear(); - u = u_; - u->jarout = this; + BYTES_OF(*this).clear(); + u = u_; + u->jarout = this; } // Write data to the ZIP output stream. void jar::write_data(void *buff, int len) { - while (len > 0) - { - int rc = (int)fwrite(buff, 1, len, jarfp); - if (rc <= 0) - { - fprintf(stderr, "Error: write on output file failed err=%d\n", errno); - exit(1); // Called only from the native standalone unpacker - } - output_file_offset += rc; - buff = ((char *)buff) + rc; - len -= rc; - } + while (len > 0) + { + int rc = (int)fwrite(buff, 1, len, jarfp); + if (rc <= 0) + { + fprintf(stderr, "Error: write on output file failed err=%d\n", errno); + exit(1); // Called only from the native standalone unpacker + } + output_file_offset += rc; + buff = ((char *)buff) + rc; + len -= rc; + } } void jar::add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen, - uint32_t crc) + uint32_t crc) { - uint32_t fname_length = (uint32_t)strlen(fname); - ushort header[23]; - if (modtime == 0) - modtime = default_modtime; - uint32_t dostime = get_dostime(modtime); - - header[0] = (ushort)SWAP_BYTES(0x4B50); - header[1] = (ushort)SWAP_BYTES(0x0201); - header[2] = (ushort)SWAP_BYTES(0xA); - - // required version - header[3] = (ushort)SWAP_BYTES(0xA); - - // flags 02 = maximum sub-compression flag - header[4] = (store) ? 0x0 : SWAP_BYTES(0x2); - - // Compression method 8=deflate. - header[5] = (store) ? 0x0 : SWAP_BYTES(0x08); - - // Last modified date and time. - header[6] = (ushort)GET_INT_LO(dostime); - header[7] = (ushort)GET_INT_HI(dostime); - - // CRC - header[8] = (ushort)GET_INT_LO(crc); - header[9] = (ushort)GET_INT_HI(crc); - - // Compressed length: - header[10] = (ushort)GET_INT_LO(clen); - header[11] = (ushort)GET_INT_HI(clen); - - // Uncompressed length. - header[12] = (ushort)GET_INT_LO(len); - header[13] = (ushort)GET_INT_HI(len); - - // Filename length - header[14] = (ushort)SWAP_BYTES(fname_length); - // So called "extra field" length. - header[15] = 0; - // So called "comment" length. - header[16] = 0; - // Disk number start - header[17] = 0; - // File flags => binary - header[18] = 0; - // More file flags - header[19] = 0; - header[20] = 0; - // Offset within ZIP file. - header[21] = (ushort)GET_INT_LO(output_file_offset); - header[22] = (ushort)GET_INT_HI(output_file_offset); - - // Copy the whole thing into the central directory. - central_directory.append(header, sizeof(header)); - - // Copy the fname to the header. - central_directory.append(fname, fname_length); - - central_directory_count++; + uint32_t fname_length = (uint32_t)strlen(fname); + ushort header[23]; + if (modtime == 0) + modtime = default_modtime; + uint32_t dostime = get_dostime(modtime); + + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0201); + header[2] = (ushort)SWAP_BYTES(0xA); + + // required version + header[3] = (ushort)SWAP_BYTES(0xA); + + // flags 02 = maximum sub-compression flag + header[4] = (store) ? 0x0 : SWAP_BYTES(0x2); + + // Compression method 8=deflate. + header[5] = (store) ? 0x0 : SWAP_BYTES(0x08); + + // Last modified date and time. + header[6] = (ushort)GET_INT_LO(dostime); + header[7] = (ushort)GET_INT_HI(dostime); + + // CRC + header[8] = (ushort)GET_INT_LO(crc); + header[9] = (ushort)GET_INT_HI(crc); + + // Compressed length: + header[10] = (ushort)GET_INT_LO(clen); + header[11] = (ushort)GET_INT_HI(clen); + + // Uncompressed length. + header[12] = (ushort)GET_INT_LO(len); + header[13] = (ushort)GET_INT_HI(len); + + // Filename length + header[14] = (ushort)SWAP_BYTES(fname_length); + // So called "extra field" length. + header[15] = 0; + // So called "comment" length. + header[16] = 0; + // Disk number start + header[17] = 0; + // File flags => binary + header[18] = 0; + // More file flags + header[19] = 0; + header[20] = 0; + // Offset within ZIP file. + header[21] = (ushort)GET_INT_LO(output_file_offset); + header[22] = (ushort)GET_INT_HI(output_file_offset); + + // Copy the whole thing into the central directory. + central_directory.append(header, sizeof(header)); + + // Copy the fname to the header. + central_directory.append(fname, fname_length); + + central_directory_count++; } void jar::write_jar_header(const char *fname, bool store, int modtime, int len, int clen, - uint32_t crc) + uint32_t crc) { - uint32_t fname_length = (uint32_t)strlen(fname); - ushort header[15]; - if (modtime == 0) - modtime = default_modtime; - uint32_t dostime = get_dostime(modtime); + uint32_t fname_length = (uint32_t)strlen(fname); + ushort header[15]; + if (modtime == 0) + modtime = default_modtime; + uint32_t dostime = get_dostime(modtime); - // ZIP LOC magic. - header[0] = (ushort)SWAP_BYTES(0x4B50); - header[1] = (ushort)SWAP_BYTES(0x0403); + // ZIP LOC magic. + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0403); - // Version - header[2] = (ushort)SWAP_BYTES(0xA); + // Version + header[2] = (ushort)SWAP_BYTES(0xA); - // flags 02 = maximum sub-compression flag - header[3] = (store) ? 0x0 : SWAP_BYTES(0x2); + // flags 02 = maximum sub-compression flag + header[3] = (store) ? 0x0 : SWAP_BYTES(0x2); - // Compression method = deflate - header[4] = (store) ? 0x0 : SWAP_BYTES(0x08); + // Compression method = deflate + header[4] = (store) ? 0x0 : SWAP_BYTES(0x08); - // Last modified date and time. - header[5] = (ushort)GET_INT_LO(dostime); - header[6] = (ushort)GET_INT_HI(dostime); + // Last modified date and time. + header[5] = (ushort)GET_INT_LO(dostime); + header[6] = (ushort)GET_INT_HI(dostime); - // CRC - header[7] = (ushort)GET_INT_LO(crc); - header[8] = (ushort)GET_INT_HI(crc); + // CRC + header[7] = (ushort)GET_INT_LO(crc); + header[8] = (ushort)GET_INT_HI(crc); - // Compressed length: - header[9] = (ushort)GET_INT_LO(clen); - header[10] = (ushort)GET_INT_HI(clen); + // Compressed length: + header[9] = (ushort)GET_INT_LO(clen); + header[10] = (ushort)GET_INT_HI(clen); - // Uncompressed length. - header[11] = (ushort)GET_INT_LO(len); - header[12] = (ushort)GET_INT_HI(len); + // Uncompressed length. + header[11] = (ushort)GET_INT_LO(len); + header[12] = (ushort)GET_INT_HI(len); - // Filename length - header[13] = (ushort)SWAP_BYTES(fname_length); - // So called "extra field" length. - header[14] = 0; + // Filename length + header[13] = (ushort)SWAP_BYTES(fname_length); + // So called "extra field" length. + header[14] = 0; - // Write the LOC header to the output file. - write_data(header, (int)sizeof(header)); + // Write the LOC header to the output file. + write_data(header, (int)sizeof(header)); - // Copy the fname to the header. - write_data((char *)fname, (int)fname_length); + // Copy the fname to the header. + write_data((char *)fname, (int)fname_length); } void jar::write_central_directory() { - bytes mc; - mc.set("PACK200"); - - ushort header[11]; - - // Create the End of Central Directory structure. - header[0] = (ushort)SWAP_BYTES(0x4B50); - header[1] = (ushort)SWAP_BYTES(0x0605); - // disk numbers - header[2] = 0; - header[3] = 0; - // Number of entries in central directory. - header[4] = (ushort)SWAP_BYTES(central_directory_count); - header[5] = (ushort)SWAP_BYTES(central_directory_count); - // Size of the central directory} - header[6] = (ushort)GET_INT_LO((int)central_directory.size()); - header[7] = (ushort)GET_INT_HI((int)central_directory.size()); - // Offset of central directory within disk. - header[8] = (ushort)GET_INT_LO(output_file_offset); - header[9] = (ushort)GET_INT_HI(output_file_offset); - // zipfile comment length; - header[10] = (ushort)SWAP_BYTES((int)mc.len); - - // Write the central directory. - write_data(central_directory.b); - - // Write the End of Central Directory structure. - write_data(header, (int)sizeof(header)); - - // Write the comment. - write_data(mc); + bytes mc; + mc.set("PACK200"); + + ushort header[11]; + + // Create the End of Central Directory structure. + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0605); + // disk numbers + header[2] = 0; + header[3] = 0; + // Number of entries in central directory. + header[4] = (ushort)SWAP_BYTES(central_directory_count); + header[5] = (ushort)SWAP_BYTES(central_directory_count); + // Size of the central directory} + header[6] = (ushort)GET_INT_LO((int)central_directory.size()); + header[7] = (ushort)GET_INT_HI((int)central_directory.size()); + // Offset of central directory within disk. + header[8] = (ushort)GET_INT_LO(output_file_offset); + header[9] = (ushort)GET_INT_HI(output_file_offset); + // zipfile comment length; + header[10] = (ushort)SWAP_BYTES((int)mc.len); + + // Write the central directory. + write_data(central_directory.b); + + // Write the End of Central Directory structure. + write_data(header, (int)sizeof(header)); + + // Write the comment. + write_data(mc); } // Public API @@ -244,74 +244,74 @@ void jar::write_central_directory() // Open a Jar file and initialize. void jar::openJarFile(const char *fname) { - if (!jarfp) - { - jarfp = fopen(fname, "wb"); - if (!jarfp) - { - fprintf(stderr, "Error: Could not open jar file: %s\n", fname); - exit(3); // Called only from the native standalone unpacker - } - } + if (!jarfp) + { + jarfp = fopen(fname, "wb"); + if (!jarfp) + { + fprintf(stderr, "Error: Could not open jar file: %s\n", fname); + exit(3); // Called only from the native standalone unpacker + } + } } // Add a ZIP entry and copy the file data void jar::addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head, - bytes &tail) + bytes &tail) { - int len = (int)(head.len + tail.len); - int clen = 0; - - uint32_t crc = get_crc32(0, Z_NULL, 0); - if (head.len != 0) - crc = get_crc32(crc, (uchar *)head.ptr, (uint32_t)head.len); - if (tail.len != 0) - crc = get_crc32(crc, (uchar *)tail.ptr, (uint32_t)tail.len); - - bool deflate = (deflate_hint && len > 0); - - if (deflate) - { - if (deflate_bytes(head, tail) == false) - { - deflate = false; - } - } - clen = (int)((deflate) ? deflated.size() : len); - add_to_jar_directory(fname, !deflate, modtime, len, clen, crc); - write_jar_header(fname, !deflate, modtime, len, clen, crc); - - if (deflate) - { - write_data(deflated.b); - } - else - { - write_data(head); - write_data(tail); - } + int len = (int)(head.len + tail.len); + int clen = 0; + + uint32_t crc = get_crc32(0, Z_NULL, 0); + if (head.len != 0) + crc = get_crc32(crc, (uchar *)head.ptr, (uint32_t)head.len); + if (tail.len != 0) + crc = get_crc32(crc, (uchar *)tail.ptr, (uint32_t)tail.len); + + bool deflate = (deflate_hint && len > 0); + + if (deflate) + { + if (deflate_bytes(head, tail) == false) + { + deflate = false; + } + } + clen = (int)((deflate) ? deflated.size() : len); + add_to_jar_directory(fname, !deflate, modtime, len, clen, crc); + write_jar_header(fname, !deflate, modtime, len, clen, crc); + + if (deflate) + { + write_data(deflated.b); + } + else + { + write_data(head); + write_data(tail); + } } // Add a ZIP entry for a directory name no data void jar::addDirectoryToJarFile(const char *dir_name) { - bool store = true; - add_to_jar_directory((const char *)dir_name, store, default_modtime, 0, 0, 0); - write_jar_header((const char *)dir_name, store, default_modtime, 0, 0, 0); + bool store = true; + add_to_jar_directory((const char *)dir_name, store, default_modtime, 0, 0, 0); + write_jar_header((const char *)dir_name, store, default_modtime, 0, 0, 0); } // Write out the central directory and close the jar file. void jar::closeJarFile(bool central) { - if (jarfp) - { - fflush(jarfp); - if (central) - write_central_directory(); - fflush(jarfp); - fclose(jarfp); - } - reset(); + if (jarfp) + { + fflush(jarfp); + if (central) + write_central_directory(); + fflush(jarfp); + fclose(jarfp); + } + reset(); } /* Convert the date y/n/d and time h:m:s to a four byte DOS date and @@ -320,9 +320,9 @@ void jar::closeJarFile(bool central) */ inline uint32_t jar::dostime(int y, int n, int d, int h, int m, int s) { - return y < 1980 ? dostime(1980, 1, 1, 0, 0, 0) - : (((uint32_t)y - 1980) << 25) | ((uint32_t)n << 21) | ((uint32_t)d << 16) | - ((uint32_t)h << 11) | ((uint32_t)m << 5) | ((uint32_t)s >> 1); + return y < 1980 ? dostime(1980, 1, 1, 0, 0, 0) + : (((uint32_t)y - 1980) << 25) | ((uint32_t)n << 21) | ((uint32_t)d << 16) | + ((uint32_t)h << 11) | ((uint32_t)m << 5) | ((uint32_t)s >> 1); } /* #ifdef _REENTRANT // solaris @@ -336,20 +336,20 @@ extern "C" struct tm *gmtime_r(const time_t *, struct tm *); */ uint32_t jar::get_dostime(int modtime) { - // see defines.h - if (modtime != 0 && modtime == modtime_cache) - return dostime_cache; - if (modtime != 0 && default_modtime == 0) - default_modtime = modtime; // catch a reasonable default - time_t t = modtime; - struct tm sbuf; - (void)memset((void *)&sbuf, 0, sizeof(sbuf)); - struct tm *s = gmtime_r(&t, &sbuf); - modtime_cache = modtime; - dostime_cache = - dostime(s->tm_year + 1900, s->tm_mon + 1, s->tm_mday, s->tm_hour, s->tm_min, s->tm_sec); - // printf("modtime %d => %d\n", modtime_cache, dostime_cache); - return dostime_cache; + // see defines.h + if (modtime != 0 && modtime == modtime_cache) + return dostime_cache; + if (modtime != 0 && default_modtime == 0) + default_modtime = modtime; // catch a reasonable default + time_t t = modtime; + struct tm sbuf; + (void)memset((void *)&sbuf, 0, sizeof(sbuf)); + struct tm *s = gmtime_r(&t, &sbuf); + modtime_cache = modtime; + dostime_cache = + dostime(s->tm_year + 1900, s->tm_mon + 1, s->tm_mday, s->tm_hour, s->tm_min, s->tm_sec); + // printf("modtime %d => %d\n", modtime_cache, dostime_cache); + return dostime_cache; } /* Returns true on success, and will set the clen to the compressed @@ -358,232 +358,232 @@ uint32_t jar::get_dostime(int modtime) */ bool jar::deflate_bytes(bytes &head, bytes &tail) { - int len = (int)(head.len + tail.len); - - z_stream zs; - BYTES_OF(zs).clear(); - - // NOTE: the window size should always be -MAX_WBITS normally -15. - // unzip/zipup.c and java/Deflater.c - - int error = - deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); - if (error != Z_OK) - { - /* - switch (error) - { - case Z_MEM_ERROR: - PRINTCR((2, "Error: deflate error : Out of memory \n")); - break; - case Z_STREAM_ERROR: - PRINTCR((2, "Error: deflate error : Invalid compression level \n")); - break; - case Z_VERSION_ERROR: - PRINTCR((2, "Error: deflate error : Invalid version\n")); - break; - default: - PRINTCR((2, "Error: Internal deflate error error = %d\n", error)); - } - */ - return false; - } - - deflated.empty(); - zs.next_out = (uchar *)deflated.grow(len + (len / 2)); - zs.avail_out = (int)deflated.size(); - - zs.next_in = (uchar *)head.ptr; - zs.avail_in = (int)head.len; - - bytes *first = &head; - bytes *last = &tail; - if (last->len == 0) - { - first = nullptr; - last = &head; - } - else if (first->len == 0) - { - first = nullptr; - } - - if (first != nullptr && error == Z_OK) - { - zs.next_in = (uchar *)first->ptr; - zs.avail_in = (int)first->len; - error = deflate(&zs, Z_NO_FLUSH); - } - if (error == Z_OK) - { - zs.next_in = (uchar *)last->ptr; - zs.avail_in = (int)last->len; - error = deflate(&zs, Z_FINISH); - } - if (error == Z_STREAM_END) - { - if (len > (int)zs.total_out) - { - deflated.b.len = zs.total_out; - deflateEnd(&zs); - return true; - } - deflateEnd(&zs); - return false; - } - - deflateEnd(&zs); - return false; + int len = (int)(head.len + tail.len); + + z_stream zs; + BYTES_OF(zs).clear(); + + // NOTE: the window size should always be -MAX_WBITS normally -15. + // unzip/zipup.c and java/Deflater.c + + int error = + deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + if (error != Z_OK) + { + /* + switch (error) + { + case Z_MEM_ERROR: + PRINTCR((2, "Error: deflate error : Out of memory \n")); + break; + case Z_STREAM_ERROR: + PRINTCR((2, "Error: deflate error : Invalid compression level \n")); + break; + case Z_VERSION_ERROR: + PRINTCR((2, "Error: deflate error : Invalid version\n")); + break; + default: + PRINTCR((2, "Error: Internal deflate error error = %d\n", error)); + } + */ + return false; + } + + deflated.empty(); + zs.next_out = (uchar *)deflated.grow(len + (len / 2)); + zs.avail_out = (int)deflated.size(); + + zs.next_in = (uchar *)head.ptr; + zs.avail_in = (int)head.len; + + bytes *first = &head; + bytes *last = &tail; + if (last->len == 0) + { + first = nullptr; + last = &head; + } + else if (first->len == 0) + { + first = nullptr; + } + + if (first != nullptr && error == Z_OK) + { + zs.next_in = (uchar *)first->ptr; + zs.avail_in = (int)first->len; + error = deflate(&zs, Z_NO_FLUSH); + } + if (error == Z_OK) + { + zs.next_in = (uchar *)last->ptr; + zs.avail_in = (int)last->len; + error = deflate(&zs, Z_FINISH); + } + if (error == Z_STREAM_END) + { + if (len > (int)zs.total_out) + { + deflated.b.len = zs.total_out; + deflateEnd(&zs); + return true; + } + deflateEnd(&zs); + return false; + } + + deflateEnd(&zs); + return false; } // Callback for fetching data from a GZIP input stream static int64_t read_input_via_gzip(unpacker *u, void *buf, int64_t minlen, int64_t maxlen) { - assert(minlen <= maxlen); // don't talk nonsense - int64_t numread = 0; - char *bufptr = (char *)buf; - char *inbuf = u->gzin->inbuf; - size_t inbuflen = sizeof(u->gzin->inbuf); - unpacker::read_input_fn_t read_gzin_fn = (unpacker::read_input_fn_t)u->gzin->read_input_fn; - z_stream &zs = *(z_stream *)u->gzin->zstream; - while (numread < minlen) - { - int readlen = (1 << 16); // pretty arbitrary - if (readlen > (maxlen - numread)) - readlen = (int)(maxlen - numread); - zs.next_out = (uchar *)bufptr; - zs.avail_out = readlen; - if (zs.avail_in == 0) - { - zs.avail_in = (int)read_gzin_fn(u, inbuf, 1, inbuflen); - zs.next_in = (uchar *)inbuf; - } - int error = inflate(&zs, Z_NO_FLUSH); - if (error != Z_OK && error != Z_STREAM_END) - { - unpack_abort("error inflating input"); - break; - } - int nr = readlen - zs.avail_out; - numread += nr; - bufptr += nr; - assert(numread <= maxlen); - if (error == Z_STREAM_END) - { - enum - { - TRAILER_LEN = 8 - }; - // skip 8-byte trailer - if (zs.avail_in >= TRAILER_LEN) - { - zs.avail_in -= TRAILER_LEN; - } - else - { - // Bug: 5023768,we read past the TRAILER_LEN to see if there is - // any extraneous data, as we dont support concatenated .gz - // files just yet. - int extra = (int)read_gzin_fn(u, inbuf, 1, inbuflen); - zs.avail_in += extra - TRAILER_LEN; - } - // %%% should check final CRC and length here - // %%% should check for concatenated *.gz files here - if (zs.avail_in > 0) - unpack_abort("garbage after end of deflated input stream"); - // pop this filter off: - u->gzin->free(); - break; - } - } - - // fprintf(u->errstrm, "readInputFn(%d,%d) => %d (gunzip)\n", - // (int)minlen, (int)maxlen, (int)numread); - return numread; + assert(minlen <= maxlen); // don't talk nonsense + int64_t numread = 0; + char *bufptr = (char *)buf; + char *inbuf = u->gzin->inbuf; + size_t inbuflen = sizeof(u->gzin->inbuf); + read_input_fn_t read_gzin_fn = u->gzin->read_input_fn; + z_stream &zs = *(z_stream *)u->gzin->zstream; + while (numread < minlen) + { + int readlen = (1 << 16); // pretty arbitrary + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + zs.next_out = (uchar *)bufptr; + zs.avail_out = readlen; + if (zs.avail_in == 0) + { + zs.avail_in = (int)read_gzin_fn(u, inbuf, 1, inbuflen); + zs.next_in = (uchar *)inbuf; + } + int error = inflate(&zs, Z_NO_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + { + unpack_abort("error inflating input"); + break; + } + int nr = readlen - zs.avail_out; + numread += nr; + bufptr += nr; + assert(numread <= maxlen); + if (error == Z_STREAM_END) + { + enum + { + TRAILER_LEN = 8 + }; + // skip 8-byte trailer + if (zs.avail_in >= TRAILER_LEN) + { + zs.avail_in -= TRAILER_LEN; + } + else + { + // Bug: 5023768,we read past the TRAILER_LEN to see if there is + // any extraneous data, as we dont support concatenated .gz + // files just yet. + int extra = (int)read_gzin_fn(u, inbuf, 1, inbuflen); + zs.avail_in += extra - TRAILER_LEN; + } + // %%% should check final CRC and length here + // %%% should check for concatenated *.gz files here + if (zs.avail_in > 0) + unpack_abort("garbage after end of deflated input stream"); + // pop this filter off: + u->gzin->free(); + break; + } + } + + // fprintf(u->errstrm, "readInputFn(%d,%d) => %d (gunzip)\n", + // (int)minlen, (int)maxlen, (int)numread); + return numread; } void gunzip::init(unpacker *u_) { - BYTES_OF(*this).clear(); - u = u_; - assert(u->gzin == nullptr); // once only, please - read_input_fn = (void *)u->read_input_fn; - zstream = NEW(z_stream, 1); - u->gzin = this; - u->read_input_fn = read_input_via_gzip; + BYTES_OF(*this).clear(); + u = u_; + assert(u->gzin == nullptr); // once only, please + read_input_fn = u->read_input_fn; + zstream = NEW(z_stream, 1); + u->gzin = this; + u->read_input_fn = read_input_via_gzip; } void gunzip::start(int magic) { - assert((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC); - int gz_flg = (magic & 0xFF); // keep "flg", discard other 3 bytes - enum - { - FHCRC = (1 << 1), - FEXTRA = (1 << 2), - FNAME = (1 << 3), - FCOMMENT = (1 << 4) - }; - char gz_mtime[4]; - char gz_xfl[1]; - char gz_os[1]; - char gz_extra_len[2]; - char gz_hcrc[2]; - char gz_ignore; - // do not save extra, name, comment - read_fixed_field(gz_mtime, sizeof(gz_mtime)); - read_fixed_field(gz_xfl, sizeof(gz_xfl)); - read_fixed_field(gz_os, sizeof(gz_os)); - if (gz_flg & FEXTRA) - { - read_fixed_field(gz_extra_len, sizeof(gz_extra_len)); - int extra_len = gz_extra_len[0] & 0xFF; - extra_len += (gz_extra_len[1] & 0xFF) << 8; - for (; extra_len > 0; extra_len--) - { - read_fixed_field(&gz_ignore, 1); - } - } - int null_terms = 0; - if (gz_flg & FNAME) - null_terms++; - if (gz_flg & FCOMMENT) - null_terms++; - for (; null_terms; null_terms--) - { - for (;;) - { - gz_ignore = 0; - read_fixed_field(&gz_ignore, 1); - if (gz_ignore == 0) - break; - } - } - if (gz_flg & FHCRC) - read_fixed_field(gz_hcrc, sizeof(gz_hcrc)); - - // now the input stream is ready to read into the inflater - int error = inflateInit2((z_stream *)zstream, -MAX_WBITS); - if (error != Z_OK) - { - unpack_abort("cannot create input"); - } + assert((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC); + int gz_flg = (magic & 0xFF); // keep "flg", discard other 3 bytes + enum + { + FHCRC = (1 << 1), + FEXTRA = (1 << 2), + FNAME = (1 << 3), + FCOMMENT = (1 << 4) + }; + char gz_mtime[4]; + char gz_xfl[1]; + char gz_os[1]; + char gz_extra_len[2]; + char gz_hcrc[2]; + char gz_ignore; + // do not save extra, name, comment + read_fixed_field(gz_mtime, sizeof(gz_mtime)); + read_fixed_field(gz_xfl, sizeof(gz_xfl)); + read_fixed_field(gz_os, sizeof(gz_os)); + if (gz_flg & FEXTRA) + { + read_fixed_field(gz_extra_len, sizeof(gz_extra_len)); + int extra_len = gz_extra_len[0] & 0xFF; + extra_len += (gz_extra_len[1] & 0xFF) << 8; + for (; extra_len > 0; extra_len--) + { + read_fixed_field(&gz_ignore, 1); + } + } + int null_terms = 0; + if (gz_flg & FNAME) + null_terms++; + if (gz_flg & FCOMMENT) + null_terms++; + for (; null_terms; null_terms--) + { + for (;;) + { + gz_ignore = 0; + read_fixed_field(&gz_ignore, 1); + if (gz_ignore == 0) + break; + } + } + if (gz_flg & FHCRC) + read_fixed_field(gz_hcrc, sizeof(gz_hcrc)); + + // now the input stream is ready to read into the inflater + int error = inflateInit2((z_stream *)zstream, -MAX_WBITS); + if (error != Z_OK) + { + unpack_abort("cannot create input"); + } } void gunzip::free() { - assert(u->gzin == this); - u->gzin = nullptr; - u->read_input_fn = (unpacker::read_input_fn_t) this->read_input_fn; - inflateEnd((z_stream *)zstream); - ::free(zstream); - zstream = nullptr; - ::free(this); + assert(u->gzin == this); + u->gzin = nullptr; + u->read_input_fn = this->read_input_fn; + inflateEnd((z_stream *)zstream); + ::free(zstream); + zstream = nullptr; + ::free(this); } void gunzip::read_fixed_field(char *buf, size_t buflen) { - int64_t nr = ((unpacker::read_input_fn_t)read_input_fn)(u, buf, buflen, buflen); - if ((size_t)nr != buflen) - unpack_abort("short stream header"); + int64_t nr = read_input_fn(u, buf, buflen, buflen); + if ((size_t)nr != buflen) + unpack_abort("short stream header"); } diff --git a/libraries/pack200/src/zip.h b/libraries/pack200/src/zip.h index 67ec24da..ff973a86 100644 --- a/libraries/pack200/src/zip.h +++ b/libraries/pack200/src/zip.h @@ -22,89 +22,91 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +#pragma once + #include typedef unsigned short ushort; typedef unsigned int uint32_t; typedef unsigned char uchar; -struct unpacker; +#include "unpack.h" struct jar { - // JAR file writer - FILE *jarfp; - int default_modtime; - - // Used by unix2dostime: - int modtime_cache; - uint32_t dostime_cache; - - // Private members - fillbytes central_directory; - ushort central_directory_count; - uint32_t output_file_offset; - fillbytes deflated; // temporary buffer - - // pointer to outer unpacker, for error checks etc. - unpacker *u; - - // Public Methods - void openJarFile(const char *fname); - void addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head, - bytes &tail); - void addDirectoryToJarFile(const char *dir_name); - void closeJarFile(bool central); - - void init(unpacker *u_); - - void free() - { - central_directory.free(); - deflated.free(); - } - - void reset() - { - free(); - init(u); - } - - // Private Methods - void write_data(void *ptr, int len); - void write_data(bytes &b) - { - write_data(b.ptr, (int)b.len); - } - void add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen, - uint32_t crc); - void write_jar_header(const char *fname, bool store, int modtime, int len, int clen, - unsigned int crc); - void write_central_directory(); - uint32_t dostime(int y, int n, int d, int h, int m, int s); - uint32_t get_dostime(int modtime); - - // The definitions of these depend on the NO_ZLIB option: - bool deflate_bytes(bytes &head, bytes &tail); - static uint32_t get_crc32(uint32_t c, unsigned char *ptr, uint32_t len); + // JAR file writer + FILE *jarfp; + int default_modtime; + + // Used by unix2dostime: + int modtime_cache; + uint32_t dostime_cache; + + // Private members + fillbytes central_directory; + ushort central_directory_count; + uint32_t output_file_offset; + fillbytes deflated; // temporary buffer + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + // Public Methods + void openJarFile(const char *fname); + void addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head, + bytes &tail); + void addDirectoryToJarFile(const char *dir_name); + void closeJarFile(bool central); + + void init(unpacker *u_); + + void free() + { + central_directory.free(); + deflated.free(); + } + + void reset() + { + free(); + init(u); + } + + // Private Methods + void write_data(void *ptr, int len); + void write_data(bytes &b) + { + write_data(b.ptr, (int)b.len); + } + void add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen, + uint32_t crc); + void write_jar_header(const char *fname, bool store, int modtime, int len, int clen, + unsigned int crc); + void write_central_directory(); + uint32_t dostime(int y, int n, int d, int h, int m, int s); + uint32_t get_dostime(int modtime); + + // The definitions of these depend on the NO_ZLIB option: + bool deflate_bytes(bytes &head, bytes &tail); + static uint32_t get_crc32(uint32_t c, unsigned char *ptr, uint32_t len); }; struct gunzip { - // optional gzip input stream control block + // optional gzip input stream control block - // pointer to outer unpacker, for error checks etc. - unpacker *u; + // pointer to outer unpacker, for error checks etc. + unpacker *u; - void *read_input_fn; // underlying \bchar\b stream - void *zstream; // inflater state - char inbuf[1 << 14]; // input buffer + read_input_fn_t read_input_fn; // underlying \bchar\b stream + void *zstream; // inflater state + char inbuf[1 << 14]; // input buffer - void init(unpacker *u_); // pushes new value on u->read_input_fn + void init(unpacker *u_); // pushes new value on u->read_input_fn - void free(); + void free(); - void start(int magic); + void start(int magic); - // private stuff - void read_fixed_field(char *buf, size_t buflen); + // private stuff + void read_fixed_field(char *buf, size_t buflen); }; diff --git a/libraries/quazip b/libraries/quazip index 683e2ec8..3691d57d 160000 --- a/libraries/quazip +++ b/libraries/quazip @@ -1 +1 @@ -Subproject commit 683e2ec8ada758d6e48d31ec606840802e6941b7 +Subproject commit 3691d57d3af13f49b2be2b62accddefee3c26b9c diff --git a/libraries/rainbow/CMakeLists.txt b/libraries/rainbow/CMakeLists.txt index bc561800..ad806faa 100644 --- a/libraries/rainbow/CMakeLists.txt +++ b/libraries/rainbow/CMakeLists.txt @@ -12,11 +12,11 @@ add_definitions(-DRAINBOW_LIBRARY) add_library(MultiMC_rainbow SHARED ${RAINBOW_SOURCES}) target_include_directories(MultiMC_rainbow PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -qt5_use_modules(MultiMC_rainbow Core Gui) +target_link_libraries(MultiMC_rainbow Qt5::Core Qt5::Gui) # Install it install( - TARGETS MultiMC_rainbow - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} + TARGETS MultiMC_rainbow + RUNTIME DESTINATION ${LIBRARY_DEST_DIR} + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} ) diff --git a/libraries/rainbow/include/rainbow.h b/libraries/rainbow/include/rainbow.h index b12052b1..67c46300 100644 --- a/libraries/rainbow/include/rainbow.h +++ b/libraries/rainbow/include/rainbow.h @@ -50,7 +50,7 @@ RAINBOW_EXPORT qreal luma(const QColor &); * @since 5.0 */ RAINBOW_EXPORT void getHcy(const QColor &, qreal *hue, qreal *chroma, qreal *luma, - qreal *alpha = 0); + qreal *alpha = 0); /** * Calculate the contrast ratio between two colors, according to the @@ -132,7 +132,7 @@ RAINBOW_EXPORT QColor tint(const QColor &base, const QColor &color, qreal amount /** * Blend two colors into a new color by linear combination. * @code - QColor lighter = Rainbow::mix(myColor, Qt::white) + QColor lighter = Rainbow::mix(myColor, Qt::white) * @endcode * @param c1 first color. * @param c2 second color. @@ -146,9 +146,9 @@ RAINBOW_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias = 0.5); * Blend two colors into a new color by painting the second color over the * first using the specified composition mode. * @code - QColor white(Qt::white); - white.setAlphaF(0.5); - QColor lighter = Rainbow::overlayColors(myColor, white); + QColor white(Qt::white); + white.setAlphaF(0.5); + QColor lighter = Rainbow::overlayColors(myColor, white); @endcode * @param base the base color (alpha channel is ignored). * @param paint the color to be overlayed onto the base color. @@ -156,5 +156,5 @@ RAINBOW_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias = 0.5); */ RAINBOW_EXPORT QColor overlayColors(const QColor &base, const QColor &paint, - QPainter::CompositionMode comp = QPainter::CompositionMode_SourceOver); + QPainter::CompositionMode comp = QPainter::CompositionMode_SourceOver); } diff --git a/libraries/rainbow/include/rainbow_config.h b/libraries/rainbow/include/rainbow_config.h index 9290dc8a..6e467bc7 100644 --- a/libraries/rainbow/include/rainbow_config.h +++ b/libraries/rainbow/include/rainbow_config.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ #include #ifdef RAINBOW_STATIC - #define RAINBOW_EXPORT + #define RAINBOW_EXPORT #else - #ifdef RAINBOW_LIBRARY - #define RAINBOW_EXPORT Q_DECL_EXPORT - #else - #define RAINBOW_EXPORT Q_DECL_IMPORT - #endif + #ifdef RAINBOW_LIBRARY + #define RAINBOW_EXPORT Q_DECL_EXPORT + #else + #define RAINBOW_EXPORT Q_DECL_IMPORT + #endif #endif \ No newline at end of file diff --git a/libraries/rainbow/src/rainbow.cpp b/libraries/rainbow/src/rainbow.cpp index 8502fcd0..9054d71d 100644 --- a/libraries/rainbow/src/rainbow.cpp +++ b/libraries/rainbow/src/rainbow.cpp @@ -33,8 +33,8 @@ static inline qreal wrap(qreal a, qreal d = 1.0) { - qreal r = fmod(a, d); - return (r < 0.0 ? d + r : (r > 0.0 ? r : 0.0)); + qreal r = fmod(a, d); + return (r < 0.0 ? d + r : (r > 0.0 ? r : 0.0)); } // normalize: like qBound(a, 0.0, 1.0) but without needing the args and with @@ -60,306 +60,306 @@ static const qreal yc[3] = {0.34375, 0.5, 0.15625}; class KHCY { public: - explicit KHCY(const QColor &color) - { - qreal r = gamma(color.redF()); - qreal g = gamma(color.greenF()); - qreal b = gamma(color.blueF()); - a = color.alphaF(); - - // luma component - y = lumag(r, g, b); - - // hue component - qreal p = qMax(qMax(r, g), b); - qreal n = qMin(qMin(r, g), b); - qreal d = 6.0 * (p - n); - if (n == p) - { - h = 0.0; - } - else if (r == p) - { - h = ((g - b) / d); - } - else if (g == p) - { - h = ((b - r) / d) + (1.0 / 3.0); - } - else - { - h = ((r - g) / d) + (2.0 / 3.0); - } - - // chroma component - if (r == g && g == b) - { - c = 0.0; - } - else - { - c = qMax((y - n) / y, (p - y) / (1 - y)); - } - } - explicit KHCY(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0) - { - h = h_; - c = c_; - y = y_; - a = a_; - } - - QColor qColor() const - { - // start with sane component values - qreal _h = wrap(h); - qreal _c = normalize(c); - qreal _y = normalize(y); - - // calculate some needed variables - qreal _hs = _h * 6.0, th, tm; - if (_hs < 1.0) - { - th = _hs; - tm = yc[0] + yc[1] * th; - } - else if (_hs < 2.0) - { - th = 2.0 - _hs; - tm = yc[1] + yc[0] * th; - } - else if (_hs < 3.0) - { - th = _hs - 2.0; - tm = yc[1] + yc[2] * th; - } - else if (_hs < 4.0) - { - th = 4.0 - _hs; - tm = yc[2] + yc[1] * th; - } - else if (_hs < 5.0) - { - th = _hs - 4.0; - tm = yc[2] + yc[0] * th; - } - else - { - th = 6.0 - _hs; - tm = yc[0] + yc[2] * th; - } - - // calculate RGB channels in sorted order - qreal tn, to, tp; - if (tm >= _y) - { - tp = _y + _y * _c * (1.0 - tm) / tm; - to = _y + _y * _c * (th - tm) / tm; - tn = _y - (_y * _c); - } - else - { - tp = _y + (1.0 - _y) * _c; - to = _y + (1.0 - _y) * _c * (th - tm) / (1.0 - tm); - tn = _y - (1.0 - _y) * _c * tm / (1.0 - tm); - } - - // return RGB channels in appropriate order - if (_hs < 1.0) - { - return QColor::fromRgbF(igamma(tp), igamma(to), igamma(tn), a); - } - else if (_hs < 2.0) - { - return QColor::fromRgbF(igamma(to), igamma(tp), igamma(tn), a); - } - else if (_hs < 3.0) - { - return QColor::fromRgbF(igamma(tn), igamma(tp), igamma(to), a); - } - else if (_hs < 4.0) - { - return QColor::fromRgbF(igamma(tn), igamma(to), igamma(tp), a); - } - else if (_hs < 5.0) - { - return QColor::fromRgbF(igamma(to), igamma(tn), igamma(tp), a); - } - else - { - return QColor::fromRgbF(igamma(tp), igamma(tn), igamma(to), a); - } - } - - qreal h, c, y, a; - static qreal luma(const QColor &color) - { - return lumag(gamma(color.redF()), gamma(color.greenF()), gamma(color.blueF())); - } + explicit KHCY(const QColor &color) + { + qreal r = gamma(color.redF()); + qreal g = gamma(color.greenF()); + qreal b = gamma(color.blueF()); + a = color.alphaF(); + + // luma component + y = lumag(r, g, b); + + // hue component + qreal p = qMax(qMax(r, g), b); + qreal n = qMin(qMin(r, g), b); + qreal d = 6.0 * (p - n); + if (n == p) + { + h = 0.0; + } + else if (r == p) + { + h = ((g - b) / d); + } + else if (g == p) + { + h = ((b - r) / d) + (1.0 / 3.0); + } + else + { + h = ((r - g) / d) + (2.0 / 3.0); + } + + // chroma component + if (r == g && g == b) + { + c = 0.0; + } + else + { + c = qMax((y - n) / y, (p - y) / (1 - y)); + } + } + explicit KHCY(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0) + { + h = h_; + c = c_; + y = y_; + a = a_; + } + + QColor qColor() const + { + // start with sane component values + qreal _h = wrap(h); + qreal _c = normalize(c); + qreal _y = normalize(y); + + // calculate some needed variables + qreal _hs = _h * 6.0, th, tm; + if (_hs < 1.0) + { + th = _hs; + tm = yc[0] + yc[1] * th; + } + else if (_hs < 2.0) + { + th = 2.0 - _hs; + tm = yc[1] + yc[0] * th; + } + else if (_hs < 3.0) + { + th = _hs - 2.0; + tm = yc[1] + yc[2] * th; + } + else if (_hs < 4.0) + { + th = 4.0 - _hs; + tm = yc[2] + yc[1] * th; + } + else if (_hs < 5.0) + { + th = _hs - 4.0; + tm = yc[2] + yc[0] * th; + } + else + { + th = 6.0 - _hs; + tm = yc[0] + yc[2] * th; + } + + // calculate RGB channels in sorted order + qreal tn, to, tp; + if (tm >= _y) + { + tp = _y + _y * _c * (1.0 - tm) / tm; + to = _y + _y * _c * (th - tm) / tm; + tn = _y - (_y * _c); + } + else + { + tp = _y + (1.0 - _y) * _c; + to = _y + (1.0 - _y) * _c * (th - tm) / (1.0 - tm); + tn = _y - (1.0 - _y) * _c * tm / (1.0 - tm); + } + + // return RGB channels in appropriate order + if (_hs < 1.0) + { + return QColor::fromRgbF(igamma(tp), igamma(to), igamma(tn), a); + } + else if (_hs < 2.0) + { + return QColor::fromRgbF(igamma(to), igamma(tp), igamma(tn), a); + } + else if (_hs < 3.0) + { + return QColor::fromRgbF(igamma(tn), igamma(tp), igamma(to), a); + } + else if (_hs < 4.0) + { + return QColor::fromRgbF(igamma(tn), igamma(to), igamma(tp), a); + } + else if (_hs < 5.0) + { + return QColor::fromRgbF(igamma(to), igamma(tn), igamma(tp), a); + } + else + { + return QColor::fromRgbF(igamma(tp), igamma(tn), igamma(to), a); + } + } + + qreal h, c, y, a; + static qreal luma(const QColor &color) + { + return lumag(gamma(color.redF()), gamma(color.greenF()), gamma(color.blueF())); + } private: - static qreal gamma(qreal n) - { - return pow(normalize(n), 2.2); - } - static qreal igamma(qreal n) - { - return pow(normalize(n), 1.0 / 2.2); - } - static qreal lumag(qreal r, qreal g, qreal b) - { - return r * yc[0] + g * yc[1] + b * yc[2]; - } + static qreal gamma(qreal n) + { + return pow(normalize(n), 2.2); + } + static qreal igamma(qreal n) + { + return pow(normalize(n), 1.0 / 2.2); + } + static qreal lumag(qreal r, qreal g, qreal b) + { + return r * yc[0] + g * yc[1] + b * yc[2]; + } }; static inline qreal mixQreal(qreal a, qreal b, qreal bias) { - return a + (b - a) * bias; + return a + (b - a) * bias; } //END internal helper functions qreal Rainbow::luma(const QColor &color) { - return KHCY::luma(color); + return KHCY::luma(color); } void Rainbow::getHcy(const QColor &color, qreal *h, qreal *c, qreal *y, qreal *a) { - if (!c || !h || !y) - { - return; - } - KHCY khcy(color); - *c = khcy.c; - *h = khcy.h; - *y = khcy.y; - if (a) - { - *a = khcy.a; - } + if (!c || !h || !y) + { + return; + } + KHCY khcy(color); + *c = khcy.c; + *h = khcy.h; + *y = khcy.y; + if (a) + { + *a = khcy.a; + } } static qreal contrastRatioForLuma(qreal y1, qreal y2) { - if (y1 > y2) - { - return (y1 + 0.05) / (y2 + 0.05); - } - else - { - return (y2 + 0.05) / (y1 + 0.05); - } + if (y1 > y2) + { + return (y1 + 0.05) / (y2 + 0.05); + } + else + { + return (y2 + 0.05) / (y1 + 0.05); + } } qreal Rainbow::contrastRatio(const QColor &c1, const QColor &c2) { - return contrastRatioForLuma(luma(c1), luma(c2)); + return contrastRatioForLuma(luma(c1), luma(c2)); } QColor Rainbow::lighten(const QColor &color, qreal ky, qreal kc) { - KHCY c(color); - c.y = 1.0 - normalize((1.0 - c.y) * (1.0 - ky)); - c.c = 1.0 - normalize((1.0 - c.c) * kc); - return c.qColor(); + KHCY c(color); + c.y = 1.0 - normalize((1.0 - c.y) * (1.0 - ky)); + c.c = 1.0 - normalize((1.0 - c.c) * kc); + return c.qColor(); } QColor Rainbow::darken(const QColor &color, qreal ky, qreal kc) { - KHCY c(color); - c.y = normalize(c.y * (1.0 - ky)); - c.c = normalize(c.c * kc); - return c.qColor(); + KHCY c(color); + c.y = normalize(c.y * (1.0 - ky)); + c.c = normalize(c.c * kc); + return c.qColor(); } QColor Rainbow::shade(const QColor &color, qreal ky, qreal kc) { - KHCY c(color); - c.y = normalize(c.y + ky); - c.c = normalize(c.c + kc); - return c.qColor(); + KHCY c(color); + c.y = normalize(c.y + ky); + c.c = normalize(c.c + kc); + return c.qColor(); } static QColor tintHelper(const QColor &base, qreal baseLuma, const QColor &color, qreal amount) { - KHCY result(Rainbow::mix(base, color, pow(amount, 0.3))); - result.y = mixQreal(baseLuma, result.y, amount); + KHCY result(Rainbow::mix(base, color, pow(amount, 0.3))); + result.y = mixQreal(baseLuma, result.y, amount); - return result.qColor(); + return result.qColor(); } QColor Rainbow::tint(const QColor &base, const QColor &color, qreal amount) { - if (amount <= 0.0) - { - return base; - } - if (amount >= 1.0) - { - return color; - } - if (qIsNaN(amount)) - { - return base; - } - - qreal baseLuma = luma(base); // cache value because luma call is expensive - double ri = contrastRatioForLuma(baseLuma, luma(color)); - double rg = 1.0 + ((ri + 1.0) * amount * amount * amount); - double u = 1.0, l = 0.0; - QColor result; - for (int i = 12; i; --i) - { - double a = 0.5 * (l + u); - result = tintHelper(base, baseLuma, color, a); - double ra = contrastRatioForLuma(baseLuma, luma(result)); - if (ra > rg) - { - u = a; - } - else - { - l = a; - } - } - return result; + if (amount <= 0.0) + { + return base; + } + if (amount >= 1.0) + { + return color; + } + if (qIsNaN(amount)) + { + return base; + } + + qreal baseLuma = luma(base); // cache value because luma call is expensive + double ri = contrastRatioForLuma(baseLuma, luma(color)); + double rg = 1.0 + ((ri + 1.0) * amount * amount * amount); + double u = 1.0, l = 0.0; + QColor result; + for (int i = 12; i; --i) + { + double a = 0.5 * (l + u); + result = tintHelper(base, baseLuma, color, a); + double ra = contrastRatioForLuma(baseLuma, luma(result)); + if (ra > rg) + { + u = a; + } + else + { + l = a; + } + } + return result; } QColor Rainbow::mix(const QColor &c1, const QColor &c2, qreal bias) { - if (bias <= 0.0) - { - return c1; - } - if (bias >= 1.0) - { - return c2; - } - if (qIsNaN(bias)) - { - return c1; - } - - qreal r = mixQreal(c1.redF(), c2.redF(), bias); - qreal g = mixQreal(c1.greenF(), c2.greenF(), bias); - qreal b = mixQreal(c1.blueF(), c2.blueF(), bias); - qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias); - - return QColor::fromRgbF(r, g, b, a); + if (bias <= 0.0) + { + return c1; + } + if (bias >= 1.0) + { + return c2; + } + if (qIsNaN(bias)) + { + return c1; + } + + qreal r = mixQreal(c1.redF(), c2.redF(), bias); + qreal g = mixQreal(c1.greenF(), c2.greenF(), bias); + qreal b = mixQreal(c1.blueF(), c2.blueF(), bias); + qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias); + + return QColor::fromRgbF(r, g, b, a); } QColor Rainbow::overlayColors(const QColor &base, const QColor &paint, - QPainter::CompositionMode comp) + QPainter::CompositionMode comp) { - // This isn't the fastest way, but should be "fast enough". - // It's also the only safe way to use QPainter::CompositionMode - QImage img(1, 1, QImage::Format_ARGB32_Premultiplied); - QPainter p(&img); - QColor start = base; - start.setAlpha(255); // opaque - p.fillRect(0, 0, 1, 1, start); - p.setCompositionMode(comp); - p.fillRect(0, 0, 1, 1, paint); - p.end(); - return img.pixel(0, 0); + // This isn't the fastest way, but should be "fast enough". + // It's also the only safe way to use QPainter::CompositionMode + QImage img(1, 1, QImage::Format_ARGB32_Premultiplied); + QPainter p(&img); + QColor start = base; + start.setAlpha(255); // opaque + p.fillRect(0, 0, 1, 1, start); + p.setCompositionMode(comp); + p.fillRect(0, 0, 1, 1, paint); + p.end(); + return img.pixel(0, 0); } diff --git a/libraries/systeminfo/CMakeLists.txt b/libraries/systeminfo/CMakeLists.txt index 393b5318..548a589c 100644 --- a/libraries/systeminfo/CMakeLists.txt +++ b/libraries/systeminfo/CMakeLists.txt @@ -9,21 +9,21 @@ src/distroutils.cpp ) if (WIN32) - list(APPEND systeminfo_SOURCES src/sys_win32.cpp) + list(APPEND systeminfo_SOURCES src/sys_win32.cpp) elseif (UNIX) - if(APPLE) - list(APPEND systeminfo_SOURCES src/sys_apple.cpp) - else() - list(APPEND systeminfo_SOURCES src/sys_unix.cpp) - endif() + if(APPLE) + list(APPEND systeminfo_SOURCES src/sys_apple.cpp) + else() + list(APPEND systeminfo_SOURCES src/sys_unix.cpp) + endif() endif() add_library(systeminfo STATIC ${systeminfo_SOURCES}) -qt5_use_modules(systeminfo Core Gui Network) +target_link_libraries(systeminfo Qt5::Core Qt5::Gui Qt5::Network) target_include_directories(systeminfo PUBLIC include) include (UnitTest) add_unit_test(sys - SOURCES src/sys_test.cpp - LIBS systeminfo + SOURCES src/sys_test.cpp + LIBS systeminfo ) diff --git a/libraries/systeminfo/include/distroutils.h b/libraries/systeminfo/include/distroutils.h index 5ff8d591..2e85f433 100644 --- a/libraries/systeminfo/include/distroutils.h +++ b/libraries/systeminfo/include/distroutils.h @@ -4,10 +4,10 @@ namespace Sys { struct LsbInfo { - QString distributor; - QString version; - QString description; - QString codename; + QString distributor; + QString version; + QString description; + QString codename; }; bool main_lsb_info(LsbInfo & out); diff --git a/libraries/systeminfo/include/sys.h b/libraries/systeminfo/include/sys.h index c573eb53..7980dfdf 100644 --- a/libraries/systeminfo/include/sys.h +++ b/libraries/systeminfo/include/sys.h @@ -6,37 +6,37 @@ namespace Sys const uint64_t megabyte = 1024ull * 1024ull; struct KernelInfo { - QString kernelName; - QString kernelVersion; + QString kernelName; + QString kernelVersion; }; KernelInfo getKernelInfo(); struct DistributionInfo { - DistributionInfo operator+(const DistributionInfo& rhs) const - { - DistributionInfo out; - if(!distributionName.isEmpty()) - { - out.distributionName = distributionName; - } - else - { - out.distributionName = rhs.distributionName; - } - if(!distributionVersion.isEmpty()) - { - out.distributionVersion = distributionVersion; - } - else - { - out.distributionVersion = rhs.distributionVersion; - } - return out; - } - QString distributionName; - QString distributionVersion; + DistributionInfo operator+(const DistributionInfo& rhs) const + { + DistributionInfo out; + if(!distributionName.isEmpty()) + { + out.distributionName = distributionName; + } + else + { + out.distributionName = rhs.distributionName; + } + if(!distributionVersion.isEmpty()) + { + out.distributionVersion = distributionVersion; + } + else + { + out.distributionVersion = rhs.distributionVersion; + } + return out; + } + QString distributionName; + QString distributionVersion; }; DistributionInfo getDistributionInfo(); diff --git a/libraries/systeminfo/src/distroutils.cpp b/libraries/systeminfo/src/distroutils.cpp index cdba05d0..fb9ae25d 100644 --- a/libraries/systeminfo/src/distroutils.cpp +++ b/libraries/systeminfo/src/distroutils.cpp @@ -41,243 +41,243 @@ SOFTWARE. Sys::DistributionInfo Sys::read_os_release() { - Sys::DistributionInfo out; - QStringList files = { "/etc/os-release", "/usr/lib/os-release" }; - QString name; - QString version; - for (auto &file: files) - { - if(!QFile::exists(file)) - { - continue; - } - QSettings settings(file, QSettings::IniFormat); - if(settings.contains("ID")) - { - name = settings.value("ID").toString().toLower(); - } - else if (settings.contains("NAME")) - { - name = settings.value("NAME").toString().toLower(); - } - else - { - continue; - } + Sys::DistributionInfo out; + QStringList files = { "/etc/os-release", "/usr/lib/os-release" }; + QString name; + QString version; + for (auto &file: files) + { + if(!QFile::exists(file)) + { + continue; + } + QSettings settings(file, QSettings::IniFormat); + if(settings.contains("ID")) + { + name = settings.value("ID").toString().toLower(); + } + else if (settings.contains("NAME")) + { + name = settings.value("NAME").toString().toLower(); + } + else + { + continue; + } - if(settings.contains("VERSION_ID")) - { - version = settings.value("VERSION_ID").toString().toLower(); - } - else if(settings.contains("VERSION")) - { - version = settings.value("VERSION").toString().toLower(); - } - break; - } - if(name.isEmpty()) - { - return out; - } - out.distributionName = name; - out.distributionVersion = version; - return out; + if(settings.contains("VERSION_ID")) + { + version = settings.value("VERSION_ID").toString().toLower(); + } + else if(settings.contains("VERSION")) + { + version = settings.value("VERSION").toString().toLower(); + } + break; + } + if(name.isEmpty()) + { + return out; + } + out.distributionName = name; + out.distributionVersion = version; + return out; } bool Sys::main_lsb_info(Sys::LsbInfo & out) { - int status=0; - QProcess lsbProcess; - lsbProcess.start("lsb_release -a"); - lsbProcess.waitForFinished(); - status = lsbProcess.exitStatus(); - QString output = lsbProcess.readAllStandardOutput(); - qDebug() << output; - lsbProcess.close(); - if(status == 0) - { - auto lines = output.split('\n'); - for(auto line:lines) - { - int index = line.indexOf(':'); - auto key = line.left(index).trimmed(); - auto value = line.mid(index + 1).toLower().trimmed(); - if(key == "Distributor ID") - out.distributor = value; - else if(key == "Release") - out.version = value; - else if(key == "Description") - out.description = value; - else if(key == "Codename") - out.codename = value; - } - return !out.distributor.isEmpty(); - } - return false; + int status=0; + QProcess lsbProcess; + lsbProcess.start("lsb_release -a"); + lsbProcess.waitForFinished(); + status = lsbProcess.exitStatus(); + QString output = lsbProcess.readAllStandardOutput(); + qDebug() << output; + lsbProcess.close(); + if(status == 0) + { + auto lines = output.split('\n'); + for(auto line:lines) + { + int index = line.indexOf(':'); + auto key = line.left(index).trimmed(); + auto value = line.mid(index + 1).toLower().trimmed(); + if(key == "Distributor ID") + out.distributor = value; + else if(key == "Release") + out.version = value; + else if(key == "Description") + out.description = value; + else if(key == "Codename") + out.codename = value; + } + return !out.distributor.isEmpty(); + } + return false; } bool Sys::fallback_lsb_info(Sys::LsbInfo & out) { - // running lsb_release failed, try to read the file instead - // /etc/lsb-release format, if the file even exists, is non-standard. - // Only the `lsb_release` command is specified by LSB. Nonetheless, some - // distributions install an /etc/lsb-release as part of the base - // distribution, but `lsb_release` remains optional. - QString file = "/etc/lsb-release"; - if (QFile::exists(file)) - { - QSettings settings(file, QSettings::IniFormat); - if(settings.contains("DISTRIB_ID")) - { - out.distributor = settings.value("DISTRIB_ID").toString().toLower(); - } - if(settings.contains("DISTRIB_RELEASE")) - { - out.version = settings.value("DISTRIB_RELEASE").toString().toLower(); - } - return !out.distributor.isEmpty(); - } - return false; + // running lsb_release failed, try to read the file instead + // /etc/lsb-release format, if the file even exists, is non-standard. + // Only the `lsb_release` command is specified by LSB. Nonetheless, some + // distributions install an /etc/lsb-release as part of the base + // distribution, but `lsb_release` remains optional. + QString file = "/etc/lsb-release"; + if (QFile::exists(file)) + { + QSettings settings(file, QSettings::IniFormat); + if(settings.contains("DISTRIB_ID")) + { + out.distributor = settings.value("DISTRIB_ID").toString().toLower(); + } + if(settings.contains("DISTRIB_RELEASE")) + { + out.version = settings.value("DISTRIB_RELEASE").toString().toLower(); + } + return !out.distributor.isEmpty(); + } + return false; } void Sys::lsb_postprocess(Sys::LsbInfo & lsb, Sys::DistributionInfo & out) { - QString dist = lsb.distributor; - QString vers = lsb.version; - if(dist.startsWith("redhatenterprise")) - { - dist = "rhel"; - } - else if(dist == "archlinux") - { - dist = "arch"; - } - else if (dist.startsWith("suse")) - { - if(lsb.description.startsWith("opensuse")) - { - dist = "opensuse"; - } - else if (lsb.description.startsWith("suse linux enterprise")) - { - dist = "sles"; - } - } - else if (dist == "debian" and vers == "testing") - { - vers = lsb.codename; - } - else - { - // ubuntu, debian, gentoo, scientific, slackware, ... ? - auto parts = dist.split(QRegExp("\\s+"), QString::SkipEmptyParts); - if(parts.size()) - { - dist = parts[0]; - } - } - if(!dist.isEmpty()) - { - out.distributionName = dist; - out.distributionVersion = vers; - } + QString dist = lsb.distributor; + QString vers = lsb.version; + if(dist.startsWith("redhatenterprise")) + { + dist = "rhel"; + } + else if(dist == "archlinux") + { + dist = "arch"; + } + else if (dist.startsWith("suse")) + { + if(lsb.description.startsWith("opensuse")) + { + dist = "opensuse"; + } + else if (lsb.description.startsWith("suse linux enterprise")) + { + dist = "sles"; + } + } + else if (dist == "debian" and vers == "testing") + { + vers = lsb.codename; + } + else + { + // ubuntu, debian, gentoo, scientific, slackware, ... ? + auto parts = dist.split(QRegExp("\\s+"), QString::SkipEmptyParts); + if(parts.size()) + { + dist = parts[0]; + } + } + if(!dist.isEmpty()) + { + out.distributionName = dist; + out.distributionVersion = vers; + } } Sys::DistributionInfo Sys::read_lsb_release() { - LsbInfo lsb; - if(!main_lsb_info(lsb)) - { - if(!fallback_lsb_info(lsb)) - { - return Sys::DistributionInfo(); - } - } - Sys::DistributionInfo out; - lsb_postprocess(lsb, out); - return out; + LsbInfo lsb; + if(!main_lsb_info(lsb)) + { + if(!fallback_lsb_info(lsb)) + { + return Sys::DistributionInfo(); + } + } + Sys::DistributionInfo out; + lsb_postprocess(lsb, out); + return out; } QString Sys::_extract_distribution(const QString & x) { - QString release = x.toLower(); - if (release.startsWith("red hat enterprise")) - { - return "rhel"; - } - if (release.startsWith("suse linux enterprise")) - { - return "sles"; - } - QStringList list = release.split(QRegExp("\\s+"), QString::SkipEmptyParts); - if(list.size()) - { - return list[0]; - } - return QString(); + QString release = x.toLower(); + if (release.startsWith("red hat enterprise")) + { + return "rhel"; + } + if (release.startsWith("suse linux enterprise")) + { + return "sles"; + } + QStringList list = release.split(QRegExp("\\s+"), QString::SkipEmptyParts); + if(list.size()) + { + return list[0]; + } + return QString(); } QString Sys::_extract_version(const QString & x) { - QRegExp versionish_string("\\d+(?:\\.\\d+)*$"); - QStringList list = x.split(QRegExp("\\s+"), QString::SkipEmptyParts); - for(int i = list.size() - 1; i >= 0; --i) - { - QString chunk = list[i]; - if(versionish_string.exactMatch(chunk)) - { - return chunk; - } - } - return QString(); + QRegExp versionish_string("\\d+(?:\\.\\d+)*$"); + QStringList list = x.split(QRegExp("\\s+"), QString::SkipEmptyParts); + for(int i = list.size() - 1; i >= 0; --i) + { + QString chunk = list[i]; + if(versionish_string.exactMatch(chunk)) + { + return chunk; + } + } + return QString(); } Sys::DistributionInfo Sys::read_legacy_release() { - struct checkEntry - { - QString file; - std::function extract_distro; - std::function extract_version; - }; - QList checks = - { - {"/etc/arch-release", [](const QString &){ return "arch";}, [](const QString &){ return "rolling";}}, - {"/etc/slackware-version", &Sys::_extract_distribution, &Sys::_extract_version}, - {QString(), &Sys::_extract_distribution, &Sys::_extract_version}, - {"/etc/debian_version", [](const QString &){ return "debian";}, [](const QString & x){ return x;}}, - }; - for(auto & check: checks) - { - QStringList files; - if(check.file.isNull()) - { - QDir etcDir("/etc"); - etcDir.setNameFilters({"*-release"}); - etcDir.setFilter(QDir::Files | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden); - files = etcDir.entryList(); - } - else - { - files.append(check.file); - } - for (auto file : files) - { - QFile relfile(file); - if(!relfile.open(QIODevice::ReadOnly | QIODevice::Text)) - continue; - QString contents = QString::fromUtf8(relfile.readLine()).trimmed(); - QString dist = check.extract_distro(contents); - QString vers = check.extract_version(contents); - if(!dist.isEmpty()) - { - Sys::DistributionInfo out; - out.distributionName = dist; - out.distributionVersion = vers; - return out; - } - } - } - return Sys::DistributionInfo(); + struct checkEntry + { + QString file; + std::function extract_distro; + std::function extract_version; + }; + QList checks = + { + {"/etc/arch-release", [](const QString &){ return "arch";}, [](const QString &){ return "rolling";}}, + {"/etc/slackware-version", &Sys::_extract_distribution, &Sys::_extract_version}, + {QString(), &Sys::_extract_distribution, &Sys::_extract_version}, + {"/etc/debian_version", [](const QString &){ return "debian";}, [](const QString & x){ return x;}}, + }; + for(auto & check: checks) + { + QStringList files; + if(check.file.isNull()) + { + QDir etcDir("/etc"); + etcDir.setNameFilters({"*-release"}); + etcDir.setFilter(QDir::Files | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden); + files = etcDir.entryList(); + } + else + { + files.append(check.file); + } + for (auto file : files) + { + QFile relfile(file); + if(!relfile.open(QIODevice::ReadOnly | QIODevice::Text)) + continue; + QString contents = QString::fromUtf8(relfile.readLine()).trimmed(); + QString dist = check.extract_distro(contents); + QString vers = check.extract_version(contents); + if(!dist.isEmpty()) + { + Sys::DistributionInfo out; + out.distributionName = dist; + out.distributionVersion = vers; + return out; + } + } + } + return Sys::DistributionInfo(); } diff --git a/libraries/systeminfo/src/sys_apple.cpp b/libraries/systeminfo/src/sys_apple.cpp index 62e6037d..4bcffae4 100644 --- a/libraries/systeminfo/src/sys_apple.cpp +++ b/libraries/systeminfo/src/sys_apple.cpp @@ -4,44 +4,44 @@ Sys::KernelInfo Sys::getKernelInfo() { - Sys::KernelInfo out; - struct utsname buf; - uname(&buf); - out.kernelName = buf.sysname; - out.kernelVersion = buf.release; - return out; + Sys::KernelInfo out; + struct utsname buf; + uname(&buf); + out.kernelName = buf.sysname; + out.kernelVersion = buf.release; + return out; } #include uint64_t Sys::getSystemRam() { - uint64_t memsize; - size_t memsizesize = sizeof(memsize); - if(!sysctlbyname("hw.memsize", &memsize, &memsizesize, NULL, 0)) - { - return memsize; - } - else - { - return 0; - } + uint64_t memsize; + size_t memsizesize = sizeof(memsize); + if(!sysctlbyname("hw.memsize", &memsize, &memsizesize, NULL, 0)) + { + return memsize; + } + else + { + return 0; + } } bool Sys::isCPU64bit() { - // not even going to pretend I'm going to support anything else - return true; + // not even going to pretend I'm going to support anything else + return true; } bool Sys::isSystem64bit() { - // yep. maybe when we have 128bit CPUs on consumer devices. - return true; + // yep. maybe when we have 128bit CPUs on consumer devices. + return true; } Sys::DistributionInfo Sys::getDistributionInfo() { - DistributionInfo result; - return result; + DistributionInfo result; + return result; } diff --git a/libraries/systeminfo/src/sys_test.cpp b/libraries/systeminfo/src/sys_test.cpp index 5888e02b..315050d2 100644 --- a/libraries/systeminfo/src/sys_test.cpp +++ b/libraries/systeminfo/src/sys_test.cpp @@ -5,24 +5,24 @@ class SysTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_kernelNotNull() - { - auto kinfo = Sys::getKernelInfo(); - QVERIFY(!kinfo.kernelName.isEmpty()); - QVERIFY(kinfo.kernelVersion != "0.0"); - } + void test_kernelNotNull() + { + auto kinfo = Sys::getKernelInfo(); + QVERIFY(!kinfo.kernelName.isEmpty()); + QVERIFY(kinfo.kernelVersion != "0.0"); + } /* - void test_systemDistroNotNull() - { - auto kinfo = Sys::getDistributionInfo(); - QVERIFY(!kinfo.distributionName.isEmpty()); - QVERIFY(!kinfo.distributionVersion.isEmpty()); - qDebug() << "Distro: " << kinfo.distributionName << "version" << kinfo.distributionVersion; - } + void test_systemDistroNotNull() + { + auto kinfo = Sys::getDistributionInfo(); + QVERIFY(!kinfo.distributionName.isEmpty()); + QVERIFY(!kinfo.distributionVersion.isEmpty()); + qDebug() << "Distro: " << kinfo.distributionName << "version" << kinfo.distributionVersion; + } */ }; diff --git a/libraries/systeminfo/src/sys_unix.cpp b/libraries/systeminfo/src/sys_unix.cpp index 313908f3..ab3f302e 100644 --- a/libraries/systeminfo/src/sys_unix.cpp +++ b/libraries/systeminfo/src/sys_unix.cpp @@ -7,69 +7,69 @@ Sys::KernelInfo Sys::getKernelInfo() { - Sys::KernelInfo out; - struct utsname buf; - uname(&buf); - out.kernelName = buf.sysname; - out.kernelVersion = buf.release; - return out; + Sys::KernelInfo out; + struct utsname buf; + uname(&buf); + out.kernelName = buf.sysname; + out.kernelVersion = buf.release; + return out; } uint64_t Sys::getSystemRam() { - std::string token; - std::ifstream file("/proc/meminfo"); - while(file >> token) - { - if(token == "MemTotal:") - { - uint64_t mem; - if(file >> mem) - { - return mem * 1024ull; - } - else - { - return 0; - } - } - // ignore rest of the line - file.ignore(std::numeric_limits::max(), '\n'); - } - return 0; // nothing found + std::string token; + std::ifstream file("/proc/meminfo"); + while(file >> token) + { + if(token == "MemTotal:") + { + uint64_t mem; + if(file >> mem) + { + return mem * 1024ull; + } + else + { + return 0; + } + } + // ignore rest of the line + file.ignore(std::numeric_limits::max(), '\n'); + } + return 0; // nothing found } bool Sys::isCPU64bit() { - return isSystem64bit(); + return isSystem64bit(); } bool Sys::isSystem64bit() { - // kernel build arch on linux - return QSysInfo::currentCpuArchitecture() == "x86_64"; + // kernel build arch on linux + return QSysInfo::currentCpuArchitecture() == "x86_64"; } Sys::DistributionInfo Sys::getDistributionInfo() { - DistributionInfo systemd_info = read_os_release(); - DistributionInfo lsb_info = read_lsb_release(); - DistributionInfo legacy_info = read_legacy_release(); - DistributionInfo result = systemd_info + lsb_info + legacy_info; - if(result.distributionName.isNull()) - { - result.distributionName = "unknown"; - } - if(result.distributionVersion.isNull()) - { - if(result.distributionName == "arch") - { - result.distributionVersion = "rolling"; - } - else - { - result.distributionVersion = "unknown"; - } - } - return result; + DistributionInfo systemd_info = read_os_release(); + DistributionInfo lsb_info = read_lsb_release(); + DistributionInfo legacy_info = read_legacy_release(); + DistributionInfo result = systemd_info + lsb_info + legacy_info; + if(result.distributionName.isNull()) + { + result.distributionName = "unknown"; + } + if(result.distributionVersion.isNull()) + { + if(result.distributionName == "arch") + { + result.distributionVersion = "rolling"; + } + else + { + result.distributionVersion = "unknown"; + } + } + return result; } diff --git a/libraries/systeminfo/src/sys_win32.cpp b/libraries/systeminfo/src/sys_win32.cpp index cc1d61c1..a750b3a7 100644 --- a/libraries/systeminfo/src/sys_win32.cpp +++ b/libraries/systeminfo/src/sys_win32.cpp @@ -4,49 +4,49 @@ Sys::KernelInfo Sys::getKernelInfo() { - Sys::KernelInfo out; - out.kernelName = "Windows"; - OSVERSIONINFOW osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFOW)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); - GetVersionExW(&osvi); - out.kernelVersion = QString("%1.%2").arg(osvi.dwMajorVersion).arg(osvi.dwMinorVersion); - return out; + Sys::KernelInfo out; + out.kernelName = "Windows"; + OSVERSIONINFOW osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOW)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); + GetVersionExW(&osvi); + out.kernelVersion = QString("%1.%2").arg(osvi.dwMajorVersion).arg(osvi.dwMinorVersion); + return out; } uint64_t Sys::getSystemRam() { - MEMORYSTATUSEX status; - status.dwLength = sizeof(status); - GlobalMemoryStatusEx( &status ); - // bytes - return (uint64_t)status.ullTotalPhys; + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx( &status ); + // bytes + return (uint64_t)status.ullTotalPhys; } bool Sys::isSystem64bit() { #if defined(_WIN64) - return true; + return true; #elif defined(_WIN32) - BOOL f64 = false; - return IsWow64Process(GetCurrentProcess(), &f64) && f64; + BOOL f64 = false; + return IsWow64Process(GetCurrentProcess(), &f64) && f64; #else - // it's some other kind of system... - return false; + // it's some other kind of system... + return false; #endif } bool Sys::isCPU64bit() { - SYSTEM_INFO info; - ZeroMemory(&info, sizeof(SYSTEM_INFO)); - GetNativeSystemInfo(&info); - auto arch = info.wProcessorArchitecture; - return arch == PROCESSOR_ARCHITECTURE_AMD64 || arch == PROCESSOR_ARCHITECTURE_IA64; + SYSTEM_INFO info; + ZeroMemory(&info, sizeof(SYSTEM_INFO)); + GetNativeSystemInfo(&info); + auto arch = info.wProcessorArchitecture; + return arch == PROCESSOR_ARCHITECTURE_AMD64 || arch == PROCESSOR_ARCHITECTURE_IA64; } Sys::DistributionInfo Sys::getDistributionInfo() { - DistributionInfo result; - return result; + DistributionInfo result; + return result; } diff --git a/libraries/xz-embedded/CMakeLists.txt b/libraries/xz-embedded/CMakeLists.txt index 5f744671..86ac60c8 100644 --- a/libraries/xz-embedded/CMakeLists.txt +++ b/libraries/xz-embedded/CMakeLists.txt @@ -9,18 +9,18 @@ option(XZ_BUILD_MINIDEC "Build a tiny utility that decompresses xz streams" OFF) # tweak this list and xz.h to fit your needs set(XZ_SOURCES - src/xz_crc32.c - src/xz_crc64.c - src/xz_dec_lzma2.c - src/xz_dec_stream.c -# src/xz_dec_bcj.c + src/xz_crc32.c + src/xz_crc64.c + src/xz_dec_lzma2.c + src/xz_dec_stream.c +# src/xz_dec_bcj.c ) add_library(xz-embedded STATIC ${XZ_SOURCES}) target_include_directories(xz-embedded PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") set_property(TARGET xz-embedded PROPERTY C_STANDARD 99) if(${XZ_BUILD_MINIDEC}) - add_executable(xzminidec xzminidec.c) - target_link_libraries(xzminidec xz-embedded) - set_property(TARGET xzminidec PROPERTY C_STANDARD 99) + add_executable(xzminidec xzminidec.c) + target_link_libraries(xzminidec xz-embedded) + set_property(TARGET xzminidec PROPERTY C_STANDARD 99) endif() diff --git a/libraries/xz-embedded/include/xz.h b/libraries/xz-embedded/include/xz.h index eef8ef69..3779124c 100644 --- a/libraries/xz-embedded/include/xz.h +++ b/libraries/xz-embedded/include/xz.h @@ -69,9 +69,9 @@ extern "C" { */ enum xz_mode { - XZ_SINGLE, - XZ_PREALLOC, - XZ_DYNALLOC + XZ_SINGLE, + XZ_PREALLOC, + XZ_DYNALLOC }; /** @@ -126,15 +126,15 @@ enum xz_mode */ enum xz_ret { - XZ_OK, - XZ_STREAM_END, - XZ_UNSUPPORTED_CHECK, - XZ_MEM_ERROR, - XZ_MEMLIMIT_ERROR, - XZ_FORMAT_ERROR, - XZ_OPTIONS_ERROR, - XZ_DATA_ERROR, - XZ_BUF_ERROR + XZ_OK, + XZ_STREAM_END, + XZ_UNSUPPORTED_CHECK, + XZ_MEM_ERROR, + XZ_MEMLIMIT_ERROR, + XZ_FORMAT_ERROR, + XZ_OPTIONS_ERROR, + XZ_DATA_ERROR, + XZ_BUF_ERROR }; /** @@ -155,13 +155,13 @@ enum xz_ret */ struct xz_buf { - const uint8_t *in; - size_t in_pos; - size_t in_size; + const uint8_t *in; + size_t in_pos; + size_t in_size; - uint8_t *out; - size_t out_pos; - size_t out_size; + uint8_t *out; + size_t out_pos; + size_t out_size; }; /** diff --git a/libraries/xz-embedded/src/xz_config.h b/libraries/xz-embedded/src/xz_config.h index 40805b75..effdb1bd 100644 --- a/libraries/xz-embedded/src/xz_config.h +++ b/libraries/xz-embedded/src/xz_config.h @@ -74,36 +74,36 @@ typedef unsigned char bool; #ifndef get_unaligned_le32 static inline uint32_t get_unaligned_le32(const uint8_t *buf) { - return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2] << 16) | - ((uint32_t)buf[3] << 24); + return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2] << 16) | + ((uint32_t)buf[3] << 24); } #endif #ifndef get_unaligned_be32 static inline uint32_t get_unaligned_be32(const uint8_t *buf) { - return (uint32_t)(buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | - (uint32_t)buf[3]; + return (uint32_t)(buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | + (uint32_t)buf[3]; } #endif #ifndef put_unaligned_le32 static inline void put_unaligned_le32(uint32_t val, uint8_t *buf) { - buf[0] = (uint8_t)val; - buf[1] = (uint8_t)(val >> 8); - buf[2] = (uint8_t)(val >> 16); - buf[3] = (uint8_t)(val >> 24); + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); + buf[2] = (uint8_t)(val >> 16); + buf[3] = (uint8_t)(val >> 24); } #endif #ifndef put_unaligned_be32 static inline void put_unaligned_be32(uint32_t val, uint8_t *buf) { - buf[0] = (uint8_t)(val >> 24); - buf[1] = (uint8_t)(val >> 16); - buf[2] = (uint8_t)(val >> 8); - buf[3] = (uint8_t)val; + buf[0] = (uint8_t)(val >> 24); + buf[1] = (uint8_t)(val >> 16); + buf[2] = (uint8_t)(val >> 8); + buf[3] = (uint8_t)val; } #endif diff --git a/libraries/xz-embedded/src/xz_crc32.c b/libraries/xz-embedded/src/xz_crc32.c index c412662b..65d9d5b8 100644 --- a/libraries/xz-embedded/src/xz_crc32.c +++ b/libraries/xz-embedded/src/xz_crc32.c @@ -29,33 +29,33 @@ STATIC_RW_DATA uint32_t xz_crc32_table[256]; XZ_EXTERN void xz_crc32_init(void) { - const uint32_t poly = 0xEDB88320; + const uint32_t poly = 0xEDB88320; - uint32_t i; - uint32_t j; - uint32_t r; + uint32_t i; + uint32_t j; + uint32_t r; - for (i = 0; i < 256; ++i) - { - r = i; - for (j = 0; j < 8; ++j) - r = (r >> 1) ^ (poly & ~((r & 1) - 1)); + for (i = 0; i < 256; ++i) + { + r = i; + for (j = 0; j < 8; ++j) + r = (r >> 1) ^ (poly & ~((r & 1) - 1)); - xz_crc32_table[i] = r; - } + xz_crc32_table[i] = r; + } - return; + return; } XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc) { - crc = ~crc; + crc = ~crc; - while (size != 0) - { - crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); - --size; - } + while (size != 0) + { + crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); + --size; + } - return ~crc; + return ~crc; } diff --git a/libraries/xz-embedded/src/xz_crc64.c b/libraries/xz-embedded/src/xz_crc64.c index 4794b9d3..0f711d8d 100644 --- a/libraries/xz-embedded/src/xz_crc64.c +++ b/libraries/xz-embedded/src/xz_crc64.c @@ -20,33 +20,33 @@ STATIC_RW_DATA uint64_t xz_crc64_table[256]; XZ_EXTERN void xz_crc64_init(void) { - const uint64_t poly = 0xC96C5795D7870F42; + const uint64_t poly = 0xC96C5795D7870F42; - uint32_t i; - uint32_t j; - uint64_t r; + uint32_t i; + uint32_t j; + uint64_t r; - for (i = 0; i < 256; ++i) - { - r = i; - for (j = 0; j < 8; ++j) - r = (r >> 1) ^ (poly & ~((r & 1) - 1)); + for (i = 0; i < 256; ++i) + { + r = i; + for (j = 0; j < 8; ++j) + r = (r >> 1) ^ (poly & ~((r & 1) - 1)); - xz_crc64_table[i] = r; - } + xz_crc64_table[i] = r; + } - return; + return; } XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc) { - crc = ~crc; + crc = ~crc; - while (size != 0) - { - crc = xz_crc64_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); - --size; - } + while (size != 0) + { + crc = xz_crc64_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); + --size; + } - return ~crc; + return ~crc; } diff --git a/libraries/xz-embedded/src/xz_dec_bcj.c b/libraries/xz-embedded/src/xz_dec_bcj.c index 9ffda3bd..a79fa76d 100644 --- a/libraries/xz-embedded/src/xz_dec_bcj.c +++ b/libraries/xz-embedded/src/xz_dec_bcj.c @@ -18,64 +18,64 @@ struct xz_dec_bcj { - /* Type of the BCJ filter being used */ - enum - { - BCJ_X86 = 4, /* x86 or x86-64 */ - BCJ_POWERPC = 5, /* Big endian only */ - BCJ_IA64 = 6, /* Big or little endian */ - BCJ_ARM = 7, /* Little endian only */ - BCJ_ARMTHUMB = 8, /* Little endian only */ - BCJ_SPARC = 9 /* Big or little endian */ - } type; - - /* - * Return value of the next filter in the chain. We need to preserve - * this information across calls, because we must not call the next - * filter anymore once it has returned XZ_STREAM_END. - */ - enum xz_ret ret; - - /* True if we are operating in single-call mode. */ - bool single_call; - - /* - * Absolute position relative to the beginning of the uncompressed - * data (in a single .xz Block). We care only about the lowest 32 - * bits so this doesn't need to be uint64_t even with big files. - */ - uint32_t pos; - - /* x86 filter state */ - uint32_t x86_prev_mask; - - /* Temporary space to hold the variables from struct xz_buf */ - uint8_t *out; - size_t out_pos; - size_t out_size; - - struct - { - /* Amount of already filtered data in the beginning of buf */ - size_t filtered; - - /* Total amount of data currently stored in buf */ - size_t size; - - /* - * Buffer to hold a mix of filtered and unfiltered data. This - * needs to be big enough to hold Alignment + 2 * Look-ahead: - * - * Type Alignment Look-ahead - * x86 1 4 - * PowerPC 4 0 - * IA-64 16 0 - * ARM 4 0 - * ARM-Thumb 2 2 - * SPARC 4 0 - */ - uint8_t buf[16]; - } temp; + /* Type of the BCJ filter being used */ + enum + { + BCJ_X86 = 4, /* x86 or x86-64 */ + BCJ_POWERPC = 5, /* Big endian only */ + BCJ_IA64 = 6, /* Big or little endian */ + BCJ_ARM = 7, /* Little endian only */ + BCJ_ARMTHUMB = 8, /* Little endian only */ + BCJ_SPARC = 9 /* Big or little endian */ + } type; + + /* + * Return value of the next filter in the chain. We need to preserve + * this information across calls, because we must not call the next + * filter anymore once it has returned XZ_STREAM_END. + */ + enum xz_ret ret; + + /* True if we are operating in single-call mode. */ + bool single_call; + + /* + * Absolute position relative to the beginning of the uncompressed + * data (in a single .xz Block). We care only about the lowest 32 + * bits so this doesn't need to be uint64_t even with big files. + */ + uint32_t pos; + + /* x86 filter state */ + uint32_t x86_prev_mask; + + /* Temporary space to hold the variables from struct xz_buf */ + uint8_t *out; + size_t out_pos; + size_t out_size; + + struct + { + /* Amount of already filtered data in the beginning of buf */ + size_t filtered; + + /* Total amount of data currently stored in buf */ + size_t size; + + /* + * Buffer to hold a mix of filtered and unfiltered data. This + * needs to be big enough to hold Alignment + 2 * Look-ahead: + * + * Type Alignment Look-ahead + * x86 1 4 + * PowerPC 4 0 + * IA-64 16 0 + * ARM 4 0 + * ARM-Thumb 2 2 + * SPARC 4 0 + */ + uint8_t buf[16]; + } temp; }; #ifdef XZ_DEC_X86 @@ -85,264 +85,264 @@ struct xz_dec_bcj */ static inline int bcj_x86_test_msbyte(uint8_t b) { - return b == 0x00 || b == 0xFF; + return b == 0x00 || b == 0xFF; } static size_t bcj_x86(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - static const bool mask_to_allowed_status[8] = {true, true, true, false, - true, false, false, false}; - - static const uint8_t mask_to_bit_num[8] = {0, 1, 2, 2, 3, 3, 3, 3}; - - size_t i; - size_t prev_pos = (size_t) - 1; - uint32_t prev_mask = s->x86_prev_mask; - uint32_t src; - uint32_t dest; - uint32_t j; - uint8_t b; - - if (size <= 4) - return 0; - - size -= 4; - for (i = 0; i < size; ++i) - { - if ((buf[i] & 0xFE) != 0xE8) - continue; - - prev_pos = i - prev_pos; - if (prev_pos > 3) - { - prev_mask = 0; - } - else - { - prev_mask = (prev_mask << (prev_pos - 1)) & 7; - if (prev_mask != 0) - { - b = buf[i + 4 - mask_to_bit_num[prev_mask]]; - if (!mask_to_allowed_status[prev_mask] || bcj_x86_test_msbyte(b)) - { - prev_pos = i; - prev_mask = (prev_mask << 1) | 1; - continue; - } - } - } - - prev_pos = i; - - if (bcj_x86_test_msbyte(buf[i + 4])) - { - src = get_unaligned_le32(buf + i + 1); - while (true) - { - dest = src - (s->pos + (uint32_t)i + 5); - if (prev_mask == 0) - break; - - j = mask_to_bit_num[prev_mask] * 8; - b = (uint8_t)(dest >> (24 - j)); - if (!bcj_x86_test_msbyte(b)) - break; - - src = dest ^ (((uint32_t)1 << (32 - j)) - 1); - } - - dest &= 0x01FFFFFF; - dest |= (uint32_t)0 - (dest & 0x01000000); - put_unaligned_le32(dest, buf + i + 1); - i += 4; - } - else - { - prev_mask = (prev_mask << 1) | 1; - } - } - - prev_pos = i - prev_pos; - s->x86_prev_mask = prev_pos > 3 ? 0 : prev_mask << (prev_pos - 1); - return i; + static const bool mask_to_allowed_status[8] = {true, true, true, false, + true, false, false, false}; + + static const uint8_t mask_to_bit_num[8] = {0, 1, 2, 2, 3, 3, 3, 3}; + + size_t i; + size_t prev_pos = (size_t) - 1; + uint32_t prev_mask = s->x86_prev_mask; + uint32_t src; + uint32_t dest; + uint32_t j; + uint8_t b; + + if (size <= 4) + return 0; + + size -= 4; + for (i = 0; i < size; ++i) + { + if ((buf[i] & 0xFE) != 0xE8) + continue; + + prev_pos = i - prev_pos; + if (prev_pos > 3) + { + prev_mask = 0; + } + else + { + prev_mask = (prev_mask << (prev_pos - 1)) & 7; + if (prev_mask != 0) + { + b = buf[i + 4 - mask_to_bit_num[prev_mask]]; + if (!mask_to_allowed_status[prev_mask] || bcj_x86_test_msbyte(b)) + { + prev_pos = i; + prev_mask = (prev_mask << 1) | 1; + continue; + } + } + } + + prev_pos = i; + + if (bcj_x86_test_msbyte(buf[i + 4])) + { + src = get_unaligned_le32(buf + i + 1); + while (true) + { + dest = src - (s->pos + (uint32_t)i + 5); + if (prev_mask == 0) + break; + + j = mask_to_bit_num[prev_mask] * 8; + b = (uint8_t)(dest >> (24 - j)); + if (!bcj_x86_test_msbyte(b)) + break; + + src = dest ^ (((uint32_t)1 << (32 - j)) - 1); + } + + dest &= 0x01FFFFFF; + dest |= (uint32_t)0 - (dest & 0x01000000); + put_unaligned_le32(dest, buf + i + 1); + i += 4; + } + else + { + prev_mask = (prev_mask << 1) | 1; + } + } + + prev_pos = i - prev_pos; + s->x86_prev_mask = prev_pos > 3 ? 0 : prev_mask << (prev_pos - 1); + return i; } #endif #ifdef XZ_DEC_POWERPC static size_t bcj_powerpc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t instr; - - for (i = 0; i + 4 <= size; i += 4) - { - instr = get_unaligned_be32(buf + i); - if ((instr & 0xFC000003) == 0x48000001) - { - instr &= 0x03FFFFFC; - instr -= s->pos + (uint32_t)i; - instr &= 0x03FFFFFC; - instr |= 0x48000001; - put_unaligned_be32(instr, buf + i); - } - } - - return i; + size_t i; + uint32_t instr; + + for (i = 0; i + 4 <= size; i += 4) + { + instr = get_unaligned_be32(buf + i); + if ((instr & 0xFC000003) == 0x48000001) + { + instr &= 0x03FFFFFC; + instr -= s->pos + (uint32_t)i; + instr &= 0x03FFFFFC; + instr |= 0x48000001; + put_unaligned_be32(instr, buf + i); + } + } + + return i; } #endif #ifdef XZ_DEC_IA64 static size_t bcj_ia64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - static const uint8_t branch_table[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0}; - - /* - * The local variables take a little bit stack space, but it's less - * than what LZMA2 decoder takes, so it doesn't make sense to reduce - * stack usage here without doing that for the LZMA2 decoder too. - */ - - /* Loop counters */ - size_t i; - size_t j; - - /* Instruction slot (0, 1, or 2) in the 128-bit instruction word */ - uint32_t slot; - - /* Bitwise offset of the instruction indicated by slot */ - uint32_t bit_pos; - - /* bit_pos split into byte and bit parts */ - uint32_t byte_pos; - uint32_t bit_res; - - /* Address part of an instruction */ - uint32_t addr; - - /* Mask used to detect which instructions to convert */ - uint32_t mask; - - /* 41-bit instruction stored somewhere in the lowest 48 bits */ - uint64_t instr; - - /* Instruction normalized with bit_res for easier manipulation */ - uint64_t norm; - - for (i = 0; i + 16 <= size; i += 16) - { - mask = branch_table[buf[i] & 0x1F]; - for (slot = 0, bit_pos = 5; slot < 3; ++slot, bit_pos += 41) - { - if (((mask >> slot) & 1) == 0) - continue; - - byte_pos = bit_pos >> 3; - bit_res = bit_pos & 7; - instr = 0; - for (j = 0; j < 6; ++j) - instr |= (uint64_t)(buf[i + j + byte_pos]) << (8 * j); - - norm = instr >> bit_res; - - if (((norm >> 37) & 0x0F) == 0x05 && ((norm >> 9) & 0x07) == 0) - { - addr = (norm >> 13) & 0x0FFFFF; - addr |= ((uint32_t)(norm >> 36) & 1) << 20; - addr <<= 4; - addr -= s->pos + (uint32_t)i; - addr >>= 4; - - norm &= ~((uint64_t)0x8FFFFF << 13); - norm |= (uint64_t)(addr & 0x0FFFFF) << 13; - norm |= (uint64_t)(addr & 0x100000) << (36 - 20); - - instr &= (1 << bit_res) - 1; - instr |= norm << bit_res; - - for (j = 0; j < 6; j++) - buf[i + j + byte_pos] = (uint8_t)(instr >> (8 * j)); - } - } - } - - return i; + static const uint8_t branch_table[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0}; + + /* + * The local variables take a little bit stack space, but it's less + * than what LZMA2 decoder takes, so it doesn't make sense to reduce + * stack usage here without doing that for the LZMA2 decoder too. + */ + + /* Loop counters */ + size_t i; + size_t j; + + /* Instruction slot (0, 1, or 2) in the 128-bit instruction word */ + uint32_t slot; + + /* Bitwise offset of the instruction indicated by slot */ + uint32_t bit_pos; + + /* bit_pos split into byte and bit parts */ + uint32_t byte_pos; + uint32_t bit_res; + + /* Address part of an instruction */ + uint32_t addr; + + /* Mask used to detect which instructions to convert */ + uint32_t mask; + + /* 41-bit instruction stored somewhere in the lowest 48 bits */ + uint64_t instr; + + /* Instruction normalized with bit_res for easier manipulation */ + uint64_t norm; + + for (i = 0; i + 16 <= size; i += 16) + { + mask = branch_table[buf[i] & 0x1F]; + for (slot = 0, bit_pos = 5; slot < 3; ++slot, bit_pos += 41) + { + if (((mask >> slot) & 1) == 0) + continue; + + byte_pos = bit_pos >> 3; + bit_res = bit_pos & 7; + instr = 0; + for (j = 0; j < 6; ++j) + instr |= (uint64_t)(buf[i + j + byte_pos]) << (8 * j); + + norm = instr >> bit_res; + + if (((norm >> 37) & 0x0F) == 0x05 && ((norm >> 9) & 0x07) == 0) + { + addr = (norm >> 13) & 0x0FFFFF; + addr |= ((uint32_t)(norm >> 36) & 1) << 20; + addr <<= 4; + addr -= s->pos + (uint32_t)i; + addr >>= 4; + + norm &= ~((uint64_t)0x8FFFFF << 13); + norm |= (uint64_t)(addr & 0x0FFFFF) << 13; + norm |= (uint64_t)(addr & 0x100000) << (36 - 20); + + instr &= (1 << bit_res) - 1; + instr |= norm << bit_res; + + for (j = 0; j < 6; j++) + buf[i + j + byte_pos] = (uint8_t)(instr >> (8 * j)); + } + } + } + + return i; } #endif #ifdef XZ_DEC_ARM static size_t bcj_arm(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t addr; - - for (i = 0; i + 4 <= size; i += 4) - { - if (buf[i + 3] == 0xEB) - { - addr = - (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) | ((uint32_t)buf[i + 2] << 16); - addr <<= 2; - addr -= s->pos + (uint32_t)i + 8; - addr >>= 2; - buf[i] = (uint8_t)addr; - buf[i + 1] = (uint8_t)(addr >> 8); - buf[i + 2] = (uint8_t)(addr >> 16); - } - } - - return i; + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 4) + { + if (buf[i + 3] == 0xEB) + { + addr = + (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) | ((uint32_t)buf[i + 2] << 16); + addr <<= 2; + addr -= s->pos + (uint32_t)i + 8; + addr >>= 2; + buf[i] = (uint8_t)addr; + buf[i + 1] = (uint8_t)(addr >> 8); + buf[i + 2] = (uint8_t)(addr >> 16); + } + } + + return i; } #endif #ifdef XZ_DEC_ARMTHUMB static size_t bcj_armthumb(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t addr; - - for (i = 0; i + 4 <= size; i += 2) - { - if ((buf[i + 1] & 0xF8) == 0xF0 && (buf[i + 3] & 0xF8) == 0xF8) - { - addr = (((uint32_t)buf[i + 1] & 0x07) << 19) | ((uint32_t)buf[i] << 11) | - (((uint32_t)buf[i + 3] & 0x07) << 8) | (uint32_t)buf[i + 2]; - addr <<= 1; - addr -= s->pos + (uint32_t)i + 4; - addr >>= 1; - buf[i + 1] = (uint8_t)(0xF0 | ((addr >> 19) & 0x07)); - buf[i] = (uint8_t)(addr >> 11); - buf[i + 3] = (uint8_t)(0xF8 | ((addr >> 8) & 0x07)); - buf[i + 2] = (uint8_t)addr; - i += 2; - } - } - - return i; + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 2) + { + if ((buf[i + 1] & 0xF8) == 0xF0 && (buf[i + 3] & 0xF8) == 0xF8) + { + addr = (((uint32_t)buf[i + 1] & 0x07) << 19) | ((uint32_t)buf[i] << 11) | + (((uint32_t)buf[i + 3] & 0x07) << 8) | (uint32_t)buf[i + 2]; + addr <<= 1; + addr -= s->pos + (uint32_t)i + 4; + addr >>= 1; + buf[i + 1] = (uint8_t)(0xF0 | ((addr >> 19) & 0x07)); + buf[i] = (uint8_t)(addr >> 11); + buf[i + 3] = (uint8_t)(0xF8 | ((addr >> 8) & 0x07)); + buf[i + 2] = (uint8_t)addr; + i += 2; + } + } + + return i; } #endif #ifdef XZ_DEC_SPARC static size_t bcj_sparc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t instr; - - for (i = 0; i + 4 <= size; i += 4) - { - instr = get_unaligned_be32(buf + i); - if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) - { - instr <<= 2; - instr -= s->pos + (uint32_t)i; - instr >>= 2; - instr = - ((uint32_t)0x40000000 - (instr & 0x400000)) | 0x40000000 | (instr & 0x3FFFFF); - put_unaligned_be32(instr, buf + i); - } - } - - return i; + size_t i; + uint32_t instr; + + for (i = 0; i + 4 <= size; i += 4) + { + instr = get_unaligned_be32(buf + i); + if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) + { + instr <<= 2; + instr -= s->pos + (uint32_t)i; + instr >>= 2; + instr = + ((uint32_t)0x40000000 - (instr & 0x400000)) | 0x40000000 | (instr & 0x3FFFFF); + put_unaligned_be32(instr, buf + i); + } + } + + return i; } #endif @@ -356,51 +356,51 @@ static size_t bcj_sparc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) */ static void bcj_apply(struct xz_dec_bcj *s, uint8_t *buf, size_t *pos, size_t size) { - size_t filtered; + size_t filtered; - buf += *pos; - size -= *pos; + buf += *pos; + size -= *pos; - switch (s->type) - { + switch (s->type) + { #ifdef XZ_DEC_X86 - case BCJ_X86: - filtered = bcj_x86(s, buf, size); - break; + case BCJ_X86: + filtered = bcj_x86(s, buf, size); + break; #endif #ifdef XZ_DEC_POWERPC - case BCJ_POWERPC: - filtered = bcj_powerpc(s, buf, size); - break; + case BCJ_POWERPC: + filtered = bcj_powerpc(s, buf, size); + break; #endif #ifdef XZ_DEC_IA64 - case BCJ_IA64: - filtered = bcj_ia64(s, buf, size); - break; + case BCJ_IA64: + filtered = bcj_ia64(s, buf, size); + break; #endif #ifdef XZ_DEC_ARM - case BCJ_ARM: - filtered = bcj_arm(s, buf, size); - break; + case BCJ_ARM: + filtered = bcj_arm(s, buf, size); + break; #endif #ifdef XZ_DEC_ARMTHUMB - case BCJ_ARMTHUMB: - filtered = bcj_armthumb(s, buf, size); - break; + case BCJ_ARMTHUMB: + filtered = bcj_armthumb(s, buf, size); + break; #endif #ifdef XZ_DEC_SPARC - case BCJ_SPARC: - filtered = bcj_sparc(s, buf, size); - break; + case BCJ_SPARC: + filtered = bcj_sparc(s, buf, size); + break; #endif - default: - /* Never reached but silence compiler warnings. */ - filtered = 0; - break; - } - - *pos += filtered; - s->pos += filtered; + default: + /* Never reached but silence compiler warnings. */ + filtered = 0; + break; + } + + *pos += filtered; + s->pos += filtered; } /* @@ -410,15 +410,15 @@ static void bcj_apply(struct xz_dec_bcj *s, uint8_t *buf, size_t *pos, size_t si */ static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) { - size_t copy_size; + size_t copy_size; - copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); - memcpy(b->out + b->out_pos, s->temp.buf, copy_size); - b->out_pos += copy_size; + copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); + memcpy(b->out + b->out_pos, s->temp.buf, copy_size); + b->out_pos += copy_size; - s->temp.filtered -= copy_size; - s->temp.size -= copy_size; - memmove(s->temp.buf, s->temp.buf + copy_size, s->temp.size); + s->temp.filtered -= copy_size; + s->temp.size -= copy_size; + memmove(s->temp.buf, s->temp.buf + copy_size, s->temp.size); } /* @@ -427,162 +427,162 @@ static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) * some buffering. */ XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, struct xz_dec_lzma2 *lzma2, - struct xz_buf *b) + struct xz_buf *b) { - size_t out_start; - - /* - * Flush pending already filtered data to the output buffer. Return - * immediatelly if we couldn't flush everything, or if the next - * filter in the chain had already returned XZ_STREAM_END. - */ - if (s->temp.filtered > 0) - { - bcj_flush(s, b); - if (s->temp.filtered > 0) - return XZ_OK; - - if (s->ret == XZ_STREAM_END) - return XZ_STREAM_END; - } - - /* - * If we have more output space than what is currently pending in - * temp, copy the unfiltered data from temp to the output buffer - * and try to fill the output buffer by decoding more data from the - * next filter in the chain. Apply the BCJ filter on the new data - * in the output buffer. If everything cannot be filtered, copy it - * to temp and rewind the output buffer position accordingly. - * - * This needs to be always run when temp.size == 0 to handle a special - * case where the output buffer is full and the next filter has no - * more output coming but hasn't returned XZ_STREAM_END yet. - */ - if (s->temp.size < b->out_size - b->out_pos || s->temp.size == 0) - { - out_start = b->out_pos; - memcpy(b->out + b->out_pos, s->temp.buf, s->temp.size); - b->out_pos += s->temp.size; - - s->ret = xz_dec_lzma2_run(lzma2, b); - if (s->ret != XZ_STREAM_END && (s->ret != XZ_OK || s->single_call)) - return s->ret; - - bcj_apply(s, b->out, &out_start, b->out_pos); - - /* - * As an exception, if the next filter returned XZ_STREAM_END, - * we can do that too, since the last few bytes that remain - * unfiltered are meant to remain unfiltered. - */ - if (s->ret == XZ_STREAM_END) - return XZ_STREAM_END; - - s->temp.size = b->out_pos - out_start; - b->out_pos -= s->temp.size; - memcpy(s->temp.buf, b->out + b->out_pos, s->temp.size); - - /* - * If there wasn't enough input to the next filter to fill - * the output buffer with unfiltered data, there's no point - * to try decoding more data to temp. - */ - if (b->out_pos + s->temp.size < b->out_size) - return XZ_OK; - } - - /* - * We have unfiltered data in temp. If the output buffer isn't full - * yet, try to fill the temp buffer by decoding more data from the - * next filter. Apply the BCJ filter on temp. Then we hopefully can - * fill the actual output buffer by copying filtered data from temp. - * A mix of filtered and unfiltered data may be left in temp; it will - * be taken care on the next call to this function. - */ - if (b->out_pos < b->out_size) - { - /* Make b->out{,_pos,_size} temporarily point to s->temp. */ - s->out = b->out; - s->out_pos = b->out_pos; - s->out_size = b->out_size; - b->out = s->temp.buf; - b->out_pos = s->temp.size; - b->out_size = sizeof(s->temp.buf); - - s->ret = xz_dec_lzma2_run(lzma2, b); - - s->temp.size = b->out_pos; - b->out = s->out; - b->out_pos = s->out_pos; - b->out_size = s->out_size; - - if (s->ret != XZ_OK && s->ret != XZ_STREAM_END) - return s->ret; - - bcj_apply(s, s->temp.buf, &s->temp.filtered, s->temp.size); - - /* - * If the next filter returned XZ_STREAM_END, we mark that - * everything is filtered, since the last unfiltered bytes - * of the stream are meant to be left as is. - */ - if (s->ret == XZ_STREAM_END) - s->temp.filtered = s->temp.size; - - bcj_flush(s, b); - if (s->temp.filtered > 0) - return XZ_OK; - } - - return s->ret; + size_t out_start; + + /* + * Flush pending already filtered data to the output buffer. Return + * immediatelly if we couldn't flush everything, or if the next + * filter in the chain had already returned XZ_STREAM_END. + */ + if (s->temp.filtered > 0) + { + bcj_flush(s, b); + if (s->temp.filtered > 0) + return XZ_OK; + + if (s->ret == XZ_STREAM_END) + return XZ_STREAM_END; + } + + /* + * If we have more output space than what is currently pending in + * temp, copy the unfiltered data from temp to the output buffer + * and try to fill the output buffer by decoding more data from the + * next filter in the chain. Apply the BCJ filter on the new data + * in the output buffer. If everything cannot be filtered, copy it + * to temp and rewind the output buffer position accordingly. + * + * This needs to be always run when temp.size == 0 to handle a special + * case where the output buffer is full and the next filter has no + * more output coming but hasn't returned XZ_STREAM_END yet. + */ + if (s->temp.size < b->out_size - b->out_pos || s->temp.size == 0) + { + out_start = b->out_pos; + memcpy(b->out + b->out_pos, s->temp.buf, s->temp.size); + b->out_pos += s->temp.size; + + s->ret = xz_dec_lzma2_run(lzma2, b); + if (s->ret != XZ_STREAM_END && (s->ret != XZ_OK || s->single_call)) + return s->ret; + + bcj_apply(s, b->out, &out_start, b->out_pos); + + /* + * As an exception, if the next filter returned XZ_STREAM_END, + * we can do that too, since the last few bytes that remain + * unfiltered are meant to remain unfiltered. + */ + if (s->ret == XZ_STREAM_END) + return XZ_STREAM_END; + + s->temp.size = b->out_pos - out_start; + b->out_pos -= s->temp.size; + memcpy(s->temp.buf, b->out + b->out_pos, s->temp.size); + + /* + * If there wasn't enough input to the next filter to fill + * the output buffer with unfiltered data, there's no point + * to try decoding more data to temp. + */ + if (b->out_pos + s->temp.size < b->out_size) + return XZ_OK; + } + + /* + * We have unfiltered data in temp. If the output buffer isn't full + * yet, try to fill the temp buffer by decoding more data from the + * next filter. Apply the BCJ filter on temp. Then we hopefully can + * fill the actual output buffer by copying filtered data from temp. + * A mix of filtered and unfiltered data may be left in temp; it will + * be taken care on the next call to this function. + */ + if (b->out_pos < b->out_size) + { + /* Make b->out{,_pos,_size} temporarily point to s->temp. */ + s->out = b->out; + s->out_pos = b->out_pos; + s->out_size = b->out_size; + b->out = s->temp.buf; + b->out_pos = s->temp.size; + b->out_size = sizeof(s->temp.buf); + + s->ret = xz_dec_lzma2_run(lzma2, b); + + s->temp.size = b->out_pos; + b->out = s->out; + b->out_pos = s->out_pos; + b->out_size = s->out_size; + + if (s->ret != XZ_OK && s->ret != XZ_STREAM_END) + return s->ret; + + bcj_apply(s, s->temp.buf, &s->temp.filtered, s->temp.size); + + /* + * If the next filter returned XZ_STREAM_END, we mark that + * everything is filtered, since the last unfiltered bytes + * of the stream are meant to be left as is. + */ + if (s->ret == XZ_STREAM_END) + s->temp.filtered = s->temp.size; + + bcj_flush(s, b); + if (s->temp.filtered > 0) + return XZ_OK; + } + + return s->ret; } XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call) { - struct xz_dec_bcj *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s != NULL) - s->single_call = single_call; + struct xz_dec_bcj *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s != NULL) + s->single_call = single_call; - return s; + return s; } XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id) { - switch (id) - { + switch (id) + { #ifdef XZ_DEC_X86 - case BCJ_X86: + case BCJ_X86: #endif #ifdef XZ_DEC_POWERPC - case BCJ_POWERPC: + case BCJ_POWERPC: #endif #ifdef XZ_DEC_IA64 - case BCJ_IA64: + case BCJ_IA64: #endif #ifdef XZ_DEC_ARM - case BCJ_ARM: + case BCJ_ARM: #endif #ifdef XZ_DEC_ARMTHUMB - case BCJ_ARMTHUMB: + case BCJ_ARMTHUMB: #endif #ifdef XZ_DEC_SPARC - case BCJ_SPARC: + case BCJ_SPARC: #endif - break; + break; - default: - /* Unsupported Filter ID */ - return XZ_OPTIONS_ERROR; - } + default: + /* Unsupported Filter ID */ + return XZ_OPTIONS_ERROR; + } - s->type = id; - s->ret = XZ_OK; - s->pos = 0; - s->x86_prev_mask = 0; - s->temp.filtered = 0; - s->temp.size = 0; + s->type = id; + s->ret = XZ_OK; + s->pos = 0; + s->x86_prev_mask = 0; + s->temp.filtered = 0; + s->temp.size = 0; - return XZ_OK; + return XZ_OK; } #endif diff --git a/libraries/xz-embedded/src/xz_dec_lzma2.c b/libraries/xz-embedded/src/xz_dec_lzma2.c index 3d7b9a2e..365ace2b 100644 --- a/libraries/xz-embedded/src/xz_dec_lzma2.c +++ b/libraries/xz-embedded/src/xz_dec_lzma2.c @@ -43,244 +43,244 @@ */ struct dictionary { - /* Beginning of the history buffer */ - uint8_t *buf; - - /* Old position in buf (before decoding more data) */ - size_t start; - - /* Position in buf */ - size_t pos; - - /* - * How full dictionary is. This is used to detect corrupt input that - * would read beyond the beginning of the uncompressed stream. - */ - size_t full; - - /* Write limit; we don't write to buf[limit] or later bytes. */ - size_t limit; - - /* - * End of the dictionary buffer. In multi-call mode, this is - * the same as the dictionary size. In single-call mode, this - * indicates the size of the output buffer. - */ - size_t end; - - /* - * Size of the dictionary as specified in Block Header. This is used - * together with "full" to detect corrupt input that would make us - * read beyond the beginning of the uncompressed stream. - */ - uint32_t size; - - /* - * Maximum allowed dictionary size in multi-call mode. - * This is ignored in single-call mode. - */ - uint32_t size_max; - - /* - * Amount of memory currently allocated for the dictionary. - * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC, - * size_max is always the same as the allocated size.) - */ - uint32_t allocated; - - /* Operation mode */ - enum xz_mode mode; + /* Beginning of the history buffer */ + uint8_t *buf; + + /* Old position in buf (before decoding more data) */ + size_t start; + + /* Position in buf */ + size_t pos; + + /* + * How full dictionary is. This is used to detect corrupt input that + * would read beyond the beginning of the uncompressed stream. + */ + size_t full; + + /* Write limit; we don't write to buf[limit] or later bytes. */ + size_t limit; + + /* + * End of the dictionary buffer. In multi-call mode, this is + * the same as the dictionary size. In single-call mode, this + * indicates the size of the output buffer. + */ + size_t end; + + /* + * Size of the dictionary as specified in Block Header. This is used + * together with "full" to detect corrupt input that would make us + * read beyond the beginning of the uncompressed stream. + */ + uint32_t size; + + /* + * Maximum allowed dictionary size in multi-call mode. + * This is ignored in single-call mode. + */ + uint32_t size_max; + + /* + * Amount of memory currently allocated for the dictionary. + * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC, + * size_max is always the same as the allocated size.) + */ + uint32_t allocated; + + /* Operation mode */ + enum xz_mode mode; }; /* Range decoder */ struct rc_dec { - uint32_t range; - uint32_t code; - - /* - * Number of initializing bytes remaining to be read - * by rc_read_init(). - */ - uint32_t init_bytes_left; - - /* - * Buffer from which we read our input. It can be either - * temp.buf or the caller-provided input buffer. - */ - const uint8_t *in; - size_t in_pos; - size_t in_limit; + uint32_t range; + uint32_t code; + + /* + * Number of initializing bytes remaining to be read + * by rc_read_init(). + */ + uint32_t init_bytes_left; + + /* + * Buffer from which we read our input. It can be either + * temp.buf or the caller-provided input buffer. + */ + const uint8_t *in; + size_t in_pos; + size_t in_limit; }; /* Probabilities for a length decoder. */ struct lzma_len_dec { - /* Probability of match length being at least 10 */ - uint16_t choice; + /* Probability of match length being at least 10 */ + uint16_t choice; - /* Probability of match length being at least 18 */ - uint16_t choice2; + /* Probability of match length being at least 18 */ + uint16_t choice2; - /* Probabilities for match lengths 2-9 */ - uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS]; + /* Probabilities for match lengths 2-9 */ + uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS]; - /* Probabilities for match lengths 10-17 */ - uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS]; + /* Probabilities for match lengths 10-17 */ + uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS]; - /* Probabilities for match lengths 18-273 */ - uint16_t high[LEN_HIGH_SYMBOLS]; + /* Probabilities for match lengths 18-273 */ + uint16_t high[LEN_HIGH_SYMBOLS]; }; struct lzma_dec { - /* Distances of latest four matches */ - uint32_t rep0; - uint32_t rep1; - uint32_t rep2; - uint32_t rep3; - - /* Types of the most recently seen LZMA symbols */ - enum lzma_state state; - - /* - * Length of a match. This is updated so that dict_repeat can - * be called again to finish repeating the whole match. - */ - uint32_t len; - - /* - * LZMA properties or related bit masks (number of literal - * context bits, a mask dervied from the number of literal - * position bits, and a mask dervied from the number - * position bits) - */ - uint32_t lc; - uint32_t literal_pos_mask; /* (1 << lp) - 1 */ - uint32_t pos_mask; /* (1 << pb) - 1 */ - - /* If 1, it's a match. Otherwise it's a single 8-bit literal. */ - uint16_t is_match[STATES][POS_STATES_MAX]; - - /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */ - uint16_t is_rep[STATES]; - - /* - * If 0, distance of a repeated match is rep0. - * Otherwise check is_rep1. - */ - uint16_t is_rep0[STATES]; - - /* - * If 0, distance of a repeated match is rep1. - * Otherwise check is_rep2. - */ - uint16_t is_rep1[STATES]; - - /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */ - uint16_t is_rep2[STATES]; - - /* - * If 1, the repeated match has length of one byte. Otherwise - * the length is decoded from rep_len_decoder. - */ - uint16_t is_rep0_long[STATES][POS_STATES_MAX]; - - /* - * Probability tree for the highest two bits of the match - * distance. There is a separate probability tree for match - * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273]. - */ - uint16_t dist_slot[DIST_STATES][DIST_SLOTS]; - - /* - * Probility trees for additional bits for match distance - * when the distance is in the range [4, 127]. - */ - uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END]; - - /* - * Probability tree for the lowest four bits of a match - * distance that is equal to or greater than 128. - */ - uint16_t dist_align[ALIGN_SIZE]; - - /* Length of a normal match */ - struct lzma_len_dec match_len_dec; - - /* Length of a repeated match */ - struct lzma_len_dec rep_len_dec; - - /* Probabilities of literals */ - uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; + /* Distances of latest four matches */ + uint32_t rep0; + uint32_t rep1; + uint32_t rep2; + uint32_t rep3; + + /* Types of the most recently seen LZMA symbols */ + enum lzma_state state; + + /* + * Length of a match. This is updated so that dict_repeat can + * be called again to finish repeating the whole match. + */ + uint32_t len; + + /* + * LZMA properties or related bit masks (number of literal + * context bits, a mask dervied from the number of literal + * position bits, and a mask dervied from the number + * position bits) + */ + uint32_t lc; + uint32_t literal_pos_mask; /* (1 << lp) - 1 */ + uint32_t pos_mask; /* (1 << pb) - 1 */ + + /* If 1, it's a match. Otherwise it's a single 8-bit literal. */ + uint16_t is_match[STATES][POS_STATES_MAX]; + + /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */ + uint16_t is_rep[STATES]; + + /* + * If 0, distance of a repeated match is rep0. + * Otherwise check is_rep1. + */ + uint16_t is_rep0[STATES]; + + /* + * If 0, distance of a repeated match is rep1. + * Otherwise check is_rep2. + */ + uint16_t is_rep1[STATES]; + + /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */ + uint16_t is_rep2[STATES]; + + /* + * If 1, the repeated match has length of one byte. Otherwise + * the length is decoded from rep_len_decoder. + */ + uint16_t is_rep0_long[STATES][POS_STATES_MAX]; + + /* + * Probability tree for the highest two bits of the match + * distance. There is a separate probability tree for match + * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273]. + */ + uint16_t dist_slot[DIST_STATES][DIST_SLOTS]; + + /* + * Probility trees for additional bits for match distance + * when the distance is in the range [4, 127]. + */ + uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END]; + + /* + * Probability tree for the lowest four bits of a match + * distance that is equal to or greater than 128. + */ + uint16_t dist_align[ALIGN_SIZE]; + + /* Length of a normal match */ + struct lzma_len_dec match_len_dec; + + /* Length of a repeated match */ + struct lzma_len_dec rep_len_dec; + + /* Probabilities of literals */ + uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; }; struct lzma2_dec { - /* Position in xz_dec_lzma2_run(). */ - enum lzma2_seq - { - SEQ_CONTROL, - SEQ_UNCOMPRESSED_1, - SEQ_UNCOMPRESSED_2, - SEQ_COMPRESSED_0, - SEQ_COMPRESSED_1, - SEQ_PROPERTIES, - SEQ_LZMA_PREPARE, - SEQ_LZMA_RUN, - SEQ_COPY - } sequence; - - /* Next position after decoding the compressed size of the chunk. */ - enum lzma2_seq next_sequence; - - /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ - uint32_t uncompressed; - - /* - * Compressed size of LZMA chunk or compressed/uncompressed - * size of uncompressed chunk (64 KiB at maximum) - */ - uint32_t compressed; - - /* - * True if dictionary reset is needed. This is false before - * the first chunk (LZMA or uncompressed). - */ - bool need_dict_reset; - - /* - * True if new LZMA properties are needed. This is false - * before the first LZMA chunk. - */ - bool need_props; + /* Position in xz_dec_lzma2_run(). */ + enum lzma2_seq + { + SEQ_CONTROL, + SEQ_UNCOMPRESSED_1, + SEQ_UNCOMPRESSED_2, + SEQ_COMPRESSED_0, + SEQ_COMPRESSED_1, + SEQ_PROPERTIES, + SEQ_LZMA_PREPARE, + SEQ_LZMA_RUN, + SEQ_COPY + } sequence; + + /* Next position after decoding the compressed size of the chunk. */ + enum lzma2_seq next_sequence; + + /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ + uint32_t uncompressed; + + /* + * Compressed size of LZMA chunk or compressed/uncompressed + * size of uncompressed chunk (64 KiB at maximum) + */ + uint32_t compressed; + + /* + * True if dictionary reset is needed. This is false before + * the first chunk (LZMA or uncompressed). + */ + bool need_dict_reset; + + /* + * True if new LZMA properties are needed. This is false + * before the first LZMA chunk. + */ + bool need_props; }; struct xz_dec_lzma2 { - /* - * The order below is important on x86 to reduce code size and - * it shouldn't hurt on other platforms. Everything up to and - * including lzma.pos_mask are in the first 128 bytes on x86-32, - * which allows using smaller instructions to access those - * variables. On x86-64, fewer variables fit into the first 128 - * bytes, but this is still the best order without sacrificing - * the readability by splitting the structures. - */ - struct rc_dec rc; - struct dictionary dict; - struct lzma2_dec lzma2; - struct lzma_dec lzma; - - /* - * Temporary buffer which holds small number of input bytes between - * decoder calls. See lzma2_lzma() for details. - */ - struct - { - uint32_t size; - uint8_t buf[3 * LZMA_IN_REQUIRED]; - } temp; + /* + * The order below is important on x86 to reduce code size and + * it shouldn't hurt on other platforms. Everything up to and + * including lzma.pos_mask are in the first 128 bytes on x86-32, + * which allows using smaller instructions to access those + * variables. On x86-64, fewer variables fit into the first 128 + * bytes, but this is still the best order without sacrificing + * the readability by splitting the structures. + */ + struct rc_dec rc; + struct dictionary dict; + struct lzma2_dec lzma2; + struct lzma_dec lzma; + + /* + * Temporary buffer which holds small number of input bytes between + * decoder calls. See lzma2_lzma() for details. + */ + struct + { + uint32_t size; + uint8_t buf[3 * LZMA_IN_REQUIRED]; + } temp; }; /************** @@ -293,31 +293,31 @@ struct xz_dec_lzma2 */ static void dict_reset(struct dictionary *dict, struct xz_buf *b) { - if (DEC_IS_SINGLE(dict->mode)) - { - dict->buf = b->out + b->out_pos; - dict->end = b->out_size - b->out_pos; - } - - dict->start = 0; - dict->pos = 0; - dict->limit = 0; - dict->full = 0; + if (DEC_IS_SINGLE(dict->mode)) + { + dict->buf = b->out + b->out_pos; + dict->end = b->out_size - b->out_pos; + } + + dict->start = 0; + dict->pos = 0; + dict->limit = 0; + dict->full = 0; } /* Set dictionary write limit */ static void dict_limit(struct dictionary *dict, size_t out_max) { - if (dict->end - dict->pos <= out_max) - dict->limit = dict->end; - else - dict->limit = dict->pos + out_max; + if (dict->end - dict->pos <= out_max) + dict->limit = dict->end; + else + dict->limit = dict->pos + out_max; } /* Return true if at least one byte can be written into the dictionary. */ static inline bool dict_has_space(const struct dictionary *dict) { - return dict->pos < dict->limit; + return dict->pos < dict->limit; } /* @@ -328,12 +328,12 @@ static inline bool dict_has_space(const struct dictionary *dict) */ static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) { - size_t offset = dict->pos - dist - 1; + size_t offset = dict->pos - dist - 1; - if (dist >= dict->pos) - offset += dict->end; + if (dist >= dict->pos) + offset += dict->end; - return dict->full > 0 ? dict->buf[offset] : 0; + return dict->full > 0 ? dict->buf[offset] : 0; } /* @@ -341,10 +341,10 @@ static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) */ static inline void dict_put(struct dictionary *dict, uint8_t byte) { - dict->buf[dict->pos++] = byte; + dict->buf[dict->pos++] = byte; - if (dict->full < dict->pos) - dict->full = dict->pos; + if (dict->full < dict->pos) + dict->full = dict->pos; } /* @@ -354,66 +354,66 @@ static inline void dict_put(struct dictionary *dict, uint8_t byte) */ static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) { - size_t back; - uint32_t left; + size_t back; + uint32_t left; - if (dist >= dict->full || dist >= dict->size) - return false; + if (dist >= dict->full || dist >= dict->size) + return false; - left = min_t(size_t, dict->limit - dict->pos, *len); - *len -= left; + left = min_t(size_t, dict->limit - dict->pos, *len); + *len -= left; - back = dict->pos - dist - 1; - if (dist >= dict->pos) - back += dict->end; + back = dict->pos - dist - 1; + if (dist >= dict->pos) + back += dict->end; - do - { - dict->buf[dict->pos++] = dict->buf[back++]; - if (back == dict->end) - back = 0; - } while (--left > 0); + do + { + dict->buf[dict->pos++] = dict->buf[back++]; + if (back == dict->end) + back = 0; + } while (--left > 0); - if (dict->full < dict->pos) - dict->full = dict->pos; + if (dict->full < dict->pos) + dict->full = dict->pos; - return true; + return true; } /* Copy uncompressed data as is from input to dictionary and output buffers. */ static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, uint32_t *left) { - size_t copy_size; + size_t copy_size; - while (*left > 0 && b->in_pos < b->in_size && b->out_pos < b->out_size) - { - copy_size = min(b->in_size - b->in_pos, b->out_size - b->out_pos); - if (copy_size > dict->end - dict->pos) - copy_size = dict->end - dict->pos; - if (copy_size > *left) - copy_size = *left; + while (*left > 0 && b->in_pos < b->in_size && b->out_pos < b->out_size) + { + copy_size = min(b->in_size - b->in_pos, b->out_size - b->out_pos); + if (copy_size > dict->end - dict->pos) + copy_size = dict->end - dict->pos; + if (copy_size > *left) + copy_size = *left; - *left -= copy_size; + *left -= copy_size; - memcpy(dict->buf + dict->pos, b->in + b->in_pos, copy_size); - dict->pos += copy_size; + memcpy(dict->buf + dict->pos, b->in + b->in_pos, copy_size); + dict->pos += copy_size; - if (dict->full < dict->pos) - dict->full = dict->pos; + if (dict->full < dict->pos) + dict->full = dict->pos; - if (DEC_IS_MULTI(dict->mode)) - { - if (dict->pos == dict->end) - dict->pos = 0; + if (DEC_IS_MULTI(dict->mode)) + { + if (dict->pos == dict->end) + dict->pos = 0; - memcpy(b->out + b->out_pos, b->in + b->in_pos, copy_size); - } + memcpy(b->out + b->out_pos, b->in + b->in_pos, copy_size); + } - dict->start = dict->pos; + dict->start = dict->pos; - b->out_pos += copy_size; - b->in_pos += copy_size; - } + b->out_pos += copy_size; + b->in_pos += copy_size; + } } /* @@ -423,19 +423,19 @@ static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, uint32_ */ static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) { - size_t copy_size = dict->pos - dict->start; + size_t copy_size = dict->pos - dict->start; - if (DEC_IS_MULTI(dict->mode)) - { - if (dict->pos == dict->end) - dict->pos = 0; + if (DEC_IS_MULTI(dict->mode)) + { + if (dict->pos == dict->end) + dict->pos = 0; - memcpy(b->out + b->out_pos, dict->buf + dict->start, copy_size); - } + memcpy(b->out + b->out_pos, dict->buf + dict->start, copy_size); + } - dict->start = dict->pos; - b->out_pos += copy_size; - return copy_size; + dict->start = dict->pos; + b->out_pos += copy_size; + return copy_size; } /***************** @@ -445,9 +445,9 @@ static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) /* Reset the range decoder. */ static void rc_reset(struct rc_dec *rc) { - rc->range = (uint32_t) - 1; - rc->code = 0; - rc->init_bytes_left = RC_INIT_BYTES; + rc->range = (uint32_t) - 1; + rc->code = 0; + rc->init_bytes_left = RC_INIT_BYTES; } /* @@ -456,22 +456,22 @@ static void rc_reset(struct rc_dec *rc) */ static bool rc_read_init(struct rc_dec *rc, struct xz_buf *b) { - while (rc->init_bytes_left > 0) - { - if (b->in_pos == b->in_size) - return false; + while (rc->init_bytes_left > 0) + { + if (b->in_pos == b->in_size) + return false; - rc->code = (rc->code << 8) + b->in[b->in_pos++]; - --rc->init_bytes_left; - } + rc->code = (rc->code << 8) + b->in[b->in_pos++]; + --rc->init_bytes_left; + } - return true; + return true; } /* Return true if there may not be enough input for the next decoding loop. */ static inline bool rc_limit_exceeded(const struct rc_dec *rc) { - return rc->in_pos > rc->in_limit; + return rc->in_pos > rc->in_limit; } /* @@ -480,17 +480,17 @@ static inline bool rc_limit_exceeded(const struct rc_dec *rc) */ static inline bool rc_is_finished(const struct rc_dec *rc) { - return rc->code == 0; + return rc->code == 0; } /* Read the next input byte if needed. */ static __always_inline void rc_normalize(struct rc_dec *rc) { - if (rc->range < RC_TOP_VALUE) - { - rc->range <<= RC_SHIFT_BITS; - rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++]; - } + if (rc->range < RC_TOP_VALUE) + { + rc->range <<= RC_SHIFT_BITS; + rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++]; + } } /* @@ -506,79 +506,79 @@ static __always_inline void rc_normalize(struct rc_dec *rc) */ static __always_inline int rc_bit(struct rc_dec *rc, uint16_t *prob) { - uint32_t bound; - int bit; - - rc_normalize(rc); - bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob; - if (rc->code < bound) - { - rc->range = bound; - *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS; - bit = 0; - } - else - { - rc->range -= bound; - rc->code -= bound; - *prob -= *prob >> RC_MOVE_BITS; - bit = 1; - } - - return bit; + uint32_t bound; + int bit; + + rc_normalize(rc); + bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob; + if (rc->code < bound) + { + rc->range = bound; + *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS; + bit = 0; + } + else + { + rc->range -= bound; + rc->code -= bound; + *prob -= *prob >> RC_MOVE_BITS; + bit = 1; + } + + return bit; } /* Decode a bittree starting from the most significant bit. */ static __always_inline uint32_t rc_bittree(struct rc_dec *rc, uint16_t *probs, uint32_t limit) { - uint32_t symbol = 1; + uint32_t symbol = 1; - do - { - if (rc_bit(rc, &probs[symbol])) - symbol = (symbol << 1) + 1; - else - symbol <<= 1; - } while (symbol < limit); + do + { + if (rc_bit(rc, &probs[symbol])) + symbol = (symbol << 1) + 1; + else + symbol <<= 1; + } while (symbol < limit); - return symbol; + return symbol; } /* Decode a bittree starting from the least significant bit. */ static __always_inline void rc_bittree_reverse(struct rc_dec *rc, uint16_t *probs, - uint32_t *dest, uint32_t limit) + uint32_t *dest, uint32_t limit) { - uint32_t symbol = 1; - uint32_t i = 0; - - do - { - if (rc_bit(rc, &probs[symbol])) - { - symbol = (symbol << 1) + 1; - *dest += 1 << i; - } - else - { - symbol <<= 1; - } - } while (++i < limit); + uint32_t symbol = 1; + uint32_t i = 0; + + do + { + if (rc_bit(rc, &probs[symbol])) + { + symbol = (symbol << 1) + 1; + *dest += 1 << i; + } + else + { + symbol <<= 1; + } + } while (++i < limit); } /* Decode direct bits (fixed fifty-fifty probability) */ static inline void rc_direct(struct rc_dec *rc, uint32_t *dest, uint32_t limit) { - uint32_t mask; - - do - { - rc_normalize(rc); - rc->range >>= 1; - rc->code -= rc->range; - mask = (uint32_t)0 - (rc->code >> 31); - rc->code += rc->range & mask; - *dest = (*dest << 1) + (mask + 1); - } while (--limit > 0); + uint32_t mask; + + do + { + rc_normalize(rc); + rc->range >>= 1; + rc->code -= rc->range; + mask = (uint32_t)0 - (rc->code >> 31); + rc->code += rc->range & mask; + *dest = (*dest << 1) + (mask + 1); + } while (--limit > 0); } /******** @@ -588,128 +588,128 @@ static inline void rc_direct(struct rc_dec *rc, uint32_t *dest, uint32_t limit) /* Get pointer to literal coder probability array. */ static uint16_t *lzma_literal_probs(struct xz_dec_lzma2 *s) { - uint32_t prev_byte = dict_get(&s->dict, 0); - uint32_t low = prev_byte >> (8 - s->lzma.lc); - uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc; - return s->lzma.literal[low + high]; + uint32_t prev_byte = dict_get(&s->dict, 0); + uint32_t low = prev_byte >> (8 - s->lzma.lc); + uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc; + return s->lzma.literal[low + high]; } /* Decode a literal (one 8-bit byte) */ static void lzma_literal(struct xz_dec_lzma2 *s) { - uint16_t *probs; - uint32_t symbol; - uint32_t match_byte; - uint32_t match_bit; - uint32_t offset; - uint32_t i; - - probs = lzma_literal_probs(s); - - if (lzma_state_is_literal(s->lzma.state)) - { - symbol = rc_bittree(&s->rc, probs, 0x100); - } - else - { - symbol = 1; - match_byte = dict_get(&s->dict, s->lzma.rep0) << 1; - offset = 0x100; - - do - { - match_bit = match_byte & offset; - match_byte <<= 1; - i = offset + match_bit + symbol; - - if (rc_bit(&s->rc, &probs[i])) - { - symbol = (symbol << 1) + 1; - offset &= match_bit; - } - else - { - symbol <<= 1; - offset &= ~match_bit; - } - } while (symbol < 0x100); - } - - dict_put(&s->dict, (uint8_t)symbol); - lzma_state_literal(&s->lzma.state); + uint16_t *probs; + uint32_t symbol; + uint32_t match_byte; + uint32_t match_bit; + uint32_t offset; + uint32_t i; + + probs = lzma_literal_probs(s); + + if (lzma_state_is_literal(s->lzma.state)) + { + symbol = rc_bittree(&s->rc, probs, 0x100); + } + else + { + symbol = 1; + match_byte = dict_get(&s->dict, s->lzma.rep0) << 1; + offset = 0x100; + + do + { + match_bit = match_byte & offset; + match_byte <<= 1; + i = offset + match_bit + symbol; + + if (rc_bit(&s->rc, &probs[i])) + { + symbol = (symbol << 1) + 1; + offset &= match_bit; + } + else + { + symbol <<= 1; + offset &= ~match_bit; + } + } while (symbol < 0x100); + } + + dict_put(&s->dict, (uint8_t)symbol); + lzma_state_literal(&s->lzma.state); } /* Decode the length of the match into s->lzma.len. */ static void lzma_len(struct xz_dec_lzma2 *s, struct lzma_len_dec *l, uint32_t pos_state) { - uint16_t *probs; - uint32_t limit; - - if (!rc_bit(&s->rc, &l->choice)) - { - probs = l->low[pos_state]; - limit = LEN_LOW_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN; - } - else - { - if (!rc_bit(&s->rc, &l->choice2)) - { - probs = l->mid[pos_state]; - limit = LEN_MID_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS; - } - else - { - probs = l->high; - limit = LEN_HIGH_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS; - } - } - - s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit; + uint16_t *probs; + uint32_t limit; + + if (!rc_bit(&s->rc, &l->choice)) + { + probs = l->low[pos_state]; + limit = LEN_LOW_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN; + } + else + { + if (!rc_bit(&s->rc, &l->choice2)) + { + probs = l->mid[pos_state]; + limit = LEN_MID_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS; + } + else + { + probs = l->high; + limit = LEN_HIGH_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS; + } + } + + s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit; } /* Decode a match. The distance will be stored in s->lzma.rep0. */ static void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state) { - uint16_t *probs; - uint32_t dist_slot; - uint32_t limit; - - lzma_state_match(&s->lzma.state); - - s->lzma.rep3 = s->lzma.rep2; - s->lzma.rep2 = s->lzma.rep1; - s->lzma.rep1 = s->lzma.rep0; - - lzma_len(s, &s->lzma.match_len_dec, pos_state); - - probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)]; - dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS; - - if (dist_slot < DIST_MODEL_START) - { - s->lzma.rep0 = dist_slot; - } - else - { - limit = (dist_slot >> 1) - 1; - s->lzma.rep0 = 2 + (dist_slot & 1); - - if (dist_slot < DIST_MODEL_END) - { - s->lzma.rep0 <<= limit; - probs = s->lzma.dist_special + s->lzma.rep0 - dist_slot - 1; - rc_bittree_reverse(&s->rc, probs, &s->lzma.rep0, limit); - } - else - { - rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS); - s->lzma.rep0 <<= ALIGN_BITS; - rc_bittree_reverse(&s->rc, s->lzma.dist_align, &s->lzma.rep0, ALIGN_BITS); - } - } + uint16_t *probs; + uint32_t dist_slot; + uint32_t limit; + + lzma_state_match(&s->lzma.state); + + s->lzma.rep3 = s->lzma.rep2; + s->lzma.rep2 = s->lzma.rep1; + s->lzma.rep1 = s->lzma.rep0; + + lzma_len(s, &s->lzma.match_len_dec, pos_state); + + probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)]; + dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS; + + if (dist_slot < DIST_MODEL_START) + { + s->lzma.rep0 = dist_slot; + } + else + { + limit = (dist_slot >> 1) - 1; + s->lzma.rep0 = 2 + (dist_slot & 1); + + if (dist_slot < DIST_MODEL_END) + { + s->lzma.rep0 <<= limit; + probs = s->lzma.dist_special + s->lzma.rep0 - dist_slot - 1; + rc_bittree_reverse(&s->rc, probs, &s->lzma.rep0, limit); + } + else + { + rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS); + s->lzma.rep0 <<= ALIGN_BITS; + rc_bittree_reverse(&s->rc, s->lzma.dist_align, &s->lzma.rep0, ALIGN_BITS); + } + } } /* @@ -718,89 +718,89 @@ static void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state) */ static void lzma_rep_match(struct xz_dec_lzma2 *s, uint32_t pos_state) { - uint32_t tmp; - - if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) - { - if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[s->lzma.state][pos_state])) - { - lzma_state_short_rep(&s->lzma.state); - s->lzma.len = 1; - return; - } - } - else - { - if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) - { - tmp = s->lzma.rep1; - } - else - { - if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) - { - tmp = s->lzma.rep2; - } - else - { - tmp = s->lzma.rep3; - s->lzma.rep3 = s->lzma.rep2; - } - - s->lzma.rep2 = s->lzma.rep1; - } - - s->lzma.rep1 = s->lzma.rep0; - s->lzma.rep0 = tmp; - } - - lzma_state_long_rep(&s->lzma.state); - lzma_len(s, &s->lzma.rep_len_dec, pos_state); + uint32_t tmp; + + if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) + { + if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[s->lzma.state][pos_state])) + { + lzma_state_short_rep(&s->lzma.state); + s->lzma.len = 1; + return; + } + } + else + { + if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) + { + tmp = s->lzma.rep1; + } + else + { + if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) + { + tmp = s->lzma.rep2; + } + else + { + tmp = s->lzma.rep3; + s->lzma.rep3 = s->lzma.rep2; + } + + s->lzma.rep2 = s->lzma.rep1; + } + + s->lzma.rep1 = s->lzma.rep0; + s->lzma.rep0 = tmp; + } + + lzma_state_long_rep(&s->lzma.state); + lzma_len(s, &s->lzma.rep_len_dec, pos_state); } /* LZMA decoder core */ static bool lzma_main(struct xz_dec_lzma2 *s) { - uint32_t pos_state; - - /* - * If the dictionary was reached during the previous call, try to - * finish the possibly pending repeat in the dictionary. - */ - if (dict_has_space(&s->dict) && s->lzma.len > 0) - dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); - - /* - * Decode more LZMA symbols. One iteration may consume up to - * LZMA_IN_REQUIRED - 1 bytes. - */ - while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) - { - pos_state = s->dict.pos & s->lzma.pos_mask; - - if (!rc_bit(&s->rc, &s->lzma.is_match[s->lzma.state][pos_state])) - { - lzma_literal(s); - } - else - { - if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state])) - lzma_rep_match(s, pos_state); - else - lzma_match(s, pos_state); - - if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0)) - return false; - } - } - - /* - * Having the range decoder always normalized when we are outside - * this function makes it easier to correctly handle end of the chunk. - */ - rc_normalize(&s->rc); - - return true; + uint32_t pos_state; + + /* + * If the dictionary was reached during the previous call, try to + * finish the possibly pending repeat in the dictionary. + */ + if (dict_has_space(&s->dict) && s->lzma.len > 0) + dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); + + /* + * Decode more LZMA symbols. One iteration may consume up to + * LZMA_IN_REQUIRED - 1 bytes. + */ + while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) + { + pos_state = s->dict.pos & s->lzma.pos_mask; + + if (!rc_bit(&s->rc, &s->lzma.is_match[s->lzma.state][pos_state])) + { + lzma_literal(s); + } + else + { + if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state])) + lzma_rep_match(s, pos_state); + else + lzma_match(s, pos_state); + + if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0)) + return false; + } + } + + /* + * Having the range decoder always normalized when we are outside + * this function makes it easier to correctly handle end of the chunk. + */ + rc_normalize(&s->rc); + + return true; } /* @@ -809,29 +809,29 @@ static bool lzma_main(struct xz_dec_lzma2 *s) */ static void lzma_reset(struct xz_dec_lzma2 *s) { - uint16_t *probs; - size_t i; - - s->lzma.state = STATE_LIT_LIT; - s->lzma.rep0 = 0; - s->lzma.rep1 = 0; - s->lzma.rep2 = 0; - s->lzma.rep3 = 0; - - /* - * All probabilities are initialized to the same value. This hack - * makes the code smaller by avoiding a separate loop for each - * probability array. - * - * This could be optimized so that only that part of literal - * probabilities that are actually required. In the common case - * we would write 12 KiB less. - */ - probs = s->lzma.is_match[0]; - for (i = 0; i < PROBS_TOTAL; ++i) - probs[i] = RC_BIT_MODEL_TOTAL / 2; - - rc_reset(&s->rc); + uint16_t *probs; + size_t i; + + s->lzma.state = STATE_LIT_LIT; + s->lzma.rep0 = 0; + s->lzma.rep1 = 0; + s->lzma.rep2 = 0; + s->lzma.rep3 = 0; + + /* + * All probabilities are initialized to the same value. This hack + * makes the code smaller by avoiding a separate loop for each + * probability array. + * + * This could be optimized so that only that part of literal + * probabilities that are actually required. In the common case + * we would write 12 KiB less. + */ + probs = s->lzma.is_match[0]; + for (i = 0; i < PROBS_TOTAL; ++i) + probs[i] = RC_BIT_MODEL_TOTAL / 2; + + rc_reset(&s->rc); } /* @@ -841,35 +841,35 @@ static void lzma_reset(struct xz_dec_lzma2 *s) */ static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) { - if (props > (4 * 5 + 4) * 9 + 8) - return false; + if (props > (4 * 5 + 4) * 9 + 8) + return false; - s->lzma.pos_mask = 0; - while (props >= 9 * 5) - { - props -= 9 * 5; - ++s->lzma.pos_mask; - } + s->lzma.pos_mask = 0; + while (props >= 9 * 5) + { + props -= 9 * 5; + ++s->lzma.pos_mask; + } - s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1; + s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1; - s->lzma.literal_pos_mask = 0; - while (props >= 9) - { - props -= 9; - ++s->lzma.literal_pos_mask; - } + s->lzma.literal_pos_mask = 0; + while (props >= 9) + { + props -= 9; + ++s->lzma.literal_pos_mask; + } - s->lzma.lc = props; + s->lzma.lc = props; - if (s->lzma.lc + s->lzma.literal_pos_mask > 4) - return false; + if (s->lzma.lc + s->lzma.literal_pos_mask > 4) + return false; - s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1; + s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1; - lzma_reset(s); + lzma_reset(s); - return true; + return true; } /********* @@ -890,89 +890,89 @@ static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) */ static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) { - size_t in_avail; - uint32_t tmp; - - in_avail = b->in_size - b->in_pos; - if (s->temp.size > 0 || s->lzma2.compressed == 0) - { - tmp = 2 * LZMA_IN_REQUIRED - s->temp.size; - if (tmp > s->lzma2.compressed - s->temp.size) - tmp = s->lzma2.compressed - s->temp.size; - if (tmp > in_avail) - tmp = in_avail; - - memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp); - - if (s->temp.size + tmp == s->lzma2.compressed) - { - memzero(s->temp.buf + s->temp.size + tmp, sizeof(s->temp.buf) - s->temp.size - tmp); - s->rc.in_limit = s->temp.size + tmp; - } - else if (s->temp.size + tmp < LZMA_IN_REQUIRED) - { - s->temp.size += tmp; - b->in_pos += tmp; - return true; - } - else - { - s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED; - } - - s->rc.in = s->temp.buf; - s->rc.in_pos = 0; - - if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp) - return false; - - s->lzma2.compressed -= s->rc.in_pos; - - if (s->rc.in_pos < s->temp.size) - { - s->temp.size -= s->rc.in_pos; - memmove(s->temp.buf, s->temp.buf + s->rc.in_pos, s->temp.size); - return true; - } - - b->in_pos += s->rc.in_pos - s->temp.size; - s->temp.size = 0; - } - - in_avail = b->in_size - b->in_pos; - if (in_avail >= LZMA_IN_REQUIRED) - { - s->rc.in = b->in; - s->rc.in_pos = b->in_pos; - - if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED) - s->rc.in_limit = b->in_pos + s->lzma2.compressed; - else - s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED; - - if (!lzma_main(s)) - return false; - - in_avail = s->rc.in_pos - b->in_pos; - if (in_avail > s->lzma2.compressed) - return false; - - s->lzma2.compressed -= in_avail; - b->in_pos = s->rc.in_pos; - } - - in_avail = b->in_size - b->in_pos; - if (in_avail < LZMA_IN_REQUIRED) - { - if (in_avail > s->lzma2.compressed) - in_avail = s->lzma2.compressed; - - memcpy(s->temp.buf, b->in + b->in_pos, in_avail); - s->temp.size = in_avail; - b->in_pos += in_avail; - } - - return true; + size_t in_avail; + uint32_t tmp; + + in_avail = b->in_size - b->in_pos; + if (s->temp.size > 0 || s->lzma2.compressed == 0) + { + tmp = 2 * LZMA_IN_REQUIRED - s->temp.size; + if (tmp > s->lzma2.compressed - s->temp.size) + tmp = s->lzma2.compressed - s->temp.size; + if (tmp > in_avail) + tmp = in_avail; + + memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp); + + if (s->temp.size + tmp == s->lzma2.compressed) + { + memzero(s->temp.buf + s->temp.size + tmp, sizeof(s->temp.buf) - s->temp.size - tmp); + s->rc.in_limit = s->temp.size + tmp; + } + else if (s->temp.size + tmp < LZMA_IN_REQUIRED) + { + s->temp.size += tmp; + b->in_pos += tmp; + return true; + } + else + { + s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED; + } + + s->rc.in = s->temp.buf; + s->rc.in_pos = 0; + + if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp) + return false; + + s->lzma2.compressed -= s->rc.in_pos; + + if (s->rc.in_pos < s->temp.size) + { + s->temp.size -= s->rc.in_pos; + memmove(s->temp.buf, s->temp.buf + s->rc.in_pos, s->temp.size); + return true; + } + + b->in_pos += s->rc.in_pos - s->temp.size; + s->temp.size = 0; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail >= LZMA_IN_REQUIRED) + { + s->rc.in = b->in; + s->rc.in_pos = b->in_pos; + + if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED) + s->rc.in_limit = b->in_pos + s->lzma2.compressed; + else + s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED; + + if (!lzma_main(s)) + return false; + + in_avail = s->rc.in_pos - b->in_pos; + if (in_avail > s->lzma2.compressed) + return false; + + s->lzma2.compressed -= in_avail; + b->in_pos = s->rc.in_pos; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail < LZMA_IN_REQUIRED) + { + if (in_avail > s->lzma2.compressed) + in_avail = s->lzma2.compressed; + + memcpy(s->temp.buf, b->in + b->in_pos, in_avail); + s->temp.size = in_avail; + b->in_pos += in_avail; + } + + return true; } /* @@ -981,251 +981,251 @@ static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) */ XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) { - uint32_t tmp; - - while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) - { - switch (s->lzma2.sequence) - { - case SEQ_CONTROL: - /* - * LZMA2 control byte - * - * Exact values: - * 0x00 End marker - * 0x01 Dictionary reset followed by - * an uncompressed chunk - * 0x02 Uncompressed chunk (no dictionary reset) - * - * Highest three bits (s->control & 0xE0): - * 0xE0 Dictionary reset, new properties and state - * reset, followed by LZMA compressed chunk - * 0xC0 New properties and state reset, followed - * by LZMA compressed chunk (no dictionary - * reset) - * 0xA0 State reset using old properties, - * followed by LZMA compressed chunk (no - * dictionary reset) - * 0x80 LZMA chunk (no dictionary or state reset) - * - * For LZMA compressed chunks, the lowest five bits - * (s->control & 1F) are the highest bits of the - * uncompressed size (bits 16-20). - * - * A new LZMA2 stream must begin with a dictionary - * reset. The first LZMA chunk must set new - * properties and reset the LZMA state. - * - * Values that don't match anything described above - * are invalid and we return XZ_DATA_ERROR. - */ - tmp = b->in[b->in_pos++]; - - if (tmp == 0x00) - return XZ_STREAM_END; - - if (tmp >= 0xE0 || tmp == 0x01) - { - s->lzma2.need_props = true; - s->lzma2.need_dict_reset = false; - dict_reset(&s->dict, b); - } - else if (s->lzma2.need_dict_reset) - { - return XZ_DATA_ERROR; - } - - if (tmp >= 0x80) - { - s->lzma2.uncompressed = (tmp & 0x1F) << 16; - s->lzma2.sequence = SEQ_UNCOMPRESSED_1; - - if (tmp >= 0xC0) - { - /* - * When there are new properties, - * state reset is done at - * SEQ_PROPERTIES. - */ - s->lzma2.need_props = false; - s->lzma2.next_sequence = SEQ_PROPERTIES; - } - else if (s->lzma2.need_props) - { - return XZ_DATA_ERROR; - } - else - { - s->lzma2.next_sequence = SEQ_LZMA_PREPARE; - if (tmp >= 0xA0) - lzma_reset(s); - } - } - else - { - if (tmp > 0x02) - return XZ_DATA_ERROR; - - s->lzma2.sequence = SEQ_COMPRESSED_0; - s->lzma2.next_sequence = SEQ_COPY; - } - - break; - - case SEQ_UNCOMPRESSED_1: - s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] << 8; - s->lzma2.sequence = SEQ_UNCOMPRESSED_2; - break; - - case SEQ_UNCOMPRESSED_2: - s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] + 1; - s->lzma2.sequence = SEQ_COMPRESSED_0; - break; - - case SEQ_COMPRESSED_0: - s->lzma2.compressed = (uint32_t)b->in[b->in_pos++] << 8; - s->lzma2.sequence = SEQ_COMPRESSED_1; - break; - - case SEQ_COMPRESSED_1: - s->lzma2.compressed += (uint32_t)b->in[b->in_pos++] + 1; - s->lzma2.sequence = s->lzma2.next_sequence; - break; - - case SEQ_PROPERTIES: - if (!lzma_props(s, b->in[b->in_pos++])) - return XZ_DATA_ERROR; - - s->lzma2.sequence = SEQ_LZMA_PREPARE; - - case SEQ_LZMA_PREPARE: - if (s->lzma2.compressed < RC_INIT_BYTES) - return XZ_DATA_ERROR; - - if (!rc_read_init(&s->rc, b)) - return XZ_OK; - - s->lzma2.compressed -= RC_INIT_BYTES; - s->lzma2.sequence = SEQ_LZMA_RUN; - - case SEQ_LZMA_RUN: - /* - * Set dictionary limit to indicate how much we want - * to be encoded at maximum. Decode new data into the - * dictionary. Flush the new data from dictionary to - * b->out. Check if we finished decoding this chunk. - * In case the dictionary got full but we didn't fill - * the output buffer yet, we may run this loop - * multiple times without changing s->lzma2.sequence. - */ - dict_limit(&s->dict, - min_t(size_t, b->out_size - b->out_pos, s->lzma2.uncompressed)); - if (!lzma2_lzma(s, b)) - return XZ_DATA_ERROR; - - s->lzma2.uncompressed -= dict_flush(&s->dict, b); - - if (s->lzma2.uncompressed == 0) - { - if (s->lzma2.compressed > 0 || s->lzma.len > 0 || !rc_is_finished(&s->rc)) - return XZ_DATA_ERROR; - - rc_reset(&s->rc); - s->lzma2.sequence = SEQ_CONTROL; - } - else if (b->out_pos == b->out_size || - (b->in_pos == b->in_size && s->temp.size < s->lzma2.compressed)) - { - return XZ_OK; - } - - break; - - case SEQ_COPY: - dict_uncompressed(&s->dict, b, &s->lzma2.compressed); - if (s->lzma2.compressed > 0) - return XZ_OK; - - s->lzma2.sequence = SEQ_CONTROL; - break; - } - } - - return XZ_OK; + uint32_t tmp; + + while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) + { + switch (s->lzma2.sequence) + { + case SEQ_CONTROL: + /* + * LZMA2 control byte + * + * Exact values: + * 0x00 End marker + * 0x01 Dictionary reset followed by + * an uncompressed chunk + * 0x02 Uncompressed chunk (no dictionary reset) + * + * Highest three bits (s->control & 0xE0): + * 0xE0 Dictionary reset, new properties and state + * reset, followed by LZMA compressed chunk + * 0xC0 New properties and state reset, followed + * by LZMA compressed chunk (no dictionary + * reset) + * 0xA0 State reset using old properties, + * followed by LZMA compressed chunk (no + * dictionary reset) + * 0x80 LZMA chunk (no dictionary or state reset) + * + * For LZMA compressed chunks, the lowest five bits + * (s->control & 1F) are the highest bits of the + * uncompressed size (bits 16-20). + * + * A new LZMA2 stream must begin with a dictionary + * reset. The first LZMA chunk must set new + * properties and reset the LZMA state. + * + * Values that don't match anything described above + * are invalid and we return XZ_DATA_ERROR. + */ + tmp = b->in[b->in_pos++]; + + if (tmp == 0x00) + return XZ_STREAM_END; + + if (tmp >= 0xE0 || tmp == 0x01) + { + s->lzma2.need_props = true; + s->lzma2.need_dict_reset = false; + dict_reset(&s->dict, b); + } + else if (s->lzma2.need_dict_reset) + { + return XZ_DATA_ERROR; + } + + if (tmp >= 0x80) + { + s->lzma2.uncompressed = (tmp & 0x1F) << 16; + s->lzma2.sequence = SEQ_UNCOMPRESSED_1; + + if (tmp >= 0xC0) + { + /* + * When there are new properties, + * state reset is done at + * SEQ_PROPERTIES. + */ + s->lzma2.need_props = false; + s->lzma2.next_sequence = SEQ_PROPERTIES; + } + else if (s->lzma2.need_props) + { + return XZ_DATA_ERROR; + } + else + { + s->lzma2.next_sequence = SEQ_LZMA_PREPARE; + if (tmp >= 0xA0) + lzma_reset(s); + } + } + else + { + if (tmp > 0x02) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_COMPRESSED_0; + s->lzma2.next_sequence = SEQ_COPY; + } + + break; + + case SEQ_UNCOMPRESSED_1: + s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_UNCOMPRESSED_2; + break; + + case SEQ_UNCOMPRESSED_2: + s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = SEQ_COMPRESSED_0; + break; + + case SEQ_COMPRESSED_0: + s->lzma2.compressed = (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_COMPRESSED_1; + break; + + case SEQ_COMPRESSED_1: + s->lzma2.compressed += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = s->lzma2.next_sequence; + break; + + case SEQ_PROPERTIES: + if (!lzma_props(s, b->in[b->in_pos++])) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_LZMA_PREPARE; + + case SEQ_LZMA_PREPARE: + if (s->lzma2.compressed < RC_INIT_BYTES) + return XZ_DATA_ERROR; + + if (!rc_read_init(&s->rc, b)) + return XZ_OK; + + s->lzma2.compressed -= RC_INIT_BYTES; + s->lzma2.sequence = SEQ_LZMA_RUN; + + case SEQ_LZMA_RUN: + /* + * Set dictionary limit to indicate how much we want + * to be encoded at maximum. Decode new data into the + * dictionary. Flush the new data from dictionary to + * b->out. Check if we finished decoding this chunk. + * In case the dictionary got full but we didn't fill + * the output buffer yet, we may run this loop + * multiple times without changing s->lzma2.sequence. + */ + dict_limit(&s->dict, + min_t(size_t, b->out_size - b->out_pos, s->lzma2.uncompressed)); + if (!lzma2_lzma(s, b)) + return XZ_DATA_ERROR; + + s->lzma2.uncompressed -= dict_flush(&s->dict, b); + + if (s->lzma2.uncompressed == 0) + { + if (s->lzma2.compressed > 0 || s->lzma.len > 0 || !rc_is_finished(&s->rc)) + return XZ_DATA_ERROR; + + rc_reset(&s->rc); + s->lzma2.sequence = SEQ_CONTROL; + } + else if (b->out_pos == b->out_size || + (b->in_pos == b->in_size && s->temp.size < s->lzma2.compressed)) + { + return XZ_OK; + } + + break; + + case SEQ_COPY: + dict_uncompressed(&s->dict, b, &s->lzma2.compressed); + if (s->lzma2.compressed > 0) + return XZ_OK; + + s->lzma2.sequence = SEQ_CONTROL; + break; + } + } + + return XZ_OK; } XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, uint32_t dict_max) { - struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s == NULL) - return NULL; - - s->dict.mode = mode; - s->dict.size_max = dict_max; - - if (DEC_IS_PREALLOC(mode)) - { - s->dict.buf = vmalloc(dict_max); - if (s->dict.buf == NULL) - { - kfree(s); - return NULL; - } - } - else if (DEC_IS_DYNALLOC(mode)) - { - s->dict.buf = NULL; - s->dict.allocated = 0; - } - - return s; + struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->dict.mode = mode; + s->dict.size_max = dict_max; + + if (DEC_IS_PREALLOC(mode)) + { + s->dict.buf = vmalloc(dict_max); + if (s->dict.buf == NULL) + { + kfree(s); + return NULL; + } + } + else if (DEC_IS_DYNALLOC(mode)) + { + s->dict.buf = NULL; + s->dict.allocated = 0; + } + + return s; } XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props) { - /* This limits dictionary size to 3 GiB to keep parsing simpler. */ - if (props > 39) - return XZ_OPTIONS_ERROR; - - s->dict.size = 2 + (props & 1); - s->dict.size <<= (props >> 1) + 11; - - if (DEC_IS_MULTI(s->dict.mode)) - { - if (s->dict.size > s->dict.size_max) - return XZ_MEMLIMIT_ERROR; - - s->dict.end = s->dict.size; - - if (DEC_IS_DYNALLOC(s->dict.mode)) - { - if (s->dict.allocated < s->dict.size) - { - vfree(s->dict.buf); - s->dict.buf = vmalloc(s->dict.size); - if (s->dict.buf == NULL) - { - s->dict.allocated = 0; - return XZ_MEM_ERROR; - } - } - } - } - - s->lzma.len = 0; - - s->lzma2.sequence = SEQ_CONTROL; - s->lzma2.need_dict_reset = true; - - s->temp.size = 0; - - return XZ_OK; + /* This limits dictionary size to 3 GiB to keep parsing simpler. */ + if (props > 39) + return XZ_OPTIONS_ERROR; + + s->dict.size = 2 + (props & 1); + s->dict.size <<= (props >> 1) + 11; + + if (DEC_IS_MULTI(s->dict.mode)) + { + if (s->dict.size > s->dict.size_max) + return XZ_MEMLIMIT_ERROR; + + s->dict.end = s->dict.size; + + if (DEC_IS_DYNALLOC(s->dict.mode)) + { + if (s->dict.allocated < s->dict.size) + { + vfree(s->dict.buf); + s->dict.buf = vmalloc(s->dict.size); + if (s->dict.buf == NULL) + { + s->dict.allocated = 0; + return XZ_MEM_ERROR; + } + } + } + } + + s->lzma.len = 0; + + s->lzma2.sequence = SEQ_CONTROL; + s->lzma2.need_dict_reset = true; + + s->temp.size = 0; + + return XZ_OK; } XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s) { - if (DEC_IS_MULTI(s->dict.mode)) - vfree(s->dict.buf); + if (DEC_IS_MULTI(s->dict.mode)) + vfree(s->dict.buf); - kfree(s); + kfree(s); } diff --git a/libraries/xz-embedded/src/xz_dec_stream.c b/libraries/xz-embedded/src/xz_dec_stream.c index 6e935ded..f8d7be05 100644 --- a/libraries/xz-embedded/src/xz_dec_stream.c +++ b/libraries/xz-embedded/src/xz_dec_stream.c @@ -19,146 +19,146 @@ /* Hash used to validate the Index field */ struct xz_dec_hash { - vli_type unpadded; - vli_type uncompressed; - uint32_t crc32; + vli_type unpadded; + vli_type uncompressed; + uint32_t crc32; }; struct xz_dec { - /* Position in dec_main() */ - enum - { - SEQ_STREAM_HEADER, - SEQ_BLOCK_START, - SEQ_BLOCK_HEADER, - SEQ_BLOCK_UNCOMPRESS, - SEQ_BLOCK_PADDING, - SEQ_BLOCK_CHECK, - SEQ_INDEX, - SEQ_INDEX_PADDING, - SEQ_INDEX_CRC32, - SEQ_STREAM_FOOTER - } sequence; - - /* Position in variable-length integers and Check fields */ - uint32_t pos; - - /* Variable-length integer decoded by dec_vli() */ - vli_type vli; - - /* Saved in_pos and out_pos */ - size_t in_start; - size_t out_start; + /* Position in dec_main() */ + enum + { + SEQ_STREAM_HEADER, + SEQ_BLOCK_START, + SEQ_BLOCK_HEADER, + SEQ_BLOCK_UNCOMPRESS, + SEQ_BLOCK_PADDING, + SEQ_BLOCK_CHECK, + SEQ_INDEX, + SEQ_INDEX_PADDING, + SEQ_INDEX_CRC32, + SEQ_STREAM_FOOTER + } sequence; + + /* Position in variable-length integers and Check fields */ + uint32_t pos; + + /* Variable-length integer decoded by dec_vli() */ + vli_type vli; + + /* Saved in_pos and out_pos */ + size_t in_start; + size_t out_start; #ifdef XZ_USE_CRC64 - /* CRC32 or CRC64 value in Block or CRC32 value in Index */ - uint64_t crc; + /* CRC32 or CRC64 value in Block or CRC32 value in Index */ + uint64_t crc; #else - /* CRC32 value in Block or Index */ - uint32_t crc; + /* CRC32 value in Block or Index */ + uint32_t crc; #endif - /* Type of the integrity check calculated from uncompressed data */ - enum xz_check check_type; - - /* Operation mode */ - enum xz_mode mode; - - /* - * True if the next call to xz_dec_run() is allowed to return - * XZ_BUF_ERROR. - */ - bool allow_buf_error; - - /* Information stored in Block Header */ - struct - { - /* - * Value stored in the Compressed Size field, or - * VLI_UNKNOWN if Compressed Size is not present. - */ - vli_type compressed; - - /* - * Value stored in the Uncompressed Size field, or - * VLI_UNKNOWN if Uncompressed Size is not present. - */ - vli_type uncompressed; - - /* Size of the Block Header field */ - uint32_t size; - } block_header; - - /* Information collected when decoding Blocks */ - struct - { - /* Observed compressed size of the current Block */ - vli_type compressed; - - /* Observed uncompressed size of the current Block */ - vli_type uncompressed; - - /* Number of Blocks decoded so far */ - vli_type count; - - /* - * Hash calculated from the Block sizes. This is used to - * validate the Index field. - */ - struct xz_dec_hash hash; - } block; - - /* Variables needed when verifying the Index field */ - struct - { - /* Position in dec_index() */ - enum - { - SEQ_INDEX_COUNT, - SEQ_INDEX_UNPADDED, - SEQ_INDEX_UNCOMPRESSED - } sequence; - - /* Size of the Index in bytes */ - vli_type size; - - /* Number of Records (matches block.count in valid files) */ - vli_type count; - - /* - * Hash calculated from the Records (matches block.hash in - * valid files). - */ - struct xz_dec_hash hash; - } index; - - /* - * Temporary buffer needed to hold Stream Header, Block Header, - * and Stream Footer. The Block Header is the biggest (1 KiB) - * so we reserve space according to that. buf[] has to be aligned - * to a multiple of four bytes; the size_t variables before it - * should guarantee this. - */ - struct - { - size_t pos; - size_t size; - uint8_t buf[1024]; - } temp; - - struct xz_dec_lzma2 *lzma2; + /* Type of the integrity check calculated from uncompressed data */ + enum xz_check check_type; + + /* Operation mode */ + enum xz_mode mode; + + /* + * True if the next call to xz_dec_run() is allowed to return + * XZ_BUF_ERROR. + */ + bool allow_buf_error; + + /* Information stored in Block Header */ + struct + { + /* + * Value stored in the Compressed Size field, or + * VLI_UNKNOWN if Compressed Size is not present. + */ + vli_type compressed; + + /* + * Value stored in the Uncompressed Size field, or + * VLI_UNKNOWN if Uncompressed Size is not present. + */ + vli_type uncompressed; + + /* Size of the Block Header field */ + uint32_t size; + } block_header; + + /* Information collected when decoding Blocks */ + struct + { + /* Observed compressed size of the current Block */ + vli_type compressed; + + /* Observed uncompressed size of the current Block */ + vli_type uncompressed; + + /* Number of Blocks decoded so far */ + vli_type count; + + /* + * Hash calculated from the Block sizes. This is used to + * validate the Index field. + */ + struct xz_dec_hash hash; + } block; + + /* Variables needed when verifying the Index field */ + struct + { + /* Position in dec_index() */ + enum + { + SEQ_INDEX_COUNT, + SEQ_INDEX_UNPADDED, + SEQ_INDEX_UNCOMPRESSED + } sequence; + + /* Size of the Index in bytes */ + vli_type size; + + /* Number of Records (matches block.count in valid files) */ + vli_type count; + + /* + * Hash calculated from the Records (matches block.hash in + * valid files). + */ + struct xz_dec_hash hash; + } index; + + /* + * Temporary buffer needed to hold Stream Header, Block Header, + * and Stream Footer. The Block Header is the biggest (1 KiB) + * so we reserve space according to that. buf[] has to be aligned + * to a multiple of four bytes; the size_t variables before it + * should guarantee this. + */ + struct + { + size_t pos; + size_t size; + uint8_t buf[1024]; + } temp; + + struct xz_dec_lzma2 *lzma2; #ifdef XZ_DEC_BCJ - struct xz_dec_bcj *bcj; - bool bcj_active; + struct xz_dec_bcj *bcj; + bool bcj_active; #endif }; #ifdef XZ_DEC_ANY_CHECK /* Sizes of the Check field with different Check IDs */ static const uint8_t check_sizes[16] = {0, 4, 4, 4, 8, 8, 8, 16, - 16, 16, 32, 32, 32, 64, 64, 64}; + 16, 16, 32, 32, 32, 64, 64, 64}; #endif /* @@ -169,52 +169,52 @@ static const uint8_t check_sizes[16] = {0, 4, 4, 4, 8, 8, 8, 16, */ static bool fill_temp(struct xz_dec *s, struct xz_buf *b) { - size_t copy_size = min_t(size_t, b->in_size - b->in_pos, s->temp.size - s->temp.pos); + size_t copy_size = min_t(size_t, b->in_size - b->in_pos, s->temp.size - s->temp.pos); - memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); - b->in_pos += copy_size; - s->temp.pos += copy_size; + memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); + b->in_pos += copy_size; + s->temp.pos += copy_size; - if (s->temp.pos == s->temp.size) - { - s->temp.pos = 0; - return true; - } + if (s->temp.pos == s->temp.size) + { + s->temp.pos = 0; + return true; + } - return false; + return false; } /* Decode a variable-length integer (little-endian base-128 encoding) */ static enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in, size_t *in_pos, size_t in_size) { - uint8_t byte; + uint8_t byte; - if (s->pos == 0) - s->vli = 0; + if (s->pos == 0) + s->vli = 0; - while (*in_pos < in_size) - { - byte = in[*in_pos]; - ++*in_pos; + while (*in_pos < in_size) + { + byte = in[*in_pos]; + ++*in_pos; - s->vli |= (vli_type)(byte & 0x7F) << s->pos; + s->vli |= (vli_type)(byte & 0x7F) << s->pos; - if ((byte & 0x80) == 0) - { - /* Don't allow non-minimal encodings. */ - if (byte == 0 && s->pos != 0) - return XZ_DATA_ERROR; + if ((byte & 0x80) == 0) + { + /* Don't allow non-minimal encodings. */ + if (byte == 0 && s->pos != 0) + return XZ_DATA_ERROR; - s->pos = 0; - return XZ_STREAM_END; - } + s->pos = 0; + return XZ_STREAM_END; + } - s->pos += 7; - if (s->pos == 7 * VLI_BYTES_MAX) - return XZ_DATA_ERROR; - } + s->pos += 7; + if (s->pos == 7 * VLI_BYTES_MAX) + return XZ_DATA_ERROR; + } - return XZ_OK; + return XZ_OK; } /* @@ -231,73 +231,73 @@ static enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in, size_t *in_pos, */ static enum xz_ret dec_block(struct xz_dec *s, struct xz_buf *b) { - enum xz_ret ret; + enum xz_ret ret; - s->in_start = b->in_pos; - s->out_start = b->out_pos; + s->in_start = b->in_pos; + s->out_start = b->out_pos; #ifdef XZ_DEC_BCJ - if (s->bcj_active) - ret = xz_dec_bcj_run(s->bcj, s->lzma2, b); - else + if (s->bcj_active) + ret = xz_dec_bcj_run(s->bcj, s->lzma2, b); + else #endif - ret = xz_dec_lzma2_run(s->lzma2, b); + ret = xz_dec_lzma2_run(s->lzma2, b); - s->block.compressed += b->in_pos - s->in_start; - s->block.uncompressed += b->out_pos - s->out_start; + s->block.compressed += b->in_pos - s->in_start; + s->block.uncompressed += b->out_pos - s->out_start; - /* - * There is no need to separately check for VLI_UNKNOWN, since - * the observed sizes are always smaller than VLI_UNKNOWN. - */ - if (s->block.compressed > s->block_header.compressed || - s->block.uncompressed > s->block_header.uncompressed) - return XZ_DATA_ERROR; + /* + * There is no need to separately check for VLI_UNKNOWN, since + * the observed sizes are always smaller than VLI_UNKNOWN. + */ + if (s->block.compressed > s->block_header.compressed || + s->block.uncompressed > s->block_header.uncompressed) + return XZ_DATA_ERROR; - if (s->check_type == XZ_CHECK_CRC32) - s->crc = xz_crc32(b->out + s->out_start, b->out_pos - s->out_start, s->crc); + if (s->check_type == XZ_CHECK_CRC32) + s->crc = xz_crc32(b->out + s->out_start, b->out_pos - s->out_start, s->crc); #ifdef XZ_USE_CRC64 - else if (s->check_type == XZ_CHECK_CRC64) - s->crc = xz_crc64(b->out + s->out_start, b->out_pos - s->out_start, s->crc); + else if (s->check_type == XZ_CHECK_CRC64) + s->crc = xz_crc64(b->out + s->out_start, b->out_pos - s->out_start, s->crc); #endif - if (ret == XZ_STREAM_END) - { - if (s->block_header.compressed != VLI_UNKNOWN && - s->block_header.compressed != s->block.compressed) - return XZ_DATA_ERROR; + if (ret == XZ_STREAM_END) + { + if (s->block_header.compressed != VLI_UNKNOWN && + s->block_header.compressed != s->block.compressed) + return XZ_DATA_ERROR; - if (s->block_header.uncompressed != VLI_UNKNOWN && - s->block_header.uncompressed != s->block.uncompressed) - return XZ_DATA_ERROR; + if (s->block_header.uncompressed != VLI_UNKNOWN && + s->block_header.uncompressed != s->block.uncompressed) + return XZ_DATA_ERROR; - s->block.hash.unpadded += s->block_header.size + s->block.compressed; + s->block.hash.unpadded += s->block_header.size + s->block.compressed; #ifdef XZ_DEC_ANY_CHECK - s->block.hash.unpadded += check_sizes[s->check_type]; + s->block.hash.unpadded += check_sizes[s->check_type]; #else - if (s->check_type == XZ_CHECK_CRC32) - s->block.hash.unpadded += 4; - else if (IS_CRC64(s->check_type)) - s->block.hash.unpadded += 8; + if (s->check_type == XZ_CHECK_CRC32) + s->block.hash.unpadded += 4; + else if (IS_CRC64(s->check_type)) + s->block.hash.unpadded += 8; #endif - s->block.hash.uncompressed += s->block.uncompressed; - s->block.hash.crc32 = xz_crc32((const uint8_t *)&s->block.hash, sizeof(s->block.hash), - s->block.hash.crc32); + s->block.hash.uncompressed += s->block.uncompressed; + s->block.hash.crc32 = xz_crc32((const uint8_t *)&s->block.hash, sizeof(s->block.hash), + s->block.hash.crc32); - ++s->block.count; - } + ++s->block.count; + } - return ret; + return ret; } /* Update the Index size and the CRC32 value. */ static void index_update(struct xz_dec *s, const struct xz_buf *b) { - size_t in_used = b->in_pos - s->in_start; - s->index.size += in_used; - s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc); + size_t in_used = b->in_pos - s->in_start; + s->index.size += in_used; + s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc); } /* @@ -310,49 +310,49 @@ static void index_update(struct xz_dec *s, const struct xz_buf *b) */ static enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b) { - enum xz_ret ret; - - do - { - ret = dec_vli(s, b->in, &b->in_pos, b->in_size); - if (ret != XZ_STREAM_END) - { - index_update(s, b); - return ret; - } - - switch (s->index.sequence) - { - case SEQ_INDEX_COUNT: - s->index.count = s->vli; - - /* - * Validate that the Number of Records field - * indicates the same number of Records as - * there were Blocks in the Stream. - */ - if (s->index.count != s->block.count) - return XZ_DATA_ERROR; - - s->index.sequence = SEQ_INDEX_UNPADDED; - break; - - case SEQ_INDEX_UNPADDED: - s->index.hash.unpadded += s->vli; - s->index.sequence = SEQ_INDEX_UNCOMPRESSED; - break; - - case SEQ_INDEX_UNCOMPRESSED: - s->index.hash.uncompressed += s->vli; - s->index.hash.crc32 = xz_crc32((const uint8_t *)&s->index.hash, - sizeof(s->index.hash), s->index.hash.crc32); - --s->index.count; - s->index.sequence = SEQ_INDEX_UNPADDED; - break; - } - } while (s->index.count > 0); - - return XZ_STREAM_END; + enum xz_ret ret; + + do + { + ret = dec_vli(s, b->in, &b->in_pos, b->in_size); + if (ret != XZ_STREAM_END) + { + index_update(s, b); + return ret; + } + + switch (s->index.sequence) + { + case SEQ_INDEX_COUNT: + s->index.count = s->vli; + + /* + * Validate that the Number of Records field + * indicates the same number of Records as + * there were Blocks in the Stream. + */ + if (s->index.count != s->block.count) + return XZ_DATA_ERROR; + + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + + case SEQ_INDEX_UNPADDED: + s->index.hash.unpadded += s->vli; + s->index.sequence = SEQ_INDEX_UNCOMPRESSED; + break; + + case SEQ_INDEX_UNCOMPRESSED: + s->index.hash.uncompressed += s->vli; + s->index.hash.crc32 = xz_crc32((const uint8_t *)&s->index.hash, + sizeof(s->index.hash), s->index.hash.crc32); + --s->index.count; + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + } + } while (s->index.count > 0); + + return XZ_STREAM_END; } /* @@ -362,22 +362,22 @@ static enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b) */ static enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b, uint32_t bits) { - do - { - if (b->in_pos == b->in_size) - return XZ_OK; + do + { + if (b->in_pos == b->in_size) + return XZ_OK; - if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++]) - return XZ_DATA_ERROR; + if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++]) + return XZ_DATA_ERROR; - s->pos += 8; + s->pos += 8; - } while (s->pos < bits); + } while (s->pos < bits); - s->crc = 0; - s->pos = 0; + s->crc = 0; + s->pos = 0; - return XZ_STREAM_END; + return XZ_STREAM_END; } #ifdef XZ_DEC_ANY_CHECK @@ -387,358 +387,358 @@ static enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b, uint32_t bit */ static bool check_skip(struct xz_dec *s, struct xz_buf *b) { - while (s->pos < check_sizes[s->check_type]) - { - if (b->in_pos == b->in_size) - return false; + while (s->pos < check_sizes[s->check_type]) + { + if (b->in_pos == b->in_size) + return false; - ++b->in_pos; - ++s->pos; - } + ++b->in_pos; + ++s->pos; + } - s->pos = 0; + s->pos = 0; - return true; + return true; } #endif /* Decode the Stream Header field (the first 12 bytes of the .xz Stream). */ static enum xz_ret dec_stream_header(struct xz_dec *s) { - if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE)) - return XZ_FORMAT_ERROR; + if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE)) + return XZ_FORMAT_ERROR; - if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0) != - get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2)) - return XZ_DATA_ERROR; + if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0) != + get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2)) + return XZ_DATA_ERROR; - if (s->temp.buf[HEADER_MAGIC_SIZE] != 0) - return XZ_OPTIONS_ERROR; + if (s->temp.buf[HEADER_MAGIC_SIZE] != 0) + return XZ_OPTIONS_ERROR; - /* - * Of integrity checks, we support none (Check ID = 0), - * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4). - * However, if XZ_DEC_ANY_CHECK is defined, we will accept other - * check types too, but then the check won't be verified and - * a warning (XZ_UNSUPPORTED_CHECK) will be given. - */ - s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1]; + /* + * Of integrity checks, we support none (Check ID = 0), + * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4). + * However, if XZ_DEC_ANY_CHECK is defined, we will accept other + * check types too, but then the check won't be verified and + * a warning (XZ_UNSUPPORTED_CHECK) will be given. + */ + s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1]; #ifdef XZ_DEC_ANY_CHECK - if (s->check_type > XZ_CHECK_MAX) - return XZ_OPTIONS_ERROR; + if (s->check_type > XZ_CHECK_MAX) + return XZ_OPTIONS_ERROR; - if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) - return XZ_UNSUPPORTED_CHECK; + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_UNSUPPORTED_CHECK; #else - if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) - return XZ_OPTIONS_ERROR; + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_OPTIONS_ERROR; #endif - return XZ_OK; + return XZ_OK; } /* Decode the Stream Footer field (the last 12 bytes of the .xz Stream) */ static enum xz_ret dec_stream_footer(struct xz_dec *s) { - if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE)) - return XZ_DATA_ERROR; - - if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf)) - return XZ_DATA_ERROR; - - /* - * Validate Backward Size. Note that we never added the size of the - * Index CRC32 field to s->index.size, thus we use s->index.size / 4 - * instead of s->index.size / 4 - 1. - */ - if ((s->index.size >> 2) != get_le32(s->temp.buf + 4)) - return XZ_DATA_ERROR; - - if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type) - return XZ_DATA_ERROR; - - /* - * Use XZ_STREAM_END instead of XZ_OK to be more convenient - * for the caller. - */ - return XZ_STREAM_END; + if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE)) + return XZ_DATA_ERROR; + + if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf)) + return XZ_DATA_ERROR; + + /* + * Validate Backward Size. Note that we never added the size of the + * Index CRC32 field to s->index.size, thus we use s->index.size / 4 + * instead of s->index.size / 4 - 1. + */ + if ((s->index.size >> 2) != get_le32(s->temp.buf + 4)) + return XZ_DATA_ERROR; + + if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type) + return XZ_DATA_ERROR; + + /* + * Use XZ_STREAM_END instead of XZ_OK to be more convenient + * for the caller. + */ + return XZ_STREAM_END; } /* Decode the Block Header and initialize the filter chain. */ static enum xz_ret dec_block_header(struct xz_dec *s) { - enum xz_ret ret; + enum xz_ret ret; - /* - * Validate the CRC32. We know that the temp buffer is at least - * eight bytes so this is safe. - */ - s->temp.size -= 4; - if (xz_crc32(s->temp.buf, s->temp.size, 0) != get_le32(s->temp.buf + s->temp.size)) - return XZ_DATA_ERROR; + /* + * Validate the CRC32. We know that the temp buffer is at least + * eight bytes so this is safe. + */ + s->temp.size -= 4; + if (xz_crc32(s->temp.buf, s->temp.size, 0) != get_le32(s->temp.buf + s->temp.size)) + return XZ_DATA_ERROR; - s->temp.pos = 2; + s->temp.pos = 2; /* * Catch unsupported Block Flags. We support only one or two filters * in the chain, so we catch that with the same test. */ #ifdef XZ_DEC_BCJ - if (s->temp.buf[1] & 0x3E) + if (s->temp.buf[1] & 0x3E) #else - if (s->temp.buf[1] & 0x3F) + if (s->temp.buf[1] & 0x3F) #endif - return XZ_OPTIONS_ERROR; - - /* Compressed Size */ - if (s->temp.buf[1] & 0x40) - { - if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) - return XZ_DATA_ERROR; - - s->block_header.compressed = s->vli; - } - else - { - s->block_header.compressed = VLI_UNKNOWN; - } - - /* Uncompressed Size */ - if (s->temp.buf[1] & 0x80) - { - if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) - return XZ_DATA_ERROR; - - s->block_header.uncompressed = s->vli; - } - else - { - s->block_header.uncompressed = VLI_UNKNOWN; - } + return XZ_OPTIONS_ERROR; + + /* Compressed Size */ + if (s->temp.buf[1] & 0x40) + { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.compressed = s->vli; + } + else + { + s->block_header.compressed = VLI_UNKNOWN; + } + + /* Uncompressed Size */ + if (s->temp.buf[1] & 0x80) + { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.uncompressed = s->vli; + } + else + { + s->block_header.uncompressed = VLI_UNKNOWN; + } #ifdef XZ_DEC_BCJ - /* If there are two filters, the first one must be a BCJ filter. */ - s->bcj_active = s->temp.buf[1] & 0x01; - if (s->bcj_active) - { - if (s->temp.size - s->temp.pos < 2) - return XZ_OPTIONS_ERROR; - - ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]); - if (ret != XZ_OK) - return ret; - - /* - * We don't support custom start offset, - * so Size of Properties must be zero. - */ - if (s->temp.buf[s->temp.pos++] != 0x00) - return XZ_OPTIONS_ERROR; - } + /* If there are two filters, the first one must be a BCJ filter. */ + s->bcj_active = s->temp.buf[1] & 0x01; + if (s->bcj_active) + { + if (s->temp.size - s->temp.pos < 2) + return XZ_OPTIONS_ERROR; + + ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; + + /* + * We don't support custom start offset, + * so Size of Properties must be zero. + */ + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; + } #endif - /* Valid Filter Flags always take at least two bytes. */ - if (s->temp.size - s->temp.pos < 2) - return XZ_DATA_ERROR; + /* Valid Filter Flags always take at least two bytes. */ + if (s->temp.size - s->temp.pos < 2) + return XZ_DATA_ERROR; - /* Filter ID = LZMA2 */ - if (s->temp.buf[s->temp.pos++] != 0x21) - return XZ_OPTIONS_ERROR; + /* Filter ID = LZMA2 */ + if (s->temp.buf[s->temp.pos++] != 0x21) + return XZ_OPTIONS_ERROR; - /* Size of Properties = 1-byte Filter Properties */ - if (s->temp.buf[s->temp.pos++] != 0x01) - return XZ_OPTIONS_ERROR; + /* Size of Properties = 1-byte Filter Properties */ + if (s->temp.buf[s->temp.pos++] != 0x01) + return XZ_OPTIONS_ERROR; - /* Filter Properties contains LZMA2 dictionary size. */ - if (s->temp.size - s->temp.pos < 1) - return XZ_DATA_ERROR; + /* Filter Properties contains LZMA2 dictionary size. */ + if (s->temp.size - s->temp.pos < 1) + return XZ_DATA_ERROR; - ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]); - if (ret != XZ_OK) - return ret; + ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; - /* The rest must be Header Padding. */ - while (s->temp.pos < s->temp.size) - if (s->temp.buf[s->temp.pos++] != 0x00) - return XZ_OPTIONS_ERROR; + /* The rest must be Header Padding. */ + while (s->temp.pos < s->temp.size) + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; - s->temp.pos = 0; - s->block.compressed = 0; - s->block.uncompressed = 0; + s->temp.pos = 0; + s->block.compressed = 0; + s->block.uncompressed = 0; - return XZ_OK; + return XZ_OK; } static enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b) { - enum xz_ret ret; - - /* - * Store the start position for the case when we are in the middle - * of the Index field. - */ - s->in_start = b->in_pos; - - while (true) - { - switch (s->sequence) - { - case SEQ_STREAM_HEADER: - /* - * Stream Header is copied to s->temp, and then - * decoded from there. This way if the caller - * gives us only little input at a time, we can - * still keep the Stream Header decoding code - * simple. Similar approach is used in many places - * in this file. - */ - if (!fill_temp(s, b)) - return XZ_OK; - - /* - * If dec_stream_header() returns - * XZ_UNSUPPORTED_CHECK, it is still possible - * to continue decoding if working in multi-call - * mode. Thus, update s->sequence before calling - * dec_stream_header(). - */ - s->sequence = SEQ_BLOCK_START; - - ret = dec_stream_header(s); - if (ret != XZ_OK) - return ret; - - case SEQ_BLOCK_START: - /* We need one byte of input to continue. */ - if (b->in_pos == b->in_size) - return XZ_OK; - - /* See if this is the beginning of the Index field. */ - if (b->in[b->in_pos] == 0) - { - s->in_start = b->in_pos++; - s->sequence = SEQ_INDEX; - break; - } - - /* - * Calculate the size of the Block Header and - * prepare to decode it. - */ - s->block_header.size = ((uint32_t)b->in[b->in_pos] + 1) * 4; - - s->temp.size = s->block_header.size; - s->temp.pos = 0; - s->sequence = SEQ_BLOCK_HEADER; - - case SEQ_BLOCK_HEADER: - if (!fill_temp(s, b)) - return XZ_OK; - - ret = dec_block_header(s); - if (ret != XZ_OK) - return ret; - - s->sequence = SEQ_BLOCK_UNCOMPRESS; - - case SEQ_BLOCK_UNCOMPRESS: - ret = dec_block(s, b); - if (ret != XZ_STREAM_END) - return ret; - - s->sequence = SEQ_BLOCK_PADDING; - - case SEQ_BLOCK_PADDING: - /* - * Size of Compressed Data + Block Padding - * must be a multiple of four. We don't need - * s->block.compressed for anything else - * anymore, so we use it here to test the size - * of the Block Padding field. - */ - while (s->block.compressed & 3) - { - if (b->in_pos == b->in_size) - return XZ_OK; - - if (b->in[b->in_pos++] != 0) - return XZ_DATA_ERROR; - - ++s->block.compressed; - } - - s->sequence = SEQ_BLOCK_CHECK; - - case SEQ_BLOCK_CHECK: - if (s->check_type == XZ_CHECK_CRC32) - { - ret = crc_validate(s, b, 32); - if (ret != XZ_STREAM_END) - return ret; - } - else if (IS_CRC64(s->check_type)) - { - ret = crc_validate(s, b, 64); - if (ret != XZ_STREAM_END) - return ret; - } + enum xz_ret ret; + + /* + * Store the start position for the case when we are in the middle + * of the Index field. + */ + s->in_start = b->in_pos; + + while (true) + { + switch (s->sequence) + { + case SEQ_STREAM_HEADER: + /* + * Stream Header is copied to s->temp, and then + * decoded from there. This way if the caller + * gives us only little input at a time, we can + * still keep the Stream Header decoding code + * simple. Similar approach is used in many places + * in this file. + */ + if (!fill_temp(s, b)) + return XZ_OK; + + /* + * If dec_stream_header() returns + * XZ_UNSUPPORTED_CHECK, it is still possible + * to continue decoding if working in multi-call + * mode. Thus, update s->sequence before calling + * dec_stream_header(). + */ + s->sequence = SEQ_BLOCK_START; + + ret = dec_stream_header(s); + if (ret != XZ_OK) + return ret; + + case SEQ_BLOCK_START: + /* We need one byte of input to continue. */ + if (b->in_pos == b->in_size) + return XZ_OK; + + /* See if this is the beginning of the Index field. */ + if (b->in[b->in_pos] == 0) + { + s->in_start = b->in_pos++; + s->sequence = SEQ_INDEX; + break; + } + + /* + * Calculate the size of the Block Header and + * prepare to decode it. + */ + s->block_header.size = ((uint32_t)b->in[b->in_pos] + 1) * 4; + + s->temp.size = s->block_header.size; + s->temp.pos = 0; + s->sequence = SEQ_BLOCK_HEADER; + + case SEQ_BLOCK_HEADER: + if (!fill_temp(s, b)) + return XZ_OK; + + ret = dec_block_header(s); + if (ret != XZ_OK) + return ret; + + s->sequence = SEQ_BLOCK_UNCOMPRESS; + + case SEQ_BLOCK_UNCOMPRESS: + ret = dec_block(s, b); + if (ret != XZ_STREAM_END) + return ret; + + s->sequence = SEQ_BLOCK_PADDING; + + case SEQ_BLOCK_PADDING: + /* + * Size of Compressed Data + Block Padding + * must be a multiple of four. We don't need + * s->block.compressed for anything else + * anymore, so we use it here to test the size + * of the Block Padding field. + */ + while (s->block.compressed & 3) + { + if (b->in_pos == b->in_size) + return XZ_OK; + + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + + ++s->block.compressed; + } + + s->sequence = SEQ_BLOCK_CHECK; + + case SEQ_BLOCK_CHECK: + if (s->check_type == XZ_CHECK_CRC32) + { + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; + } + else if (IS_CRC64(s->check_type)) + { + ret = crc_validate(s, b, 64); + if (ret != XZ_STREAM_END) + return ret; + } #ifdef XZ_DEC_ANY_CHECK - else if (!check_skip(s, b)) - { - return XZ_OK; - } + else if (!check_skip(s, b)) + { + return XZ_OK; + } #endif - s->sequence = SEQ_BLOCK_START; - break; + s->sequence = SEQ_BLOCK_START; + break; - case SEQ_INDEX: - ret = dec_index(s, b); - if (ret != XZ_STREAM_END) - return ret; + case SEQ_INDEX: + ret = dec_index(s, b); + if (ret != XZ_STREAM_END) + return ret; - s->sequence = SEQ_INDEX_PADDING; + s->sequence = SEQ_INDEX_PADDING; - case SEQ_INDEX_PADDING: - while ((s->index.size + (b->in_pos - s->in_start)) & 3) - { - if (b->in_pos == b->in_size) - { - index_update(s, b); - return XZ_OK; - } + case SEQ_INDEX_PADDING: + while ((s->index.size + (b->in_pos - s->in_start)) & 3) + { + if (b->in_pos == b->in_size) + { + index_update(s, b); + return XZ_OK; + } - if (b->in[b->in_pos++] != 0) - return XZ_DATA_ERROR; - } + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + } - /* Finish the CRC32 value and Index size. */ - index_update(s, b); + /* Finish the CRC32 value and Index size. */ + index_update(s, b); - /* Compare the hashes to validate the Index field. */ - if (!memeq(&s->block.hash, &s->index.hash, sizeof(s->block.hash))) - return XZ_DATA_ERROR; + /* Compare the hashes to validate the Index field. */ + if (!memeq(&s->block.hash, &s->index.hash, sizeof(s->block.hash))) + return XZ_DATA_ERROR; - s->sequence = SEQ_INDEX_CRC32; + s->sequence = SEQ_INDEX_CRC32; - case SEQ_INDEX_CRC32: - ret = crc_validate(s, b, 32); - if (ret != XZ_STREAM_END) - return ret; + case SEQ_INDEX_CRC32: + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; - s->temp.size = STREAM_HEADER_SIZE; - s->sequence = SEQ_STREAM_FOOTER; + s->temp.size = STREAM_HEADER_SIZE; + s->sequence = SEQ_STREAM_FOOTER; - case SEQ_STREAM_FOOTER: - if (!fill_temp(s, b)) - return XZ_OK; + case SEQ_STREAM_FOOTER: + if (!fill_temp(s, b)) + return XZ_OK; - return dec_stream_footer(s); - } - } + return dec_stream_footer(s); + } + } - /* Never reached */ + /* Never reached */ } /* @@ -768,93 +768,93 @@ static enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b) */ XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b) { - size_t in_start; - size_t out_start; - enum xz_ret ret; - - if (DEC_IS_SINGLE(s->mode)) - xz_dec_reset(s); - - in_start = b->in_pos; - out_start = b->out_pos; - ret = dec_main(s, b); - - if (DEC_IS_SINGLE(s->mode)) - { - if (ret == XZ_OK) - ret = b->in_pos == b->in_size ? XZ_DATA_ERROR : XZ_BUF_ERROR; - - if (ret != XZ_STREAM_END) - { - b->in_pos = in_start; - b->out_pos = out_start; - } - } - else if (ret == XZ_OK && in_start == b->in_pos && out_start == b->out_pos) - { - if (s->allow_buf_error) - ret = XZ_BUF_ERROR; - - s->allow_buf_error = true; - } - else - { - s->allow_buf_error = false; - } - - return ret; + size_t in_start; + size_t out_start; + enum xz_ret ret; + + if (DEC_IS_SINGLE(s->mode)) + xz_dec_reset(s); + + in_start = b->in_pos; + out_start = b->out_pos; + ret = dec_main(s, b); + + if (DEC_IS_SINGLE(s->mode)) + { + if (ret == XZ_OK) + ret = b->in_pos == b->in_size ? XZ_DATA_ERROR : XZ_BUF_ERROR; + + if (ret != XZ_STREAM_END) + { + b->in_pos = in_start; + b->out_pos = out_start; + } + } + else if (ret == XZ_OK && in_start == b->in_pos && out_start == b->out_pos) + { + if (s->allow_buf_error) + ret = XZ_BUF_ERROR; + + s->allow_buf_error = true; + } + else + { + s->allow_buf_error = false; + } + + return ret; } XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max) { - struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s == NULL) - return NULL; + struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; - s->mode = mode; + s->mode = mode; #ifdef XZ_DEC_BCJ - s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode)); - if (s->bcj == NULL) - goto error_bcj; + s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode)); + if (s->bcj == NULL) + goto error_bcj; #endif - s->lzma2 = xz_dec_lzma2_create(mode, dict_max); - if (s->lzma2 == NULL) - goto error_lzma2; + s->lzma2 = xz_dec_lzma2_create(mode, dict_max); + if (s->lzma2 == NULL) + goto error_lzma2; - xz_dec_reset(s); - return s; + xz_dec_reset(s); + return s; error_lzma2: #ifdef XZ_DEC_BCJ - xz_dec_bcj_end(s->bcj); + xz_dec_bcj_end(s->bcj); error_bcj: #endif - kfree(s); - return NULL; + kfree(s); + return NULL; } XZ_EXTERN void xz_dec_reset(struct xz_dec *s) { - s->sequence = SEQ_STREAM_HEADER; - s->allow_buf_error = false; - s->pos = 0; - s->crc = 0; - memzero(&s->block, sizeof(s->block)); - memzero(&s->index, sizeof(s->index)); - s->temp.pos = 0; - s->temp.size = STREAM_HEADER_SIZE; + s->sequence = SEQ_STREAM_HEADER; + s->allow_buf_error = false; + s->pos = 0; + s->crc = 0; + memzero(&s->block, sizeof(s->block)); + memzero(&s->index, sizeof(s->index)); + s->temp.pos = 0; + s->temp.size = STREAM_HEADER_SIZE; } XZ_EXTERN void xz_dec_end(struct xz_dec *s) { - if (s != NULL) - { - xz_dec_lzma2_end(s->lzma2); + if (s != NULL) + { + xz_dec_lzma2_end(s->lzma2); #ifdef XZ_DEC_BCJ - xz_dec_bcj_end(s->bcj); + xz_dec_bcj_end(s->bcj); #endif - kfree(s); - } + kfree(s); + } } diff --git a/libraries/xz-embedded/src/xz_lzma2.h b/libraries/xz-embedded/src/xz_lzma2.h index 3976033a..82a425f2 100644 --- a/libraries/xz-embedded/src/xz_lzma2.h +++ b/libraries/xz-embedded/src/xz_lzma2.h @@ -41,18 +41,18 @@ */ enum lzma_state { - STATE_LIT_LIT, - STATE_MATCH_LIT_LIT, - STATE_REP_LIT_LIT, - STATE_SHORTREP_LIT_LIT, - STATE_MATCH_LIT, - STATE_REP_LIT, - STATE_SHORTREP_LIT, - STATE_LIT_MATCH, - STATE_LIT_LONGREP, - STATE_LIT_SHORTREP, - STATE_NONLIT_MATCH, - STATE_NONLIT_REP + STATE_LIT_LIT, + STATE_MATCH_LIT_LIT, + STATE_REP_LIT_LIT, + STATE_SHORTREP_LIT_LIT, + STATE_MATCH_LIT, + STATE_REP_LIT, + STATE_SHORTREP_LIT, + STATE_LIT_MATCH, + STATE_LIT_LONGREP, + STATE_LIT_SHORTREP, + STATE_NONLIT_MATCH, + STATE_NONLIT_REP }; /* Total number of states */ @@ -64,36 +64,36 @@ enum lzma_state /* Indicate that the latest symbol was a literal. */ static inline void lzma_state_literal(enum lzma_state *state) { - if (*state <= STATE_SHORTREP_LIT_LIT) - *state = STATE_LIT_LIT; - else if (*state <= STATE_LIT_SHORTREP) - *state -= 3; - else - *state -= 6; + if (*state <= STATE_SHORTREP_LIT_LIT) + *state = STATE_LIT_LIT; + else if (*state <= STATE_LIT_SHORTREP) + *state -= 3; + else + *state -= 6; } /* Indicate that the latest symbol was a match. */ static inline void lzma_state_match(enum lzma_state *state) { - *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH; + *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH; } /* Indicate that the latest state was a long repeated match. */ static inline void lzma_state_long_rep(enum lzma_state *state) { - *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP; + *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP; } /* Indicate that the latest symbol was a short match. */ static inline void lzma_state_short_rep(enum lzma_state *state) { - *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP; + *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP; } /* Test if the previous symbol was a literal. */ static inline bool lzma_state_is_literal(enum lzma_state state) { - return state < LIT_STATES; + return state < LIT_STATES; } /* Each literal coder is divided in three sections: @@ -147,7 +147,7 @@ static inline bool lzma_state_is_literal(enum lzma_state state) */ static inline uint32_t lzma_get_dist_state(uint32_t len) { - return len < DIST_STATES + MATCH_LEN_MIN ? len - MATCH_LEN_MIN : DIST_STATES - 1; + return len < DIST_STATES + MATCH_LEN_MIN ? len - MATCH_LEN_MIN : DIST_STATES - 1; } /* diff --git a/libraries/xz-embedded/src/xz_private.h b/libraries/xz-embedded/src/xz_private.h index 55a3af1c..1b616430 100644 --- a/libraries/xz-embedded/src/xz_private.h +++ b/libraries/xz-embedded/src/xz_private.h @@ -94,8 +94,8 @@ */ #ifndef XZ_DEC_BCJ #if defined(XZ_DEC_X86) || defined(XZ_DEC_POWERPC) || defined(XZ_DEC_IA64) || \ - defined(XZ_DEC_ARM) || defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) || \ - defined(XZ_DEC_SPARC) + defined(XZ_DEC_ARM) || defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) || \ + defined(XZ_DEC_SPARC) #define XZ_DEC_BCJ #endif #endif @@ -141,7 +141,7 @@ XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id); * must be called directly. */ XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, struct xz_dec_lzma2 *lzma2, - struct xz_buf *b); + struct xz_buf *b); /* Free the memory allocated for the BCJ filters. */ #define xz_dec_bcj_end(s) kfree(s) diff --git a/libraries/xz-embedded/src/xz_stream.h b/libraries/xz-embedded/src/xz_stream.h index c0e191e6..b3d2c9fd 100644 --- a/libraries/xz-embedded/src/xz_stream.h +++ b/libraries/xz-embedded/src/xz_stream.h @@ -50,10 +50,10 @@ typedef uint64_t vli_type; /* Integrity Check types */ enum xz_check { - XZ_CHECK_NONE = 0, - XZ_CHECK_CRC32 = 1, - XZ_CHECK_CRC64 = 4, - XZ_CHECK_SHA256 = 10 + XZ_CHECK_NONE = 0, + XZ_CHECK_CRC32 = 1, + XZ_CHECK_CRC64 = 4, + XZ_CHECK_SHA256 = 10 }; /* Maximum possible Check ID */ diff --git a/libraries/xz-embedded/xzminidec.c b/libraries/xz-embedded/xzminidec.c index bb62c3ac..44f60602 100644 --- a/libraries/xz-embedded/xzminidec.c +++ b/libraries/xz-embedded/xzminidec.c @@ -24,121 +24,121 @@ static uint8_t out[BUFSIZ]; int main(int argc, char **argv) { - struct xz_buf b; - struct xz_dec *s; - enum xz_ret ret; - const char *msg; - - if (argc >= 2 && strcmp(argv[1], "--help") == 0) - { - fputs("Uncompress a .xz file from stdin to stdout.\n" - "Arguments other than `--help' are ignored.\n", - stdout); - return 0; - } - - xz_crc32_init(); + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + const char *msg; + + if (argc >= 2 && strcmp(argv[1], "--help") == 0) + { + fputs("Uncompress a .xz file from stdin to stdout.\n" + "Arguments other than `--help' are ignored.\n", + stdout); + return 0; + } + + xz_crc32_init(); #ifdef XZ_USE_CRC64 - xz_crc64_init(); + xz_crc64_init(); #endif - /* - * Support up to 64 MiB dictionary. The actually needed memory - * is allocated once the headers have been parsed. - */ - s = xz_dec_init(XZ_DYNALLOC, 1 << 26); - if (s == NULL) - { - msg = "Memory allocation failed\n"; - goto error; - } - - b.in = in; - b.in_pos = 0; - b.in_size = 0; - b.out = out; - b.out_pos = 0; - b.out_size = BUFSIZ; - - while (true) - { - if (b.in_pos == b.in_size) - { - b.in_size = fread(in, 1, sizeof(in), stdin); - b.in_pos = 0; - } - - ret = xz_dec_run(s, &b); - - if (b.out_pos == sizeof(out)) - { - if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos) - { - msg = "Write error\n"; - goto error; - } - - b.out_pos = 0; - } - - if (ret == XZ_OK) - continue; + /* + * Support up to 64 MiB dictionary. The actually needed memory + * is allocated once the headers have been parsed. + */ + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == NULL) + { + msg = "Memory allocation failed\n"; + goto error; + } + + b.in = in; + b.in_pos = 0; + b.in_size = 0; + b.out = out; + b.out_pos = 0; + b.out_size = BUFSIZ; + + while (true) + { + if (b.in_pos == b.in_size) + { + b.in_size = fread(in, 1, sizeof(in), stdin); + b.in_pos = 0; + } + + ret = xz_dec_run(s, &b); + + if (b.out_pos == sizeof(out)) + { + if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos) + { + msg = "Write error\n"; + goto error; + } + + b.out_pos = 0; + } + + if (ret == XZ_OK) + continue; #ifdef XZ_DEC_ANY_CHECK - if (ret == XZ_UNSUPPORTED_CHECK) - { - fputs(argv[0], stderr); - fputs(": ", stderr); - fputs("Unsupported check; not verifying " - "file integrity\n", - stderr); - continue; - } + if (ret == XZ_UNSUPPORTED_CHECK) + { + fputs(argv[0], stderr); + fputs(": ", stderr); + fputs("Unsupported check; not verifying " + "file integrity\n", + stderr); + continue; + } #endif - if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos || fclose(stdout)) - { - msg = "Write error\n"; - goto error; - } - - switch (ret) - { - case XZ_STREAM_END: - xz_dec_end(s); - return 0; - - case XZ_MEM_ERROR: - msg = "Memory allocation failed\n"; - goto error; - - case XZ_MEMLIMIT_ERROR: - msg = "Memory usage limit reached\n"; - goto error; - - case XZ_FORMAT_ERROR: - msg = "Not a .xz file\n"; - goto error; - - case XZ_OPTIONS_ERROR: - msg = "Unsupported options in the .xz headers\n"; - goto error; - - case XZ_DATA_ERROR: - case XZ_BUF_ERROR: - msg = "File is corrupt\n"; - goto error; - - default: - msg = "Bug!\n"; - goto error; - } - } + if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos || fclose(stdout)) + { + msg = "Write error\n"; + goto error; + } + + switch (ret) + { + case XZ_STREAM_END: + xz_dec_end(s); + return 0; + + case XZ_MEM_ERROR: + msg = "Memory allocation failed\n"; + goto error; + + case XZ_MEMLIMIT_ERROR: + msg = "Memory usage limit reached\n"; + goto error; + + case XZ_FORMAT_ERROR: + msg = "Not a .xz file\n"; + goto error; + + case XZ_OPTIONS_ERROR: + msg = "Unsupported options in the .xz headers\n"; + goto error; + + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + msg = "File is corrupt\n"; + goto error; + + default: + msg = "Bug!\n"; + goto error; + } + } error: - xz_dec_end(s); - fputs(argv[0], stderr); - fputs(": ", stderr); - fputs(msg, stderr); - return 1; + xz_dec_end(s); + fputs(argv[0], stderr); + fputs(": ", stderr); + fputs(msg, stderr); + return 1; } diff --git a/travis/prepare.sh b/travis/prepare.sh index edf9df73..9395bc78 100644 --- a/travis/prepare.sh +++ b/travis/prepare.sh @@ -2,45 +2,45 @@ set -e if [ "$TRAVIS_OS_NAME" = "linux" ] then - QT_WITHOUT_DOTS=qt$(echo $QT_VERSION | grep -oP "[^\.]*" | tr -d '\n' | tr '[:upper:]' '[:lower]') - QT_PKG_PREFIX=$(echo $QT_WITHOUT_DOTS | cut -c1-4) - QT_PKG_INSTALL=$QT_PKG_PREFIX - if [ "$QT_PKG_PREFIX" = "qt50" ]; then QT_PKG_PREFIX=qt QT_PKG_INSTALL=qt5; fi - echo $QT_WITHOUT_DOTS - echo $QT_PKG_PREFIX - echo $QT_PKG_INSTALL - if [ "$TRAVIS_DIST" = "precise" ]; then - sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS} - else - sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS}-$TRAVIS_DIST - fi - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test # for a recent GCC - sudo add-apt-repository "deb http://llvm.org/apt/${TRAVIS_DIST}/ llvm-toolchain-${TRAVIS_DIST}-3.5 main" + QT_WITHOUT_DOTS=qt$(echo $QT_VERSION | grep -oP "[^\.]*" | tr -d '\n' | tr '[:upper:]' '[:lower]') + QT_PKG_PREFIX=$(echo $QT_WITHOUT_DOTS | cut -c1-4) + QT_PKG_INSTALL=$QT_PKG_PREFIX + if [ "$QT_PKG_PREFIX" = "qt50" ]; then QT_PKG_PREFIX=qt QT_PKG_INSTALL=qt5; fi + echo $QT_WITHOUT_DOTS + echo $QT_PKG_PREFIX + echo $QT_PKG_INSTALL + if [ "$TRAVIS_DIST" = "precise" ]; then + sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS} + else + sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS}-$TRAVIS_DIST + fi + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test # for a recent GCC + sudo add-apt-repository "deb http://llvm.org/apt/${TRAVIS_DIST}/ llvm-toolchain-${TRAVIS_DIST}-3.5 main" - sudo apt-get update -qq - sudo apt-get install ${QT_PKG_PREFIX}base ${QT_PKG_PREFIX}svg ${QT_PKG_PREFIX}tools + sudo apt-get update -qq + sudo apt-get install ${QT_PKG_PREFIX}base ${QT_PKG_PREFIX}svg ${QT_PKG_PREFIX}tools - sudo mkdir -p /opt/cmake-3/ - wget --no-check-certificate http://www.cmake.org/files/v3.9/cmake-3.9.3-Linux-x86_64.sh - sudo sh cmake-3.9.3-Linux-x86_64.sh --skip-license --prefix=/opt/cmake-3/ + sudo mkdir -p /opt/cmake-3/ + wget --no-check-certificate http://www.cmake.org/files/v3.9/cmake-3.9.3-Linux-x86_64.sh + sudo sh cmake-3.9.3-Linux-x86_64.sh --skip-license --prefix=/opt/cmake-3/ - export CMAKE_PREFIX_PATH=/opt/$QT_PKG_INSTALL/lib/cmake - export PATH=/opt/cmake-3/bin:/opt/$QT_PKG_INSTALL/bin:$PATH + export CMAKE_PREFIX_PATH=/opt/$QT_PKG_INSTALL/lib/cmake + export PATH=/opt/cmake-3/bin:/opt/$QT_PKG_INSTALL/bin:$PATH - if [ "$CXX" = "g++" ]; then - sudo apt-get install -y -qq g++-5 - export CXX='g++-5' CC='gcc-5' - fi - if [ "$CXX" = "clang++" ]; then - wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add - - sudo apt-get install -y -qq clang-3.5 liblldb-3.5 libclang1-3.5 libllvm3.5 lldb-3.5 llvm-3.5 llvm-3.5-runtime - export CXX='clang++-3.5' CC='clang-3.5' - fi + if [ "$CXX" = "g++" ]; then + sudo apt-get install -y -qq g++-5 + export CXX='g++-5' CC='gcc-5' + fi + if [ "$CXX" = "clang++" ]; then + wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add - + sudo apt-get install -y -qq clang-3.5 liblldb-3.5 libclang1-3.5 libllvm3.5 lldb-3.5 llvm-3.5 llvm-3.5-runtime + export CXX='clang++-3.5' CC='clang-3.5' + fi else - brew update - brew install qt5 - brew install cmake - export CMAKE_PREFIX_PATH=/usr/local/lib/cmake + brew update + brew install qt5 + brew install cmake + export CMAKE_PREFIX_PATH=/usr/local/lib/cmake fi # Output versions -- cgit v1.2.3