From 83817868369fa74b8f616fcf79dcfe372b4f6edf Mon Sep 17 00:00:00 2001 From: skidam Date: Fri, 25 Jul 2025 16:28:10 +0200 Subject: [PATCH 1/2] Refactor modpack handling to support group-based configurations with V3 config and update related data structures --- .../automodpack_core/GlobalVariables.java | 6 +- .../pl/skidam/automodpack_core/Server.java | 23 +++---- .../automodpack_core/config/ConfigTools.java | 6 +- .../skidam/automodpack_core/config/Jsons.java | 68 +++++++++++++++++-- .../modpack/ModpackContent.java | 54 ++++++++------- .../modpack/ModpackExecutor.java | 28 ++++---- .../utils/CustomFileUtils.java | 8 +-- .../utils/ModpackContentTools.java | 4 +- .../utils/WorkaroundUtil.java | 4 +- .../automodpack_core/modpack/ModpackTest.java | 2 +- .../automodpack_loader_core/Preload.java | 33 +++++++-- .../automodpack_loader_core/SelfUpdater.java | 8 +-- .../client/ModpackUpdater.java | 22 +++--- .../client/ModpackUtils.java | 38 +++++------ .../client/ui/ChangelogScreen.java | 5 +- .../pl/skidam/automodpack/init/Common.java | 31 +++++---- .../skidam/automodpack/modpack/Commands.java | 16 ++++- 17 files changed, 232 insertions(+), 124 deletions(-) diff --git a/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java b/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java index 4b743f828..f3236b889 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java +++ b/core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java @@ -25,7 +25,7 @@ public class GlobalVariables { public static Path MODS_DIR; public static ModpackExecutor modpackExecutor; public static NettyServer hostServer; - public static Jsons.ServerConfigFieldsV2 serverConfig; + public static Jsons.ServerConfigFieldsV3 serverConfig; public static Jsons.ClientConfigFieldsV2 clientConfig; public static Jsons.KnownHostsFields knownHosts; public static final Path automodpackDir = Path.of("automodpack"); @@ -34,8 +34,8 @@ public class GlobalVariables { // Main - required // Addons - optional addon packs // Switches - optional or required packs, chosen by the player, only one can be installed at a time - public static final Path hostContentModpackDir = hostModpackDir.resolve("main"); - public static Path hostModpackContentFile = hostModpackDir.resolve("automodpack-content.json"); + public static final Path mainHostContentModpackDir = hostModpackDir.resolve("main"); + public static final Path hostModpackContentFile = hostModpackDir.resolve("automodpack-content.json"); public static Path serverConfigFile = automodpackDir.resolve("automodpack-server.json"); public static Path serverCoreConfigFile = automodpackDir.resolve("automodpack-core.json"); public static final Path privateDir = automodpackDir.resolve(".private"); diff --git a/core/src/main/java/pl/skidam/automodpack_core/Server.java b/core/src/main/java/pl/skidam/automodpack_core/Server.java index 763e8d7cd..d266bc0ef 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/Server.java +++ b/core/src/main/java/pl/skidam/automodpack_core/Server.java @@ -3,11 +3,9 @@ import pl.skidam.automodpack_core.config.ConfigTools; import pl.skidam.automodpack_core.config.Jsons; import pl.skidam.automodpack_core.modpack.ModpackExecutor; -import pl.skidam.automodpack_core.modpack.ModpackContent; import pl.skidam.automodpack_core.protocol.netty.NettyServer; import java.nio.file.Path; -import java.util.ArrayList; import static pl.skidam.automodpack_core.GlobalVariables.*; @@ -31,13 +29,12 @@ public static void main(String[] args) { modpackDir.toFile().mkdirs(); - hostModpackContentFile = modpackDir.resolve("automodpack-content.json"); +// hostModpackContentFile = modpackDir.resolve("automodpack-content.json"); serverConfigFile = modpackDir.resolve("automodpack-server.json"); serverCoreConfigFile = modpackDir.resolve("automodpack-core.json"); - serverConfig = ConfigTools.load(serverConfigFile, Jsons.ServerConfigFieldsV2.class); + serverConfig = ConfigTools.load(serverConfigFile, Jsons.ServerConfigFieldsV3.class); if (serverConfig != null) { - serverConfig.syncedFiles = new ArrayList<>(); serverConfig.validateSecrets = false; ConfigTools.save(serverConfigFile, serverConfig); @@ -60,14 +57,14 @@ public static void main(String[] args) { mainModpackDir.toFile().mkdirs(); ModpackExecutor modpackExecutor = new ModpackExecutor(); - ModpackContent modpackContent = new ModpackContent(serverConfig.modpackName, null, mainModpackDir, serverConfig.syncedFiles, serverConfig.allowEditsInFiles, serverConfig.forceCopyFilesToStandardLocation, modpackExecutor.getExecutor()); - boolean generated = modpackExecutor.generateNew(modpackContent); - - if (generated) { - LOGGER.info("Modpack generated!"); - } else { - LOGGER.error("Failed to generate modpack!"); - } +// ModpackContent modpackContent = new ModpackContent(serverConfig.modpackName, null, mainModpackDir, serverConfig.syncedFiles, serverConfig.allowEditsInFiles, serverConfig.forceCopyFilesToStandardLocation, modpackExecutor.getExecutor()); +// boolean generated = modpackExecutor.generateNew(modpackContent); + +// if (generated) { +// LOGGER.info("Modpack generated!"); +// } else { +// LOGGER.error("Failed to generate modpack!"); +// } modpackExecutor.stop(); diff --git a/core/src/main/java/pl/skidam/automodpack_core/config/ConfigTools.java b/core/src/main/java/pl/skidam/automodpack_core/config/ConfigTools.java index 7f921017e..873b3820a 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/config/ConfigTools.java +++ b/core/src/main/java/pl/skidam/automodpack_core/config/ConfigTools.java @@ -123,11 +123,11 @@ public static void save(Path configFile, Object configObject) { // Modpack content stuff - public static Jsons.ModpackContentFields loadModpackContent(Path modpackContentFile) { + public static Jsons.ModpackGroupFields loadModpackContent(Path modpackContentFile) { try { if (Files.isRegularFile(modpackContentFile)) { String json = Files.readString(modpackContentFile); - return GSON.fromJson(json, Jsons.ModpackContentFields.class); + return GSON.fromJson(json, Jsons.ModpackGroupFields.class); } } catch (Exception e) { LOGGER.error("Couldn't load modpack content! {}", modpackContentFile.toAbsolutePath().normalize(), e); @@ -135,7 +135,7 @@ public static Jsons.ModpackContentFields loadModpackContent(Path modpackContentF return null; } - public static void saveModpackContent(Path modpackContentFile, Jsons.ModpackContentFields configObject) { + public static void saveModpackContent(Path modpackContentFile, Jsons.ModpackGroupFields configObject) { try { if (!Files.isDirectory(modpackContentFile.getParent())) { Files.createDirectories(modpackContentFile.getParent()); diff --git a/core/src/main/java/pl/skidam/automodpack_core/config/Jsons.java b/core/src/main/java/pl/skidam/automodpack_core/config/Jsons.java index d2123e17c..60a222dfc 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/config/Jsons.java +++ b/core/src/main/java/pl/skidam/automodpack_core/config/Jsons.java @@ -112,6 +112,54 @@ public static class ServerConfigFieldsV2 { public List acceptedLoaders; } + public static class GroupDeclaration { + public String groupName = ""; + public boolean generateModpackOnStart = true; + public List syncedFiles = List.of(); + public List allowEditsInFiles = List.of(); + public List forceCopyFilesToStandardLocation = List.of(); + public boolean autoExcludeServerSideMods = true; + public boolean autoExcludeUnnecessaryFiles = true; + public boolean required = false; + public boolean checkByDefault = false; + public List breaksWith = List.of(); + public List requiredBy = List.of(); + public List compatibleOS = List.of(); + } + + public static GroupDeclaration mainGroupDeclaration() { + GroupDeclaration decl = new GroupDeclaration(); + decl.required = true; + decl.checkByDefault = true; + decl.syncedFiles = List.of("/mods/*.jar", "/kubejs/**", "!/kubejs/server_scripts/**", "/emotes/*"); + decl.allowEditsInFiles = List.of("/options.txt", "/config/**"); + return decl; + } + + public static class ServerConfigFieldsV3 { + public int DO_NOT_CHANGE_IT = 3; // file version + public boolean modpackHost = true; + public Map groups = Map.of( + "main", mainGroupDeclaration() + ); + public boolean requireAutoModpackOnClient = true; + public boolean nagUnModdedClients = true; + public String nagMessage = "This server provides dedicated modpack through AutoModpack!"; + public String nagClickableMessage = "Click here to get the AutoModpack!"; + public String nagClickableLink = "https://modrinth.com/project/automodpack"; + public String bindAddress = ""; + public int bindPort = -1; + public String addressToSend = ""; + public int portToSend = -1; + public boolean disableInternalTLS = false; + public boolean updateIpsOnEveryStart = false; + public int bandwidthLimit = 0; + public boolean validateSecrets = true; + public long secretLifetime = 336; // 336 hours = 14 days + public boolean selfUpdater = false; + public List acceptedLoaders; + } + public static class ServerCoreConfigFields { public String automodpackVersion = "4.0.0-beta37"; // TODO: dont hardcode it public String loader = "fabric"; @@ -127,19 +175,31 @@ public static class KnownHostsFields { public Map hosts; // host, fingerprint } - public static class ModpackContentFields { - public String modpackName = ""; + public static class ModpackContentMasterFields { public String automodpackVersion = ""; public String loader = ""; public String loaderVersion = ""; public String mcVersion = ""; + public Set groups; + + public ModpackContentMasterFields(Set groups) { + this.groups = groups; + } + + public ModpackContentMasterFields() { + this.groups = Set.of(); + } + } + + public static class ModpackGroupFields { + public String groupName = ""; public Set list; - public ModpackContentFields(Set list) { + public ModpackGroupFields(Set list) { this.list = list; } - public ModpackContentFields() { + public ModpackGroupFields() { this.list = Set.of(); } diff --git a/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java b/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java index 222366dd9..1d0dafe5f 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java +++ b/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java @@ -15,17 +15,19 @@ import static pl.skidam.automodpack_core.GlobalVariables.LOGGER; public class ModpackContent { - public final Set list = Collections.synchronizedSet(new HashSet<>()); + public final Set list = Collections.synchronizedSet(new HashSet<>()); public final ObservableMap pathsMap = new ObservableMap<>(); private final String MODPACK_NAME; private final WildCards SYNCED_FILES_CARDS; private final WildCards EDITABLE_CARDS; private final WildCards FORCE_COPY_FILES_TO_STANDARD_LOCATION; + private final boolean AUTO_EXCLUDE_UNNECESSARY_FILES; + private final boolean AUTO_EXCLUDE_SERVER_SIDE_MODS; private final Path MODPACK_DIR; private final ThreadPoolExecutor CREATION_EXECUTOR; private final Map sha1MurmurMapPreviousContent = new HashMap<>(); - public ModpackContent(String modpackName, Path cwd, Path modpackDir, List syncedFiles, List allowEditsInFiles, List forceCopyFilesToStandardLocation, ThreadPoolExecutor CREATION_EXECUTOR) { + public ModpackContent(String modpackName, Path cwd, Path modpackDir, List syncedFiles, List allowEditsInFiles, List forceCopyFilesToStandardLocation, boolean autoExcludeUnnecessaryFiles, boolean autoExcludeServerSideMods, ThreadPoolExecutor CREATION_EXECUTOR) { this.MODPACK_NAME = modpackName; this.MODPACK_DIR = modpackDir; Set directoriesToSearch = new HashSet<>(2); @@ -38,6 +40,8 @@ public ModpackContent(String modpackName, Path cwd, Path modpackDir, List getPreviousContent() { + public Optional getPreviousContent() { var optionalModpackContentFile = ModpackContentTools.getModpackContentFile(MODPACK_DIR); return optionalModpackContentFile.map(ConfigTools::loadModpackContent); } @@ -103,12 +107,12 @@ public Optional getPreviousContent() { public boolean loadPreviousContent() { var optionalModpackContent = getPreviousContent(); if (optionalModpackContent.isEmpty()) return false; - Jsons.ModpackContentFields modpackContent = optionalModpackContent.get(); + Jsons.ModpackGroupFields modpackContent = optionalModpackContent.get(); synchronized (list) { list.addAll(modpackContent.list); - for (Jsons.ModpackContentFields.ModpackContentItem modpackContentItem : list) { + for (Jsons.ModpackGroupFields.ModpackContentItem modpackContentItem : list) { Path file = CustomFileUtils.getPath(MODPACK_DIR, modpackContentItem.file); if (!Files.exists(file)) file = CustomFileUtils.getPathFromCWD(modpackContentItem.file); if (!Files.exists(file)) { @@ -133,15 +137,24 @@ public boolean loadPreviousContent() { // This is important to make it synchronized otherwise it could corrupt the file and crash public synchronized void saveModpackContent() { synchronized (list) { - Jsons.ModpackContentFields modpackContent = new Jsons.ModpackContentFields(list); + Jsons.ModpackGroupFields modpackContent = new Jsons.ModpackGroupFields(list); + modpackContent.groupName = MODPACK_NAME; + ConfigTools.saveModpackContent(MODPACK_DIR.resolve(hostModpackContentFile.getFileName().toString()), modpackContent); + + synchronized (hostModpackContentFile) { + Jsons.ModpackContentMasterFields masterContent = ConfigTools.load(hostModpackContentFile, Jsons.ModpackContentMasterFields.class); + if (masterContent == null) { + masterContent = new Jsons.ModpackContentMasterFields(); + } - modpackContent.automodpackVersion = AM_VERSION; - modpackContent.mcVersion = MC_VERSION; - modpackContent.loaderVersion = LOADER_VERSION; - modpackContent.loader = LOADER; - modpackContent.modpackName = MODPACK_NAME; + masterContent.automodpackVersion = AM_VERSION; + masterContent.mcVersion = MC_VERSION; + masterContent.loaderVersion = LOADER_VERSION; + masterContent.loader = LOADER; - ConfigTools.saveModpackContent(hostModpackContentFile, modpackContent); + masterContent.groups.removeIf(group -> group.groupName.equals(MODPACK_NAME)); + masterContent.groups.add(modpackContent); + } } } @@ -158,7 +171,7 @@ private List> generateAsync(List files) { private void generate(Path file) { try { - Jsons.ModpackContentFields.ModpackContentItem item = generateContent(file); + Jsons.ModpackGroupFields.ModpackContentItem item = generateContent(file); if (item != null) { LOGGER.info("generated content for {}", item.file); synchronized (list) { @@ -185,7 +198,7 @@ public void remove(Path file) { String modpackFile = CustomFileUtils.formatPath(file, MODPACK_DIR); synchronized (list) { - for (Jsons.ModpackContentFields.ModpackContentItem item : this.list) { + for (Jsons.ModpackGroupFields.ModpackContentItem item : this.list) { if (item.file.equals(modpackFile)) { this.pathsMap.remove(item.sha1); this.list.remove(item); @@ -208,14 +221,9 @@ public static boolean isInnerFile(Path file) { return isInner; } - private Jsons.ModpackContentFields.ModpackContentItem generateContent(final Path file) throws Exception { + private Jsons.ModpackGroupFields.ModpackContentItem generateContent(final Path file) throws Exception { if (!Files.isRegularFile(file)) return null; - if (serverConfig == null) { - LOGGER.error("Server config is null!"); - return null; - } - if (isInnerFile(file)) { return null; } @@ -229,7 +237,7 @@ private Jsons.ModpackContentFields.ModpackContentItem generateContent(final Path final String size = String.valueOf(Files.size(file)); - if (serverConfig.autoExcludeUnnecessaryFiles) { + if (AUTO_EXCLUDE_UNNECESSARY_FILES) { if (size.equals("0")) { LOGGER.info("Skipping file {} because it is empty", formattedFile); return null; @@ -260,7 +268,7 @@ private Jsons.ModpackContentFields.ModpackContentItem generateContent(final Path if (FileInspection.isMod(file)) { type = "mod"; - if (serverConfig.autoExcludeServerSideMods && Objects.equals(FileInspection.getModEnvironment(file), LoaderManagerService.EnvironmentType.SERVER)) { + if (AUTO_EXCLUDE_SERVER_SIDE_MODS && Objects.equals(FileInspection.getModEnvironment(file), LoaderManagerService.EnvironmentType.SERVER)) { LOGGER.info("File {} is server mod! Skipping...", formattedFile); return null; } @@ -305,7 +313,7 @@ private Jsons.ModpackContentFields.ModpackContentItem generateContent(final Path LOGGER.info("File {} is forced to copy to standard location!", formattedFile); } - return new Jsons.ModpackContentFields.ModpackContentItem(formattedFile, size, type, isEditable, forcedToCopy, sha1, murmur); + return new Jsons.ModpackGroupFields.ModpackContentItem(formattedFile, size, type, isEditable, forcedToCopy, sha1, murmur); } } \ No newline at end of file diff --git a/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackExecutor.java b/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackExecutor.java index 887dd7aeb..3cf3da213 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackExecutor.java +++ b/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackExecutor.java @@ -14,26 +14,30 @@ public class ModpackExecutor { private final ThreadPoolExecutor CREATION_EXECUTOR = (ThreadPoolExecutor) Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors() * 2), new CustomThreadFactoryBuilder().setNameFormat("AutoModpackCreation-%d").build()); public final Map modpacks = Collections.synchronizedMap(new HashMap<>()); - private ModpackContent init() { + private ModpackContent init(String groupId) { if (isGenerating()) { LOGGER.error("Called generate() twice!"); return null; } + Path modpackHostPath = hostModpackDir.resolve(groupId); + try { - if (!Files.exists(hostContentModpackDir)) { - Files.createDirectories(hostContentModpackDir); - Files.createDirectory(hostContentModpackDir.resolve("mods")); - Files.createDirectory(hostContentModpackDir.resolve("config")); - Files.createDirectory(hostContentModpackDir.resolve("shaderpacks")); - Files.createDirectory(hostContentModpackDir.resolve("resourcepacks")); + if (!Files.exists(modpackHostPath)) { + Files.createDirectories(modpackHostPath); + Files.createDirectory(modpackHostPath.resolve("mods")); + Files.createDirectory(modpackHostPath.resolve("config")); + Files.createDirectory(modpackHostPath.resolve("shaderpacks")); + Files.createDirectory(modpackHostPath.resolve("resourcepacks")); } } catch (IOException e) { e.printStackTrace(); } + var groupDecl = serverConfig.groups.get(groupId); + Path cwd = Path.of(System.getProperty("user.dir")); - return new ModpackContent(serverConfig.modpackName, cwd, hostContentModpackDir, serverConfig.syncedFiles, serverConfig.allowEditsInFiles, serverConfig.forceCopyFilesToStandardLocation, CREATION_EXECUTOR); + return new ModpackContent(groupDecl.groupName, cwd, modpackHostPath, groupDecl.syncedFiles, groupDecl.allowEditsInFiles, groupDecl.forceCopyFilesToStandardLocation, groupDecl.autoExcludeUnnecessaryFiles, groupDecl.autoExcludeServerSideMods, CREATION_EXECUTOR); } public boolean generateNew(ModpackContent content) { @@ -43,16 +47,16 @@ public boolean generateNew(ModpackContent content) { return generated; } - public boolean generateNew() { - ModpackContent content = init(); + public boolean generateNew(String groupId) { + ModpackContent content = init(groupId); if (content == null) return false; boolean generated = content.create(); modpacks.put(content.getModpackName(), content); return generated; } - public boolean loadLast() { - ModpackContent content = init(); + public boolean loadLast(String groupId) { + ModpackContent content = init(groupId); if (content == null) return false; boolean generated = content.loadPreviousContent(); modpacks.put(content.getModpackName(), content); diff --git a/core/src/main/java/pl/skidam/automodpack_core/utils/CustomFileUtils.java b/core/src/main/java/pl/skidam/automodpack_core/utils/CustomFileUtils.java index 8fa727290..9871277b4 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/utils/CustomFileUtils.java +++ b/core/src/main/java/pl/skidam/automodpack_core/utils/CustomFileUtils.java @@ -163,7 +163,7 @@ public static String formatPath(final Path modpackFile, final Path modpackPath) } - public static void deleteDummyFiles(Path directory, Set ignoreList) { + public static void deleteDummyFiles(Path directory, Set ignoreList) { if (directory == null || ignoreList == null) { return; } @@ -180,14 +180,14 @@ public static void deleteDummyFiles(Path directory, Set ignoreList) { + private static boolean shouldIgnore(Path file, Set ignoreList) { if (ignoreList == null) { return false; } - String modpackFile = CustomFileUtils.formatPath(file, Objects.requireNonNullElse(selectedModpackDir, hostContentModpackDir)); + String modpackFile = CustomFileUtils.formatPath(file, Objects.requireNonNullElse(selectedModpackDir, mainHostContentModpackDir)); - for (Jsons.ModpackContentFields.ModpackContentItem item : ignoreList) { + for (Jsons.ModpackGroupFields.ModpackContentItem item : ignoreList) { if (item.file.equals(modpackFile)) { return true; } diff --git a/core/src/main/java/pl/skidam/automodpack_core/utils/ModpackContentTools.java b/core/src/main/java/pl/skidam/automodpack_core/utils/ModpackContentTools.java index bd24f32bc..91b190e23 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/utils/ModpackContentTools.java +++ b/core/src/main/java/pl/skidam/automodpack_core/utils/ModpackContentTools.java @@ -9,8 +9,8 @@ import static pl.skidam.automodpack_core.GlobalVariables.*; public class ModpackContentTools { - public static String getFileType(String file, Jsons.ModpackContentFields list) { - for (Jsons.ModpackContentFields.ModpackContentItem item : list.list) { + public static String getFileType(String file, Jsons.ModpackGroupFields list) { + for (Jsons.ModpackGroupFields.ModpackContentItem item : list.list) { if (item.file.contains(file)) { // compare file absolute path if it contains item.file return item.type; } diff --git a/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java b/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java index 931fab77f..64f9e544a 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java +++ b/core/src/main/java/pl/skidam/automodpack_core/utils/WorkaroundUtil.java @@ -17,7 +17,7 @@ public WorkaroundUtil(Path modapckPath) { // returns list of formatted modpack files which are mods with services (these mods need special treatment in order to work properly) // mods returned by this method should be installed in standard `~/mods/` directory - public Set getWorkaroundMods(Jsons.ModpackContentFields modpackContentFields) { + public Set getWorkaroundMods(Jsons.ModpackGroupFields modpackGroupFields) { Set workaroundMods = new HashSet<>(); // this workaround is needed only for neo/forge mods @@ -25,7 +25,7 @@ public Set getWorkaroundMods(Jsons.ModpackContentFields modpackContentFi return workaroundMods; } - for (Jsons.ModpackContentFields.ModpackContentItem item : modpackContentFields.list) { + for (Jsons.ModpackGroupFields.ModpackContentItem item : modpackGroupFields.list) { if (item.type.equals("mod")) { Path modPath = CustomFileUtils.getPath(modpackPath, item.file); if (FileInspection.hasSpecificServices(modPath)) { diff --git a/core/src/test/java/pl/skidam/automodpack_core/modpack/ModpackTest.java b/core/src/test/java/pl/skidam/automodpack_core/modpack/ModpackTest.java index e97b197fb..92602efc6 100644 --- a/core/src/test/java/pl/skidam/automodpack_core/modpack/ModpackTest.java +++ b/core/src/test/java/pl/skidam/automodpack_core/modpack/ModpackTest.java @@ -48,7 +48,7 @@ void modpackTest() { "ModpackContentItems(file=/mods/server-mod-1.20.jar, size=1, type=other, editable=false, sha1=86f7e437faa5a7fce15d1ddcb9eaeaea377667b8, murmur=null)" ); - ModpackContent content = new ModpackContent("TestPack", null, testFilesDir, new ArrayList<>(), new ArrayList<>(editable), new ArrayList<>(), new ModpackExecutor().getExecutor()); + ModpackContent content = new ModpackContent("TestPack", null, testFilesDir, new ArrayList<>(), new ArrayList<>(editable), new ArrayList<>(), false, false, new ModpackExecutor().getExecutor()); content.create(); boolean correct = true; diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/Preload.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/Preload.java index 60217a188..5d523772a 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/Preload.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/Preload.java @@ -172,10 +172,32 @@ private void loadConfigs() { ConfigTools.save(serverConfigFile, serverConfigV2); LOGGER.info("Updated server config version to {}", serverConfigVersion.DO_NOT_CHANGE_IT); } + + if (serverConfigVersion.DO_NOT_CHANGE_IT == 2) { + // Update the configs schemes to not crash the game if loaded with old config! + var serverConfigV2 = ConfigTools.load(serverConfigFile, Jsons.ServerConfigFieldsV2.class); + var serverConfigV3 = ConfigTools.softLoad(serverConfigFile, Jsons.ServerConfigFieldsV3.class); + if (serverConfigV2 != null && serverConfigV3 != null) { + serverConfigVersion.DO_NOT_CHANGE_IT = 3; + serverConfigV3.DO_NOT_CHANGE_IT = 3; + + // copy all modpack fields from v2 to main modpack v3 + serverConfigV3.groups.get("main").groupName = serverConfigV2.modpackName; + serverConfigV3.groups.get("main").generateModpackOnStart = serverConfigV2.generateModpackOnStart; + serverConfigV3.groups.get("main").autoExcludeUnnecessaryFiles = serverConfigV2.autoExcludeUnnecessaryFiles; + serverConfigV3.groups.get("main").autoExcludeServerSideMods = serverConfigV2.autoExcludeServerSideMods; + serverConfigV3.groups.get("main").allowEditsInFiles = serverConfigV2.allowEditsInFiles; + serverConfigV3.groups.get("main").syncedFiles = serverConfigV2.syncedFiles; + serverConfigV3.groups.get("main").forceCopyFilesToStandardLocation = serverConfigV2.forceCopyFilesToStandardLocation; + + ConfigTools.save(serverConfigFile, serverConfigV3); + LOGGER.info("Updated server config version to {}", serverConfigVersion.DO_NOT_CHANGE_IT); + } + } } // load server config - serverConfig = ConfigTools.load(serverConfigFile, Jsons.ServerConfigFieldsV2.class); + serverConfig = ConfigTools.load(serverConfigFile, Jsons.ServerConfigFieldsV3.class); if (serverConfig != null) { // Add current loader to the list @@ -186,9 +208,12 @@ private void loadConfigs() { } // Check modpack name and fix it if needed, because it will be used for naming a folder on client - if (!serverConfig.modpackName.isEmpty() && FileInspection.isInValidFileName(serverConfig.modpackName)) { - serverConfig.modpackName = FileInspection.fixFileName(serverConfig.modpackName); - LOGGER.info("Changed modpack name to {}", serverConfig.modpackName); + for (String groupId : serverConfig.groups.keySet()) { + if (!groupId.isEmpty() && FileInspection.isInValidFileName(groupId)) { + serverConfig.groups.put(FileInspection.fixFileName(groupId), serverConfig.groups.get(groupId)); + serverConfig.groups.remove(groupId); + LOGGER.info("Changed modpack name to {}", groupId); + } } // Save changes diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/SelfUpdater.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/SelfUpdater.java index f2e9ef8ee..4016f77a6 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/SelfUpdater.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/SelfUpdater.java @@ -31,7 +31,7 @@ public static boolean update () { return update(null); } - public static boolean update(Jsons.ModpackContentFields serverModpackContent) { + public static boolean update(Jsons.ModpackContentMasterFields serverModpackContentMaster) { if (LOADER_MANAGER.isDevelopmentEnvironment()) return false; if (LOADER_MANAGER.getEnvironmentType() == LoaderManagerService.EnvironmentType.SERVER && !serverConfig.selfUpdater) { @@ -43,15 +43,15 @@ public static boolean update(Jsons.ModpackContentFields serverModpackContent) { boolean gettingServerVersion = false; // Check if server version is available - if (serverModpackContent != null && serverModpackContent.automodpackVersion != null) { - modrinthAPIList.add(ModrinthAPI.getModSpecificVersion(AUTOMODPACK_ID, serverModpackContent.automodpackVersion, serverModpackContent.mcVersion)); + if (serverModpackContentMaster != null && serverModpackContentMaster.automodpackVersion != null) { + modrinthAPIList.add(ModrinthAPI.getModSpecificVersion(AUTOMODPACK_ID, serverModpackContentMaster.automodpackVersion, serverModpackContentMaster.mcVersion)); gettingServerVersion = true; } else { modrinthAPIList = ModrinthAPI.getModInfosFromID(AUTOMODPACK_ID); } if (gettingServerVersion) { - LOGGER.info("Syncing AutoModpack to server version: {}", serverModpackContent.automodpackVersion); + LOGGER.info("Syncing AutoModpack to server version: {}", serverModpackContentMaster.automodpackVersion); } else if (LOADER_MANAGER.getEnvironmentType() == LoaderManagerService.EnvironmentType.CLIENT && !clientConfig.selfUpdater) { LOGGER.info("AutoModpack self-updater is disabled in client config."); return false; diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java index fe18cbc7c..eb3d992f3 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUpdater.java @@ -28,9 +28,9 @@ public class ModpackUpdater { public FetchManager fetchManager; public long totalBytesToDownload = 0; public boolean fullDownload = false; - private Jsons.ModpackContentFields serverModpackContent; + private Jsons.ModpackGroupFields serverModpackContent; private String modpackContentJson; - public Map> failedDownloads = new HashMap<>(); + public Map> failedDownloads = new HashMap<>(); private final Set newDownloadedFiles = new HashSet<>(); // Only files which did not exist before. Because some files may have the same name/path and be updated. private Jsons.ModpackAddresses modpackAddresses; private Secrets.Secret modpackSecret; @@ -39,10 +39,10 @@ public class ModpackUpdater { public String getModpackName() { - return serverModpackContent.modpackName; + return serverModpackContent.groupName; } - public void prepareUpdate(Jsons.ModpackContentFields modpackContent, Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, Path modpackPath) { + public void prepareUpdate(Jsons.ModpackGroupFields modpackContent, Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, Path modpackPath) { this.serverModpackContent = modpackContent; this.modpackAddresses = modpackAddresses; this.modpackSecret = secret; @@ -159,7 +159,7 @@ public void startUpdate() { modpackDir = ModpackUtils.renameModpackDir(serverModpackContent, modpackDir); modpackContentFile = modpackDir.resolve(modpackContentFile.getFileName()); - Iterator iterator = serverModpackContent.list.iterator(); + Iterator iterator = serverModpackContent.list.iterator(); // CLEAN UP THE LIST @@ -167,7 +167,7 @@ public void startUpdate() { int skippedEditableFiles = 0; while (iterator.hasNext()) { - Jsons.ModpackContentFields.ModpackContentItem modpackContentField = iterator.next(); + Jsons.ModpackGroupFields.ModpackContentItem modpackContentField = iterator.next(); String file = modpackContentField.file; String serverSHA1 = modpackContentField.sha1; @@ -208,7 +208,7 @@ public void startUpdate() { List fetchDatas = new LinkedList<>(); - for (Jsons.ModpackContentFields.ModpackContentItem field : serverModpackContent.list) { + for (Jsons.ModpackGroupFields.ModpackContentItem field : serverModpackContent.list) { totalBytesToDownload += Long.parseLong(field.size); @@ -418,7 +418,7 @@ private boolean applyModpack() throws Exception { } catch (IllegalArgumentException e) { LOGGER.error("Failed to save client secret", e); } - Jsons.ModpackContentFields modpackContent = ConfigTools.loadModpackContent(modpackContentFile); + Jsons.ModpackGroupFields modpackContent = ConfigTools.loadModpackContent(modpackContentFile); if (modpackContent == null) { throw new IllegalStateException("Failed to load modpack content"); // Something gone very wrong... @@ -511,11 +511,11 @@ private boolean applyModpack() throws Exception { } // returns set of formated files which we should not copy to the cwd - let them stay in the modpack directory - private Set getFilesNotToCopy(Set modpackContentItems, Set workaroundMods) { + private Set getFilesNotToCopy(Set modpackContentItems, Set workaroundMods) { Set filesNotToCopy = new HashSet<>(); // Make list of files which we do not copy to the running directory - for (Jsons.ModpackContentFields.ModpackContentItem item : modpackContentItems) { + for (Jsons.ModpackGroupFields.ModpackContentItem item : modpackContentItems) { if (item.forceCopy) { continue; } @@ -535,7 +535,7 @@ private Set getFilesNotToCopy(Set modpackFiles = modpackContent.list.stream().map(modpackContentField -> modpackContentField.file).toList(); List pathList; try (Stream pathStream = Files.walk(modpackDir)) { diff --git a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java index d66b3fe9e..090e6c854 100644 --- a/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java +++ b/loader/core/src/main/java/pl/skidam/automodpack_loader_core/client/ModpackUtils.java @@ -25,7 +25,7 @@ public class ModpackUtils { - public static boolean isUpdate(Jsons.ModpackContentFields serverModpackContent, Path modpackDir) { + public static boolean isUpdate(Jsons.ModpackGroupFields serverModpackContent, Path modpackDir) { if (serverModpackContent == null || serverModpackContent.list == null) { throw new IllegalArgumentException("Server modpack content list is null"); } @@ -35,13 +35,13 @@ public static boolean isUpdate(Jsons.ModpackContentFields serverModpackContent, var optionalClientModpackContentFile = ModpackContentTools.getModpackContentFile(modpackDir); if (optionalClientModpackContentFile.isPresent() && Files.exists(optionalClientModpackContentFile.get())) { - Jsons.ModpackContentFields clientModpackContent = ConfigTools.loadModpackContent(optionalClientModpackContentFile.get()); + Jsons.ModpackGroupFields clientModpackContent = ConfigTools.loadModpackContent(optionalClientModpackContentFile.get()); if (clientModpackContent == null) { return true; } LOGGER.info("Checking files..."); - for (Jsons.ModpackContentFields.ModpackContentItem modpackContentField : serverModpackContent.list) { + for (Jsons.ModpackGroupFields.ModpackContentItem modpackContentField : serverModpackContent.list) { String file = modpackContentField.file; String serverSHA1 = modpackContentField.sha1; @@ -62,7 +62,7 @@ public static boolean isUpdate(Jsons.ModpackContentFields serverModpackContent, } // Server also might have deleted some files - for (Jsons.ModpackContentFields.ModpackContentItem modpackContentField : clientModpackContent.list) { + for (Jsons.ModpackGroupFields.ModpackContentItem modpackContentField : clientModpackContent.list) { var serverItemOpt = serverModpackContent.list.stream().filter(item -> item.file.equals(modpackContentField.file)).findFirst(); if (serverItemOpt.isEmpty()) { LOGGER.info("File does not exist on server {}", modpackContentField.file); @@ -77,11 +77,11 @@ public static boolean isUpdate(Jsons.ModpackContentFields serverModpackContent, } } - public static boolean correctFilesLocations(Path modpackDir, Jsons.ModpackContentFields serverModpackContent, Set filesNotToCopy) throws IOException { + public static boolean correctFilesLocations(Path modpackDir, Jsons.ModpackGroupFields serverModpackContent, Set filesNotToCopy) throws IOException { boolean needsRestart = false; // correct the files locations - for (Jsons.ModpackContentFields.ModpackContentItem contentItem : serverModpackContent.list) { + for (Jsons.ModpackGroupFields.ModpackContentItem contentItem : serverModpackContent.list) { String formattedFile = contentItem.file; Path modpackFile = CustomFileUtils.getPath(modpackDir, formattedFile); Path runFile = CustomFileUtils.getPathFromCWD(formattedFile); @@ -131,10 +131,10 @@ public static boolean correctFilesLocations(Path modpackDir, Jsons.ModpackConten return needsRestart; } - public static boolean removeRestModsNotToCopy(Jsons.ModpackContentFields serverModpackContent, Set filesNotToCopy, Set modsToKeep) { + public static boolean removeRestModsNotToCopy(Jsons.ModpackGroupFields serverModpackContent, Set filesNotToCopy, Set modsToKeep) { boolean needsRestart = false; - for (Jsons.ModpackContentFields.ModpackContentItem contentItem : serverModpackContent.list) { + for (Jsons.ModpackGroupFields.ModpackContentItem contentItem : serverModpackContent.list) { String formattedFile = contentItem.file; Path runFile = CustomFileUtils.getPathFromCWD(formattedFile); boolean isMod = "mod".equals(contentItem.type); @@ -299,14 +299,14 @@ private static void addDependenciesRecursively(FileInspection.Mod mod, Collectio } } - public static Path renameModpackDir(Jsons.ModpackContentFields serverModpackContent, Path modpackDir) { + public static Path renameModpackDir(Jsons.ModpackGroupFields serverModpackContent, Path modpackDir) { if (clientConfig.installedModpacks == null || clientConfig.selectedModpack == null || clientConfig.selectedModpack.isBlank()) { return modpackDir; } String installedModpackName = clientConfig.selectedModpack; Jsons.ModpackAddresses installedModpackAddresses = clientConfig.installedModpacks.get(installedModpackName); - String serverModpackName = serverModpackContent.modpackName; + String serverModpackName = serverModpackContent.groupName; if (installedModpackAddresses != null && !serverModpackName.equals(installedModpackName) && !serverModpackName.isEmpty()) { @@ -340,7 +340,7 @@ public static boolean selectModpack(Path modpackDirToSelect, Jsons.ModpackAddres // Save current editable files Path selectedModpackDir = modpacksDir.resolve(selectedModpack); Path selectedModpackContentFile = selectedModpackDir.resolve(hostModpackContentFile.getFileName()); - Jsons.ModpackContentFields modpackContent = ConfigTools.loadModpackContent(selectedModpackContentFile); + Jsons.ModpackGroupFields modpackContent = ConfigTools.loadModpackContent(selectedModpackContentFile); if (modpackContent != null) { Set editableFiles = getEditableFiles(modpackContent.list); ModpackUtils.preserveEditableFiles(selectedModpackDir, editableFiles, newDownloadedFiles); @@ -349,7 +349,7 @@ public static boolean selectModpack(Path modpackDirToSelect, Jsons.ModpackAddres // Copy editable files from modpack to select Path modpackContentFile = modpackDirToSelect.resolve(hostModpackContentFile.getFileName()); - Jsons.ModpackContentFields modpackContentToSelect = ConfigTools.loadModpackContent(modpackContentFile); + Jsons.ModpackGroupFields modpackContentToSelect = ConfigTools.loadModpackContent(modpackContentFile); if (modpackContentToSelect != null) { Set editableFiles = getEditableFiles(modpackContentToSelect.list); ModpackUtils.copyPreviousEditableFiles(modpackDirToSelect, editableFiles, newDownloadedFiles); @@ -412,19 +412,19 @@ public static Path getModpackPath(InetSocketAddress address, String modpackName) return modpackDir; } - public static Optional requestServerModpackContent(Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, boolean allowAskingUser) { + public static Optional requestServerModpackContent(Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, boolean allowAskingUser) { return fetchModpackContent(modpackAddresses, secret, (client) -> client.downloadFile(new byte[0], modpackContentTempFile, null), "Fetched", allowAskingUser); } - public static Optional refreshServerModpackContent(Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, byte[][] fileHashes, boolean allowAskingUser) { + public static Optional refreshServerModpackContent(Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, byte[][] fileHashes, boolean allowAskingUser) { return fetchModpackContent(modpackAddresses, secret, (client) -> client.requestRefresh(fileHashes, modpackContentTempFile), "Re-fetched", allowAskingUser); } - private static Optional fetchModpackContent(Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, Function> operation, String fetchType, boolean allowAskingUser) { + private static Optional fetchModpackContent(Jsons.ModpackAddresses modpackAddresses, Secrets.Secret secret, Function> operation, String fetchType, boolean allowAskingUser) { if (secret == null) return Optional.empty(); if (modpackAddresses.isAnyEmpty()) @@ -522,8 +522,8 @@ private static Boolean askUserAboutCertificate(InetSocketAddress address, String } // check if modpackContent is valid/isn't malicious - public static boolean potentiallyMalicious(Jsons.ModpackContentFields serverModpackContent) { - String modpackName = serverModpackContent.modpackName; + public static boolean potentiallyMalicious(Jsons.ModpackGroupFields serverModpackContent) { + String modpackName = serverModpackContent.groupName; if (modpackName.contains("../") || modpackName.contains("/..")) { LOGGER.error("Modpack content is invalid, it contains /../ in modpack name"); return true; @@ -583,10 +583,10 @@ public static void copyPreviousEditableFiles(Path modpackDir, Set editab } } - static Set getEditableFiles(Set modpackContentItems) { + static Set getEditableFiles(Set modpackContentItems) { Set editableFiles = new HashSet<>(); - for (Jsons.ModpackContentFields.ModpackContentItem modpackContentItem : modpackContentItems) { + for (Jsons.ModpackGroupFields.ModpackContentItem modpackContentItem : modpackContentItems) { if (modpackContentItem.editable) { editableFiles.add(modpackContentItem.file); } diff --git a/src/main/java/pl/skidam/automodpack/client/ui/ChangelogScreen.java b/src/main/java/pl/skidam/automodpack/client/ui/ChangelogScreen.java index a20a6fc1c..fc0d9e834 100644 --- a/src/main/java/pl/skidam/automodpack/client/ui/ChangelogScreen.java +++ b/src/main/java/pl/skidam/automodpack/client/ui/ChangelogScreen.java @@ -14,8 +14,7 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; + import net.minecraft.Util; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; @@ -101,7 +100,7 @@ public void versionedRender(VersionedMatrices matrices, int mouseX, int mouseY, drawSummaryOfChanges(matrices); } - private Jsons.ModpackContentFields modpackContent = null; + private Jsons.ModpackGroupFields modpackContent = null; private void drawSummaryOfChanges(VersionedMatrices matrices) { diff --git a/src/main/java/pl/skidam/automodpack/init/Common.java b/src/main/java/pl/skidam/automodpack/init/Common.java index b69d53471..f28f47288 100644 --- a/src/main/java/pl/skidam/automodpack/init/Common.java +++ b/src/main/java/pl/skidam/automodpack/init/Common.java @@ -19,21 +19,24 @@ public class Common { public static MinecraftServer server = null; public static void serverInit() { - if (serverConfig.generateModpackOnStart) { - LOGGER.info("Generating modpack..."); - long genStart = System.currentTimeMillis(); - if (modpackExecutor.generateNew()) { - LOGGER.info("Modpack generated! took " + (System.currentTimeMillis() - genStart) + "ms"); + for (String groupId : serverConfig.groups.keySet()) { + var groupDecl = serverConfig.groups.get(groupId); + if (groupDecl.generateModpackOnStart) { + LOGGER.info("Generating modpack..."); + long genStart = System.currentTimeMillis(); + if (modpackExecutor.generateNew(groupId)) { + LOGGER.info("Modpack generated! took " + (System.currentTimeMillis() - genStart) + "ms"); + } else { + LOGGER.error("Failed to generate modpack!"); + } } else { - LOGGER.error("Failed to generate modpack!"); - } - } else { - LOGGER.info("Loading last modpack..."); - long genStart = System.currentTimeMillis(); - if (modpackExecutor.loadLast()) { - LOGGER.info("Modpack loaded! took " + (System.currentTimeMillis() - genStart) + "ms"); - } else { - LOGGER.error("Failed to load modpack!"); + LOGGER.info("Loading last modpack..."); + long genStart = System.currentTimeMillis(); + if (modpackExecutor.loadLast(groupId)) { + LOGGER.info("Modpack loaded! took " + (System.currentTimeMillis() - genStart) + "ms"); + } else { + LOGGER.error("Failed to load modpack!"); + } } } diff --git a/src/main/java/pl/skidam/automodpack/modpack/Commands.java b/src/main/java/pl/skidam/automodpack/modpack/Commands.java index c804b1fdb..0aa3fccad 100644 --- a/src/main/java/pl/skidam/automodpack/modpack/Commands.java +++ b/src/main/java/pl/skidam/automodpack/modpack/Commands.java @@ -114,7 +114,7 @@ private static int connections(CommandContext context) { private static int reload(CommandContext context) { Util.backgroundExecutor().execute(() -> { - var tempServerConfig = ConfigTools.load(serverConfigFile, Jsons.ServerConfigFieldsV2.class); + var tempServerConfig = ConfigTools.load(serverConfigFile, Jsons.ServerConfigFieldsV3.class); if (tempServerConfig != null) { serverConfig = tempServerConfig; send(context, "AutoModpack server config reloaded!", ChatFormatting.GREEN, true); @@ -208,7 +208,19 @@ private static int generateModpack(CommandContext context) { } send(context, "Generating Modpack...", ChatFormatting.YELLOW, true); long start = System.currentTimeMillis(); - if (modpackExecutor.generateNew()) { + + // generate every group + boolean allGenerated = true; + for (String groupId : serverConfig.groups.keySet()) { + if (!modpackExecutor.generateNew(groupId)) { + allGenerated = false; + send(context, "Failed to generate modpack for group: " + groupId, ChatFormatting.RED, true); + } else { + send(context, "Modpack generated for group: " + groupId, ChatFormatting.GREEN, true); + } + } + + if (allGenerated) { send(context, "Modpack generated! took " + (System.currentTimeMillis() - start) + "ms", ChatFormatting.GREEN, true); } else { send(context, "Modpack generation failed! Check logs for more info.", ChatFormatting.RED, true); From 71bfad2619edb4a4162758c32c6204e0727c300b Mon Sep 17 00:00:00 2001 From: skidam Date: Sat, 26 Jul 2025 20:40:10 +0200 Subject: [PATCH 2/2] groups in the protocol --- .../modpack/ModpackContent.java | 6 +- .../protocol/DownloadClient.java | 45 ++++++++++++++ .../automodpack_core/protocol/NetUtils.java | 2 +- .../netty/handler/ProtocolMessageDecoder.java | 6 +- .../netty/handler/ProtocolMessageEncoder.java | 45 -------------- .../netty/handler/ServerMessageHandler.java | 58 +++++++++++++------ ...ssage.java => PackMetaRequestMessage.java} | 10 ++-- 7 files changed, 100 insertions(+), 72 deletions(-) delete mode 100644 core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageEncoder.java rename core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/{EchoMessage.java => PackMetaRequestMessage.java} (55%) diff --git a/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java b/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java index 1d0dafe5f..358106c5d 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java +++ b/core/src/main/java/pl/skidam/automodpack_core/modpack/ModpackContent.java @@ -49,6 +49,10 @@ public String getModpackName() { return MODPACK_NAME; } + public Path getModpackContentFile() { + return MODPACK_DIR.resolve(hostModpackContentFile.getFileName().toString()); + } + public boolean create() { try { pathsMap.clear(); @@ -139,7 +143,7 @@ public synchronized void saveModpackContent() { synchronized (list) { Jsons.ModpackGroupFields modpackContent = new Jsons.ModpackGroupFields(list); modpackContent.groupName = MODPACK_NAME; - ConfigTools.saveModpackContent(MODPACK_DIR.resolve(hostModpackContentFile.getFileName().toString()), modpackContent); + ConfigTools.saveModpackContent(getModpackContentFile(), modpackContent); synchronized (hostModpackContentFile) { Jsons.ModpackContentMasterFields masterContent = ConfigTools.load(hostModpackContentFile, Jsons.ModpackContentMasterFields.class); diff --git a/core/src/main/java/pl/skidam/automodpack_core/protocol/DownloadClient.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/DownloadClient.java index 912c4c22b..27589312f 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/protocol/DownloadClient.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/DownloadClient.java @@ -148,6 +148,15 @@ public CompletableFuture downloadFile(byte[] fileHash, Path destination, I return conn.sendDownloadFile(fileHash, destination, chunkCallback); } + /** + * + */ + public CompletableFuture fetchPackMeta(String groupId, Path destination, IntConsumer chunkCallback) { + Connection conn = getFreeConnection(); + return conn.sendPackMetaRequest(groupId, destination, chunkCallback); + } + + /** * Sends a refresh request with the given file hashes. */ @@ -395,6 +404,42 @@ public CompletableFuture sendDownloadFile(byte[] fileHash, Path destinatio }, executor); } + /** + * Sends a pack meta request over this connection. + */ + public CompletableFuture sendPackMetaRequest(String groupId, Path destination, IntConsumer chunkCallback) { + if (destination == null) { + throw new IllegalArgumentException("Destination cannot be null"); + } + + byte[] groupIdBytes = groupId.getBytes(); + + return CompletableFuture.supplyAsync(() -> { + Exception exception = null; + try { + // Build File Request message: + // [protocolVersion][FILE_REQUEST_TYPE][secret][int: groupIdBytes.length][groupIdBytes] + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeByte(PROTOCOL_VERSION); + dos.writeByte(FILE_REQUEST_TYPE); + dos.write(secretBytes); + dos.writeInt(groupIdBytes.length); + dos.write(groupIdBytes); + dos.flush(); + byte[] payload = baos.toByteArray(); + + writeProtocolMessage(payload); + return readFileResponse(destination, chunkCallback); + } catch (Exception e) { + exception = e; + throw new CompletionException(e); + } finally { + finalBlock(exception); + } + }, executor); + } + /** * Sends a refresh request over this connection. */ diff --git a/core/src/main/java/pl/skidam/automodpack_core/protocol/NetUtils.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/NetUtils.java index cc4a0994d..a58086f8e 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/protocol/NetUtils.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/NetUtils.java @@ -28,7 +28,7 @@ public class NetUtils { public static final int MAGIC_AMMC = 0x414D4D43; public static final int MAGIC_AMOK = 0x414D4F4B; - public static final byte ECHO_TYPE = 0x00; + public static final byte PACK_META_REQUEST_TYPE = 0x00; public static final byte FILE_REQUEST_TYPE = 0x01; public static final byte FILE_RESPONSE_TYPE = 0x02; public static final byte REFRESH_REQUEST_TYPE = 0x03; diff --git a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageDecoder.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageDecoder.java index d73095f3c..e38845601 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageDecoder.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageDecoder.java @@ -4,7 +4,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import pl.skidam.automodpack_core.protocol.NetUtils; -import pl.skidam.automodpack_core.protocol.netty.message.EchoMessage; +import pl.skidam.automodpack_core.protocol.netty.message.PackMetaRequestMessage; import pl.skidam.automodpack_core.protocol.netty.message.FileRequestMessage; import pl.skidam.automodpack_core.protocol.netty.message.FileResponseMessage; import pl.skidam.automodpack_core.protocol.netty.message.RefreshRequestMessage; @@ -23,11 +23,11 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t in.readBytes(secret); switch (type) { - case ECHO_TYPE: + case PACK_META_REQUEST_TYPE: int dataLength = in.readInt(); byte[] data = new byte[dataLength]; in.readBytes(data); - out.add(new EchoMessage(version, secret, data)); + out.add(new PackMetaRequestMessage(version, secret, data)); break; case FILE_REQUEST_TYPE: int fileHashLength = in.readInt(); diff --git a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageEncoder.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageEncoder.java deleted file mode 100644 index 9a8c59691..000000000 --- a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ProtocolMessageEncoder.java +++ /dev/null @@ -1,45 +0,0 @@ -package pl.skidam.automodpack_core.protocol.netty.handler; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; -import pl.skidam.automodpack_core.protocol.netty.message.*; - -import static pl.skidam.automodpack_core.protocol.NetUtils.*; - -public class ProtocolMessageEncoder extends MessageToByteEncoder { - @Override - protected void encode(ChannelHandlerContext ctx, ProtocolMessage msg, ByteBuf out) throws Exception { - out.writeByte(msg.getVersion()); - out.writeByte(msg.getType()); - out.writeBytes(msg.getSecret()); - - switch (msg.getType()) { - case ECHO_TYPE: - EchoMessage echoMsg = (EchoMessage) msg; - out.writeInt(echoMsg.getDataLength()); - out.writeBytes(echoMsg.getData()); - break; - case FILE_REQUEST_TYPE: - FileRequestMessage fileRequestMessage = (FileRequestMessage) msg; - out.writeInt(fileRequestMessage.getFileHashLength()); - out.writeBytes(fileRequestMessage.getFileHash()); - break; - case FILE_RESPONSE_TYPE: - FileResponseMessage fileResponseMessage = (FileResponseMessage) msg; - out.writeInt(fileResponseMessage.getDataLength()); - out.writeBytes(fileResponseMessage.getData()); - break; - case REFRESH_REQUEST_TYPE: - RefreshRequestMessage refreshRequestMessage = (RefreshRequestMessage) msg; - out.writeInt(refreshRequestMessage.getFileHashesCount()); - out.writeInt(refreshRequestMessage.getFileHashesLength()); - for (byte[] fileHash : refreshRequestMessage.getFileHashesList()) { - out.writeBytes(fileHash); - } - break; - default: - throw new IllegalArgumentException("Unknown message type: " + msg.getType()); - } - } -} diff --git a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ServerMessageHandler.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ServerMessageHandler.java index f21968b58..fc39df5d8 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ServerMessageHandler.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/handler/ServerMessageHandler.java @@ -8,8 +8,9 @@ import io.netty.handler.stream.ChunkedFile; import io.netty.util.CharsetUtil; import pl.skidam.automodpack_core.auth.Secrets; +import pl.skidam.automodpack_core.config.Jsons; import pl.skidam.automodpack_core.modpack.ModpackContent; -import pl.skidam.automodpack_core.protocol.netty.message.EchoMessage; +import pl.skidam.automodpack_core.protocol.netty.message.PackMetaRequestMessage; import pl.skidam.automodpack_core.protocol.netty.message.FileRequestMessage; import pl.skidam.automodpack_core.protocol.netty.message.ProtocolMessage; import pl.skidam.automodpack_core.protocol.netty.message.RefreshRequestMessage; @@ -48,19 +49,18 @@ protected void channelRead0(ChannelHandlerContext ctx, ProtocolMessage msg) thro } switch (msg.getType()) { - case ECHO_TYPE: - EchoMessage echoMsg = (EchoMessage) msg; - ByteBuf echoBuf = Unpooled.buffer(1 + 1 + msg.getSecret().length + echoMsg.getData().length); - echoBuf.writeByte(clientProtocolVersion); - echoBuf.writeByte(ECHO_TYPE); - echoBuf.writeBytes(echoMsg.getSecret()); - echoBuf.writeBytes(echoMsg.getData()); - ctx.writeAndFlush(echoBuf); - ctx.channel().close(); + case PACK_META_REQUEST_TYPE: + PackMetaRequestMessage metaRequest = (PackMetaRequestMessage) msg; + sendPackMeta(ctx, metaRequest.getData()); break; case FILE_REQUEST_TYPE: FileRequestMessage fileRequest = (FileRequestMessage) msg; - sendFile(ctx, fileRequest.getFileHash()); + Path filePath = resolveFile(fileRequest.getFileHash()); + if (filePath == null) { + sendError(ctx, (byte) 1, "File not found"); + } else { + sendFile(ctx, filePath); + } break; case REFRESH_REQUEST_TYPE: RefreshRequestMessage refreshRequest = (RefreshRequestMessage) msg; @@ -108,7 +108,7 @@ private void refreshModpackFiles(ChannelHandlerContext context, byte[][] FileHas LOGGER.info("Sending new modpack-content.json"); // Sends new json - sendFile(context, new byte[0]); + sendPackMeta(context, new byte[0]); } @@ -130,16 +130,40 @@ private boolean validateSecret(ChannelHandlerContext ctx, SocketAddress address, return valid; } - private void sendFile(ChannelHandlerContext ctx, byte[] bsha1) throws IOException { + private void sendPackMeta(ChannelHandlerContext ctx, byte[] data) throws IOException { + final String groupId = new String(data, CharsetUtil.UTF_8); + Jsons.GroupDeclaration requestedGroup = null; + if (!groupId.isBlank()) { + requestedGroup = serverConfig.groups.getOrDefault(groupId, null); + } + + if (requestedGroup == null) { + sendError(ctx, PROTOCOL_VERSION, "Group not found: " + groupId); + return; + } + + ModpackContent modpackContent = modpackExecutor.modpacks.get(requestedGroup.groupName); + Path hostModpackContentFile = modpackContent.getModpackContentFile(); + if (hostModpackContentFile == null || !Files.exists(hostModpackContentFile)) { + sendError(ctx, PROTOCOL_VERSION, "Modpack content file not found for group: " + groupId); + return; + } + + sendFile(ctx, hostModpackContentFile); + } + + private Path resolveFile(byte[] bsha1) { final String sha1 = new String(bsha1, CharsetUtil.UTF_8); final Optional optionalPath = resolvePath(sha1); - if (optionalPath.isEmpty() || !Files.exists(optionalPath.get())) { - sendError(ctx, (byte) 1, "File not found"); - return; + if (optionalPath.isPresent() && Files.exists(optionalPath.get())) { + return optionalPath.get(); } - final Path path = optionalPath.get(); + return null; + } + + private void sendFile(ChannelHandlerContext ctx, Path path) throws IOException { final long fileSize = Files.size(path); // Send file response header: version, FILE_RESPONSE type, then file size (8 bytes) diff --git a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/EchoMessage.java b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/PackMetaRequestMessage.java similarity index 55% rename from core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/EchoMessage.java rename to core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/PackMetaRequestMessage.java index e13a0110d..1a72e4bc6 100644 --- a/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/EchoMessage.java +++ b/core/src/main/java/pl/skidam/automodpack_core/protocol/netty/message/PackMetaRequestMessage.java @@ -1,13 +1,13 @@ package pl.skidam.automodpack_core.protocol.netty.message; -import static pl.skidam.automodpack_core.protocol.NetUtils.ECHO_TYPE; +import static pl.skidam.automodpack_core.protocol.NetUtils.*; -public class EchoMessage extends ProtocolMessage { +public class PackMetaRequestMessage extends ProtocolMessage { private final int dataLength; private final byte[] data; - public EchoMessage(byte version, byte[] secret, byte[] data) { - super(version, ECHO_TYPE, secret); + public PackMetaRequestMessage(byte version, byte[] secret, byte[] data) { + super(version, PACK_META_REQUEST_TYPE, secret); this.dataLength = data.length; this.data = data; } @@ -19,4 +19,4 @@ public int getDataLength() { public byte[] getData() { return data; } -} +} \ No newline at end of file