diff --git a/README.md b/README.md index 4a37cc229..d87657dd6 100644 --- a/README.md +++ b/README.md @@ -62,12 +62,13 @@ content selecting on `#minecraft:is_overworld` and friends. ## Install **Plugin (Paper/Purpur/Leaf/Canvas/Folia/Spigot):** drop the plugin jar into `plugins/` and start -the server. On first boot Iris downloads the managed `overworld` and `underworld` beta packs automatically. +the server. First boot performs no pack download. Run `/iris download overworld`, +`/iris download underworld`, or `/iris download https://host/path/pack.zip`, then restart manually. **Mod (Fabric/Forge/NeoForge):** drop the mod jar into `mods/` and start the server. The jar is -self-contained (core, SPI, and required Fabric API modules are bundled). On first boot Iris -prefetches the managed `overworld` and `underworld` beta packs and rebuilds the worldgen datapack; -restart once if startup reports that registry-visible pack data was installed too late for that boot. Packs installed later register their custom dimension types +self-contained (core, SPI, and required Fabric API modules are bundled). First boot compiles only +packs already on disk and never accesses the network. `/iris download` installs a pack atomically +without stopping the server; restart manually afterward. Packs register their custom dimension types (height ranges) and custom biomes through the forced datapack at server start - restart once after adding a pack so worlds get its full heights and biomes; worlds created before that restart run with fallback heights. diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java index 3bfd34412..91083be75 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java @@ -34,9 +34,7 @@ import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisWorld; import art.arcane.iris.engine.platform.BukkitChunkGenerator; import art.arcane.iris.util.common.plugin.VolmitPlugin; -import art.arcane.iris.util.common.plugin.VolmitSender; import lombok.NonNull; -import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; @@ -177,13 +175,8 @@ public final class IrisWorldGeneratorResolver { + " but its dimension failed to load; not redownloading. Fix or delete the pack folder."); return null; } - Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); - Iris.service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false); - dimension = IrisData.loadAnyDimension(id, null); - - if (dimension != null) { - Iris.info("Resolved missing dimension, proceeding."); - } + Iris.warn("Unable to find dimension type " + id + ". Install its pack with /iris download " + + id + " and restart the server."); } return dimension; diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java index c9ea114dc..dd21f68f7 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java @@ -710,9 +710,14 @@ public class CommandIris implements DirectorExecutor { @Param(name = "overwrite", description = "Whether or not to overwrite the pack with the downloaded one", descriptionKey = "iris.director.commandiris.param.whether_not_overwrite_pack_with_downloaded_one", aliases = "force", defaultValue = "false") boolean overwrite ) { - if (PackDownloader.isManagedBetaPack(pack)) { - sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_DOWNLOADING_PACK_BETA_RELEASE, MessageArgument.untrusted("pack", pack), MessageArgument.trusted("value", overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : ""))); - Iris.service(StudioSVC.class).downloadManagedBeta(sender(), pack, overwrite); + if (PackDownloader.isDirectZipUrl(pack)) { + sender().sendMessage("Downloading Iris pack from " + pack + + (overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : "") + "."); + Iris.service(StudioSVC.class).downloadUrl(sender(), pack, overwrite); + } else if (PackDownloader.isManagedPack(pack)) { + sender().sendMessage("Downloading managed Iris pack '" + pack + "' from its configured Git source" + + (overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : "") + "."); + Iris.service(StudioSVC.class).downloadManaged(sender(), pack, overwrite); } else { sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_DOWNLOADING_PACK, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("branch", branch), MessageArgument.trusted("value", overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : ""))); Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, overwrite); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java index b796d73d7..7d3c92ef0 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java @@ -260,7 +260,7 @@ public final class ModdedEngineBootstrap { selfTest(moddedLoader.getClass().getClassLoader()); bind(); IrisLanguage.initialize(); - ModdedStartup.prefetchDefaultPack(); + ModdedStartup.prefetchStartupDatapack(); if (!moddedLoader.clientEnvironment()) { MainWorldService.reconcileEarly(); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java index 6fa1d8dbb..d6be9b58c 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java @@ -253,8 +253,6 @@ public final class ModdedForcedDatapack { } private static Path write() throws IOException { - // No ensureDefaultPack() here: write() is reachable from loadPacks on the first boot, and loadPacks - // must never touch the network. ModdedStartup.prefetchDefaultPack covers the download at boot. String packsHash = packsHash(); Path datapackRoot = datapackRoot(); Files.createDirectories(datapackRoot); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java index 025e38cb6..9387cad8a 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java @@ -33,17 +33,15 @@ public final class ModdedModConfig { private static volatile ModdedModConfig instance; private final String defaultPack; - private final boolean autoDownloadDefaultPack; private final String primaryWorld; private final boolean routePlayersToPrimaryWorld; private final String mainWorldPack; private final long mainWorldSeed; private final boolean mainWorldAutoRestart; - private ModdedModConfig(String defaultPack, boolean autoDownloadDefaultPack, String primaryWorld, boolean routePlayersToPrimaryWorld, + private ModdedModConfig(String defaultPack, String primaryWorld, boolean routePlayersToPrimaryWorld, String mainWorldPack, long mainWorldSeed, boolean mainWorldAutoRestart) { this.defaultPack = defaultPack; - this.autoDownloadDefaultPack = autoDownloadDefaultPack; this.primaryWorld = primaryWorld == null ? "" : primaryWorld.trim(); this.routePlayersToPrimaryWorld = routePlayersToPrimaryWorld; this.mainWorldPack = mainWorldPack == null ? "" : mainWorldPack.trim(); @@ -68,7 +66,7 @@ public final class ModdedModConfig { public static void setPrimaryWorld(String dimensionId) { synchronized (LOCK) { ModdedModConfig current = get(); - ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.autoDownloadDefaultPack, dimensionId, current.routePlayersToPrimaryWorld, + ModdedModConfig updated = new ModdedModConfig(current.defaultPack, dimensionId, current.routePlayersToPrimaryWorld, current.mainWorldPack, current.mainWorldSeed, current.mainWorldAutoRestart); instance = updated; write(configFile(), updated); @@ -78,7 +76,7 @@ public final class ModdedModConfig { public static void setMainWorld(String packRef, long seed) { synchronized (LOCK) { ModdedModConfig current = get(); - ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.autoDownloadDefaultPack, current.primaryWorld, current.routePlayersToPrimaryWorld, + ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.primaryWorld, current.routePlayersToPrimaryWorld, packRef == null ? "" : packRef.trim(), seed, current.mainWorldAutoRestart); instance = updated; write(configFile(), updated); @@ -89,10 +87,6 @@ public final class ModdedModConfig { return defaultPack; } - public boolean autoDownloadDefaultPack() { - return autoDownloadDefaultPack; - } - public String primaryWorld() { return primaryWorld; } @@ -119,7 +113,7 @@ public final class ModdedModConfig { private static ModdedModConfig load() { Path file = configFile(); - ModdedModConfig defaults = new ModdedModConfig("overworld", true, "", true, "", 0L, false); + ModdedModConfig defaults = new ModdedModConfig("overworld", "", true, "", 0L, false); if (!Files.isRegularFile(file)) { write(file, defaults); return defaults; @@ -128,7 +122,6 @@ public final class ModdedModConfig { JSONObject json = new JSONObject(Files.readString(file, StandardCharsets.UTF_8)); return new ModdedModConfig( json.optString("defaultPack", defaults.defaultPack), - json.optBoolean("autoDownloadDefaultPack", defaults.autoDownloadDefaultPack), json.optString("primaryWorld", defaults.primaryWorld), json.optBoolean("routePlayersToPrimaryWorld", defaults.routePlayersToPrimaryWorld), json.optString("mainWorldPack", defaults.mainWorldPack), @@ -143,7 +136,6 @@ public final class ModdedModConfig { private static void write(Path file, ModdedModConfig config) { JSONObject json = new JSONObject(); json.put("defaultPack", config.defaultPack); - json.put("autoDownloadDefaultPack", config.autoDownloadDefaultPack); json.put("primaryWorld", config.primaryWorld); json.put("routePlayersToPrimaryWorld", config.routePlayersToPrimaryWorld); json.put("mainWorldPack", config.mainWorldPack); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java index 541245152..6168575f3 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java @@ -48,7 +48,7 @@ public final class ModdedPackInstaller { feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_PACK_NAME, MessageArgument.untrusted("pack", String.valueOf(pack)))); return false; } - boolean managedBeta = PackDownloader.isManagedBetaPack(pack); + boolean managed = PackDownloader.isManagedPack(pack); if (!acceptsBranch(pack, branch)) { feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_BRANCH_NAME, MessageArgument.untrusted("branch", String.valueOf(branch)))); return false; @@ -58,8 +58,8 @@ public final class ModdedPackInstaller { synchronized (installLock) { File packs = configDir.resolve("irisworldgen").resolve("packs").toFile(); try { - PackDownloader.PackInstallResult result = managedBeta - ? PackDownloader.downloadManagedBeta(packs, pack, forceOverwrite, feedback) + PackDownloader.PackInstallResult result = managed + ? PackDownloader.downloadManaged(packs, pack, forceOverwrite, feedback) : PackDownloader.download( packs, "IrisDimensions/" + pack, @@ -84,7 +84,7 @@ public final class ModdedPackInstaller { } return installed; } catch (IOException error) { - String source = managedBeta ? "beta release" : "branch " + branch; + String source = managed ? "configured Git source" : "branch " + branch; LOGGER.error("Iris pack download failed for IrisDimensions/{} ({})", pack, source, error); feedback.accept(IrisLanguage.plain( PackDownloadMessages.DOWNLOAD_FAILED, @@ -97,7 +97,7 @@ public final class ModdedPackInstaller { } static boolean acceptsBranch(String pack, String branch) { - return PackDownloader.isManagedBetaPack(pack) + return PackDownloader.isManagedPack(pack) || (branch != null && BRANCH_NAME.matcher(branch).matches()); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java index 1209b7680..762d02082 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java @@ -37,7 +37,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -47,7 +46,6 @@ public final class ModdedStartup { private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final AtomicBoolean PREPARED = new AtomicBoolean(false); private static final AtomicBoolean STARTED = new AtomicBoolean(false); - private static final Object PACK_LOCK = new Object(); private ModdedStartup() { } @@ -69,14 +67,13 @@ public final class ModdedStartup { * ModdedEngineBootstrap.start clears the async queue at SERVER_STARTING, which would silently drop this * one-shot task, and the datapack must be regenerated before the level PackRepository reload if it can. */ - public static void prefetchDefaultPack() { - Thread thread = new Thread(ModdedStartup::refreshPacksAndDatapack, "iris-modded-pack-prefetch"); + public static void prefetchStartupDatapack() { + Thread thread = new Thread(ModdedStartup::refreshDatapack, "iris-modded-datapack-prefetch"); thread.setDaemon(true); thread.start(); } - private static void refreshPacksAndDatapack() { - ensureDefaultPack(); + private static void refreshDatapack() { try { ModdedForcedDatapack.regenerateIfStale("boot"); } catch (Throwable failure) { @@ -98,10 +95,10 @@ public final class ModdedStartup { ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull(); if (scheduler == null) { - refreshPacksAndDatapack(); + refreshDatapack(); return; } - scheduler.async(ModdedStartup::refreshPacksAndDatapack); + scheduler.async(ModdedStartup::refreshDatapack); } public static void validateAllPacks() { @@ -248,45 +245,4 @@ public final class ModdedStartup { } } - public static void ensureDefaultPack() { - synchronized (PACK_LOCK) { - ModdedModConfig config = ModdedModConfig.get(); - if (!config.autoDownloadDefaultPack()) { - return; - } - Path configDir = ModdedEngineBootstrap.loader().configDir(); - File packsRoot = ModdedPackCommands.packsRoot(); - for (String pack : startupPacks(config.defaultPack())) { - ensureStartupPack(configDir, packsRoot, pack); - } - } - } - - static List startupPacks(String configuredDefault) { - LinkedHashSet packs = new LinkedHashSet<>(PackDownloader.managedBetaPacks()); - if (configuredDefault != null && !configuredDefault.isBlank()) { - packs.add(configuredDefault); - } - return List.copyOf(packs); - } - - private static void ensureStartupPack(Path configDir, File packsRoot, String pack) { - boolean present = PackDownloader.isManagedBetaPack(pack) - ? PackDownloader.isManagedBetaPackPresent(packsRoot, pack) - : PackDownloader.isPackPresent(packsRoot, pack); - if (present) { - return; - } - boolean managedBeta = PackDownloader.isManagedBetaPack(pack); - String branch = managedBeta ? "beta" : "master"; - String source = managedBeta ? "beta release" : "master branch"; - String role = managedBeta ? "managed beta pack" : "default pack"; - LOGGER.info("Iris {} '{}' missing; downloading IrisDimensions/{} ({})", role, pack, pack, source); - boolean installed = ModdedPackInstaller.install( - configDir, pack, branch, false, false, - (String line) -> LOGGER.info("Iris: {}", line)); - if (!installed) { - LOGGER.warn("Iris {} '{}' could not be downloaded; install it with /iris download {}", role, pack, pack); - } - } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java index f497ce7ec..d31b9d41b 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java @@ -49,6 +49,8 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -258,8 +260,10 @@ public final class IrisModdedCommands { static int download(CommandSourceStack source, String pack, String branch, boolean forceOverwrite) { - boolean managedBeta = PackDownloader.isManagedBetaPack(pack); - String baseDownloadSource = managedBeta ? "beta release" : "branch " + branch; + boolean managed = PackDownloader.isManagedPack(pack); + boolean directUrl = PackDownloader.isDirectZipUrl(pack); + String baseDownloadSource = directUrl ? "direct ZIP URL" + : managed ? "configured Git source" : "branch " + branch; String downloadSource = forceOverwrite ? baseDownloadSource + IrisLanguage.plain(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : baseDownloadSource; @@ -273,11 +277,28 @@ public final class IrisModdedCommands { return 0; } scheduler.async(() -> { - boolean installed = ModdedPackInstaller.install( - ModdedEngineBootstrap.loader().configDir(), pack, branch, forceOverwrite, true, - (String message) -> scheduler.global(() -> ok(source, message))); + boolean installed; + if (directUrl) { + File packs = ModdedPackCommands.packsRoot(); + try { + PackDownloader.PackInstallResult result = PackDownloader.downloadUrl( + packs, + pack, + forceOverwrite, + (String message) -> scheduler.global(() -> ok(source, message)) + ); + installed = result != null; + } catch (IOException error) { + LOGGER.error("Iris pack download failed for direct URL {}", pack, error); + installed = false; + } + } else { + installed = ModdedPackInstaller.install( + ModdedEngineBootstrap.loader().configDir(), pack, branch, forceOverwrite, false, + (String message) -> scheduler.global(() -> ok(source, message))); + } if (installed) { - scheduler.global(() -> ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_INSTALLED_ITS_EXACT_DIMENSION_TYPES_CUSTOM_BIOMES_JOIN_FORCED, MessageArgument.untrusted("pack", pack)))); + scheduler.global(() -> ok(source, "Pack installed on disk. Restart the server before using it.")); } else { scheduler.global(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource)))); } diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPackInstallerTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPackInstallerTest.java index 42bd09fd6..159cae9a0 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPackInstallerTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPackInstallerTest.java @@ -26,7 +26,7 @@ import static org.junit.Assert.assertTrue; public class ModdedPackInstallerTest { @Test - public void managedBetaPacksIgnoreTheRequestedBranch() { + public void managedPacksIgnoreTheRequestedBranch() { assertTrue(ModdedPackInstaller.acceptsBranch("overworld", null)); assertTrue(ModdedPackInstaller.acceptsBranch("underworld", "feature/arbitrary")); } diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedStartupPackSelectionTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedStartupPackSelectionTest.java deleted file mode 100644 index 867cae830..000000000 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedStartupPackSelectionTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Servers - * Copyright (c) 2026 Arcane Arts (Volmit Software) - * - * 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 art.arcane.iris.modded; - -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.assertEquals; - -public class ModdedStartupPackSelectionTest { - @Test - public void managedBetaPacksAreAlwaysInstalledInStableOrder() { - assertEquals(List.of("overworld", "underworld"), ModdedStartup.startupPacks("overworld")); - assertEquals(List.of("overworld", "underworld"), ModdedStartup.startupPacks("underworld")); - } - - @Test - public void configuredNonManagedDefaultIsInstalledAfterManagedBetaPacks() { - assertEquals( - List.of("overworld", "underworld", "custom"), - ModdedStartup.startupPacks("custom") - ); - } -} diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java index edcedc6ed..f1834fb46 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java +++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java @@ -151,7 +151,6 @@ public class IrisSettings { public static class IrisSettingsAutoconfiguration { public boolean configureSpigotTimeoutTime = true; public boolean configurePaperWatchdogDelay = true; - public boolean autoRestartOnCustomBiomeInstall = true; } @Data diff --git a/core/src/main/java/art/arcane/iris/core/IrisWorlds.java b/core/src/main/java/art/arcane/iris/core/IrisWorlds.java index d62e62d54..33438eee2 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisWorlds.java +++ b/core/src/main/java/art/arcane/iris/core/IrisWorlds.java @@ -7,16 +7,13 @@ import art.arcane.iris.engine.data.cache.AtomicCache; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisPlatforms; -import art.arcane.iris.spi.IrisServices; import art.arcane.iris.util.common.misc.ServerProperties; -import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.volmlib.util.bukkit.WorldIdentity; import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.io.IO; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; @@ -231,12 +228,8 @@ public class IrisWorlds { + " but its dimension failed to load; not redownloading. Fix or delete the pack folder."); return null; } - IrisLogging.warn("Unable to find dimension type " + id + " Looking for online packs..."); - IrisServices.get(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false); - dimension = IrisData.loadAnyDimension(id, null); - if (dimension != null) { - IrisLogging.info("Resolved missing dimension, proceeding."); - } + IrisLogging.warn("Unable to find dimension type " + id + ". Install it with /iris download " + + id + " and restart the server."); } return dimension; } diff --git a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java index 79901b59a..9cfb7a363 100644 --- a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java +++ b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java @@ -114,8 +114,8 @@ public class ServerConfigurator { loadedDatapackRuntimeReady = result.succeeded() && !result.restartRequired() && pinLoadedDatapackCompilerInputs(); - if (result.restartRequired() && IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()) { - restart(); + if (result.restartRequired()) { + IrisLogging.warn("Iris datapack changes require another server restart before worlds can use them."); } } } diff --git a/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java b/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java index 98b0fc4e1..98b890f02 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java +++ b/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java @@ -295,10 +295,6 @@ public final class BukkitCommandMessagesExtended { "iris.bukkit.commandiris.set_debug", C.GREEN + "Set debug to: " + "{to}" ); - public static final TextKey COMMAND_IRIS_DOWNLOADING_PACK_BETA_RELEASE = TextKey.of( - "iris.bukkit.commandiris.downloading_pack_beta_release", - C.GREEN + "Downloading pack: " + "{pack}" + " (beta release)" + "{value}" - ); public static final TextKey COMMAND_IRIS_DOWNLOADING_PACK = TextKey.of( "iris.bukkit.commandiris.downloading_pack", C.GREEN + "Downloading pack: " + "{pack}" + "/" + "{branch}" + "{value}" @@ -916,7 +912,6 @@ public final class BukkitCommandMessagesExtended { COMMAND_IRIS_LOOKS_LIKE_WORLD_WAS_ALREADY_REMOVED_FROM_BUKKIT_YML, COMMAND_IRIS_FAILED_SAVE_BUKKIT_YML_BECAUSE, COMMAND_IRIS_SET_DEBUG, - COMMAND_IRIS_DOWNLOADING_PACK_BETA_RELEASE, COMMAND_IRIS_DOWNLOADING_PACK, COMMAND_IRIS_YOU_MUST_BE_IRIS_WORLD, COMMAND_IRIS_SENDING_METRICS, diff --git a/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java b/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java index 3542f7452..203331dbd 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java @@ -446,10 +446,6 @@ public final class BukkitRuntimeMessages { "iris.bukkit.runtime.studiosvc.failed_download", "Failed to download '" + "{key}" + "'." ); - public static final TextKey STUDIO_S_V_C_FAILED_DOWNLOAD_IRISDIMENSIONS_OVERWORLD_BETA_RELEASE = TextKey.of( - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release", - "Failed to download the IrisDimensions/overworld beta release." - ); public static final TextKey STUDIO_S_V_C_FAILED_DOWNLOAD_BRANCH = TextKey.of( "iris.bukkit.runtime.studiosvc.failed_download_branch", "Failed to download '" + "{repo}" + "' (branch " + "{branch}" + ")." @@ -730,7 +726,6 @@ public final class BukkitRuntimeMessages { STUDIO_S_V_C_PACK_WAS_NOT_FOUND_PACK_LISTING, STUDIO_S_V_C_USE_IRIS_DOWNLOAD_PACK_BRANCH_BRANCH_DOWNLOAD_MANUALLY, STUDIO_S_V_C_FAILED_DOWNLOAD, - STUDIO_S_V_C_FAILED_DOWNLOAD_IRISDIMENSIONS_OVERWORLD_BETA_RELEASE, STUDIO_S_V_C_FAILED_DOWNLOAD_BRANCH, STUDIO_S_V_C_FAILED_OPEN_STUDIO_WORLD, STUDIO_S_V_C_CANNOT_OPEN_STUDIO_PACK_HAS_BLOCKING_ERRORS, diff --git a/core/src/main/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisioner.java b/core/src/main/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisioner.java index 973be2cab..1ca366118 100644 --- a/core/src/main/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisioner.java +++ b/core/src/main/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisioner.java @@ -43,20 +43,9 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public final class DefaultPackBootstrapProvisioner { - private static final List DEFAULT_PACKS = List.of( - new PackSpec( - "overworld", - URI.create("https://github.com/IrisDimensions/overworld/releases/download/beta/overworld.zip"), - "overworld" - ), - new PackSpec( - "underworld", - URI.create("https://github.com/IrisDimensions/underworld/releases/download/beta/underworld.zip"), - "underworld" - ) - ); + private static final List DEFAULT_PACKS = List.of(); private static final String WORLD_DATAPACK_DIRECTORY = "iris"; - private static final int MARKER_SCHEMA = 4; + private static final int MARKER_SCHEMA = 5; private static final int MAX_ARCHIVE_ENTRIES = 100_000; private static final long MAX_ARCHIVE_BYTES = 512L * 1024L * 1024L; private static final long MAX_EXPANDED_BYTES = 2L * 1024L * 1024L * 1024L; @@ -239,9 +228,6 @@ public final class DefaultPackBootstrapProvisioner { } List packRoots = IrisDatapackCompiler.collectPackRoots(normalizedData, options.levelRoot()); - if (packRoots.isEmpty()) { - throw new IOException("No Iris pack roots were available for bootstrap datapack compilation"); - } IDataFixer fixer = DataVersion.getLatest().get(); if (fixer == null) { throw new IOException("Latest Iris datapack fixer is unavailable during bootstrap"); @@ -260,7 +246,7 @@ public final class DefaultPackBootstrapProvisioner { Files.createDirectories(compileContainer); KList outputFolders = new KList().qadd(compileContainer.toFile()); IrisDatapackCompiler.compile(packRoots, outputFolders, fixer, false); - if (!isDatapackRoot(compileContainer)) { + if (!isDatapackRoot(compileContainer, !packRoots.isEmpty())) { throw new IOException("Canonical Iris datapack compiler produced incomplete output at " + compileContainer); } datapackBackup = replaceWithBackup(compileContainer, datapackRoot); @@ -271,12 +257,12 @@ public final class DefaultPackBootstrapProvisioner { for (PackPlan plan : plans) { validatePackRoot(plan.root(), plan.spec()); } - if (!isDatapackRoot(datapackRoot)) { - throw new IOException("Bootstrap datapack output is incomplete at " + datapackRoot); - } List finalPackRoots = IrisDatapackCompiler.collectPackRoots( normalizedData, options.levelRoot()); + if (!isDatapackRoot(datapackRoot, !finalPackRoots.isEmpty())) { + throw new IOException("Bootstrap datapack output is incomplete at " + datapackRoot); + } String finalAggregateFingerprint = packRootsFingerprint(finalPackRoots); String finalCompilerInputFingerprint = IrisDatapackCompiler.computeInputFingerprint( finalPackRoots, @@ -316,7 +302,6 @@ public final class DefaultPackBootstrapProvisioner { } else { status = ProvisionStatus.UNCHANGED; } - feedback.accept("Iris bootstrap packs are " + status.name().toLowerCase() + "."); Map provisionedPacks = new LinkedHashMap<>(); for (PackPlan plan : plans) { provisionedPacks.put(plan.spec().key(), plan.root()); @@ -605,9 +590,13 @@ public final class DefaultPackBootstrapProvisioner { } private static boolean isDatapackRoot(Path path) { + return isDatapackRoot(path, false); + } + + private static boolean isDatapackRoot(Path path, boolean requiresGeneratedTypes) { return path != null && Files.isRegularFile(path.resolve("pack.mcmeta")) - && Files.isDirectory(path.resolve("data/iris/dimension_type")); + && (!requiresGeneratedTypes || Files.isDirectory(path.resolve("data/iris/dimension_type"))); } static Path worldDatapackRoot(Path levelRoot) { @@ -908,7 +897,7 @@ public final class DefaultPackBootstrapProvisioner { Objects.requireNonNull(requestTimeout, "requestTimeout"); Objects.requireNonNull(retryDelay, "retryDelay"); Objects.requireNonNull(levelRoot, "levelRoot"); - if (packs.isEmpty() || attempts < 1 || maxArchiveBytes < 1L) { + if (attempts < 1 || maxArchiveBytes < 1L) { throw new IllegalArgumentException("Invalid bootstrap provisioning options"); } Set keys = new HashSet<>(); diff --git a/core/src/main/java/art/arcane/iris/core/pack/PackDownloader.java b/core/src/main/java/art/arcane/iris/core/pack/PackDownloader.java index fca8fe114..8d532f4b0 100644 --- a/core/src/main/java/art/arcane/iris/core/pack/PackDownloader.java +++ b/core/src/main/java/art/arcane/iris/core/pack/PackDownloader.java @@ -32,6 +32,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URI; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -56,14 +57,14 @@ import java.util.zip.ZipInputStream; public final class PackDownloader { private static final String DEFAULT_OVERWORLD_PACK = "overworld"; private static final String DEFAULT_OVERWORLD_REPOSITORY = "IrisDimensions/overworld"; - private static final String DEFAULT_OVERWORLD_RELEASE_URL = "https://github.com/IrisDimensions/overworld/releases/download/beta/overworld.zip"; + private static final String DEFAULT_OVERWORLD_REF = "master"; private static final String UNDERWORLD_PACK = "underworld"; private static final String UNDERWORLD_REPOSITORY = "IrisDimensions/underworld"; - private static final String UNDERWORLD_RELEASE_URL = "https://github.com/IrisDimensions/underworld/releases/download/beta/underworld.zip"; - private static final List MANAGED_BETA_PACK_KEYS = List.of(DEFAULT_OVERWORLD_PACK, UNDERWORLD_PACK); - private static final Map MANAGED_BETA_PACKS = Map.of( - DEFAULT_OVERWORLD_PACK, new ManagedBetaPack(DEFAULT_OVERWORLD_REPOSITORY, DEFAULT_OVERWORLD_RELEASE_URL), - UNDERWORLD_PACK, new ManagedBetaPack(UNDERWORLD_REPOSITORY, UNDERWORLD_RELEASE_URL) + private static final String UNDERWORLD_REF = "main"; + private static final List MANAGED_PACK_KEYS = List.of(DEFAULT_OVERWORLD_PACK, UNDERWORLD_PACK); + private static final Map MANAGED_PACKS = Map.of( + DEFAULT_OVERWORLD_PACK, new ManagedPack(DEFAULT_OVERWORLD_REPOSITORY, DEFAULT_OVERWORLD_REF), + UNDERWORLD_PACK, new ManagedPack(UNDERWORLD_REPOSITORY, UNDERWORLD_REF) ); private static final Pattern GITHUB_REPOSITORY = Pattern.compile("[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"); private static final Pattern GITHUB_REF = Pattern.compile("[A-Za-z0-9][A-Za-z0-9._/-]*"); @@ -84,12 +85,30 @@ public final class PackDownloader { return DEFAULT_OVERWORLD_PACK.equals(pack); } - public static boolean isManagedBetaPack(String pack) { - return pack != null && MANAGED_BETA_PACKS.containsKey(pack); + public static boolean isManagedPack(String pack) { + return pack != null && MANAGED_PACKS.containsKey(pack); } - public static List managedBetaPacks() { - return MANAGED_BETA_PACK_KEYS; + public static List managedPacks() { + return MANAGED_PACK_KEYS; + } + + public static boolean isDirectZipUrl(String value) { + if (value == null || value.isBlank()) { + return false; + } + try { + URI uri = URI.create(value.trim()); + String scheme = uri.getScheme(); + String path = uri.getPath(); + return ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) + && uri.getHost() != null + && !uri.getHost().isBlank() + && path != null + && path.toLowerCase(Locale.ROOT).endsWith(".zip"); + } catch (IllegalArgumentException exception) { + return false; + } } public static String defaultOverworldPack() { @@ -133,8 +152,8 @@ public final class PackDownloader { } } - public static boolean isManagedBetaPackPresent(File packsFolder, String key) { - if (!isManagedBetaPack(key) || !isPackPresent(packsFolder, key)) { + public static boolean isManagedPackPresent(File packsFolder, String key) { + if (!isManagedPack(key) || !isPackPresent(packsFolder, key)) { return false; } File resolvedPack = PackDirectoryResolver.resolveExisting(packsFolder, key); @@ -149,32 +168,46 @@ public final class PackDownloader { } public static PackInstallResult downloadDefaultOverworld(File packsFolder, boolean forceOverwrite, Consumer feedback) throws IOException { - return downloadManagedBeta(packsFolder, DEFAULT_OVERWORLD_PACK, forceOverwrite, feedback); + return downloadManaged(packsFolder, DEFAULT_OVERWORLD_PACK, forceOverwrite, feedback); } - public static PackInstallResult downloadManagedBeta(File packsFolder, String pack, boolean forceOverwrite, - Consumer feedback) throws IOException { - ManagedBetaPack managed = pack == null ? null : MANAGED_BETA_PACKS.get(pack); + public static PackInstallResult downloadManaged(File packsFolder, String pack, boolean forceOverwrite, + Consumer feedback) throws IOException { + ManagedPack managed = pack == null ? null : MANAGED_PACKS.get(pack); if (managed == null) { - throw new IllegalArgumentException("Pack '" + pack + "' has no managed beta release"); + throw new IllegalArgumentException("Pack '" + pack + "' has no managed Git source"); } return download( packsFolder, managed.repository(), - managed.releaseUrl(), + managed.ref(), + forceOverwrite, + false, + pack, + feedback + ); + } + + public static PackInstallResult downloadUrl(File packsFolder, String url, boolean forceOverwrite, + Consumer feedback) throws IOException { + if (!isDirectZipUrl(url)) { + throw new IllegalArgumentException("Pack URL must be an HTTP or HTTPS .zip link"); + } + return download( + packsFolder, + "direct-url", + url.trim(), forceOverwrite, true, - pack, + null, feedback ); } /** * Downloads and imports a pack. {@code expectedKey} is the pack key the caller is trying to - * obtain (null when unknown, e.g. arbitrary repo/branch downloads); when the key is already - * present on disk and {@code forceOverwrite} is false, the network is never touched. The - * per-repo lock keeps concurrent startup triggers (async default-pack install racing world - * resolution) from downloading the same archive twice. + * obtain (null when unknown, e.g. arbitrary repo, branch, or URL downloads); when the key is + * already present on disk and {@code forceOverwrite} is false, the network is never touched. */ public static PackInstallResult download(File packsFolder, String repo, String ref, boolean forceOverwrite, boolean directUrl, String expectedKey, Consumer feedback) throws IOException { Objects.requireNonNull(packsFolder, "packsFolder"); @@ -185,8 +218,8 @@ public final class PackDownloader { } String lockKey = expectedKey != null && !expectedKey.isBlank() ? "key:" + expectedKey : "ref:" + repo + "|" + ref; return withDownloadLock(lockKey, () -> { - boolean present = isManagedBetaPack(expectedKey) - ? isManagedBetaPackPresent(packsFolder, expectedKey) + boolean present = isManagedPack(expectedKey) + ? isManagedPackPresent(packsFolder, expectedKey) : isPackPresent(packsFolder, expectedKey); if (!forceOverwrite && present) { sendFeedback(output, IrisLanguage.plain(PackDownloadMessages.ALREADY_INSTALLED, MessageArgument.untrusted("key", expectedKey))); @@ -224,14 +257,7 @@ public final class PackDownloader { sendFeedback(feedback, IrisLanguage.plain(PackDownloadMessages.NO_EXTRACTED_FILES)); return null; } - File directory; - try { - directory = zipFiles.length > 1 ? work : zipFiles[0].isDirectory() ? zipFiles[0] : null; - } catch (NullPointerException exception) { - IrisLogging.reportError(exception); - sendFeedback(feedback, IrisLanguage.plain(PackDownloadMessages.HOME_DIRECTORY_ERROR)); - return null; - } + File directory = findExtractedPackDirectory(work, zipFiles); if (directory == null) { sendFeedback(feedback, IrisLanguage.plain(PackDownloadMessages.INVALID_ARCHIVE_FORMAT)); return null; @@ -242,6 +268,26 @@ public final class PackDownloader { } } + private static File findExtractedPackDirectory(File work, File[] extractedFiles) { + if (new File(work, "dimensions").isDirectory()) { + return work; + } + File candidate = null; + for (File extracted : extractedFiles) { + if (!extracted.isDirectory() || !new File(extracted, "dimensions").isDirectory()) { + continue; + } + if (candidate != null) { + return work; + } + candidate = extracted; + } + if (candidate != null) { + return candidate; + } + return extractedFiles.length == 1 && extractedFiles[0].isDirectory() ? extractedFiles[0] : work; + } + static PackInstallResult installExtractedPack(File packsFolder, File extractedPack, boolean forceOverwrite, String expectedKey, Consumer feedback) throws IOException { Objects.requireNonNull(packsFolder, "packsFolder"); @@ -311,7 +357,7 @@ public final class PackDownloader { PackValidationResult stagedValidation; try { - stagedValidation = PackValidator.validate(staging); + stagedValidation = PackValidator.validateForDatapackBootstrap(staging); } catch (RuntimeException exception) { throw new IOException("Pack validation failed before publication for '" + key + "'", exception); } @@ -375,8 +421,8 @@ public final class PackDownloader { )); return null; } - boolean present = isManagedBetaPack(prepared.key()) - ? isManagedBetaPackPresent(packsRoot.toFile(), prepared.key()) + boolean present = isManagedPack(prepared.key()) + ? isManagedPackPresent(packsRoot.toFile(), prepared.key()) : isPackPresent(packsRoot.toFile(), prepared.key()); if (!forceOverwrite && present) { sendFeedback(feedback, IrisLanguage.plain( @@ -418,7 +464,7 @@ public final class PackDownloader { PackDownloadMessages.ACQUIRED, MessageArgument.untrusted("name", prepared.name()) )); - return new PackInstallResult(prepared.key(), true, false); + return new PackInstallResult(prepared.key(), true, true); } private static Path findConflictingPack(Path packsRoot, Path staging, Path target, String key) throws IOException { @@ -648,12 +694,20 @@ public final class PackDownloader { return "https://codeload.github.com/" + repo + "/zip/" + qualifiedRef; } - static String defaultOverworldReleaseUrl() { - return DEFAULT_OVERWORLD_RELEASE_URL; + static String defaultOverworldRepository() { + return DEFAULT_OVERWORLD_REPOSITORY; } - static String underworldReleaseUrl() { - return UNDERWORLD_RELEASE_URL; + static String defaultOverworldRef() { + return DEFAULT_OVERWORLD_REF; + } + + static String underworldRepository() { + return UNDERWORLD_REPOSITORY; + } + + static String underworldRef() { + return UNDERWORLD_REF; } private static void validateGithubRef(String qualifiedRef) { @@ -681,7 +735,7 @@ public final class PackDownloader { private record PreparedPack(String key, String name, PackValidationResult validation) { } - private record ManagedBetaPack(String repository, String releaseUrl) { + private record ManagedPack(String repository, String ref) { } public record PackInstallResult(String key, boolean changed, boolean restartRequired) { diff --git a/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java b/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java index ccd23b0c7..f130511e0 100644 --- a/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java @@ -98,14 +98,8 @@ public class StudioSVC implements IrisService { @Override public void onEnable() { - VolmitSender console = BukkitPlatform.console(); - runPackMutation(console, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, - "managed-beta-packs", () -> installMissingManagedBetaPacks(console), - "Failed to install Iris managed beta packs at startup."); - String configuredPack = IrisSettings.get().getGenerator().getDefaultWorldType(); - if (!PackDownloader.isManagedBetaPack(configuredPack) - && !PackDownloader.isPackPresent(getWorkspaceFolder(), configuredPack)) { + if (!PackDownloader.isPackPresent(getWorkspaceFolder(), configuredPack)) { IrisLogging.warn("Default pack '" + configuredPack + "' is not installed. Please download it manually with /iris download " + configuredPack); } @@ -342,25 +336,41 @@ public class StudioSVC implements IrisService { public void downloadSearch(VolmitSender sender, String key, boolean forceOverwrite) { runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, key, () -> { DownloadOutcome outcome = downloadSearchLocked(sender, key, forceOverwrite); - return finishStandalonePackMutation(sender, outcome); + return finishStandalonePackMutation(outcome); }, IrisLanguage.text(BukkitRuntimeMessages.STUDIO_S_V_C_FAILED_DOWNLOAD, MessageArgument.untrusted("key", String.valueOf(key)))); } - public void downloadManagedBeta(VolmitSender sender, String key, boolean forceOverwrite) { - if (!PackDownloader.isManagedBetaPack(key)) { - sender.sendMessage("Iris pack '" + key + "' does not have a managed beta release."); + public void downloadManaged(VolmitSender sender, String key, boolean forceOverwrite) { + if (!PackDownloader.isManagedPack(key)) { + sender.sendMessage("Iris pack '" + key + "' does not have a managed Git source."); return; } runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, key, () -> { - DownloadOutcome outcome = downloadManagedBetaLocked(sender, key, forceOverwrite); - return finishStandalonePackMutation(sender, outcome); - }, "Failed to download IrisDimensions/" + key + " beta release."); + DownloadOutcome outcome = downloadManagedLocked(sender, key, forceOverwrite); + return finishStandalonePackMutation(outcome); + }, "Failed to download managed Iris pack '" + key + "'."); + } + + public void downloadUrl(VolmitSender sender, String url, boolean forceOverwrite) { + if (!PackDownloader.isDirectZipUrl(url)) { + sender.sendMessage("Iris requires a valid HTTP or HTTPS .zip URL."); + return; + } + runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, url, () -> { + DownloadOutcome outcome = DownloadOutcome.from(PackDownloader.downloadUrl( + getWorkspaceFolder(), + url, + forceOverwrite, + sender::sendMessage + )); + return finishStandalonePackMutation(outcome); + }, "Failed to download Iris pack from '" + url + "'."); } public void downloadBranch(VolmitSender sender, String repo, String branch, boolean forceOverwrite) { runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, repo + "/" + branch, () -> { DownloadOutcome outcome = downloadLocked(sender, repo, branch, forceOverwrite, false, null); - return finishStandalonePackMutation(sender, outcome); + return finishStandalonePackMutation(outcome); }, IrisLanguage.text(BukkitRuntimeMessages.STUDIO_S_V_C_FAILED_DOWNLOAD_BRANCH, MessageArgument.untrusted("repo", String.valueOf(repo)), MessageArgument.untrusted("branch", String.valueOf(branch)))); } @@ -376,13 +386,13 @@ public class StudioSVC implements IrisService { String target = expectedKey == null ? repo + "/" + branch : expectedKey; runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, target, () -> { DownloadOutcome outcome = downloadLocked(sender, repo, branch, forceOverwrite, directUrl, expectedKey); - return finishStandalonePackMutation(sender, outcome); + return finishStandalonePackMutation(outcome); }, "Failed to download Iris pack '" + target + "'."); } private DownloadOutcome downloadSearchLocked(VolmitSender sender, String key, boolean forceOverwrite) throws IOException { - if (PackDownloader.isManagedBetaPack(key)) { - return downloadManagedBetaLocked(sender, key, forceOverwrite); + if (PackDownloader.isManagedPack(key)) { + return downloadManagedLocked(sender, key, forceOverwrite); } String descriptor = key.contains("/") ? key : getListing(false).get(key); @@ -412,17 +422,17 @@ public class StudioSVC implements IrisService { return new PackListingReference(repository, ref, expectedKey); } - private DownloadOutcome downloadManagedBetaLocked( + private DownloadOutcome downloadManagedLocked( VolmitSender sender, String expectedKey, boolean forceOverwrite ) throws IOException { - if (!forceOverwrite && PackDownloader.isManagedBetaPackPresent(getWorkspaceFolder(), expectedKey)) { + if (!forceOverwrite && PackDownloader.isManagedPackPresent(getWorkspaceFolder(), expectedKey)) { sender.sendMessage(IrisLanguage.text(PackDownloadMessages.ALREADY_INSTALLED, MessageArgument.untrusted("key", expectedKey))); return DownloadOutcome.notChanged(); } - PackDownloader.PackInstallResult result = PackDownloader.downloadManagedBeta( + PackDownloader.PackInstallResult result = PackDownloader.downloadManaged( getWorkspaceFolder(), expectedKey, forceOverwrite, @@ -431,33 +441,6 @@ public class StudioSVC implements IrisService { return DownloadOutcome.from(result); } - private boolean installMissingManagedBetaPacks(VolmitSender sender) { - boolean changed = false; - boolean restartRequired = false; - for (String key : missingManagedBetaPacks(getWorkspaceFolder())) { - IrisLogging.info("Downloading managed Iris pack " + key + " (beta release)"); - try { - DownloadOutcome outcome = downloadManagedBetaLocked(sender, key, false); - changed |= outcome.changed(); - restartRequired |= outcome.restartRequired(); - } catch (Throwable failure) { - IrisLogging.reportError("Failed to download IrisDimensions/" + key + " beta release.", failure); - sender.sendMessage("Failed to download IrisDimensions/" + key + " beta release. " + errorDetail(failure)); - } - } - return finishStandalonePackMutation(sender, new DownloadOutcome(changed, restartRequired)); - } - - static List missingManagedBetaPacks(File workspaceFolder) { - List missing = new ArrayList<>(); - for (String key : PackDownloader.managedBetaPacks()) { - if (!PackDownloader.isManagedBetaPackPresent(workspaceFolder, key)) { - missing.add(key); - } - } - return List.copyOf(missing); - } - private DownloadOutcome downloadLocked( VolmitSender sender, String repo, @@ -1033,27 +1016,14 @@ public class StudioSVC implements IrisService { } if (restartRequired) { - sender.sendMessage("Iris must restart before the new pack data can be used."); - ServerConfigurator.restart(); + sender.sendMessage("Restart the server before using the downloaded Iris pack."); } }; runOffPrimaryThread(work); } - private boolean finishStandalonePackMutation(VolmitSender sender, DownloadOutcome outcome) { - if (!outcome.changed()) { - return false; - } - - DatapackInstallResult installResult = ServerConfigurator.installDataPacksIfChanged(true); - return switch (installResult.status()) { - case FAILED -> { - sender.sendMessage("The pack was downloaded, but Iris could not install its datapack output."); - yield false; - } - case RESTART_REQUIRED -> true; - case READY, UNCHANGED -> outcome.restartRequired(); - }; + private boolean finishStandalonePackMutation(DownloadOutcome outcome) { + return outcome.changed() && outcome.restartRequired(); } private void runOffPrimaryThread(Runnable work) { diff --git a/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java b/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java index df6deac03..767c04e1b 100644 --- a/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java +++ b/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java @@ -75,12 +75,7 @@ public class IrisToolbelt { private static final Method BUKKIT_IS_STOPPING_METHOD = resolveBukkitIsStoppingMethod(); /** - * Will find / download / search for the dimension or return null - *

