From ca9ab2ca12014a69f6b367268683c6277f6b8614 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 18 May 2025 03:24:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0zip=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/config.yml | 3 +- .../core/pack/AbstractPackManager.java | 222 +++++++++++------- .../core/plugin/config/Config.java | 4 + gradle.properties | 2 +- 4 files changed, 146 insertions(+), 85 deletions(-) diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index 22eebce60..778670b6e 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -62,11 +62,10 @@ resource-pack: remove-tinted-leaves-particle: true merge-external-folders: - ModelEngine/resource pack - - CustomNameplates/ResourcePack - BetterModel/build - BetterHud/build merge-external-zip-files: - - CraftEngine/external_packs/example.zip + - CustomNameplates/resourcepack.zip delivery: # Send the resource pack on joining the server send-on-join: true diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 473f1086f..e5809d59f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -420,7 +420,8 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png.mcmeta"); } - private void updateCachedConfigFiles() { + private TreeMap> updateCachedConfigFiles() { + TreeMap> cachedConfigs = new TreeMap<>(); Map previousFiles = this.cachedConfigFiles; this.cachedConfigFiles = new Object2ObjectOpenHashMap<>(32); Yaml yaml = new Yaml(new StringKeyConstructor(new LoaderOptions())); @@ -436,14 +437,20 @@ public abstract class AbstractPackManager implements PackManager { long size = attrs.size(); if (cachedFile != null && cachedFile.lastModified() == lastModifiedTime && cachedFile.size() == size) { AbstractPackManager.this.cachedConfigFiles.put(path, cachedFile); - return FileVisitResult.CONTINUE; + } else { + try (InputStreamReader inputStream = new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8)) { + Map data = yaml.load(inputStream); + if (data == null) return FileVisitResult.CONTINUE; + cachedFile = new CachedConfigFile(data, pack, lastModifiedTime, size); + AbstractPackManager.this.cachedConfigFiles.put(path, cachedFile); + } catch (IOException e) { + AbstractPackManager.this.plugin.logger().severe("Error while reading config file: " + path, e); + return FileVisitResult.CONTINUE; + } } - try (InputStreamReader inputStream = new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8)) { - Map data = yaml.load(inputStream); - if (data == null) return FileVisitResult.CONTINUE; - AbstractPackManager.this.cachedConfigFiles.put(path, new CachedConfigFile(data, pack, lastModifiedTime, size)); - } catch (IOException e) { - AbstractPackManager.this.plugin.logger().severe("Error while reading config file: " + path, e); + for (Map.Entry entry : cachedFile.config().entrySet()) { + processConfigEntry(entry, path, cachedFile.pack(), (p, c) -> + cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c)); } } return FileVisitResult.CONTINUE; @@ -453,19 +460,12 @@ public abstract class AbstractPackManager implements PackManager { this.plugin.logger().severe("Error while reading config file", e); } } + return cachedConfigs; } private void loadResourceConfigs(Predicate predicate) { - TreeMap> cachedConfigs = new TreeMap<>(); long o1 = System.nanoTime(); - this.updateCachedConfigFiles(); - for (Map.Entry fileEntry : this.cachedConfigFiles.entrySet()) { - CachedConfigFile cachedFile = fileEntry.getValue(); - for (Map.Entry entry : cachedFile.config().entrySet()) { - processConfigEntry(entry, fileEntry.getKey(), cachedFile.pack(), (p, c) -> - cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c)); - } - } + TreeMap> cachedConfigs = this.updateCachedConfigFiles(); long o2 = System.nanoTime(); this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms"); for (Map.Entry> entry : cachedConfigs.entrySet()) { @@ -549,69 +549,6 @@ public abstract class AbstractPackManager implements PackManager { // } // } - private List>> updateCachedAssets(@Nullable FileSystem fs) throws IOException { - List folders = new ArrayList<>(); - Map> conflictChecker = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); - Map previousFiles = this.cachedAssetFiles; - this.cachedAssetFiles = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); - folders.addAll(loadedPacks().stream().filter(Pack::enabled).map(Pack::resourcePackFolder).toList()); - folders.addAll(Config.foldersToMerge().stream().map(it -> plugin.dataFolderPath().getParent().resolve(it)).filter(Files::exists).toList()); - for (Path sourceFolder : folders) { - if (Files.exists(sourceFolder)) { - Files.walkFileTree(sourceFolder, new SimpleFileVisitor<>() { - @Override - public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException { - CachedAssetFile cachedAsset = previousFiles.get(file); - long lastModified = attrs.lastModifiedTime().toMillis(); - long size = attrs.size(); - if (cachedAsset != null && cachedAsset.lastModified() == lastModified && cachedAsset.size() == size) { - AbstractPackManager.this.cachedAssetFiles.put(file, cachedAsset); - } else { - cachedAsset = new CachedAssetFile(Files.readAllBytes(file), lastModified, size); - AbstractPackManager.this.cachedAssetFiles.put(file, cachedAsset); - } - if (fs == null) return FileVisitResult.CONTINUE; - Path relative = sourceFolder.relativize(file); - Path targetPath = fs.getPath("resource_pack/" + CharacterUtils.replaceBackslashWithSlash(relative.toString())); - List conflicts = conflictChecker.get(relative); - if (conflicts == null) { - Files.createDirectories(targetPath.getParent()); - Files.write(targetPath, cachedAsset.data()); - conflictChecker.put(relative, List.of(file)); - } else { - PathContext relativeCTX = PathContext.of(relative); - PathContext targetCTX = PathContext.of(targetPath); - PathContext fileCTX = PathContext.of(file); - for (ResolutionConditional resolution : Config.resolutions()) { - if (resolution.matcher().test(relativeCTX)) { - resolution.resolution().run(targetCTX, fileCTX); - return FileVisitResult.CONTINUE; - } - } - switch (conflicts.size()) { - case 1 -> conflictChecker.put(relative, List.of(conflicts.get(0), file)); - case 2 -> conflictChecker.put(relative, List.of(conflicts.get(0), conflicts.get(1), file)); - case 3 -> conflictChecker.put(relative, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), file)); - case 4 -> conflictChecker.put(relative, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), conflicts.get(3), file)); - default -> { - // just ignore it if it has many conflict files - } - } - } - return FileVisitResult.CONTINUE; - } - }); - } - } - List>> conflicts = new ArrayList<>(); - for (Map.Entry> entry : conflictChecker.entrySet()) { - if (entry.getValue().size() > 1) { - conflicts.add(Pair.of(entry.getKey(), entry.getValue())); - } - } - return conflicts; - } - @Override public void generateResourcePack() throws IOException { this.plugin.logger().info("Generating resource pack..."); @@ -621,11 +558,11 @@ public abstract class AbstractPackManager implements PackManager { try (FileSystem fs = Jimfs.newFileSystem(Configuration.forCurrentPlatform())) { // firstly merge existing folders Path generatedPackPath = fs.getPath("resource_pack"); - List>> duplicated = this.updateCachedAssets(fs); + List>> duplicated = this.updateCachedAssets(fs); if (!duplicated.isEmpty()) { plugin.logger().severe(AdventureHelper.miniMessage().stripTags(TranslationManager.instance().miniMessageTranslation("warning.config.pack.duplicated_files"))); int x = 1; - for (Pair> path : duplicated) { + for (Pair> path : duplicated) { this.plugin.logger().warn("[ " + (x++) + " ] " + path.left()); for (int i = 0, size = path.right().size(); i < size; i++) { if (i == size - 1) { @@ -1253,4 +1190,125 @@ public abstract class AbstractPackManager implements PackManager { } } } + + private List>> updateCachedAssets(@Nullable FileSystem fs) throws IOException { + Map> conflictChecker = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); + Map previousFiles = this.cachedAssetFiles; + this.cachedAssetFiles = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); + + List folders = new ArrayList<>(); + folders.addAll(loadedPacks().stream() + .filter(Pack::enabled) + .map(Pack::resourcePackFolder) + .toList()); + folders.addAll(Config.foldersToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .toList()); + for (Path sourceFolder : folders) { + if (Files.exists(sourceFolder)) { + Files.walkFileTree(sourceFolder, new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException { + processRegularFile(file, attrs, sourceFolder, fs, conflictChecker, previousFiles); + return FileVisitResult.CONTINUE; + } + }); + } + } + List externalZips = Config.zipsToMerge().stream() + .map(it -> plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".zip")) + .toList(); + for (Path zip : externalZips) { + processZipFile(zip, zip.getParent(), fs, conflictChecker, previousFiles); + } + + List>> conflicts = new ArrayList<>(); + for (Map.Entry> entry : conflictChecker.entrySet()) { + if (entry.getValue().size() > 1) { + conflicts.add(Pair.of(entry.getKey(), entry.getValue())); + } + } + return conflicts; + } + + private void processRegularFile(Path file, BasicFileAttributes attrs, Path sourceFolder, @Nullable FileSystem fs, + Map> conflictChecker, Map previousFiles) throws IOException { + CachedAssetFile cachedAsset = previousFiles.get(file); + long lastModified = attrs.lastModifiedTime().toMillis(); + long size = attrs.size(); + if (cachedAsset != null && cachedAsset.lastModified() == lastModified && cachedAsset.size() == size) { + cachedAssetFiles.put(file, cachedAsset); + } else { + cachedAsset = new CachedAssetFile(Files.readAllBytes(file), lastModified, size); + cachedAssetFiles.put(file, cachedAsset); + } + if (fs == null) return; + Path relative = sourceFolder.relativize(file); + updateConflictChecker(fs, conflictChecker, file, file, relative, cachedAsset.data()); + } + + private void processZipFile(Path zipFile, Path sourceFolder, @Nullable FileSystem fs, + Map> conflictChecker, Map previousFiles) throws IOException { + try (FileSystem zipFs = FileSystems.newFileSystem(zipFile)) { + long zipLastModified = Files.getLastModifiedTime(zipFile).toMillis(); + long zipSize = Files.size(zipFile); + Path zipRoot = zipFs.getPath("/"); + Files.walkFileTree(zipRoot, new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult visitFile(@NotNull Path entry, @NotNull BasicFileAttributes entryAttrs) throws IOException { + if (entryAttrs.isDirectory()) { + return FileVisitResult.CONTINUE; + } + Path entryPathInZip = zipRoot.relativize(entry); + Path sourcePath = Path.of(zipFile + "!" + entryPathInZip); + CachedAssetFile cachedAsset = previousFiles.get(sourcePath); + if (cachedAsset != null && cachedAsset.lastModified() == zipLastModified && cachedAsset.size() == zipSize) { + cachedAssetFiles.put(sourcePath, cachedAsset); + } else { + byte[] data = Files.readAllBytes(entry); + cachedAsset = new CachedAssetFile(data, zipLastModified, zipSize); + cachedAssetFiles.put(sourcePath, cachedAsset); + } + if (fs != null) { + updateConflictChecker(fs, conflictChecker, entry, sourcePath, entryPathInZip, cachedAsset.data()); + } + return FileVisitResult.CONTINUE; + } + }); + } + } + + private void updateConflictChecker(FileSystem fs, Map> conflictChecker, Path sourcePath, Path namedSourcePath, Path relative, byte[] data) throws IOException { + String relativePath = CharacterUtils.replaceBackslashWithSlash(relative.toString()); + Path targetPath = fs.getPath("resource_pack/" + relativePath); + List conflicts = conflictChecker.get(relativePath); + if (conflicts == null) { + Files.createDirectories(targetPath.getParent()); + Files.write(targetPath, data); + conflictChecker.put(relativePath, List.of(namedSourcePath)); + } else { + PathContext relativeCTX = PathContext.of(relative); + PathContext targetCTX = PathContext.of(targetPath); + PathContext sourceCTX = PathContext.of(sourcePath); + for (ResolutionConditional resolution : Config.resolutions()) { + if (resolution.matcher().test(relativeCTX)) { + resolution.resolution().run(targetCTX, sourceCTX); + return; + } + } + switch (conflicts.size()) { + case 1 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), namedSourcePath)); + case 2 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), conflicts.get(1), namedSourcePath)); + case 3 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), namedSourcePath)); + case 4 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), conflicts.get(3), namedSourcePath)); + default -> { + // just ignore it if it has many conflict files + } + } + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index d6faf0675..be96fe1ed 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -465,6 +465,10 @@ public class Config { return instance.resource_pack$merge_external_folders; } + public static List zipsToMerge() { + return instance.resource_pack$merge_external_zips; + } + public static boolean kickOnDeclined() { return instance.resource_pack$delivery$kick_if_declined; } diff --git a/gradle.properties b/gradle.properties index e2b6917ae..1fabb99a5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.54 +project_version=0.0.54.1 config_version=32 lang_version=12 project_group=net.momirealms