From 4e0ffc8d1e9fc0b6bf39017a6e61d62e45e0e9f3 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Mon, 11 Nov 2019 17:34:19 +0800 Subject: [PATCH] Support Server Auto-Update Task. Closes #624 --- .../hmcl/game/HMCLModpackExportTask.java | 12 +- .../jackhuang/hmcl/game/LauncherHelper.java | 13 +- .../jackhuang/hmcl/game/ModpackHelper.java | 13 +- .../hmcl/ui/download/ModpackPage.java | 2 +- .../ui/download/ModpackSelectionPage.java | 2 +- .../hmcl/ui/export/ExportWizardProvider.java | 20 ++- .../ui/export/ModpackFileSelectionPage.java | 1 + .../ui/export/ModpackTypeSelectionPage.java | 9 +- .../assets/fxml/download/modpack.fxml | 2 +- .../resources/assets/fxml/modpack/type.fxml | 12 ++ .../resources/assets/lang/I18N.properties | 4 + .../resources/assets/lang/I18N_es.properties | 1 + .../resources/assets/lang/I18N_ru.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 4 + .../assets/lang/I18N_zh_CN.properties | 4 + .../hmcl/mod/MinecraftInstanceTask.java | 4 +- .../org/jackhuang/hmcl/mod/ModAdviser.java | 2 +- .../java/org/jackhuang/hmcl/mod/Modpack.java | 13 ++ .../hmcl/mod/ModpackInstallTask.java | 9 ++ .../hmcl/mod/curse/CurseCompletionTask.java | 6 - .../hmcl/mod/curse/CurseManifest.java | 2 +- .../mod/multimc/MultiMCModpackExportTask.java | 13 +- .../server/ServerModpackCompletionTask.java | 152 ++++++++++++++++++ .../mod/server/ServerModpackExportTask.java | 106 ++++++++++++ .../mod/server/ServerModpackInstallTask.java | 101 ++++++++++++ .../mod/server/ServerModpackManifest.java | 129 +++++++++++++++ .../util/function/ExceptionalPredicate.java | 22 +++ .../org/jackhuang/hmcl/util/io/Zipper.java | 5 +- 28 files changed, 614 insertions(+), 50 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackManifest.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalPredicate.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackExportTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackExportTask.java index 742c474f7..64397e9db 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackExportTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackExportTask.java @@ -62,17 +62,7 @@ public class HMCLModpackExportTask extends Task { blackList.add(version + ".json"); Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); try (Zipper zip = new Zipper(output.toPath())) { - zip.putDirectory(repository.getRunDirectory(version).toPath(), "minecraft", path -> { - if (path.isEmpty()) - return true; - for (String s : blackList) - if (path.equals(s)) - return false; - for (String s : whitelist) - if (path.equals(s)) - return true; - return false; - }); + zip.putDirectory(repository.getRunDirectory(version).toPath(), "minecraft", path -> Modpack.acceptFile(path, blackList, whitelist)); Version mv = repository.getResolvedPreservingPatchesVersion(version); String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 7674d98c6..bdc992060 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -31,9 +31,10 @@ import org.jackhuang.hmcl.launch.NotDecompressingNativesException; import org.jackhuang.hmcl.launch.PermissionException; import org.jackhuang.hmcl.launch.ProcessCreationException; import org.jackhuang.hmcl.launch.ProcessListener; +import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.curse.CurseCompletionException; import org.jackhuang.hmcl.mod.curse.CurseCompletionTask; -import org.jackhuang.hmcl.mod.ModpackConfiguration; +import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask; import org.jackhuang.hmcl.setting.LauncherVisibility; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.VersionSetting; @@ -62,13 +63,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Queue; +import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -147,6 +142,8 @@ public final class LauncherHelper { ModpackConfiguration configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion)); if ("Curse".equals(configuration.getType())) return new CurseCompletionTask(dependencyManager, selectedVersion); + else if ("Server".equals(configuration.getType())) + return new ServerModpackCompletionTask(dependencyManager, selectedVersion); else return null; } catch (IOException e) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java index 97fc5cadf..60acf74bb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java @@ -25,6 +25,8 @@ import org.jackhuang.hmcl.mod.curse.CurseInstallTask; import org.jackhuang.hmcl.mod.curse.CurseManifest; import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration; import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask; +import org.jackhuang.hmcl.mod.server.ServerModpackInstallTask; +import org.jackhuang.hmcl.mod.server.ServerModpackManifest; import org.jackhuang.hmcl.setting.EnumGameDirectory; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.VersionSetting; @@ -65,6 +67,12 @@ public final class ModpackHelper { // ignore it, not a valid MultiMC modpack. } + try { + return ServerModpackManifest.readManifest(file, charset); + } catch (Exception e) { + // ignore it, not a valid Server modpack. + } + throw new UnsupportedModpackException(file.toString()); } @@ -120,7 +128,10 @@ public final class ModpackHelper { return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name) .whenComplete(Schedulers.defaultScheduler(), success, failure) .thenComposeAsync(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)); - else throw new IllegalArgumentException("Unrecognized modpack: " + modpack); + else if (modpack.getManifest() instanceof ServerModpackManifest) + return new ServerModpackInstallTask(profile.getDependency(), zipFile, modpack, ((ServerModpackManifest) modpack.getManifest()), name) + .whenComplete(Schedulers.defaultScheduler(), success, failure); + else throw new IllegalArgumentException("Unrecognized modpack: " + modpack.getManifest()); } public static Task getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration configuration) throws UnsupportedModpackException, MismatchedModpackTypeException { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java index 84c0bc31b..6f4968ecb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java @@ -151,7 +151,7 @@ public final class ModpackPage extends StackPane implements WizardPage { if (manifest != null) { WebStage stage = new WebStage(); stage.getWebView().getEngine().loadContent(manifest.getDescription()); - stage.setTitle(i18n("modpack.wizard.step.3")); + stage.setTitle(i18n("modpack.description")); stage.showAndWait(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java index 92356bf10..61e67dfca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java @@ -55,7 +55,7 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage Optional filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class); if (filePath.isPresent()) { controller.getSettings().put(MODPACK_FILE, filePath.get()); - controller.onNext(); + Platform.runLater(controller::onNext); } FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java index e46c81f4a..664a93b5a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration; import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask; +import org.jackhuang.hmcl.mod.server.ServerModpackExportTask; import org.jackhuang.hmcl.setting.Config; import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.setting.Profile; @@ -74,6 +75,8 @@ public final class ExportWizardProvider implements WizardProvider { return exportAsHMCL(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription, includeLauncher); case ModpackTypeSelectionPage.MODPACK_TYPE_MULTIMC: return exportAsMultiMC(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription); + case ModpackTypeSelectionPage.MODPACK_TYPE_SERVER: + return exportAsServer(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription); default: throw new IllegalStateException("Unrecognized modpack type " + modpackType); } @@ -177,7 +180,22 @@ public final class ExportWizardProvider implements WizardProvider { }; } - @Override + private Task exportAsServer(List whitelist, File modpackFile, String modpackName, String modpackAuthor, String modpackVersion, String modpackDescription) { + return new Task() { + Task dependency; + + @Override + public void execute() { + dependency = new ServerModpackExportTask(profile.getRepository(), version, whitelist, modpackName, modpackAuthor, modpackVersion, modpackDescription, modpackFile); + } + + @Override + public Collection> getDependencies() { + return Collections.singleton(dependency); + } + }; + } + @Override public Node createPage(WizardController controller, int step, Map settings) { switch (step) { case 0: return new ModpackInfoPage(controller, version); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java index 77c738706..1eb390ad9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java @@ -156,6 +156,7 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP public static final String MODPACK_FILE_SELECTION = "modpack.accepted"; private static final Map TRANSLATION = mapOf( + pair("minecraft/hmclversion.cfg", i18n("modpack.files.hmclversion_cfg")), pair("minecraft/servers.dat", i18n("modpack.files.servers_dat")), pair("minecraft/saves", i18n("modpack.files.saves")), pair("minecraft/mods", i18n("modpack.files.mods")), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java index 8884d3e05..bab0f2168 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackTypeSelectionPage.java @@ -34,13 +34,15 @@ public final class ModpackTypeSelectionPage extends StackPane implements WizardP private JFXButton btnHMCL; @FXML private JFXButton btnMultiMC; + @FXML + private JFXButton btnServer; public ModpackTypeSelectionPage(WizardController controller) { this.controller = controller; FXUtils.loadFXML(this, "/assets/fxml/modpack/type.fxml"); - JFXButton[] buttons = new JFXButton[]{btnHMCL, btnMultiMC}; - String[] types = new String[]{MODPACK_TYPE_HMCL, MODPACK_TYPE_MULTIMC}; + JFXButton[] buttons = new JFXButton[]{btnHMCL, btnMultiMC, btnServer}; + String[] types = new String[]{MODPACK_TYPE_HMCL, MODPACK_TYPE_MULTIMC, MODPACK_TYPE_SERVER}; for (int i = 0; i < types.length; ++i) { String type = types[i]; buttons[i].setOnMouseClicked(e -> { @@ -61,6 +63,7 @@ public final class ModpackTypeSelectionPage extends StackPane implements WizardP public static final String MODPACK_TYPE = "modpack.type"; - public static final String MODPACK_TYPE_MULTIMC = "multimc"; public static final String MODPACK_TYPE_HMCL = "hmcl"; + public static final String MODPACK_TYPE_MULTIMC = "multimc"; + public static final String MODPACK_TYPE_SERVER = "server"; } diff --git a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml b/HMCL/src/main/resources/assets/fxml/download/modpack.fxml index 52d019123..505e257f7 100644 --- a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml +++ b/HMCL/src/main/resources/assets/fxml/download/modpack.fxml @@ -24,7 +24,7 @@ - + diff --git a/HMCL/src/main/resources/assets/fxml/modpack/type.fxml b/HMCL/src/main/resources/assets/fxml/modpack/type.fxml index ee84c8f24..e2b7feed0 100644 --- a/HMCL/src/main/resources/assets/fxml/modpack/type.fxml +++ b/HMCL/src/main/resources/assets/fxml/modpack/type.fxml @@ -37,5 +37,17 @@ + + + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 5f6feda24..7cb0e8290 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -224,12 +224,14 @@ modpack.choose.remote=Download modpack from Internet modpack.choose.remote.detail=Requires a direct download link to the remote modpack file modpack.choose.remote.tooltip=A direct download link to the remote modpack file modpack.desc=Describe your modpack, including precautions and changelog. Markdown and online pictures are supported. +modpack.description=Description modpack.enter_name=Enter a name for this modpack. modpack.export=Export Modpack modpack.export.as=Export Modpack As... modpack.files.blueprints=BuildCraft blueprints modpack.files.config=Mod configs modpack.files.dumps=NEI debug output +modpack.files.hmclversion_cfg=Launcher configuration file modpack.files.liteconfig=Mod configurations modpack.files.mods=Mods modpack.files.mods.voxelmods=VoxelMods (including VoxelMap) options @@ -259,6 +261,8 @@ modpack.type.hmcl=Hello Minecraft! Launcher modpack.type.hmcl.export=Can be imported by Hello Minecraft! Launcher modpack.type.multimc=MultiMC modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC +modpack.type.server=Server Auto-Update Modpack +modpack.type.server.export=Allow server manager updating game client remotely modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, and Curse modpacks are supported. modpack.update=Game Update modpack.wizard=Exporting modpack wizard diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 6d3c1316e..b6ed50543 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -218,6 +218,7 @@ message.warning=Advertencia modpack=Modpack modpack.choose=Escoja el modpack en archivo zip que quieras instalar. modpack.desc=Describa su modpack, incluyendo precauciones, changelog, soportando Markdown (también soportando imágenes en línea). +modpack.description=Descripción modpack.enter_name=Entre su nombre deseado para este juego. modpack.export=Exportar modpack modpack.export.as=Exportar modpack como... diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 1dccbd9e7..d2696d65b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -218,6 +218,7 @@ message.warning=Предупреждение modpack=Модпак modpack.choose=Выберите zip-файл модпака, который вы хотите установить. modpack.desc=Опишите свой модпак, включая меры предосторожности, журнал изменений, поддерживая Markdown (также поддерживая онлайн-изображения). +modpack.description=описание modpack.enter_name=Введите желаемое имя для этой версии. modpack.export=Экспортировать модпак modpack.export.as=Экспорт модпака как... diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 69880397e..0db96c308 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -221,12 +221,14 @@ modpack.choose.remote=從網路下載整合包 modpack.choose.remote.detail=需要提供整合包的下載連結 modpack.choose.remote.tooltip=要下載的整合包的連結 modpack.desc=描述你要製作的整合包,比如整合包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。 +modpack.description=整合包描述 modpack.enter_name=給遊戲取個你喜歡的名字 modpack.export=匯出整合包 modpack.export.as=請選擇整合包類型。若你無法決定,請選擇 HMCL 類型。 modpack.files.blueprints=BuildCraft 藍圖 modpack.files.config=Mod 模組設定檔案 modpack.files.dumps=NEI 調校輸出 +modpack.files.hmclversion_cfg=啟動器設定檔案 modpack.files.liteconfig=Mod 模組設定檔案 modpack.files.mods=Mod 模組 modpack.files.mods.voxelmods=VoxelMods 設定,如小地圖 @@ -256,6 +258,8 @@ modpack.type.hmcl=HMCL modpack.type.hmcl.export=可以被 Hello Minecraft! Launcher (HMCL) 導入 modpack.type.multimc=MultiMC modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 導入 +modpack.type.server=伺服器自動更新整合包 +modpack.type.server.export=允許伺服器管理員遠程更新遊戲用戶端 modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。 modpack.update=正在升級整合包 modpack.wizard=匯出整合包引導 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index d3fb519cf..2def3b4e6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -222,12 +222,14 @@ modpack.choose.remote=从互联网下载整合包 modpack.choose.remote.detail=需要提供整合包的下载链接 modpack.choose.remote.tooltip=要下载的整合包的链接 modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown(图片请用网络图)。 +modpack.description=整合包描述 modpack.enter_name=给游戏起个你喜欢的名字 modpack.export=导出整合包 modpack.export.as=请选择整合包类型。若你无法决定,请选择 HMCL 类型。 modpack.files.blueprints=BuildCraft 蓝图 modpack.files.config=Mod 配置文件 modpack.files.dumps=NEI 调试输出 +modpack.files.hmclversion_cfg=启动器配置文件 modpack.files.liteconfig=Mod 配置文件 modpack.files.mods=Mod modpack.files.mods.voxelmods=VoxelMods 配置,如小地图 @@ -257,6 +259,8 @@ modpack.type.hmcl=HMCL modpack.type.hmcl.export=可以被 Hello Minecraft! Launcher (HMCL) 导入 modpack.type.multimc=MultiMC modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 导入 +modpack.type.server=服务器自动更新整合包 +modpack.type.server.export=允许服务器管理员远程更新游戏客户端 modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。 modpack.update=正在升级整合包 modpack.wizard=导出整合包向导 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java index f7a05b10d..4a47b5731 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java @@ -62,10 +62,10 @@ public final class MinecraftInstanceTask extends Task { Path root = fs.getPath(subDirectory); if (Files.exists(root)) - Files.walkFileTree(fs.getPath(subDirectory), new SimpleFileVisitor() { + Files.walkFileTree(root, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - String relativePath = root.relativize(file).normalize().toString(); + String relativePath = root.relativize(file).normalize().toString().replace(File.separatorChar, '/'); overrides.add(new ModpackConfiguration.FileInformation(relativePath, encodeHex(digest("SHA-1", file)))); return FileVisitResult.CONTINUE; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModAdviser.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModAdviser.java index 91e5bd37f..5205ad73d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModAdviser.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModAdviser.java @@ -36,7 +36,7 @@ public interface ModAdviser { List MODPACK_BLACK_LIST = Lang.immutableListOf( "usernamecache.json", "usercache.json", // Minecraft "launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher - "pack.json", "launcher.jar", "hmclmc.log", "cache", // HMCL + "pack.json", "launcher.jar", "hmclmc.log", "cache", "modpack.cfg", // HMCL "manifest.json", "minecraftinstance.json", ".curseclient", // Curse "minetweaker.log", // Mods ".fabric", ".mixin.out", // Fabric diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java index 51fc59233..5db63b7e9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.mod; import java.nio.charset.Charset; +import java.util.List; /** * @@ -85,4 +86,16 @@ public final class Modpack { public Modpack setManifest(Object manifest) { return new Modpack(name, author, version, gameVersion, description, encoding, manifest); } + + public static boolean acceptFile(String path, List blackList, List whiteList) { + if (path.isEmpty()) + return true; + for (String s : blackList) + if (path.equals(s)) + return false; + for (String s : whiteList) + if (path.equals(s)) + return true; + return false; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java index 291909dcd..5ca468d2f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java @@ -40,6 +40,15 @@ public class ModpackInstallTask extends Task { private final List overrides; private final Predicate callback; + /** + * Constructor + * @param modpackFile a zip file + * @param dest destination to store unpacked files + * @param charset charset of the zip file + * @param subDirectory the subdirectory of zip file to unpack + * @param callback test whether the file (given full path) in zip file should be unpacked or not + * @param oldConfiguration old modpack information if upgrade + */ public ModpackInstallTask(File modpackFile, File dest, Charset charset, String subDirectory, Predicate callback, ModpackConfiguration oldConfiguration) { this.modpackFile = modpackFile; this.dest = dest; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java index 1f7ca4c10..6ea471024 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java @@ -52,7 +52,6 @@ public final class CurseCompletionTask extends Task { private final ModManager modManager; private final String version; private CurseManifest manifest; - private final List> dependents = new LinkedList<>(); private final List> dependencies = new LinkedList<>(); /** @@ -94,11 +93,6 @@ public final class CurseCompletionTask extends Task { return dependencies; } - @Override - public Collection> getDependents() { - return dependents; - } - @Override public void execute() throws Exception { if (manifest == null) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifest.java index 2d08e0fa7..277b38c03 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifest.java @@ -113,7 +113,7 @@ public final class CurseManifest { } /** - * @param f the CurseForge modpack file. + * @param zip the CurseForge modpack file. * @throws IOException if the file is not a valid zip file. * @throws JsonParseException if the manifest.json is missing or malformed. * @return the manifest. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java index 3f35afa0e..6067f6ef0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java @@ -21,6 +21,7 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.mod.ModAdviser; +import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -67,17 +68,7 @@ public class MultiMCModpackExportTask extends Task { blackList.add(versionId + ".json"); Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); try (Zipper zip = new Zipper(output.toPath())) { - zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> { - if (path.isEmpty()) - return true; - for (String s : blackList) - if (path.equals(s)) - return false; - for (String s : whitelist) - if (path.equals(s)) - return true; - return false; - }); + zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist)); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId)); String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId)) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java new file mode 100644 index 000000000..9913557c4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java @@ -0,0 +1,152 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.mod.server; + +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.mod.ModpackConfiguration; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import static org.jackhuang.hmcl.util.DigestUtils.digest; +import static org.jackhuang.hmcl.util.Hex.encodeHex; + +public class ServerModpackCompletionTask extends Task { + + private final DefaultGameRepository repository; + private final String version; + private ModpackConfiguration manifest; + private GetTask dependent; + private ServerModpackManifest remoteManifest; + private final List> dependencies = new LinkedList<>(); + + public ServerModpackCompletionTask(DefaultDependencyManager dependencyManager, String version) { + this.repository = dependencyManager.getGameRepository(); + this.version = version; + + try { + File manifestFile = repository.getModpackConfiguration(version); + if (manifestFile.exists()) + this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), new TypeToken>() { + }.getType()); + } catch (Exception e) { + Logging.LOG.log(Level.WARNING, "Unable to read CurseForge modpack manifest.json", e); + } + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return; + dependent = new GetTask(new URL(manifest.getManifest().getFileApi() + "/server-manifest.json")); + } + + @Override + public Collection> getDependencies() { + return dependencies; + } + + @Override + public Collection> getDependents() { + return dependent == null ? Collections.emptySet() : Collections.singleton(dependent); + } + + @Override + public void execute() throws Exception { + if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return; + + try { + remoteManifest = JsonUtils.fromNonNullJson(dependent.getResult(), ServerModpackManifest.class); + } catch (JsonParseException e) { + throw new IOException(e); + } + + Path rootPath = repository.getVersionRoot(version).toPath(); + Map files = manifest.getManifest().getFiles().stream() + .collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath, + Function.identity())); + + Set remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath) + .collect(Collectors.toSet()); + + // for files in new modpack + for (ModpackConfiguration.FileInformation file : remoteManifest.getFiles()) { + Path actualPath = rootPath.resolve(file.getPath()); + boolean download; + if (!files.containsKey(file.getPath())) { + // If old modpack does not have this entry, download it + download = true; + } else if (!Files.exists(actualPath)) { + // If both old and new modpacks have this entry, but the file is deleted by user, leave it missing. + download = false; + } else { + // If user modified this entry file, we will not replace this file since this modified file is that user expects. + String fileHash = encodeHex(digest("SHA-1", Files.newInputStream(actualPath))); + String oldHash = files.get(file.getPath()).getHash(); + download = !Objects.equals(oldHash, file.getHash()) && Objects.equals(oldHash, fileHash); + } + + if (download) { + dependencies.add(new FileDownloadTask( + new URL(remoteManifest.getFileApi() + "/overrides/" + file.getPath()), + actualPath.toFile(), + new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash()))); + } + } + + // If old modpack have this entry, and new modpack deleted it. Delete this file. + for (ModpackConfiguration.FileInformation file : manifest.getManifest().getFiles()) { + Path actualPath = rootPath.resolve(file.getPath()); + if (Files.exists(actualPath) && !remoteFiles.contains(file.getPath())) + Files.deleteIfExists(actualPath); + } + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() throws Exception { + if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return; + File manifestFile = repository.getModpackConfiguration(version); + FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), remoteManifest.getFiles()))); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java new file mode 100644 index 000000000..0df5f5304 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java @@ -0,0 +1,106 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.mod.server; + +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.game.GameVersion; +import org.jackhuang.hmcl.mod.ModAdviser; +import org.jackhuang.hmcl.mod.Modpack; +import org.jackhuang.hmcl.mod.ModpackConfiguration; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.Zipper; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; +import static org.jackhuang.hmcl.util.DigestUtils.digest; +import static org.jackhuang.hmcl.util.Hex.encodeHex; + +public class ServerModpackExportTask extends Task { + private final DefaultGameRepository repository; + private final String versionId; + private final List whitelist; + private final File output; + private final String modpackName; + private final String modpackAuthor; + private final String modpackVersion; + private final String modpackDescription; + + public ServerModpackExportTask(DefaultGameRepository repository, String versionId, List whitelist, String modpackName, String modpackAuthor, String modpackVersion, String modpackDescription, File output) { + this.repository = repository; + this.versionId = versionId; + this.whitelist = whitelist; + this.output = output; + this.modpackName = modpackName; + this.modpackAuthor = modpackAuthor; + this.modpackVersion = modpackVersion; + this.modpackDescription = modpackDescription; + + onDone().register(event -> { + if (event.isFailed()) output.delete(); + }); + } + + @Override + public void execute() throws Exception { + ArrayList blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST); + blackList.add(versionId + ".jar"); + blackList.add(versionId + ".json"); + Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); + try (Zipper zip = new Zipper(output.toPath())) { + Path runDirectory = repository.getRunDirectory(versionId).toPath(); + List files = new ArrayList<>(); + zip.putDirectory(runDirectory, ".minecraft", path -> { + if (Modpack.acceptFile(path, blackList, whitelist)) { + Path file = runDirectory.resolve(path); + if (Files.isRegularFile(file)) { + String relativePath = runDirectory.relativize(file).normalize().toString().replace(File.separatorChar, '/'); + files.add(new ModpackConfiguration.FileInformation(relativePath, encodeHex(digest("SHA-1", file)))); + } + return true; + } else { + return false; + } + }); + + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId)); + String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId)) + .orElseThrow(() -> new IOException("Cannot parse the version of " + versionId)); + List addons = new ArrayList<>(); + addons.add(new ServerModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion)); + analyzer.getVersion(FORGE).ifPresent(forgeVersion -> + addons.add(new ServerModpackManifest.Addon(FORGE.getPatchId(), forgeVersion))); + analyzer.getVersion(LITELOADER).ifPresent(liteLoaderVersion -> + addons.add(new ServerModpackManifest.Addon(LITELOADER.getPatchId(), liteLoaderVersion))); + analyzer.getVersion(OPTIFINE).ifPresent(optifineVersion -> + addons.add(new ServerModpackManifest.Addon(OPTIFINE.getPatchId(), optifineVersion))); + analyzer.getVersion(FABRIC).ifPresent(fabricVersion -> + addons.add(new ServerModpackManifest.Addon(FABRIC.getPatchId(), fabricVersion))); + ServerModpackManifest manifest = new ServerModpackManifest(modpackName, modpackAuthor, modpackVersion, modpackDescription, "", files, addons); + zip.putTextFile(JsonUtils.GSON.toJson(manifest), "server-manifest.json"); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackInstallTask.java new file mode 100644 index 000000000..e6b61897b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackInstallTask.java @@ -0,0 +1,101 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.mod.server; + +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.GameBuilder; +import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.mod.MinecraftInstanceTask; +import org.jackhuang.hmcl.mod.Modpack; +import org.jackhuang.hmcl.mod.ModpackConfiguration; +import org.jackhuang.hmcl.mod.ModpackInstallTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +public class ServerModpackInstallTask extends Task { + + private final File zipFile; + private final Modpack modpack; + private final ServerModpackManifest manifest; + private final String name; + private final DefaultGameRepository repository; + private final List> dependencies = new LinkedList<>(); + private final List> dependents = new LinkedList<>(); + + public ServerModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, ServerModpackManifest manifest, String name) { + this.zipFile = zipFile; + this.modpack = modpack; + this.manifest = manifest; + this.name = name; + this.repository = dependencyManager.getGameRepository(); + File run = repository.getRunDirectory(name); + + File json = repository.getModpackConfiguration(name); + if (repository.hasVersion(name) && !json.exists()) + throw new IllegalArgumentException("Version " + name + " already exists."); + + GameBuilder builder = dependencyManager.gameBuilder().name(name); + for (ServerModpackManifest.Addon addon : manifest.getAddons()) { + builder.version(addon.getId(), addon.getVersion()); + } + + dependents.add(builder.buildAsync()); + onDone().register(event -> { + if (event.isFailed()) + repository.removeVersionFromDisk(name); + }); + + ModpackConfiguration config = null; + try { + if (json.exists()) { + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { + }.getType()); + + if (!MODPACK_TYPE.equals(config.getType())) + throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version."); + } + } catch (JsonParseException | IOException ignore) { + } + dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/overrides", any -> true, config)); + } + + @Override + public List> getDependents() { + return dependents; + } + + @Override + public List> getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name))); + } + + public static final String MODPACK_TYPE = "Server"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackManifest.java new file mode 100644 index 000000000..943fe83a5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackManifest.java @@ -0,0 +1,129 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.mod.server; + +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.mod.Modpack; +import org.jackhuang.hmcl.mod.ModpackConfiguration; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.gson.TolerableValidationException; +import org.jackhuang.hmcl.util.gson.Validation; +import org.jackhuang.hmcl.util.io.CompressingUtils; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT; + +public class ServerModpackManifest implements Validation { + private final String name; + private final String author; + private final String version; + private final String description; + private final String fileApi; + private final List files; + private final List addons; + + public ServerModpackManifest() { + this("", "", "", "", "", Collections.emptyList(), Collections.emptyList()); + } + + public ServerModpackManifest(String name, String author, String version, String description, String fileApi, List files, List addons) { + this.name = name; + this.author = author; + this.version = version; + this.description = description; + this.fileApi = fileApi; + this.files = files; + this.addons = addons; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + public String getVersion() { + return version; + } + + public String getDescription() { + return description; + } + + public String getFileApi() { + return fileApi; + } + + public List getFiles() { + return files; + } + + public List getAddons() { + return addons; + } + + @Override + public void validate() throws JsonParseException, TolerableValidationException { + if (fileApi == null) + throw new JsonParseException("ServerModpackManifest.fileApi cannot be blank"); + if (files == null) + throw new JsonParseException("ServerModpackManifest.files cannot be null"); + } + + public static final class Addon { + private final String id; + private final String version; + + public Addon() { + this("", ""); + } + + public Addon(String id, String version) { + this.id = id; + this.version = version; + } + + public String getId() { + return id; + } + + public String getVersion() { + return version; + } + } + + /** + * @param zip the CurseForge modpack file. + * @throws IOException if the file is not a valid zip file. + * @throws JsonParseException if the server-manifest.json is missing or malformed. + * @return the manifest. + */ + public static Modpack readManifest(Path zip, Charset encoding) throws IOException, JsonParseException { + String json = CompressingUtils.readTextZipEntry(zip, "server-manifest.json", encoding); + ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class); + String gameVersion = manifest.getAddons().stream().filter(x -> MINECRAFT.getPatchId().equals(x.getId())).findAny().orElseThrow(() -> new IOException("Cannot find game version")).getVersion(); + return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), gameVersion, manifest.getDescription(), encoding, manifest); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalPredicate.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalPredicate.java new file mode 100644 index 000000000..48d11e9ed --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/function/ExceptionalPredicate.java @@ -0,0 +1,22 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.function; + +public interface ExceptionalPredicate { + boolean test(T t) throws E; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java index 2ef414106..d6223805d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java @@ -18,6 +18,8 @@ package org.jackhuang.hmcl.util.io; +import org.jackhuang.hmcl.util.function.ExceptionalPredicate; + import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -25,7 +27,6 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; -import java.util.function.Predicate; /** * Non thread-safe @@ -67,7 +68,7 @@ public final class Zipper implements Closeable { * @param targetDir the path of the directory in this zip file. * @param filter returns false if you do not want that file or directory */ - public void putDirectory(Path source, String targetDir, Predicate filter) throws IOException { + public void putDirectory(Path source, String targetDir, ExceptionalPredicate filter) throws IOException { Path root = fs.getPath(targetDir); Files.createDirectories(root); Files.walkFileTree(source, new SimpleFileVisitor() {