- * - You can provide a dimenson in the packs folder by the folder name - * - You can provide a github repo by using (assumes branch is master unless specified) - * - GithubUsername/repository - * - GithubUsername/repository/branch + * Finds an installed dimension or returns null. * * @param dimension the dimension id such as overworld or flat * @return the IrisDimension or null @@ -105,18 +100,9 @@ public class IrisToolbelt { } if (pack == null) { - IrisServices.get(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender(), BukkitPlatform.volmitPlugin().getTag()), reference.pack(), false); - String installedPackName = installedPackName(reference.pack()); - File found = PackDirectoryResolver.resolveExisting(packsFolder, installedPackName); - if (found == null) { - found = findCaseInsensitivePack(packsFolder, installedPackName); - } - if (found != null) { - pack = found; - } - } - - if (pack == null) { + IrisLogging.warn("Iris pack '" + reference.pack() + + "' is not installed. Install it with /iris download " + reference.pack() + + " and restart the server."); return null; } diff --git a/core/src/main/resources/languages/de_DE.json b/core/src/main/resources/languages/de_DE.json index 1bdc537a8..a646326f0 100644 --- a/core/src/main/resources/languages/de_DE.json +++ b/core/src/main/resources/languages/de_DE.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eDie Welt wurde anscheinend bereits aus bukkit.yml entfernt", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cbukkit.yml konnte wegen {value} nicht gespeichert werden", "iris.bukkit.commandiris.set_debug": "§aDebug gesetzt auf: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aDownload des Pack: {pack} (Beta-Version){value}", "iris.bukkit.commandiris.downloading_pack": "§aDownload des Pack: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cDu musst dich in einer Iris-Welt befinden", "iris.bukkit.commandiris.sending_metrics": "§aÜbermittlung von Metriken...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Pack '{key}' wurde nicht in der Pack-Liste gefunden.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Verwende /iris download branch=, um den Download manuell auszuführen.", "iris.bukkit.runtime.studiosvc.failed_download": "Download von '{key}' fehlgeschlagen.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Download der Beta-Version IrisDimensions/overworld fehlgeschlagen.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Download von '{repo}' (Branch {branch}) fehlgeschlagen.", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Studio-Welt konnte nicht geöffnet werden: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Studio '{dimm}' kann nicht geöffnet werden – das Pack enthält blockierende Fehler:", diff --git a/core/src/main/resources/languages/es_ES.json b/core/src/main/resources/languages/es_ES.json index ea30b3829..c7f8a3d94 100644 --- a/core/src/main/resources/languages/es_ES.json +++ b/core/src/main/resources/languages/es_ES.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eParece que el mundo ya se había eliminado de bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cNo se pudo guardar bukkit.yml debido a {value}", "iris.bukkit.commandiris.set_debug": "§aDepuración establecida en: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aDescargando pack: {pack} (versión beta){value}", "iris.bukkit.commandiris.downloading_pack": "§aDescargando pack: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cDebes estar en un mundo de Iris", "iris.bukkit.commandiris.sending_metrics": "§aEnviando métricas...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "El pack '{key}' no se encontró en la lista de packs.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Usa /iris download branch= para descargarlo manualmente.", "iris.bukkit.runtime.studiosvc.failed_download": "No se pudo descargar '{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "No se pudo descargar la versión beta de IrisDimensions/overworld.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "No se pudo descargar '{repo}' (rama {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "No se pudo abrir el mundo de Studio: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "No se puede abrir Studio '{dimm}': el pack contiene errores bloqueantes:", diff --git a/core/src/main/resources/languages/fi_FI.json b/core/src/main/resources/languages/fi_FI.json index 2365bac12..7aab41c2a 100644 --- a/core/src/main/resources/languages/fi_FI.json +++ b/core/src/main/resources/languages/fi_FI.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eNäyttää siltä, että maailma on jo poistettu. bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cTallennus epäonnistui bukkit.yml koska {value}", "iris.bukkit.commandiris.set_debug": "§aAseta vianetsintä: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aNoudetaan pakkaus: {pack} (beetan vapautuminen){value}", "iris.bukkit.commandiris.downloading_pack": "§aNoudetaan pakkaus: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cSinun täytyy olla Iris maailma", "iris.bukkit.commandiris.sending_metrics": "§aLähetetään mittareita...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Pakkaus{key}' ei löytynyt pakkauslistalta.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Käyttö /iris download branch= ladata käsin.", "iris.bukkit.runtime.studiosvc.failed_download": "Ei voitu ladata \"{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Ei voitu ladata IrisDimensionsOverworld beta -julkaisu.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Ei voitu ladata \"{repo}\" (ala {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Studiomaailman avaaminen epäonnistui: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Studiota ei voi avata{dimm}\" - pakkauksessa on estovirheitä:", diff --git a/core/src/main/resources/languages/fr_FR.json b/core/src/main/resources/languages/fr_FR.json index 5ce24e026..f809d3df1 100644 --- a/core/src/main/resources/languages/fr_FR.json +++ b/core/src/main/resources/languages/fr_FR.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eIl semble que le monde avait déjà été supprimé de bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cImpossible d'enregistrer bukkit.yml à cause de {value}", "iris.bukkit.commandiris.set_debug": "§aDébogage défini sur : {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aTéléchargement du pack : {pack} (version bêta){value}", "iris.bukkit.commandiris.downloading_pack": "§aTéléchargement du pack : {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cVous devez être dans un monde Iris", "iris.bukkit.commandiris.sending_metrics": "§aEnvoi des métriques...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Le pack '{key}' est introuvable dans la liste des packs.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Utilisez /iris download branch= pour le télécharger manuellement.", "iris.bukkit.runtime.studiosvc.failed_download": "Impossible de télécharger '{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Impossible de télécharger la version bêta de IrisDimensions/overworld.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Impossible de télécharger '{repo}' (branche {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Impossible d'ouvrir le monde Studio : {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Impossible d'ouvrir Studio '{dimm}' : le pack contient des erreurs bloquantes :", diff --git a/core/src/main/resources/languages/he_IL.json b/core/src/main/resources/languages/he_IL.json index d3543d033..f52d85161 100644 --- a/core/src/main/resources/languages/he_IL.json +++ b/core/src/main/resources/languages/he_IL.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eנראה כאילו העולם כבר הוסר bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cנכשל להציל bukkit.yml בגלל {value}", "iris.bukkit.commandiris.set_debug": "§aהגדר bug: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aהורדת חבילות: {pack} (שחרור בטא){value}", "iris.bukkit.commandiris.downloading_pack": "§aהורדת חבילות: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cאתה חייב להיות בתוך Iris עולם העולם", "iris.bukkit.commandiris.sending_metrics": "§aשליחת מדדים...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Pack »{key}\"לא נמצאו בחבילה.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "שימוש בשימוש /iris download branch= להוריד באופן ידני.", "iris.bukkit.runtime.studiosvc.failed_download": "נכשל להוריד את \"{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "הורדת גרסת הבטא של IrisDimensions/overworld נכשלה.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "נכשל להוריד את \"{repo}(ברנץ' {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "נכשל לפתוח את עולם הסטודיו: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "\"לא יכול לפתוח סטודיו\"{dimm}חבילה חוסמת שגיאות:", diff --git a/core/src/main/resources/languages/it_IT.json b/core/src/main/resources/languages/it_IT.json index f89faca55..02f0ac2d5 100644 --- a/core/src/main/resources/languages/it_IT.json +++ b/core/src/main/resources/languages/it_IT.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eSembra che il mondo sia già stato rimosso da bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cImpossibile salvare bukkit.yml a causa di {value}", "iris.bukkit.commandiris.set_debug": "§aDebug impostato su: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aDownload del pack: {pack} (versione beta){value}", "iris.bukkit.commandiris.downloading_pack": "§aDownload del pack: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cDevi trovarti in un mondo Iris", "iris.bukkit.commandiris.sending_metrics": "§aInvio di metriche...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Il Pack '{key}' non è stato trovato nell'elenco dei Pack.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Usa /iris download branch= per eseguire manualmente il download.", "iris.bukkit.runtime.studiosvc.failed_download": "Download di '{key}' non riuscito.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Download della versione beta IrisDimensions/overworld non riuscito.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Download di '{repo}' (branch {branch}) non riuscito.", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Impossibile aprire il mondo studio: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Impossibile aprire lo studio '{dimm}': il Pack contiene errori bloccanti:", diff --git a/core/src/main/resources/languages/ja-JP.json b/core/src/main/resources/languages/ja-JP.json index 20aabd259..7e8905100 100644 --- a/core/src/main/resources/languages/ja-JP.json +++ b/core/src/main/resources/languages/ja-JP.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eワールドはすでに bukkit.yml から削除されているようです", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cbukkit.yml を保存できませんでした: {value}", "iris.bukkit.commandiris.set_debug": "§aデバッグ設定: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aパックをダウンロードしています: {pack}(ベータリリース){value}", "iris.bukkit.commandiris.downloading_pack": "§aパックをダウンロードしています: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cIris ワールド内にいる必要があります", "iris.bukkit.commandiris.sending_metrics": "§aメトリックの送信...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "パック一覧にパック '{key}' が見つかりません。", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "手動でダウンロードするには /iris download branch= を使用してください。", "iris.bukkit.runtime.studiosvc.failed_download": "'{key}' のダウンロードに失敗しました。", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "IrisDimensions/overworld のベータリリースをダウンロードできませんでした。", "iris.bukkit.runtime.studiosvc.failed_download_branch": "'{repo}'(ブランチ {branch})をダウンロードできませんでした。", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "スタジオワールドを開けませんでした: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "スタジオ '{dimm}' を開けません。パックに処理を妨げるエラーがあります:", diff --git a/core/src/main/resources/languages/ko_KR.json b/core/src/main/resources/languages/ko_KR.json index 3714cc87f..cc738c22a 100644 --- a/core/src/main/resources/languages/ko_KR.json +++ b/core/src/main/resources/languages/ko_KR.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§e세상과 같은 모습은 이미 제거되었습니다. bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§c{value} 때문에 bukkit.yml을 저장하지 못했습니다", "iris.bukkit.commandiris.set_debug": "§a디버그 설정: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§a다운로드 팩: {pack} (베타 릴리스){value}", "iris.bukkit.commandiris.downloading_pack": "§a다운로드 팩: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cIris 월드 안에 있어야 합니다", "iris.bukkit.commandiris.sending_metrics": "§a메트릭 전송...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "팩 '{key}'는 팩 목록에서 찾을 수 없습니다.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "수동으로 다운로드하려면 /iris download branch=을(를) 사용하세요.", "iris.bukkit.runtime.studiosvc.failed_download": "다운로드 실패 '{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "다운로드 실패 IrisDimensions/overworld 베타 릴리스.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "다운로드 실패 '{repo}' (branch {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "열린 스튜디오 세계에 실패: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "열린 스튜디오 '{dimm}' - 팩은 오류를 차단했습니다.", diff --git a/core/src/main/resources/languages/lt_LT.json b/core/src/main/resources/languages/lt_LT.json index 69d7a4809..25266adf4 100644 --- a/core/src/main/resources/languages/lt_LT.json +++ b/core/src/main/resources/languages/lt_LT.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eAtrodo, pasaulis jau buvo pašalintas iš bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cNepavyko įrašyti bukkit.yml dėl {value}", "iris.bukkit.commandiris.set_debug": "§aNustatyti derinimą į: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aSiunčiama pakuotė: {pack} (beta atsipalaidavimas){value}", "iris.bukkit.commandiris.downloading_pack": "§aSiunčiama pakuotė: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cJūs turite būti Iris pasaulis", "iris.bukkit.commandiris.sending_metrics": "§aSiunčiami metrikos...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Pakuotė \"{key}\"nebuvo rasta pakuotės sąraše.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Naudojimas /iris download branch= atsisiųsti rankiniu būdu.", "iris.bukkit.runtime.studiosvc.failed_download": "Nepavyko atsisiųsti \"{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Nepavyko atsisiųsti IrisDimensions/ overworld beta release.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Nepavyko atsisiųsti \"{repo}'(šaka {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Nepavyko atverti studijos pasaulio: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Nepavyko atverti studijos \"{dimm}\"- pakuotė turi blokavimo klaidų:", diff --git a/core/src/main/resources/languages/nl_NL.json b/core/src/main/resources/languages/nl_NL.json index 99aea4c38..2770c2dd1 100644 --- a/core/src/main/resources/languages/nl_NL.json +++ b/core/src/main/resources/languages/nl_NL.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eHet lijkt erop dat de wereld al verwijderd was van bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cOpslaan is mislukt bukkit.yml vanwege {value}", "iris.bukkit.commandiris.set_debug": "§aDebug instellen op: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aDownloadpakket: {pack} (beta vrijgeven){value}", "iris.bukkit.commandiris.downloading_pack": "§aDownloadpakket: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cJe moet in een Iris wereld", "iris.bukkit.commandiris.sending_metrics": "§aVerzenden van statistieken...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Verpakking{key}' werd niet gevonden in de verpakking lijst.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Gebruik /iris download branch= handmatig downloaden.", "iris.bukkit.runtime.studiosvc.failed_download": "Downloaden mislukt{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Downloaden van de IrisDimensions/overworld bèta release.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Downloaden mislukt{repo}\" (tak {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Openen van studiowereld mislukt: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Kan studio niet openen '{dimm}' - Pack heeft blokkerende fouten:", diff --git a/core/src/main/resources/languages/pl_PL.json b/core/src/main/resources/languages/pl_PL.json index 243fb818f..f5b6c3775 100644 --- a/core/src/main/resources/languages/pl_PL.json +++ b/core/src/main/resources/languages/pl_PL.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eWygląda na to, że świat został już usunięty z bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cNie udało się zapisać bukkit.yml z powodu {value}", "iris.bukkit.commandiris.set_debug": "§aUstaw debugowanie na: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aPobieranie opakowania: {pack} (uwalnianie beta){value}", "iris.bukkit.commandiris.downloading_pack": "§aPobieranie opakowania: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cMusisz być w Iris świat", "iris.bukkit.commandiris.sending_metrics": "§aWysyłanie wskaźników...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Paczka \"{key}'nie znaleziono go na liście.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Stosowanie /iris download branch= aby pobrać ręcznie.", "iris.bukkit.runtime.studiosvc.failed_download": "Nie można pobrać '{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Nie udało się pobrać IrisDimensions/ Overworld beta wydania.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Nie można pobrać '{repo}'(oddział {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Nie udało się otworzyć studio świata: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Nie można otworzyć studia \"{dimm}\"- opakowanie ma błędy blokujące:", diff --git a/core/src/main/resources/languages/pt_PT.json b/core/src/main/resources/languages/pt_PT.json index ce1539b76..069a45a92 100644 --- a/core/src/main/resources/languages/pt_PT.json +++ b/core/src/main/resources/languages/pt_PT.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eParece que o mundo já foi removido de bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cFalha ao salvar bukkit.yml devido a {value}", "iris.bukkit.commandiris.set_debug": "§aDefinir depuração como: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aBaixando pacote: {pack} (versão beta){value}", "iris.bukkit.commandiris.downloading_pack": "§aBaixando pacote: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cVocê deve estar em um Iris mundo", "iris.bukkit.commandiris.sending_metrics": "§aEnviando métricas...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Embalar '{key}' não foi encontrado na lista de pacotes.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Utilização /iris download branch= para baixar manualmente.", "iris.bukkit.runtime.studiosvc.failed_download": "Falha ao transferir '{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Não foi possível transferir o IrisDimensions- Liberação beta.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Falha ao transferir '{repo}' (ramo {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Falha ao abrir o mundo do estúdio: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Não foi possível abrir o estúdio '{dimm}- a pack tem erros de bloqueio:", diff --git a/core/src/main/resources/languages/ru_RU.json b/core/src/main/resources/languages/ru_RU.json index 4ae099cc6..e4b003afd 100644 --- a/core/src/main/resources/languages/ru_RU.json +++ b/core/src/main/resources/languages/ru_RU.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eПохоже, мир уже был удален из bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cНе удалось спасти bukkit.yml Потому что {value}", "iris.bukkit.commandiris.set_debug": "§aУстановить отладку для: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aСкачать пакет: {pack} (бета-релиз){value}", "iris.bukkit.commandiris.downloading_pack": "§aСкачать пакет: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cВы должны быть в Iris мир", "iris.bukkit.commandiris.sending_metrics": "§aОтправка метрик...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Пакуй.{key}Не было найдено в списке упаковки.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Использовать /iris download branch= Скачать вручную.", "iris.bukkit.runtime.studiosvc.failed_download": "Не удалось скачать{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Не удалось скачать IrisDimensions/overworld бета-версия.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Не удалось скачать{repo}' (ветка {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Не удалось открыть студию: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Не может открыть студию{dimm}У пакета есть ошибки блокировки:", diff --git a/core/src/main/resources/languages/tr_TR.json b/core/src/main/resources/languages/tr_TR.json index cd1dccdc6..32a7d8a20 100644 --- a/core/src/main/resources/languages/tr_TR.json +++ b/core/src/main/resources/languages/tr_TR.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eDünya gibi görünüyor zaten kaldırıldı bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§ckurtarmak için başarısız oldu bukkit.yml Çünkü {value}", "iris.bukkit.commandiris.set_debug": "§aSet debug: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aİndirme paketi: {pack} (Bea release){value}", "iris.bukkit.commandiris.downloading_pack": "§aİndirme paketi: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cBirde olmalısın Iris dünya dünyası", "iris.bukkit.commandiris.sending_metrics": "§ametrikleri gönderin...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Pack \"{key}\" paketin listesinde bulunamadı.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Kullanım Kullanımı /iris download branch= manuel olarak indirmek için.", "iris.bukkit.runtime.studiosvc.failed_download": "indirmek için başarısız oldu '{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "indirmek için başarısız oldu IrisDimensions/overworld beta serbest bırakılması.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "indirmek için başarısız oldu '{repo}\" (branch {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Stüdyo dünyasını açmaya başarısız oldu: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Açık stüdyo \"{dimm}\" - paket hataları engelliyor:", diff --git a/core/src/main/resources/languages/vi_VI.json b/core/src/main/resources/languages/vi_VI.json index 2cc7d1d87..25ff51662 100644 --- a/core/src/main/resources/languages/vi_VI.json +++ b/core/src/main/resources/languages/vi_VI.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§eCó vẻ như thế giới đã bị loại bỏ bukkit.yml", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§cLỗi lưu bukkit.yml bởi vì {value}", "iris.bukkit.commandiris.set_debug": "§aĐặt lỗi là: {to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§aĐang tải về: {pack} (beta phát hành){value}", "iris.bukkit.commandiris.downloading_pack": "§aĐang tải về: {pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§cChắc anh đang ở trong... Iris thế giới", "iris.bukkit.commandiris.sending_metrics": "§aName...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "Gói '{key}Không tìm thấy trong danh sách gói hàng.", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "Dùng /iris download branch= tải về bằng tay.", "iris.bukkit.runtime.studiosvc.failed_download": "Lỗi tải xuống{key}'.", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "Việc tải về bị lỗi IrisDimensionsSự giải phóng của thế giới Bta.", "iris.bukkit.runtime.studiosvc.failed_download_branch": "Lỗi tải xuống{repo}' (branch {branch}).", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "Lỗi mở thế giới studio: {error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "Không thể mở studio{dimm}- gói có lỗi chặn:", diff --git a/core/src/main/resources/languages/zh_CN.json b/core/src/main/resources/languages/zh_CN.json index 44728ccca..2f314b7dc 100644 --- a/core/src/main/resources/languages/zh_CN.json +++ b/core/src/main/resources/languages/zh_CN.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§e该世界似乎已从 bukkit.yml 中移除。", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§c无法保存 bukkit.yml:{value}", "iris.bukkit.commandiris.set_debug": "§a调试模式已设为:{to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§a正在下载包:{pack}(Beta 版本){value}", "iris.bukkit.commandiris.downloading_pack": "§a正在下载包:{pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§c你必须位于 Iris 世界中。", "iris.bukkit.commandiris.sending_metrics": "§a正在发送指标...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "内容包“{key}”不在内容包列表中。", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "使用 /iris download branch= 手动下载。", "iris.bukkit.runtime.studiosvc.failed_download": "无法下载“{key}”。", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "无法下载 IrisDimensions/overworld 测试版。", "iris.bukkit.runtime.studiosvc.failed_download_branch": "无法下载“{repo}”(分支 {branch})。", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "无法打开工作室世界:{error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "无法打开工作室“{dimm}”:内容包存在阻断错误:", diff --git a/core/src/main/resources/languages/zh_TW.json b/core/src/main/resources/languages/zh_TW.json index f635ab704..b54405a11 100644 --- a/core/src/main/resources/languages/zh_TW.json +++ b/core/src/main/resources/languages/zh_TW.json @@ -179,7 +179,6 @@ "iris.bukkit.commandiris.looks_like_world_was_already_removed_from_bukkit_yml": "§e該世界似乎已從 bukkit.yml 中移除。", "iris.bukkit.commandiris.failed_save_bukkit_yml_because": "§c無法儲存 bukkit.yml:{value}", "iris.bukkit.commandiris.set_debug": "§a偵錯模式已設為:{to}", - "iris.bukkit.commandiris.downloading_pack_beta_release": "§a正在下載包:{pack}(Beta 版本){value}", "iris.bukkit.commandiris.downloading_pack": "§a正在下載包:{pack}/{branch}{value}", "iris.bukkit.commandiris.you_must_be_iris_world": "§c你必須位於 Iris 世界中。", "iris.bukkit.commandiris.sending_metrics": "§a正在傳送指標...", @@ -1153,7 +1152,6 @@ "iris.bukkit.runtime.studiosvc.pack_was_not_found_pack_listing": "內容包「{key}」不在內容包清單中。", "iris.bukkit.runtime.studiosvc.use_iris_download_pack_branch_branch_download_manually": "使用 /iris download branch= 手動下載。", "iris.bukkit.runtime.studiosvc.failed_download": "無法下載「{key}」。", - "iris.bukkit.runtime.studiosvc.failed_download_irisdimensions_overworld_beta_release": "無法下載 IrisDimensions/overworld 測試版。", "iris.bukkit.runtime.studiosvc.failed_download_branch": "無法下載「{repo}」(分支 {branch})。", "iris.bukkit.runtime.studiosvc.failed_open_studio_world": "無法開啟工作室世界:{error}", "iris.bukkit.runtime.studiosvc.cannot_open_studio_pack_has_blocking_errors": "無法開啟工作室「{dimm}」:內容包存在阻斷錯誤:", diff --git a/core/src/test/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisionerTest.java b/core/src/test/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisionerTest.java index 03b079758..b6d7f471f 100644 --- a/core/src/test/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisionerTest.java +++ b/core/src/test/java/art/arcane/iris/core/pack/DefaultPackBootstrapProvisionerTest.java @@ -37,23 +37,45 @@ import static org.junit.Assert.assertTrue; public class DefaultPackBootstrapProvisionerTest { @Test - public void defaultBetaSourcesArePinnedPerRequiredPack() { - Map packs = new LinkedHashMap<>(); - for (DefaultPackBootstrapProvisioner.PackSpec pack : DefaultPackBootstrapProvisioner.defaultPacks()) { - packs.put(pack.key(), pack); - } + public void startupDoesNotRequireOrDownloadDefaultPacks() { + assertTrue(DefaultPackBootstrapProvisioner.defaultPacks().isEmpty()); + } - assertEquals(2, packs.size()); - assertEquals( - URI.create("https://github.com/IrisDimensions/overworld/releases/download/beta/overworld.zip"), - packs.get("overworld").source() - ); - assertEquals("overworld", packs.get("overworld").requiredDimension()); - assertEquals( - URI.create("https://github.com/IrisDimensions/underworld/releases/download/beta/underworld.zip"), - packs.get("underworld").source() - ); - assertEquals("underworld", packs.get("underworld").requiredDimension()); + @Test + public void emptyInstallPublishesValidDatapackWithoutNetworkRequests() throws Exception { + AtomicInteger requests = new AtomicInteger(); + HttpServer server = server(packArchive("overworld", "unused"), requests); + Path root = Files.createTempDirectory("iris-bootstrap-empty"); + try { + Path dataDirectory = root.resolve("plugins/Iris"); + DefaultPackBootstrapProvisioner.ProvisionOptions options = new DefaultPackBootstrapProvisioner.ProvisionOptions( + List.of(), + HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).build(), + Clock.fixed(Instant.parse("2026-07-12T12:00:00Z"), ZoneOffset.UTC), + Duration.ofHours(1), + Duration.ofSeconds(2), + 1, + Duration.ZERO, + 8L * 1024L * 1024L, + root + ); + + DefaultPackBootstrapProvisioner.ProvisionResult result = DefaultPackBootstrapProvisioner.provision( + dataDirectory, + ignored -> { + }, + options + ); + + assertEquals(DefaultPackBootstrapProvisioner.ProvisionStatus.INSTALLED, result.status()); + assertTrue(result.packRoots().isEmpty()); + assertEquals(0, requests.get()); + assertTrue(Files.isRegularFile(result.datapackRoot().resolve("pack.mcmeta"))); + assertTrue(DefaultPackBootstrapProvisioner.isProvisioned(dataDirectory, root, List.of())); + } finally { + server.stop(0); + delete(root); + } } @Test diff --git a/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java b/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java index 4b43e3e55..e41fb572e 100644 --- a/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java +++ b/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java @@ -31,8 +31,10 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.mockito.Answers; +import com.sun.net.httpserver.HttpServer; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -44,6 +46,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -53,6 +56,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -71,6 +75,13 @@ public class PackDownloaderTest { IrisPlatform platform = mock(IrisPlatform.class, Answers.CALLS_REAL_METHODS); PlatformStructureHooks structureHooks = mock(PlatformStructureHooks.class, Answers.CALLS_REAL_METHODS); when(platform.dataFolder()).thenReturn(temp.getRoot()); + when(platform.dataFile(any(String[].class))).thenAnswer(invocation -> { + File file = temp.getRoot(); + for (Object argument : invocation.getArguments()) { + file = new File(file, String.valueOf(argument)); + } + return file; + }); when(platform.structureHooks()).thenReturn(structureHooks); when(structureHooks.structureKeys()).thenReturn(List.of("minecraft:village")); when(structureHooks.jigsawStructureKeys()).thenReturn(List.of("minecraft:village")); @@ -90,23 +101,74 @@ public class PackDownloaderTest { } @Test - public void resolvesDefaultOverworldBetaRelease() { - assertEquals( - "https://github.com/IrisDimensions/overworld/releases/download/beta/overworld.zip", - PackDownloader.defaultOverworldReleaseUrl() - ); + public void resolvesDefaultOverworldGitSource() { + assertEquals("IrisDimensions/overworld", PackDownloader.defaultOverworldRepository()); + assertEquals("master", PackDownloader.defaultOverworldRef()); assertTrue(PackDownloader.isDefaultOverworld("overworld")); - assertTrue(PackDownloader.isManagedBetaPack("overworld")); - assertEquals(List.of("overworld", "underworld"), PackDownloader.managedBetaPacks()); + assertTrue(PackDownloader.isManagedPack("overworld")); + assertEquals(List.of("overworld", "underworld"), PackDownloader.managedPacks()); } @Test - public void resolvesUnderworldBetaRelease() { - assertEquals( - "https://github.com/IrisDimensions/underworld/releases/download/beta/underworld.zip", - PackDownloader.underworldReleaseUrl() - ); - assertTrue(PackDownloader.isManagedBetaPack("underworld")); + public void resolvesUnderworldGitSource() { + assertEquals("IrisDimensions/underworld", PackDownloader.underworldRepository()); + assertEquals("main", PackDownloader.underworldRef()); + assertTrue(PackDownloader.isManagedPack("underworld")); + } + + @Test + public void recognizesOnlyHttpZipUrlsAsDirectSources() { + assertTrue(PackDownloader.isDirectZipUrl("https://packs.example.test/overworld.zip")); + assertTrue(PackDownloader.isDirectZipUrl("http://127.0.0.1/pack.ZIP?token=value")); + assertFalse(PackDownloader.isDirectZipUrl("https://packs.example.test/overworld.tar.gz")); + assertFalse(PackDownloader.isDirectZipUrl("file:///tmp/overworld.zip")); + assertFalse(PackDownloader.isDirectZipUrl("not-a-url")); + } + + @Test + public void directZipUrlInstallsSingleDimensionPackAndRequiresRestart() throws Exception { + Path archive = temp.newFile("direct-pack.zip").toPath(); + Map entries = new LinkedHashMap<>(); + entries.put("README.md", "Direct pack"); + entries.put("wrapped/dimensions/direct_pack.json", "{\"name\":\"Direct\",\"regions\":[\"local\"]," + + "\"structures\":[{\"nativeStructures\":[{\"structure\":\"test:future_structure\"}]}]," + + "\"logicalHeight\":256,\"dimensionHeight\":{\"min\":-64,\"max\":320}}"); + entries.put("wrapped/regions/local.json", "{\"name\":\"Local\",\"landBiomes\":[\"local\"]}"); + entries.put("wrapped/biomes/local.json", "{\"name\":\"Local\",\"derivative\":\"minecraft:plains\"}"); + writeArchive(archive, entries); + byte[] response = Files.readAllBytes(archive); + AtomicInteger requests = new AtomicInteger(); + HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + server.createContext("/direct-pack.zip", exchange -> { + requests.incrementAndGet(); + exchange.sendResponseHeaders(200, response.length); + exchange.getResponseBody().write(response); + exchange.close(); + }); + server.start(); + try { + File packsFolder = temp.newFolder("direct-url-packs"); + String url = "http://127.0.0.1:" + server.getAddress().getPort() + "/direct-pack.zip"; + + PackDownloader.PackInstallResult result = PackDownloader.downloadUrl( + packsFolder, + url, + false, + ignored -> { + } + ); + + assertNotNull(result); + assertEquals("direct_pack", result.key()); + assertTrue(result.changed()); + assertTrue(result.restartRequired()); + assertEquals(1, requests.get()); + assertTrue(Files.isRegularFile( + packsFolder.toPath().resolve("direct_pack/dimensions/direct_pack.json") + )); + } finally { + server.stop(0); + } } @Test @@ -118,11 +180,11 @@ public class PackDownloaderTest { writeDimension(packsFolder.toPath().resolve("underworld"), "underworld_roof"); assertTrue(PackDownloader.isPackPresent(packsFolder, "underworld")); - assertFalse(PackDownloader.isManagedBetaPackPresent(packsFolder, "underworld")); + assertFalse(PackDownloader.isManagedPackPresent(packsFolder, "underworld")); writeDimension(packsFolder.toPath().resolve("underworld"), "underworld"); assertTrue(Files.isDirectory(dimensions)); - assertTrue(PackDownloader.isManagedBetaPackPresent(packsFolder, "underworld")); + assertTrue(PackDownloader.isManagedPackPresent(packsFolder, "underworld")); } @Test @@ -146,6 +208,7 @@ public class PackDownloaderTest { assertNotNull(result); assertTrue(result.changed()); + assertTrue(result.restartRequired()); assertTrue(Files.isRegularFile(target.resolve("dimensions/underworld.json"))); assertTrue(Files.isRegularFile(target.resolve("dimensions/underworld_roof.json"))); assertFalse(Files.exists(target.resolve("partial.txt"))); @@ -157,9 +220,9 @@ public class PackDownloaderTest { assertFalse(PackDownloader.isDefaultOverworld("theend")); assertFalse(PackDownloader.isDefaultOverworld("")); assertFalse(PackDownloader.isDefaultOverworld(null)); - assertFalse(PackDownloader.isManagedBetaPack("theend")); - assertFalse(PackDownloader.isManagedBetaPack("")); - assertFalse(PackDownloader.isManagedBetaPack(null)); + assertFalse(PackDownloader.isManagedPack("theend")); + assertFalse(PackDownloader.isManagedPack("")); + assertFalse(PackDownloader.isManagedPack(null)); } @Test @@ -299,7 +362,7 @@ public class PackDownloaderTest { assertEquals(feedback.toString(), "replaceable", result.key()); assertTrue(result.changed()); - assertFalse(result.restartRequired()); + assertTrue(result.restartRequired()); assertEquals("new", Files.readString(target.toPath().resolve("state.txt"), StandardCharsets.UTF_8)); assertFalse(Files.exists(target.toPath().resolve("old-only.txt"))); assertTransactionStateClean(packsFolder); diff --git a/core/src/test/java/art/arcane/iris/core/service/StudioSVCManagedBetaPackTest.java b/core/src/test/java/art/arcane/iris/core/service/StudioSVCManagedBetaPackTest.java deleted file mode 100644 index 044a9d2e3..000000000 --- a/core/src/test/java/art/arcane/iris/core/service/StudioSVCManagedBetaPackTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Servers - * Copyright (c) 2026 Arcane Arts (Volmit Software) - * - * 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 art.arcane.iris.core.service; - -import org.junit.Test; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.Assert.assertEquals; - -public class StudioSVCManagedBetaPackTest { - @Test - public void startupSelectsOnlyMissingManagedBetaPacks() throws IOException { - Path workspace = Files.createTempDirectory("iris-managed-beta-startup"); - try { - assertEquals( - List.of("overworld", "underworld"), - StudioSVC.missingManagedBetaPacks(workspace.toFile()) - ); - - createPack(workspace, "overworld"); - assertEquals( - List.of("underworld"), - StudioSVC.missingManagedBetaPacks(workspace.toFile()) - ); - - createDimension(workspace, "underworld", "underworld_roof"); - assertEquals( - List.of("underworld"), - StudioSVC.missingManagedBetaPacks(workspace.toFile()) - ); - - createPack(workspace, "underworld"); - assertEquals(List.of(), StudioSVC.missingManagedBetaPacks(workspace.toFile())); - } finally { - deleteTree(workspace); - } - } - - private static void createPack(Path workspace, String key) throws IOException { - createDimension(workspace, key, key); - } - - private static void createDimension(Path workspace, String folder, String key) throws IOException { - Path dimensions = Files.createDirectories(workspace.resolve(folder).resolve("dimensions")); - Files.writeString(dimensions.resolve(key + ".json"), "{}", StandardCharsets.UTF_8); - } - - private static void deleteTree(Path root) throws IOException { - try (Stream paths = Files.walk(root)) { - for (Path path : paths.sorted(Comparator.comparingInt(Path::getNameCount).reversed()).toList()) { - Files.deleteIfExists(path); - } - } - } -} diff --git a/core/src/test/java/art/arcane/iris/core/service/StudioSVCPackDownloadContractTest.java b/core/src/test/java/art/arcane/iris/core/service/StudioSVCPackDownloadContractTest.java new file mode 100644 index 000000000..1a321aa08 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/service/StudioSVCPackDownloadContractTest.java @@ -0,0 +1,24 @@ +package art.arcane.iris.core.service; + +import org.junit.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class StudioSVCPackDownloadContractTest { + @Test + public void downloadsRequireManualRestartWithoutMutatingLiveDatapacks() throws Exception { + String source = Files.readString(Path.of("src/main/java/art/arcane/iris/core/service/StudioSVC.java")); + int mutationStart = source.indexOf("private void runPackMutation("); + int mutationEnd = source.indexOf("private boolean finishStandalonePackMutation(", mutationStart); + int finishEnd = source.indexOf("private void runOffPrimaryThread(", mutationEnd); + String downloadMutation = source.substring(mutationStart, finishEnd); + + assertTrue(downloadMutation.contains("Restart the server before using the downloaded Iris pack.")); + assertFalse(downloadMutation.contains("ServerConfigurator.restart()")); + assertFalse(downloadMutation.contains("installDataPacksIfChanged")); + } +}