mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Iris downloader / History update
- Allow people to download in place and update in place the media
This commit is contained in:
@@ -1465,9 +1465,11 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
|
||||
private void printPacks() {
|
||||
File packFolder = Iris.service(StudioSVC.class).getWorkspaceFolder();
|
||||
for (String line : IrisSplashComposer.composePackLines(packFolder, Iris::reportError)) {
|
||||
Iris.info(line);
|
||||
}
|
||||
IrisSplashComposer.composePackLines(packFolder, Iris::reportError).thenAccept(lines -> {
|
||||
if (isEnabled()) {
|
||||
lines.forEach(Iris::info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int getIrisVersion() {
|
||||
|
||||
@@ -21,13 +21,11 @@ package art.arcane.iris.command;
|
||||
import com.google.gson.JsonObject;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.pack.datapack.ServerConfigurator;
|
||||
import art.arcane.iris.world.lifecycle.LifecycleOperationCoordinator;
|
||||
import art.arcane.iris.platform.bukkit.nms.datapack.DataVersion;
|
||||
import art.arcane.iris.world.runtime.ChunkClearer;
|
||||
import art.arcane.iris.generation.validation.GoldenHashScanner;
|
||||
import art.arcane.iris.world.runtime.InPlaceChunkRegenerator;
|
||||
import art.arcane.iris.generation.runtime.IrisEngineSVC;
|
||||
import art.arcane.iris.studio.StudioSVC;
|
||||
import art.arcane.iris.pack.IrisPackBenchmarking;
|
||||
import art.arcane.iris.world.IrisToolbelt;
|
||||
import art.arcane.iris.generation.runtime.IrisEngineMantle;
|
||||
@@ -68,7 +66,6 @@ import art.arcane.iris.localization.IrisLanguage;
|
||||
import art.arcane.iris.localization.BukkitCommandMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
|
||||
import art.arcane.iris.localization.BukkitRuntimeMessages;
|
||||
@Director(name = "Developer", origin = DirectorOrigin.BOTH, description = "Iris World Manager", descriptionKey = "iris.director.commanddeveloper.director.iris_world_manager", aliases = {"dev"})
|
||||
public class CommandDeveloper implements DirectorExecutor {
|
||||
@Director(description = "Get Loaded TectonicPlates Count", descriptionKey = "iris.director.commanddeveloper.director.get_loaded_tectonicplates_count", origin = DirectorOrigin.BOTH, sync = true)
|
||||
@@ -184,27 +181,7 @@ public class CommandDeveloper implements DirectorExecutor {
|
||||
@Param(description = "Make sure to make a backup & read the warnings first!", descriptionKey = "iris.director.commanddeveloper.param.make_sure_make_backup_read_warnings_first", defaultValue = "false", aliases = "c")
|
||||
boolean confirm
|
||||
) {
|
||||
if (!confirm) {
|
||||
sender().sendMessage(IrisLanguage.text(
|
||||
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
|
||||
MessageArgument.untrusted("world", world.getName()),
|
||||
MessageArgument.untrusted("pack", pack.getLoadKey())
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
File folder = world.getWorldFolder();
|
||||
folder.mkdirs();
|
||||
|
||||
try (LifecycleOperationCoordinator.Lease lease = LifecycleOperationCoordinator.get().acquire(
|
||||
LifecycleOperationCoordinator.Domain.PACK_MUTATION,
|
||||
LifecycleOperationCoordinator.OperationKind.PACK_PUBLISH,
|
||||
pack.getLoadKey()
|
||||
)) {
|
||||
Iris.service(StudioSVC.class).replaceIntoWorld(sender(), pack, folder, world.getSeed());
|
||||
} catch (LifecycleOperationCoordinator.BusyException e) {
|
||||
sender().sendMessage(C.YELLOW + e.getMessage());
|
||||
}
|
||||
CommandPack.stageWorldUpdate(sender(), world, pack, confirm);
|
||||
}
|
||||
|
||||
@Director(description = "Test", descriptionKey = "iris.director.commanddeveloper.director.test")
|
||||
|
||||
@@ -437,7 +437,9 @@ public class CommandIris implements DirectorExecutor {
|
||||
@Param(name = "pack", description = "The built-in pack to download", descriptionKey = "iris.director.commandiris.param.pack_download", defaultValue = NO_DOWNLOAD_SOURCE, customHandler = DownloadPackHandler.class)
|
||||
String pack,
|
||||
@Param(name = "link", description = "A direct HTTP or HTTPS zip link", defaultValue = NO_DOWNLOAD_SOURCE)
|
||||
String link
|
||||
String link,
|
||||
@Param(name = "overwrite", description = "Back up and replace an installed pack", defaultValue = "false")
|
||||
boolean overwrite
|
||||
) {
|
||||
String builtInPack = NO_DOWNLOAD_SOURCE.equals(pack) ? null : pack;
|
||||
String directLink = NO_DOWNLOAD_SOURCE.equals(link) ? null : link;
|
||||
@@ -446,14 +448,14 @@ public class CommandIris implements DirectorExecutor {
|
||||
return;
|
||||
}
|
||||
if (builtInPack != null) {
|
||||
Iris.service(StudioSVC.class).downloadBuiltIn(sender(), builtInPack);
|
||||
Iris.service(StudioSVC.class).downloadBuiltIn(sender(), builtInPack, overwrite);
|
||||
return;
|
||||
}
|
||||
if (!PackDownloader.isDirectZipUrl(directLink)) {
|
||||
sender().sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_URL));
|
||||
return;
|
||||
}
|
||||
Iris.service(StudioSVC.class).downloadUrl(sender(), directLink);
|
||||
Iris.service(StudioSVC.class).downloadUrl(sender(), directLink, overwrite);
|
||||
}
|
||||
|
||||
@Director(description = "Get metrics for your world", descriptionKey = "iris.director.commandiris.director.get_metrics_your_world", aliases = "measure", origin = DirectorOrigin.PLAYER)
|
||||
|
||||
@@ -27,6 +27,9 @@ import art.arcane.iris.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.pack.PackValidationResult;
|
||||
import art.arcane.iris.pack.PackValidator;
|
||||
import art.arcane.iris.studio.StudioSVC;
|
||||
import art.arcane.iris.world.IrisToolbelt;
|
||||
import art.arcane.iris.world.lifecycle.LifecycleOperationCoordinator;
|
||||
import art.arcane.iris.localization.C;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.command.handlers.DimensionHandler;
|
||||
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
|
||||
@@ -35,6 +38,7 @@ import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.volmlib.util.localization.TextKey;
|
||||
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -48,6 +52,42 @@ import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.iris.localization.BukkitRuntimeMessages;
|
||||
@Director(name = "pack", aliases = {"pk"}, description = "Pack validation and maintenance", descriptionKey = "iris.director.commandpack.director.pack_validation_maintenance")
|
||||
public class CommandPack implements DirectorExecutor {
|
||||
@Director(name = "update-world", description = "Stage an installed pack for a world and request a clean restart", descriptionKey = "iris.director.commanddeveloper.director.stage_world_generation_update")
|
||||
public void updateWorld(
|
||||
@Param(description = "The world to update", descriptionKey = "iris.director.commanddeveloper.param.world_update", contextual = true, contextualOverride = true)
|
||||
World world,
|
||||
@Param(description = "The installed dimension to use", descriptionKey = "iris.director.commanddeveloper.param.pack_install_into_world", contextual = true, contextualOverride = true, aliases = "dimension")
|
||||
IrisDimension pack,
|
||||
@Param(description = "Confirm that the complete world has been backed up", descriptionKey = "iris.director.commanddeveloper.param.make_sure_make_backup_read_warnings_first", defaultValue = "false", aliases = "c")
|
||||
boolean confirm
|
||||
) {
|
||||
stageWorldUpdate(sender(), world, pack, confirm);
|
||||
}
|
||||
|
||||
static void stageWorldUpdate(VolmitSender sender, World world, IrisDimension pack, boolean confirm) {
|
||||
if (!IrisToolbelt.isIrisWorld(world) || IrisToolbelt.isIrisStudioWorld(world)) {
|
||||
sender.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD));
|
||||
return;
|
||||
}
|
||||
if (!confirm) {
|
||||
sender.sendMessage(IrisLanguage.text(
|
||||
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
|
||||
MessageArgument.untrusted("world", world.getName()),
|
||||
MessageArgument.untrusted("pack", pack.getLoadKey())
|
||||
));
|
||||
return;
|
||||
}
|
||||
try (LifecycleOperationCoordinator.Lease lease = LifecycleOperationCoordinator.get().acquire(
|
||||
LifecycleOperationCoordinator.Domain.PACK_MUTATION,
|
||||
LifecycleOperationCoordinator.OperationKind.PACK_PUBLISH,
|
||||
world.getName()
|
||||
)) {
|
||||
Iris.service(StudioSVC.class).replaceIntoWorld(sender, pack, world.getWorldFolder(), world.getSeed());
|
||||
} catch (LifecycleOperationCoordinator.BusyException e) {
|
||||
sender.sendMessage(C.YELLOW + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Director treats a blank defaultValue as "required", which made the all-packs branch
|
||||
* unreachable from chat. The "*" sentinel keeps the parameter genuinely optional; a blank
|
||||
|
||||
+6
-3
@@ -15,22 +15,25 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CommandIrisDownloadContractTest {
|
||||
@Test
|
||||
public void commandExposesOnlyPackAndLinkParameters() throws NoSuchMethodException {
|
||||
Method method = CommandIris.class.getDeclaredMethod("download", String.class, String.class);
|
||||
public void commandRequiresExplicitOverwriteForPackReplacement() throws NoSuchMethodException {
|
||||
Method method = CommandIris.class.getDeclaredMethod("download", String.class, String.class, boolean.class);
|
||||
Parameter[] parameters = method.getParameters();
|
||||
Param pack = parameters[0].getAnnotation(Param.class);
|
||||
Param link = parameters[1].getAnnotation(Param.class);
|
||||
Param overwrite = parameters[2].getAnnotation(Param.class);
|
||||
List<Method> downloadMethods = Arrays.stream(CommandIris.class.getDeclaredMethods())
|
||||
.filter((Method candidate) -> candidate.getName().equals("download"))
|
||||
.toList();
|
||||
|
||||
assertEquals(1, downloadMethods.size());
|
||||
assertEquals(2, downloadMethods.getFirst().getParameterCount());
|
||||
assertEquals(3, downloadMethods.getFirst().getParameterCount());
|
||||
assertEquals("pack", pack.name());
|
||||
assertEquals(0L, Arrays.stream(pack.aliases()).filter((String alias) -> !alias.isBlank()).count());
|
||||
assertEquals(CommandIris.DownloadPackHandler.class, pack.customHandler());
|
||||
assertEquals("link", link.name());
|
||||
assertEquals(0L, Arrays.stream(link.aliases()).filter((String alias) -> !alias.isBlank()).count());
|
||||
assertEquals("overwrite", overwrite.name());
|
||||
assertEquals("false", overwrite.defaultValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package art.arcane.iris.command;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.localization.BukkitRuntimeMessages;
|
||||
import art.arcane.iris.localization.IrisLanguage;
|
||||
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
|
||||
import art.arcane.iris.studio.StudioSVC;
|
||||
import art.arcane.iris.world.IrisToolbelt;
|
||||
import art.arcane.volmlib.util.director.compat.DirectorAnnotationCompatibility;
|
||||
import art.arcane.volmlib.util.director.runtime.DirectorNodeDescriptor;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.volmlib.util.localization.TextKey;
|
||||
import org.bukkit.World;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CommandPackUpdateWorldTest {
|
||||
@Test
|
||||
public void packNamespaceExposesAnExplicitConfirmation() throws Exception {
|
||||
DirectorNodeDescriptor command = DirectorAnnotationCompatibility.fromMethod(
|
||||
CommandPack.class.getDeclaredMethod("updateWorld", World.class, IrisDimension.class, boolean.class)
|
||||
).orElseThrow();
|
||||
|
||||
assertEquals("update-world", command.getName());
|
||||
assertTrue(command.getParameters().get(0).isRequired());
|
||||
assertTrue(command.getParameters().get(1).isRequired());
|
||||
assertFalse(command.getParameters().get(2).isRequired());
|
||||
assertEquals("false", command.getParameters().get(2).getDefaultValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unconfirmedAndNonPersistentWorldUpdatesCannotPublish() {
|
||||
VolmitSender sender = mock(VolmitSender.class);
|
||||
World world = mock(World.class);
|
||||
IrisDimension pack = new IrisDimension();
|
||||
pack.setLoadKey("overworld");
|
||||
when(world.getName()).thenReturn("example");
|
||||
try (MockedStatic<Iris> iris = mockStatic(Iris.class);
|
||||
MockedStatic<IrisToolbelt> toolbelt = mockStatic(IrisToolbelt.class)) {
|
||||
CommandPack.stageWorldUpdate(sender, world, pack, true);
|
||||
toolbelt.when(() -> IrisToolbelt.isIrisWorld(world)).thenReturn(true);
|
||||
toolbelt.when(() -> IrisToolbelt.isIrisStudioWorld(world)).thenReturn(true);
|
||||
CommandPack.stageWorldUpdate(sender, world, pack, true);
|
||||
toolbelt.when(() -> IrisToolbelt.isIrisStudioWorld(world)).thenReturn(false);
|
||||
CommandPack.stageWorldUpdate(sender, world, pack, false);
|
||||
|
||||
iris.verifyNoInteractions();
|
||||
verify(sender, times(2)).sendMessage(IrisLanguage.text(
|
||||
BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD));
|
||||
verify(sender).sendMessage(IrisLanguage.text(
|
||||
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
|
||||
MessageArgument.untrusted("world", "example"),
|
||||
MessageArgument.untrusted("pack", "overworld")));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateResultsRenderTheWorldPackAndVersion() {
|
||||
for (TextKey key : List.of(BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_STAGED,
|
||||
BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_UNCHANGED)) {
|
||||
String message = IrisLanguage.text(key,
|
||||
MessageArgument.untrusted("world", "example"),
|
||||
MessageArgument.untrusted("pack", "overworld"),
|
||||
MessageArgument.untrusted("version", 4011));
|
||||
|
||||
assertTrue(message.contains("example"));
|
||||
assertTrue(message.contains("overworld"));
|
||||
assertTrue(message.contains("4011"));
|
||||
assertFalse(message.contains("{world}"));
|
||||
assertFalse(message.contains("{pack}"));
|
||||
assertFalse(message.contains("{version}"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmedUpdateUsesTheExistingWorldFolderAndSeed() {
|
||||
VolmitSender sender = mock(VolmitSender.class);
|
||||
World world = mock(World.class);
|
||||
IrisDimension pack = new IrisDimension();
|
||||
File folder = new File("example");
|
||||
StudioSVC studio = mock(StudioSVC.class);
|
||||
when(world.getName()).thenReturn("example");
|
||||
when(world.getWorldFolder()).thenReturn(folder);
|
||||
when(world.getSeed()).thenReturn(42L);
|
||||
try (MockedStatic<Iris> iris = mockStatic(Iris.class);
|
||||
MockedStatic<IrisToolbelt> toolbelt = mockStatic(IrisToolbelt.class)) {
|
||||
iris.when(() -> Iris.service(StudioSVC.class)).thenReturn(studio);
|
||||
toolbelt.when(() -> IrisToolbelt.isIrisWorld(world)).thenReturn(true);
|
||||
|
||||
CommandPack.stageWorldUpdate(sender, world, pack, true);
|
||||
|
||||
verify(studio).replaceIntoWorld(sender, pack, folder, 42L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,9 +40,8 @@ public final class ModdedIrisSplash {
|
||||
|
||||
private static void printPacks(ModdedLoader loader) {
|
||||
File packFolder = IrisPlatforms.get().packsFolderNoCreate();
|
||||
for (String line : IrisSplashComposer.composePackLines(packFolder, IrisLogging::reportError)) {
|
||||
IrisLogging.info(line);
|
||||
}
|
||||
IrisSplashComposer.composePackLines(packFolder, IrisLogging::reportError)
|
||||
.thenAccept(lines -> lines.forEach(IrisLogging::info));
|
||||
}
|
||||
|
||||
private static void printLogo(ModdedLoader loader) {
|
||||
|
||||
+29
-11
@@ -406,14 +406,14 @@ public final class IrisModdedCommands {
|
||||
? PackDownloader.downloadUrl(
|
||||
packs,
|
||||
request.url(),
|
||||
false,
|
||||
request.overwrite(),
|
||||
(String message) -> dispatchDownloadFeedback(source, () -> ok(source, message)),
|
||||
cancellation
|
||||
)
|
||||
: PackDownloader.downloadBuiltIn(
|
||||
packs,
|
||||
request.pack(),
|
||||
false,
|
||||
request.overwrite(),
|
||||
(String message) -> dispatchDownloadFeedback(source, () -> ok(source, message)),
|
||||
cancellation
|
||||
);
|
||||
@@ -478,15 +478,33 @@ public final class IrisModdedCommands {
|
||||
if (rawRequest == null || rawRequest.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
if (rawRequest.startsWith("pack=")) {
|
||||
String pack = rawRequest.substring("pack=".length()).trim().toLowerCase(Locale.ROOT);
|
||||
return PackDownloader.isBuiltInPack(pack) ? new DownloadRequest(pack, null) : null;
|
||||
String pack = null;
|
||||
String url = null;
|
||||
Boolean overwrite = null;
|
||||
for (String argument : rawRequest.trim().split("\\s+")) {
|
||||
if (argument.startsWith("pack=") && pack == null) {
|
||||
pack = argument.substring("pack=".length()).toLowerCase(Locale.ROOT);
|
||||
if (!PackDownloader.isBuiltInPack(pack)) {
|
||||
return null;
|
||||
}
|
||||
} else if (argument.startsWith("link=") && url == null) {
|
||||
url = argument.substring("link=".length());
|
||||
if (!PackDownloader.isDirectZipUrl(url)) {
|
||||
return null;
|
||||
}
|
||||
} else if (argument.startsWith("overwrite=") && overwrite == null) {
|
||||
String value = argument.substring("overwrite=".length());
|
||||
if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) {
|
||||
return null;
|
||||
}
|
||||
overwrite = Boolean.parseBoolean(value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (rawRequest.startsWith("link=")) {
|
||||
String url = rawRequest.substring("link=".length()).trim();
|
||||
return PackDownloader.isDirectZipUrl(url) ? new DownloadRequest(null, url) : null;
|
||||
}
|
||||
return null;
|
||||
return (pack == null) == (url == null)
|
||||
? null
|
||||
: new DownloadRequest(pack, url, Boolean.TRUE.equals(overwrite));
|
||||
}
|
||||
|
||||
static int metrics(CommandSourceStack source) {
|
||||
@@ -555,6 +573,6 @@ public final class IrisModdedCommands {
|
||||
ModdedCommandFeedback.fail(source, message);
|
||||
}
|
||||
|
||||
record DownloadRequest(String pack, String url) {
|
||||
record DownloadRequest(String pack, String url, boolean overwrite) {
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ final class ModdedCommandHelp {
|
||||
Entry.command("seed", "", ModdedHelpMessages.COMMAND_SEED_PRINT_WORLD_AND_ENGINE_SEED_INFORMATION),
|
||||
Entry.command("debug", "", ModdedHelpMessages.COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON),
|
||||
Entry.command("reload", "", ModdedHelpMessages.COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S),
|
||||
Entry.command("download", "<pack=overworld|pack=underworld|link=<zip-url>>", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"),
|
||||
Entry.command("download", "<pack=overworld|pack=underworld|link=<zip-url>> [overwrite=true]", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"),
|
||||
Entry.command("metrics", "", ModdedHelpMessages.COMMAND_METRICS_PRINT_GENERATION_METRICS_FOR_YOUR_CURRENT_IRIS_DIMENSION, "measure"),
|
||||
Entry.command("regen", "[radius]", ModdedHelpMessages.COMMAND_REGEN_DELETE_AND_REGENERATE_NEARBY_CHUNKS_IN_PLACE, "rg"),
|
||||
Entry.group("pregen", ModdedHelpMessages.GROUP_PREGEN_PREGENERATE_AN_IRIS_DIMENSION, "pregenerate"),
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ final class ModdedCommandTree {
|
||||
private static final SuggestionProvider<CommandSourceStack> DOWNLOAD_SOURCES =
|
||||
(context, builder) ->
|
||||
SharedSuggestionProvider.suggest(
|
||||
List.of("pack=overworld", "pack=underworld", "link="),
|
||||
List.of("pack=overworld", "pack=underworld", "link=", "pack=overworld overwrite=true", "pack=underworld overwrite=true"),
|
||||
builder
|
||||
);
|
||||
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
|
||||
|
||||
+21
-1
@@ -89,7 +89,27 @@ public class IrisModdedCommandParityTest {
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("pack=custom"));
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("link=https://packs.example.test/custom.tar.gz"));
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("pack=underworld branch=stable"));
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("pack=underworld overwrite=true"));
|
||||
assertFalse(overworld.overwrite());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void downloadOverwriteRequiresOneSourceAndAnExplicitBoolean() {
|
||||
IrisModdedCommands.DownloadRequest pack = IrisModdedCommands.parseDownloadRequest("pack=underworld overwrite=true");
|
||||
IrisModdedCommands.DownloadRequest link = IrisModdedCommands.parseDownloadRequest(
|
||||
"overwrite=true link=https://packs.example.test/custom.zip?token=a=b");
|
||||
|
||||
assertNotNull(pack);
|
||||
assertTrue(pack.overwrite());
|
||||
assertEquals("underworld", pack.pack());
|
||||
assertNotNull(link);
|
||||
assertTrue(link.overwrite());
|
||||
assertEquals("https://packs.example.test/custom.zip?token=a=b", link.url());
|
||||
assertFalse(IrisModdedCommands.parseDownloadRequest("pack=overworld overwrite=false").overwrite());
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("overwrite=true"));
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld overwrite=yes"));
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld overwrite=true overwrite=false"));
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld pack=underworld"));
|
||||
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld link=https://packs.example.test/custom.zip"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package art.arcane.iris.diagnostics.splash;
|
||||
|
||||
import art.arcane.iris.BuildConstants;
|
||||
import art.arcane.iris.pack.BuiltInPackUpdates;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public final class IrisSplashComposer {
|
||||
private static final String SPLASH_PADDING = " ".repeat(4);
|
||||
@@ -40,8 +43,14 @@ public final class IrisSplashComposer {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static List<String> composePackLines(File packFolder, IrisSplashPackScanner.SplashPackErrorReporter reporter) {
|
||||
public static CompletableFuture<List<String>> composePackLines(File packFolder, IrisSplashPackScanner.SplashPackErrorReporter reporter) {
|
||||
List<IrisSplashPackScanner.SplashPackMetadata> packs = IrisSplashPackScanner.collect(packFolder, reporter);
|
||||
return BuiltInPackUpdates.check(packs.stream().map(IrisSplashPackScanner.SplashPackMetadata::name).toList())
|
||||
.thenApply(updates -> composePackLines(packs, updates));
|
||||
}
|
||||
|
||||
static List<String> composePackLines(List<IrisSplashPackScanner.SplashPackMetadata> packs,
|
||||
Map<String, BuiltInPackUpdates.Update> updates) {
|
||||
if (packs.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -49,7 +58,15 @@ public final class IrisSplashComposer {
|
||||
List<String> lines = new ArrayList<>(packs.size() + 1);
|
||||
lines.add("Custom Dimensions: " + packs.size());
|
||||
for (IrisSplashPackScanner.SplashPackMetadata pack : packs) {
|
||||
lines.add(" " + pack.name() + " v" + pack.version());
|
||||
BuiltInPackUpdates.Update update = updates.get(pack.name());
|
||||
lines.add(" " + pack.name() + " v" + pack.version()
|
||||
+ (update == null ? "" : update.suffix(pack.version())));
|
||||
}
|
||||
for (IrisSplashPackScanner.SplashPackMetadata pack : packs) {
|
||||
BuiltInPackUpdates.Update update = updates.get(pack.name());
|
||||
if (update != null && update.newerThan(pack.version())) {
|
||||
lines.add("Update " + pack.name() + ": /iris download pack=" + pack.name() + " overwrite=true");
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
@@ -180,6 +180,18 @@ public final class BukkitRuntimeMessages {
|
||||
C.RED + "To stage the update and request a clean restart:",
|
||||
C.RED + "/iris developer update-world {world} {pack} confirm=true"
|
||||
);
|
||||
public static final TextKey COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD = TextKey.of(
|
||||
"iris.bukkit.runtime.commandpack.update_world_requires_persistent_world",
|
||||
C.RED + "Choose a loaded Iris world. Studio worlds cannot receive generation updates."
|
||||
);
|
||||
public static final TextKey COMMAND_PACK_UPDATE_WORLD_STAGED = TextKey.of(
|
||||
"iris.bukkit.runtime.commandpack.update_world_staged",
|
||||
C.GREEN + "Staged {pack} v{version} for world '{world}'. A clean restart will activate the pack with terrain blending."
|
||||
);
|
||||
public static final TextKey COMMAND_PACK_UPDATE_WORLD_UNCHANGED = TextKey.of(
|
||||
"iris.bukkit.runtime.commandpack.update_world_unchanged",
|
||||
C.GREEN + "World '{world}' already uses {pack} v{version} with the same generation settings. No restart is needed."
|
||||
);
|
||||
public static final TextKey BULK_STRUCTURE_IMPORTER_IMPORTING_VANILLA_DATAPACK_STRUCTURES_MODE_INCLUDENONJIGSAW = TextKey.of(
|
||||
"iris.bukkit.runtime.bulkstructureimporter.importing_vanilla_datapack_structures_mode_includenonjigsaw",
|
||||
C.GREEN + "Importing " + C.WHITE + "{total}" + C.GREEN + " vanilla & datapack structures (mode=" + "{mode}" + ", includeNonJigsaw=" + "{includeNonJigsaw}" + ")..."
|
||||
@@ -658,6 +670,9 @@ public final class BukkitRuntimeMessages {
|
||||
COMMAND_PACK_COMPAT_NONE,
|
||||
COMMAND_PACK_COMPAT_REMEDY,
|
||||
COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
|
||||
COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD,
|
||||
COMMAND_PACK_UPDATE_WORLD_STAGED,
|
||||
COMMAND_PACK_UPDATE_WORLD_UNCHANGED,
|
||||
BULK_STRUCTURE_IMPORTER_IMPORTING_VANILLA_DATAPACK_STRUCTURES_MODE_INCLUDENONJIGSAW,
|
||||
BULK_STRUCTURE_IMPORTER_FAIL_INVALID_KEY,
|
||||
BULK_STRUCTURE_IMPORTER_JIGSAW,
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class PackDownloadMessages {
|
||||
);
|
||||
public static final TextKey PROGRESS_UNCHANGED = TextKey.of(
|
||||
"iris.runtime.pack_download.progress.unchanged",
|
||||
C.YELLOW + "Iris pack '{pack}' is already installed."
|
||||
C.YELLOW + "Iris pack '{pack}' is already installed. Use overwrite=true to back up and replace it."
|
||||
);
|
||||
public static final TextKey PROGRESS_FAILED = TextKey.of(
|
||||
"iris.runtime.pack_download.progress.failed",
|
||||
@@ -99,6 +99,14 @@ public final class PackDownloadMessages {
|
||||
"iris.runtime.pack_download.shutting_down",
|
||||
C.YELLOW + "Iris is shutting down and is not accepting pack downloads."
|
||||
);
|
||||
public static final TextKey CLOSE_STUDIO = TextKey.of(
|
||||
"iris.runtime.pack_download.close_studio",
|
||||
"Close the active Studio project before replacing an installed pack."
|
||||
);
|
||||
public static final TextKey BACKUP_RETAINED = TextKey.of(
|
||||
"iris.runtime.pack_download.backup_retained",
|
||||
"Previous pack saved to {path}."
|
||||
);
|
||||
public static final TextKey DOWNLOADING = TextKey.of(
|
||||
"iris.runtime.pack_download.downloading",
|
||||
"Downloading {url}"
|
||||
@@ -150,7 +158,7 @@ public final class PackDownloadMessages {
|
||||
);
|
||||
public static final TextKey PACK_KEY_CONFLICT = TextKey.of(
|
||||
"iris.runtime.pack_download.pack_key_conflict",
|
||||
"Another pack is using the key {key}. Import failed!"
|
||||
"Pack {key} is already installed. Use overwrite=true to back up and replace it."
|
||||
);
|
||||
public static final TextKey ACQUIRED = TextKey.of(
|
||||
"iris.runtime.pack_download.acquired",
|
||||
@@ -158,7 +166,7 @@ public final class PackDownloadMessages {
|
||||
);
|
||||
public static final TextKey ALREADY_INSTALLED = TextKey.of(
|
||||
"iris.runtime.pack_download.already_installed",
|
||||
"Pack {key} is already installed, skipping download."
|
||||
"Pack {key} is already installed, skipping download. Use overwrite=true to back up and replace it."
|
||||
);
|
||||
public static final TextKey IN_PROGRESS = TextKey.of(
|
||||
"iris.runtime.pack_download.in_progress",
|
||||
@@ -207,6 +215,8 @@ public final class PackDownloadMessages {
|
||||
INVALID_URL,
|
||||
INVALID_BUILT_IN,
|
||||
SHUTTING_DOWN,
|
||||
CLOSE_STUDIO,
|
||||
BACKUP_RETAINED,
|
||||
DOWNLOADING,
|
||||
UNPACKING,
|
||||
UNPACK_FAILED,
|
||||
|
||||
@@ -71,7 +71,7 @@ public final class AtomicDirectoryPublisher {
|
||||
|
||||
public static final class Publication implements AutoCloseable {
|
||||
private final Path target;
|
||||
private final Path backup;
|
||||
private Path backup;
|
||||
private boolean committed;
|
||||
private boolean closed;
|
||||
|
||||
@@ -87,6 +87,22 @@ public final class AtomicDirectoryPublisher {
|
||||
committed = true;
|
||||
}
|
||||
|
||||
public synchronized boolean retainBackup(Path destination) throws IOException {
|
||||
if (closed || committed) {
|
||||
throw new IllegalStateException("Directory publication is already finalized.");
|
||||
}
|
||||
if (backup == null) {
|
||||
return false;
|
||||
}
|
||||
Path retained = Objects.requireNonNull(destination, "destination").toAbsolutePath().normalize();
|
||||
if (Files.exists(retained) || Files.isSymbolicLink(retained)) {
|
||||
throw new FileAlreadyExistsException(retained.toString());
|
||||
}
|
||||
move(backup, retained);
|
||||
backup = retained;
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized void cleanupBackup() throws IOException {
|
||||
if (!committed) {
|
||||
throw new IllegalStateException("Directory publication is not committed.");
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package art.arcane.iris.pack;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class BuiltInPackUpdates {
|
||||
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5);
|
||||
private static final Pattern VERSION = Pattern.compile("[0-9]+");
|
||||
private static final HttpClient CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(REQUEST_TIMEOUT)
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
.build();
|
||||
|
||||
private BuiltInPackUpdates() {
|
||||
}
|
||||
|
||||
public static CompletableFuture<Map<String, Update>> check(List<String> packs) {
|
||||
return check(packs, CLIENT, pack -> URI.create(
|
||||
"https://api.github.com/repos/IrisDimensions/" + pack + "/releases/latest"));
|
||||
}
|
||||
|
||||
static CompletableFuture<Map<String, Update>> check(List<String> packs, HttpClient client,
|
||||
Function<String, URI> endpoint) {
|
||||
Map<String, CompletableFuture<Update>> requests = new LinkedHashMap<>();
|
||||
for (String pack : packs) {
|
||||
if (!PackDownloader.isBuiltInPack(pack) || requests.containsKey(pack)) {
|
||||
continue;
|
||||
}
|
||||
HttpRequest request = HttpRequest.newBuilder(endpoint.apply(pack))
|
||||
.timeout(REQUEST_TIMEOUT)
|
||||
.header("Accept", "application/vnd.github+json")
|
||||
.header("User-Agent", "Iris-Pack-Updates")
|
||||
.GET()
|
||||
.build();
|
||||
CompletableFuture<HttpResponse<String>> response = client.sendAsync(request,
|
||||
HttpResponse.BodyHandlers.limiting(HttpResponse.BodyHandlers.ofString(), 1024L * 1024L));
|
||||
requests.put(pack, response.copy().orTimeout(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)
|
||||
.whenComplete((result, error) -> {
|
||||
if (error != null) {
|
||||
response.cancel(true);
|
||||
}
|
||||
})
|
||||
.thenApply(result -> readRelease(pack, result))
|
||||
.exceptionally(error -> Update.unavailable()));
|
||||
}
|
||||
return CompletableFuture.allOf(requests.values().toArray(CompletableFuture[]::new))
|
||||
.thenApply(ignored -> {
|
||||
Map<String, Update> updates = new LinkedHashMap<>();
|
||||
requests.forEach((pack, request) -> updates.put(pack, request.join()));
|
||||
return Map.copyOf(updates);
|
||||
});
|
||||
}
|
||||
|
||||
private static Update readRelease(String pack, HttpResponse<String> response) {
|
||||
if (response.statusCode() != 200) {
|
||||
return Update.unavailable();
|
||||
}
|
||||
JsonObject release = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
if (release.get("draft").getAsBoolean() || release.get("prerelease").getAsBoolean()) {
|
||||
return Update.unavailable();
|
||||
}
|
||||
String version = release.get("tag_name").getAsString().replaceFirst("^[vV]\\+?", "");
|
||||
if (!VERSION.matcher(version).matches()) {
|
||||
return Update.unavailable();
|
||||
}
|
||||
for (JsonElement element : release.getAsJsonArray("assets")) {
|
||||
JsonObject asset = element.getAsJsonObject();
|
||||
if ((pack + ".zip").equals(asset.get("name").getAsString())) {
|
||||
return new Update(version);
|
||||
}
|
||||
}
|
||||
return Update.unavailable();
|
||||
}
|
||||
|
||||
public record Update(String version) {
|
||||
public static Update unavailable() {
|
||||
return new Update(null);
|
||||
}
|
||||
|
||||
public boolean newerThan(String installedVersion) {
|
||||
return version != null && VERSION.matcher(installedVersion).matches()
|
||||
&& new BigInteger(version).compareTo(new BigInteger(installedVersion)) > 0;
|
||||
}
|
||||
|
||||
public String suffix(String installedVersion) {
|
||||
if (version == null) {
|
||||
return " (update check unavailable)";
|
||||
}
|
||||
if (!VERSION.matcher(installedVersion).matches()) {
|
||||
return " (latest v" + version + ")";
|
||||
}
|
||||
return newerThan(installedVersion)
|
||||
? " -> v" + version + " available"
|
||||
: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -646,16 +646,36 @@ public final class PackDownloader {
|
||||
}
|
||||
IrisData.getLoaded(new File(packsFolder, prepared.key())).ifPresent(IrisData::close);
|
||||
IrisData.getLoaded(target.toFile()).ifPresent(IrisData::close);
|
||||
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publish(staging, target)) {
|
||||
publication.commit();
|
||||
try {
|
||||
publication.cleanupBackup();
|
||||
} catch (IOException exception) {
|
||||
IrisLogging.reportError(
|
||||
"Pack '" + prepared.key() + "' was published, but its transaction backup could not be cleaned.",
|
||||
exception
|
||||
);
|
||||
Path retainedBackup = null;
|
||||
if (forceOverwrite && Files.exists(target)) {
|
||||
Path backupRoot = packsRoot.resolve(".backups");
|
||||
if (Files.isSymbolicLink(backupRoot)) {
|
||||
throw new IOException("Pack backup folder is an unsafe symbolic link: " + backupRoot);
|
||||
}
|
||||
Files.createDirectories(backupRoot);
|
||||
retainedBackup = backupRoot.resolve(prepared.key() + "-" + UUID.randomUUID());
|
||||
}
|
||||
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publish(staging, target)) {
|
||||
if (retainedBackup != null) {
|
||||
publication.retainBackup(retainedBackup);
|
||||
}
|
||||
publication.commit();
|
||||
if (retainedBackup == null) {
|
||||
try {
|
||||
publication.cleanupBackup();
|
||||
} catch (IOException exception) {
|
||||
IrisLogging.reportError(
|
||||
"Pack '" + prepared.key() + "' was published, but its transaction backup could not be cleaned.",
|
||||
exception
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (retainedBackup != null) {
|
||||
sendFeedback(feedback, IrisLanguage.plain(
|
||||
PackDownloadMessages.BACKUP_RETAINED,
|
||||
MessageArgument.untrusted("path", retainedBackup.toString())
|
||||
));
|
||||
}
|
||||
PackValidationRegistry.publish(prepared.validation());
|
||||
sendValidationFeedback(prepared.validation(), feedback);
|
||||
|
||||
@@ -290,6 +290,16 @@ public class StudioSVC implements IrisService {
|
||||
? history.packRoot(history.pendingActivation().orElseThrow().activationId())
|
||||
: history.activePackRoot();
|
||||
IrisDimension installed = loadInstalledDimension(installedPack, source, dimensionKey);
|
||||
if (stageUpdate) {
|
||||
sender.sendMessage(IrisLanguage.text(
|
||||
restartRequired
|
||||
? BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_STAGED
|
||||
: BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_UNCHANGED,
|
||||
MessageArgument.untrusted("world", dimensionRoot.getName()),
|
||||
MessageArgument.untrusted("pack", installed.getLoadKey()),
|
||||
MessageArgument.untrusted("version", installed.getVersion())
|
||||
));
|
||||
}
|
||||
if (restartRequired) {
|
||||
ServerConfigurator.restart("An Iris generation epoch update is pending activation.");
|
||||
}
|
||||
@@ -352,6 +362,7 @@ public class StudioSVC implements IrisService {
|
||||
packRoot,
|
||||
GenerationPackFingerprint.CURRENT_VERSION
|
||||
);
|
||||
validatePublishedPack(packRoot);
|
||||
IrisData data = IrisData.openDatapackCompiler(packRoot.toFile());
|
||||
try {
|
||||
IrisDimension dimension = data.getDimensionLoader().load(dimensionKey, false);
|
||||
@@ -378,6 +389,10 @@ public class StudioSVC implements IrisService {
|
||||
fingerprint,
|
||||
aliasPolicy
|
||||
);
|
||||
if (!fingerprint.equals(GenerationPackFingerprint.compute(
|
||||
packRoot, GenerationPackFingerprint.CURRENT_VERSION))) {
|
||||
throw new IOException("Generation pack changed during validation: " + packRoot);
|
||||
}
|
||||
return new GenerationCandidate(fingerprint, dimensionContract, registryContract);
|
||||
} finally {
|
||||
data.close();
|
||||
@@ -628,14 +643,15 @@ public class StudioSVC implements IrisService {
|
||||
return replaceIntoPackDirectory(sender, dimension, folder);
|
||||
}
|
||||
|
||||
public void downloadBuiltIn(VolmitSender sender, String key) {
|
||||
public void downloadBuiltIn(VolmitSender sender, String key, boolean overwrite) {
|
||||
if (!PackDownloader.isBuiltInPack(key)) {
|
||||
sender.sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_BUILT_IN));
|
||||
return;
|
||||
}
|
||||
PackDownloadProgressReporter reporter = new PackDownloadProgressReporter(sender, key);
|
||||
runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, key, reporter, cancellation -> {
|
||||
PackDownloader.PackInstallResult result = downloadBuiltInLocked(key, cancellation, reporter);
|
||||
requireClosedStudioForPackOverwrite(overwrite);
|
||||
PackDownloader.PackInstallResult result = downloadBuiltInLocked(key, overwrite, cancellation, reporter);
|
||||
if (result == null) {
|
||||
reporter.fail(null);
|
||||
return;
|
||||
@@ -645,7 +661,7 @@ public class StudioSVC implements IrisService {
|
||||
}, "Failed to download built-in Iris pack '" + key + "'.");
|
||||
}
|
||||
|
||||
public void downloadUrl(VolmitSender sender, String url) {
|
||||
public void downloadUrl(VolmitSender sender, String url, boolean overwrite) {
|
||||
if (!PackDownloader.isDirectZipUrl(url)) {
|
||||
sender.sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_URL));
|
||||
return;
|
||||
@@ -656,10 +672,11 @@ public class StudioSVC implements IrisService {
|
||||
url
|
||||
);
|
||||
runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, "remote-zip", reporter, cancellation -> {
|
||||
requireClosedStudioForPackOverwrite(overwrite);
|
||||
PackDownloader.PackInstallResult result = PackDownloader.downloadUrl(
|
||||
getWorkspaceFolder(),
|
||||
url,
|
||||
false,
|
||||
overwrite,
|
||||
reporter::detail,
|
||||
cancellation,
|
||||
reporter
|
||||
@@ -675,23 +692,30 @@ public class StudioSVC implements IrisService {
|
||||
|
||||
private PackDownloader.PackInstallResult downloadBuiltInLocked(
|
||||
String expectedKey,
|
||||
boolean overwrite,
|
||||
PackDownloader.DownloadCancellation cancellation,
|
||||
PackDownloadProgressReporter reporter
|
||||
) throws IOException {
|
||||
if (PackDownloader.isBuiltInPackPresent(getWorkspaceFolder(), expectedKey)) {
|
||||
if (!overwrite && PackDownloader.isBuiltInPackPresent(getWorkspaceFolder(), expectedKey)) {
|
||||
return new PackDownloader.PackInstallResult(expectedKey, false, false);
|
||||
}
|
||||
|
||||
return PackDownloader.downloadBuiltIn(
|
||||
getWorkspaceFolder(),
|
||||
expectedKey,
|
||||
false,
|
||||
overwrite,
|
||||
reporter::detail,
|
||||
cancellation,
|
||||
reporter
|
||||
);
|
||||
}
|
||||
|
||||
private void requireClosedStudioForPackOverwrite(boolean overwrite) throws IOException {
|
||||
if (overwrite && activeProject != null) {
|
||||
throw new IOException(IrisLanguage.plain(PackDownloadMessages.CLOSE_STUDIO));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isProjectOpen() {
|
||||
return activeProject != null && activeProject.isOpen();
|
||||
}
|
||||
|
||||
@@ -1577,6 +1577,10 @@ failed_fragment = " fehlgeschlagen {failed}"
|
||||
"commandpack.compat_header" = "§ePack '{pack}': Inhalte in Minecraft {version} nicht verfügbar"
|
||||
"commandpack.compat_none" = "§aPack '{pack}' verwendet keine Inhalte, die in Minecraft {version} fehlen."
|
||||
"commandpack.compat_remedy" = "§7Aktualisiere den Server auf ein neueres Minecraft, um diese Inhalte wiederherzustellen, oder deklariere Ersatzwerte (Dimension blockFallbacks, Block backup)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cWähle eine geladene Iris-Welt. Studio-Welten können keine Generierungsaktualisierungen erhalten."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} für Welt '{world}' vorgemerkt. Ein sauberer Neustart aktiviert das Pack mit Geländeübergängen."
|
||||
"commandpack.update_world_unchanged" = "§aWelt '{world}' verwendet bereits {pack} v{version} mit denselben Generierungseinstellungen. Kein Neustart erforderlich."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bÖFFNET"
|
||||
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} FEHLGESCHLAGEN: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris-Pack '{pack}' installiert§8 | §f{transferred}§7 in §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris-Pack '{pack}' ist bereits installiert."
|
||||
"progress.unchanged" = "§eIris-Pack '{pack}' ist bereits installiert. Verwende overwrite=true, um es zu sichern und zu ersetzen."
|
||||
"progress.failed" = "§cDownload des Iris-Packs fehlgeschlagen.§7 Prüfe die Download-Details oben und versuche es erneut."
|
||||
"progress.failed_detail" = "§cDownload des Iris-Packs fehlgeschlagen.§7 {error}"
|
||||
"progress.cancelled" = "§eDownload des Iris-Packs vor der Veröffentlichung abgebrochen."
|
||||
@@ -1714,14 +1718,17 @@ check_github = "Prüfe, ob die Datei auf GitHub vorhanden ist, und melde das Pro
|
||||
invalid_dimension = "Ungültiger Dimensionsordner unter dimensions/."
|
||||
importing = "{name} ({key}) wird importiert"
|
||||
dimension_key_conflict = "Eine andere Dimension im Pack-Ordner verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!"
|
||||
pack_key_conflict = "Ein anderer Pack verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!"
|
||||
"pack_key_conflict" = "Iris-Pack '{key}' ist bereits installiert. Verwende overwrite=true, um es zu sichern und zu ersetzen."
|
||||
acquired = "{name} erfolgreich abgerufen."
|
||||
already_installed = "Pack {key} ist bereits installiert, Download wird übersprungen."
|
||||
"already_installed" = "Pack {key} ist bereits installiert, Download wird übersprungen. Verwende overwrite=true, um es zu sichern und zu ersetzen."
|
||||
in_progress = "Ein anderer Iris-Pack-Download läuft bereits. Warte, bis er abgeschlossen ist, bevor du es erneut versuchst."
|
||||
validation_failed = "Pack '{pack}' hat die Validierung nicht bestanden; Welt- und Studio-Erstellung werden verweigert. Gründe:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Pack '{pack}' mit {count} Warnungen validiert.", one = "Pack '{pack}' mit {count} Warnung validiert." }
|
||||
validated = "Pack '{pack}' validiert."
|
||||
"close_studio" = "Schließe das aktive Studio-Projekt, bevor du ein installiertes Pack ersetzt."
|
||||
"backup_retained" = "Vorheriges Pack unter {path} gesichert."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Vision"
|
||||
|
||||
@@ -1577,6 +1577,10 @@ failed_fragment = " fallidos {failed}"
|
||||
"commandpack.compat_header" = "§ePack '{pack}': contenido no disponible en Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aEl pack '{pack}' no usa contenido que falte en Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Actualiza el servidor a un Minecraft más reciente para recuperar este contenido, o declara alternativas (blockFallbacks de la dimensión, backup del bloque)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cElige un mundo de Iris cargado. Los mundos de Studio no pueden recibir actualizaciones de generación."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} preparado para el mundo '{world}'. Un reinicio limpio activará el pack con transición de terreno."
|
||||
"commandpack.update_world_unchanged" = "§aEl mundo '{world}' ya usa {pack} v{version} con los mismos ajustes de generación. No hace falta reiniciar."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bABRIENDO"
|
||||
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} FALLÓ: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aEl pack de Iris '{pack}' está instalado§8 | §f{transferred}§7 en §f{elapsed}"
|
||||
"progress.unchanged" = "§eEl pack de Iris '{pack}' ya está instalado."
|
||||
"progress.unchanged" = "§eEl pack de Iris '{pack}' ya está instalado. Usa overwrite=true para crear una copia de seguridad y reemplazarlo."
|
||||
"progress.failed" = "§cLa descarga del pack de Iris ha fallado.§7 Revisa los detalles de la descarga de arriba y vuelve a intentarlo."
|
||||
"progress.failed_detail" = "§cLa descarga del pack de Iris ha fallado.§7 {error}"
|
||||
"progress.cancelled" = "§eLa descarga del pack de Iris se canceló antes de publicarlo."
|
||||
@@ -1714,14 +1718,17 @@ check_github = "¡Comprueba que esté presente en GitHub e informa de ello al pe
|
||||
invalid_dimension = "Carpeta de dimensión no válida en dimensions/."
|
||||
importing = "Importando {name} ({key})"
|
||||
dimension_key_conflict = "Otra dimensión de la carpeta packs ya usa la clave {key}. ¡La importación falló!"
|
||||
pack_key_conflict = "Otro pack usa la clave {key}. ¡La importación falló!"
|
||||
"pack_key_conflict" = "El pack de Iris '{key}' ya está instalado. Usa overwrite=true para crear una copia de seguridad y reemplazarlo."
|
||||
acquired = "{name} se obtuvo correctamente."
|
||||
already_installed = "El pack {key} ya está instalado, se omite la descarga."
|
||||
"already_installed" = "El pack {key} ya está instalado, se omite la descarga. Usa overwrite=true para crear una copia de seguridad y reemplazarlo."
|
||||
in_progress = "Ya hay otra descarga de un pack de Iris en curso. Espera a que termine antes de volver a intentarlo."
|
||||
validation_failed = "El pack '{pack}' no superó la validación; se rechazará la creación de mundos y de Studio. Motivos:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { one = "El pack '{pack}' se validó con {count} advertencia.", other = "El pack '{pack}' se validó con {count} advertencias." }
|
||||
validated = "El pack '{pack}' se validó."
|
||||
"close_studio" = "Cierra el proyecto de Studio activo antes de reemplazar un pack instalado."
|
||||
"backup_retained" = "Pack anterior guardado en {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Vision"
|
||||
|
||||
@@ -1582,6 +1582,10 @@ failed_fragment = " epäonnistui {failed}"
|
||||
"commandpack.compat_header" = "§ePaketti '{pack}': sisältö ei ole käytettävissä Minecraft {version} -versiossa"
|
||||
"commandpack.compat_none" = "§aPaketti '{pack}' ei käytä sisältöä, joka puuttuisi Minecraft {version} -versiosta."
|
||||
"commandpack.compat_remedy" = "§7Päivitä palvelin uudempaan Minecraft-versioon palauttaaksesi tämän sisällön tai määritä varasisältö (ulottuvuuden blockFallbacks, lohkon backup)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cValitse ladattu Iris-maailma. Studio-maailmat eivät voi vastaanottaa generointipäivityksiä."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} valmisteltu maailmalle '{world}'. Hallittu uudelleenkäynnistys ottaa paketin ja maaston häivytyksen käyttöön."
|
||||
"commandpack.update_world_unchanged" = "§aMaailma '{world}' käyttää jo pakettia {pack} v{version} samoilla generointiasetuksilla. Uudelleenkäynnistystä ei tarvita."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bAVATAAN"
|
||||
@@ -1693,7 +1697,7 @@ chunk_failed = "Liha {x},{z} epäonnistui: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris-paketti '{pack}' asennettu§8 | §f{transferred}§7 ajassa §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris-paketti '{pack}' on jo asennettu."
|
||||
"progress.unchanged" = "§eIris-paketti '{pack}' on jo asennettu. Varmuuskopioi ja korvaa se käyttämällä overwrite=true."
|
||||
"progress.failed" = "§cIris-paketin lataus epäonnistui.§7 Tarkista yllä olevat lataustiedot ja yritä uudelleen."
|
||||
"progress.failed_detail" = "§cIris-paketin lataus epäonnistui.§7 {error}"
|
||||
"progress.cancelled" = "§eIris-paketin lataus peruutettiin ennen julkaisua."
|
||||
@@ -1719,14 +1723,17 @@ check_github = "Tarkista, että se on läsnä GitHub Ja raportoikaa tästä henk
|
||||
invalid_dimension = "Virheellinen ulottuvuuskansio alla dimensions/."
|
||||
importing = "Tuo {name} ({key})"
|
||||
dimension_key_conflict = "Toinen ulottuvuus pakkauskansiossa jo käyttää avainta {key}. Tuonti epäonnistui!"
|
||||
pack_key_conflict = "Toinen pakkaus käyttää avainta {key}. Tuonti epäonnistui!"
|
||||
"pack_key_conflict" = "Iris-paketti '{key}' on jo asennettu. Varmuuskopioi ja korvaa se käyttämällä overwrite=true."
|
||||
acquired = "Onnistunut hankinta {name}."
|
||||
already_installed = "Pack {key} on jo asennettu, lataus ohitetaan."
|
||||
"already_installed" = "Pack {key} on jo asennettu, lataus ohitetaan. Varmuuskopioi ja korvaa se käyttämällä overwrite=true."
|
||||
in_progress = "Toinen Iris-paketin lataus on jo käynnissä. Odota sen valmistumista ennen kuin yrität uudelleen."
|
||||
validation_failed = "Pakkaus{pack}' Epäonnistunut validointi; maailma ja Studio luominen hylätään. Perusteet:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Pakkaus{pack}\" validoitu {count} varoitukset.", one = "Pakkaus{pack}\" validoitu {count} Varoitus." }
|
||||
validated = "Pakkaus{pack}' validoitu."
|
||||
"close_studio" = "Sulje aktiivinen Studio-projekti ennen asennetun paketin korvaamista."
|
||||
"backup_retained" = "Edellinen paketti tallennettu polkuun {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Näkö"
|
||||
|
||||
@@ -1577,6 +1577,10 @@ failed_fragment = " échecs {failed}"
|
||||
"commandpack.compat_header" = "§ePack '{pack}' : contenu indisponible sur Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aLe pack '{pack}' n'utilise aucun contenu absent de Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Mettez le serveur à jour vers un Minecraft plus récent pour restaurer ce contenu, ou déclarez des replis (blockFallbacks de la dimension, backup du bloc)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cChoisissez un monde Iris chargé. Les mondes Studio ne peuvent pas recevoir de mises à jour de génération."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} préparé pour le monde '{world}'. Un redémarrage propre activera le pack avec une transition du terrain."
|
||||
"commandpack.update_world_unchanged" = "§aLe monde '{world}' utilise déjà {pack} v{version} avec les mêmes paramètres de génération. Aucun redémarrage nécessaire."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bOUVERTURE"
|
||||
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} ÉCHEC : {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aLe pack Iris '{pack}' est installé§8 | §f{transferred}§7 en §f{elapsed}"
|
||||
"progress.unchanged" = "§eLe pack Iris '{pack}' est déjà installé."
|
||||
"progress.unchanged" = "§eLe pack Iris '{pack}' est déjà installé. Utilisez overwrite=true pour le sauvegarder et le remplacer."
|
||||
"progress.failed" = "§cÉchec du téléchargement du pack Iris.§7 Consultez les détails du téléchargement ci-dessus et réessayez."
|
||||
"progress.failed_detail" = "§cÉchec du téléchargement du pack Iris.§7 {error}"
|
||||
"progress.cancelled" = "§eTéléchargement du pack Iris annulé avant sa publication."
|
||||
@@ -1714,14 +1718,17 @@ check_github = "Vérifiez qu'il est présent sur GitHub et signalez-le à l'équ
|
||||
invalid_dimension = "Dossier de dimension non valide dans dimensions/."
|
||||
importing = "Importation de {name} ({key})"
|
||||
dimension_key_conflict = "Une autre dimension du dossier packs utilise déjà la clé {key}. Échec de l'importation !"
|
||||
pack_key_conflict = "Un autre pack utilise la clé {key}. Échec de l'importation !"
|
||||
"pack_key_conflict" = "Le pack Iris '{key}' est déjà installé. Utilisez overwrite=true pour le sauvegarder et le remplacer."
|
||||
acquired = "{name} obtenu avec succès."
|
||||
already_installed = "Le pack {key} est déjà installé, téléchargement ignoré."
|
||||
"already_installed" = "Le pack {key} est déjà installé, téléchargement ignoré. Utilisez overwrite=true pour le sauvegarder et le remplacer."
|
||||
in_progress = "Un autre téléchargement de pack Iris est déjà en cours. Attendez qu’il se termine avant de réessayer."
|
||||
validation_failed = "Le pack '{pack}' a échoué à la validation ; la création de mondes et de Studio sera refusée. Raisons :"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { one = "Le pack '{pack}' a été validé avec {count} avertissement.", other = "Le pack '{pack}' a été validé avec {count} avertissements." }
|
||||
validated = "Le pack '{pack}' a été validé."
|
||||
"close_studio" = "Fermez le projet Studio actif avant de remplacer un pack installé."
|
||||
"backup_retained" = "Ancien pack sauvegardé dans {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Vision"
|
||||
|
||||
@@ -1582,6 +1582,10 @@ failed_fragment = " נכשל {failed}"
|
||||
"commandpack.compat_header" = "§eחבילה '{pack}': תוכן לא זמין ב-Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aהחבילה '{pack}' אינה משתמשת בתוכן שחסר ב-Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7עדכן את השרת ל-Minecraft חדש יותר כדי לשחזר את התוכן הזה, או הגדר חלופות (blockFallbacks של הממד, backup של הבלוק)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cיש לבחור עולם Iris טעון. עולמות Studio אינם יכולים לקבל עדכוני יצירת עולם."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} הוכן עבור העולם '{world}'. הפעלה מחדש מסודרת תפעיל את החבילה עם שילוב פני השטח."
|
||||
"commandpack.update_world_unchanged" = "§aהעולם '{world}' כבר משתמש בחבילה {pack} v{version} עם אותן הגדרות יצירה. אין צורך בהפעלה מחדש."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bנפתח"
|
||||
@@ -1693,7 +1697,7 @@ chunk_failed = "צ'אנק {x},{z} נכשל: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aחבילת Iris '{pack}' הותקנה§8 | §f{transferred}§7 בתוך §f{elapsed}"
|
||||
"progress.unchanged" = "§eחבילת Iris '{pack}' כבר מותקנת."
|
||||
"progress.unchanged" = "§eחבילת Iris '{pack}' כבר מותקנת. יש להשתמש ב־overwrite=true כדי לגבות ולהחליף אותה."
|
||||
"progress.failed" = "§cהורדת חבילת Iris נכשלה.§7 יש לעיין בפרטי ההורדה שלעיל ולנסות שוב."
|
||||
"progress.failed_detail" = "§cהורדת חבילת Iris נכשלה.§7 {error}"
|
||||
"progress.cancelled" = "§eהורדת חבילת Iris בוטלה לפני הפרסום."
|
||||
@@ -1719,14 +1723,17 @@ check_github = "בדוק כי הוא נוכח GitHub לדווח על זה לצו
|
||||
invalid_dimension = "תיקיית מימד לא חוקית תחת dimensions/."
|
||||
importing = "יבוא {name} ({key})"
|
||||
dimension_key_conflict = "מימד נוסף בתיקיה של הלהקה כבר משתמש המפתח {key}. ייבוא נכשל!"
|
||||
pack_key_conflict = "חבילה נוספת משתמשת במפתח {key}. ייבוא נכשל!"
|
||||
"pack_key_conflict" = "חבילת Iris '{key}' כבר מותקנת. יש להשתמש ב־overwrite=true כדי לגבות ולהחליף אותה."
|
||||
acquired = "נרכשה בהצלחה {name}."
|
||||
already_installed = "החבילה {key} כבר מותקנת, ההורדה מדולגת."
|
||||
"already_installed" = "החבילה {key} כבר מותקנת, ההורדה מדולגת. יש להשתמש ב־overwrite=true כדי לגבות ולהחליף אותה."
|
||||
in_progress = "הורדה אחרת של חבילת Iris כבר מתבצעת. יש להמתין לסיומה לפני ניסיון נוסף."
|
||||
validation_failed = "Pack »{pack}\"התאימות הכושל; יצירת העולם והסטודיו לא תסרב. סיבות:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Pack »{pack}\"תאמת עם {count} אזהרות", one = "Pack »{pack}\"תאמת עם {count} אזהרה." }
|
||||
validated = "Pack »{pack}\"מאומת."
|
||||
"close_studio" = "יש לסגור את פרויקט Studio הפעיל לפני החלפת חבילה מותקנת."
|
||||
"backup_retained" = "החבילה הקודמת נשמרה בנתיב {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris חזון"
|
||||
|
||||
@@ -1577,6 +1577,10 @@ failed_fragment = " non riusciti {failed}"
|
||||
"commandpack.compat_header" = "§ePack '{pack}': contenuti non disponibili su Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aIl pack '{pack}' non usa contenuti assenti da Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Aggiorna il server a un Minecraft più recente per ripristinare questi contenuti, oppure dichiara delle alternative (blockFallbacks della dimensione, backup del blocco)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cScegli un mondo Iris caricato. I mondi Studio non possono ricevere aggiornamenti della generazione."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} preparato per il mondo '{world}'. Un riavvio regolare attiverà il pacchetto con raccordo del terreno."
|
||||
"commandpack.update_world_unchanged" = "§aIl mondo '{world}' usa già {pack} v{version} con le stesse impostazioni di generazione. Non serve riavviare."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bAPERTURA"
|
||||
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} NON RIUSCITO: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aPack Iris '{pack}' installato§8 | §f{transferred}§7 in §f{elapsed}"
|
||||
"progress.unchanged" = "§eIl pack Iris '{pack}' è già installato."
|
||||
"progress.unchanged" = "§eIl pack Iris '{pack}' è già installato. Usa overwrite=true per crearne una copia di sicurezza e sostituirlo."
|
||||
"progress.failed" = "§cDownload del pack Iris non riuscito.§7 Controlla i dettagli del download qui sopra e riprova."
|
||||
"progress.failed_detail" = "§cDownload del pack Iris non riuscito.§7 {error}"
|
||||
"progress.cancelled" = "§eDownload del pack Iris annullato prima della pubblicazione."
|
||||
@@ -1714,14 +1718,17 @@ check_github = "Verifica che sia presente su GitHub e segnala il problema allo s
|
||||
invalid_dimension = "Cartella di dimensione non valida in dimensions/."
|
||||
importing = "Importazione {name} ({key})"
|
||||
dimension_key_conflict = "Un'altra dimensione nella cartella dei Pack usa già la chiave {key}. Importazione non riuscita!"
|
||||
pack_key_conflict = "Un altro Pack usa già la chiave {key}. Importazione non riuscita!"
|
||||
"pack_key_conflict" = "Il pack Iris '{key}' è già installato. Usa overwrite=true per crearne una copia di sicurezza e sostituirlo."
|
||||
acquired = "{name} acquisito correttamente."
|
||||
already_installed = "Il pack {key} è già installato, download saltato."
|
||||
"already_installed" = "Il pack {key} è già installato, download saltato. Usa overwrite=true per crearne una copia di sicurezza e sostituirlo."
|
||||
in_progress = "È già in corso il download di un altro pack Iris. Attendi che termini prima di riprovare."
|
||||
validation_failed = "Il Pack '{pack}' non ha superato la convalida; la creazione di mondi e Studio verrà rifiutata. Motivi:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Pack '{pack}' convalidato con {count} avvisi.", one = "Pack '{pack}' convalidato con {count} avviso." }
|
||||
validated = "Pack '{pack}' convalidato."
|
||||
"close_studio" = "Chiudi il progetto Studio attivo prima di sostituire un pacchetto installato."
|
||||
"backup_retained" = "Pacchetto precedente salvato in {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Vision"
|
||||
|
||||
@@ -1580,6 +1580,10 @@ failed_fragment = " 失敗 {failed}"
|
||||
"commandpack.compat_header" = "§eパック '{pack}': Minecraft {version} では利用できない内容があります"
|
||||
"commandpack.compat_none" = "§aパック '{pack}' に Minecraft {version} で欠けている内容はありません。"
|
||||
"commandpack.compat_remedy" = "§7この内容を復元するにはサーバーを新しい Minecraft に更新するか、代替を宣言してください (ディメンションの blockFallbacks、ブロックの backup)。"
|
||||
"commandpack.update_world_requires_persistent_world" = "§c読み込み済みの Iris ワールドを選択してください。Studio ワールドには生成の更新を適用できません。"
|
||||
"commandpack.update_world_staged" = "§aワールド '{world}' 用に {pack} v{version} を準備しました。正常な再起動後、地形をなじませながら新しいパックが有効になります。"
|
||||
"commandpack.update_world_unchanged" = "§aワールド '{world}' は同じ生成設定の {pack} v{version} を使用しています。再起動は不要です。"
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bオープン中"
|
||||
@@ -1691,7 +1695,7 @@ chunk_failed = "チャンク {x},{z} 失敗: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris パック '{pack}' をインストールしました§8 | §f{transferred}§7、所要時間 §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris パック '{pack}' はすでにインストールされています。"
|
||||
"progress.unchanged" = "§eIris パック '{pack}' はすでにインストールされています。 バックアップして置き換えるには overwrite=true を指定してください。"
|
||||
"progress.failed" = "§cIris パックのダウンロードに失敗しました。§7 上記のダウンロード詳細を確認して、もう一度お試しください。"
|
||||
"progress.failed_detail" = "§cIris パックのダウンロードに失敗しました。§7 {error}"
|
||||
"progress.cancelled" = "§eIris パックのダウンロードは公開前にキャンセルされました。"
|
||||
@@ -1717,14 +1721,17 @@ check_github = "GitHub に存在することを確認し、スタッフへ報告
|
||||
invalid_dimension = "dimensions/ 内のディメンションフォルダーが無効です。"
|
||||
importing = "{name}({key})をインポートしています"
|
||||
dimension_key_conflict = "packs フォルダー内の別のディメンションがキー {key} をすでに使用しています。インポートに失敗しました!"
|
||||
pack_key_conflict = "別のパックがキー {key} を使用しています。インポートに失敗しました!"
|
||||
"pack_key_conflict" = "Iris パック '{key}' はすでにインストールされています。 バックアップして置き換えるには overwrite=true を指定してください。"
|
||||
acquired = "{name} を取得しました。"
|
||||
already_installed = "パック {key} は既にインストールされているため、ダウンロードをスキップします。"
|
||||
"already_installed" = "パック {key} は既にインストールされているため、ダウンロードをスキップします。 バックアップして置き換えるには overwrite=true を指定してください。"
|
||||
in_progress = "別の Iris パックのダウンロードがすでに進行中です。完了してからもう一度お試しください。"
|
||||
validation_failed = "パック '{pack}' は検証に失敗しました。ワールドと Studio の作成を拒否します。理由:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "パック '{pack}' の検証が {count} 件の警告付きで完了しました。", one = "パック '{pack}' の検証が {count} 件の警告付きで完了しました。" }
|
||||
validated = "パック '{pack}' の検証が完了しました。"
|
||||
"close_studio" = "インストール済みのパックを置き換える前に、開いている Studio プロジェクトを閉じてください。"
|
||||
"backup_retained" = "以前のパックを {path} に保存しました。"
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris ビジョン"
|
||||
|
||||
@@ -1580,6 +1580,10 @@ failed_fragment = " 실패한 {failed}"
|
||||
"commandpack.compat_header" = "§e팩 '{pack}': Minecraft {version} 에서 사용할 수 없는 콘텐츠"
|
||||
"commandpack.compat_none" = "§a팩 '{pack}' 은 Minecraft {version} 에 없는 콘텐츠를 사용하지 않습니다."
|
||||
"commandpack.compat_remedy" = "§7이 콘텐츠를 복원하려면 서버를 최신 Minecraft 로 업데이트하거나 대체를 선언하세요 (차원의 blockFallbacks, 블록의 backup)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§c로드된 Iris 월드를 선택하세요. Studio 월드는 생성 업데이트를 받을 수 없습니다."
|
||||
"commandpack.update_world_staged" = "§a월드 '{world}'에 사용할 {pack} v{version}을 준비했습니다. 정상적으로 재시작하면 지형 혼합과 함께 팩이 활성화됩니다."
|
||||
"commandpack.update_world_unchanged" = "§a월드 '{world}'는 이미 같은 생성 설정으로 {pack} v{version}을 사용 중입니다. 재시작할 필요가 없습니다."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §b여는 중"
|
||||
@@ -1691,7 +1695,7 @@ chunk_failed = "주 메뉴 {x},{z} 실패: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris 팩 '{pack}' 설치 완료§8 | §f{transferred}§7, 소요 시간 §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris 팩 '{pack}'은(는) 이미 설치되어 있습니다."
|
||||
"progress.unchanged" = "§eIris 팩 '{pack}'은(는) 이미 설치되어 있습니다. 백업하고 교체하려면 overwrite=true를 사용하세요."
|
||||
"progress.failed" = "§cIris 팩 다운로드에 실패했습니다.§7 위의 다운로드 세부 정보를 확인한 후 다시 시도하세요."
|
||||
"progress.failed_detail" = "§cIris 팩 다운로드에 실패했습니다.§7 {error}"
|
||||
"progress.cancelled" = "§eIris 팩 다운로드가 게시 전에 취소되었습니다."
|
||||
@@ -1717,14 +1721,17 @@ check_github = "GitHub에 있는지 확인하고 직원에게 보고하세요!"
|
||||
invalid_dimension = "잘못된 차원 폴더 dimensions/."
|
||||
importing = "관련 상품 {name} ({key})"
|
||||
dimension_key_conflict = "packs 폴더의 다른 차원이 이미 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!"
|
||||
pack_key_conflict = "다른 팩이 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!"
|
||||
"pack_key_conflict" = "Iris 팩 '{key}'은(는) 이미 설치되어 있습니다. 백업하고 교체하려면 overwrite=true를 사용하세요."
|
||||
acquired = "성공적으로 취득 {name}."
|
||||
already_installed = "팩 {key}이(가) 이미 설치되어 있어 다운로드를 건너뜁니다."
|
||||
"already_installed" = "팩 {key}이(가) 이미 설치되어 있어 다운로드를 건너뜁니다. 백업하고 교체하려면 overwrite=true를 사용하세요."
|
||||
in_progress = "다른 Iris 팩 다운로드가 이미 진행 중입니다. 완료될 때까지 기다린 후 다시 시도하세요."
|
||||
validation_failed = "팩 '{pack}' 유효성 검사; 세계 및 스튜디오 생성은 거부됩니다. 이유:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "팩 '{pack}' 유효성 {count} 경고.", one = "팩 '{pack}' 유효성 {count} 경고." }
|
||||
validated = "팩 '{pack}' 유효성."
|
||||
"close_studio" = "설치된 팩을 교체하기 전에 활성 Studio 프로젝트를 닫으세요."
|
||||
"backup_retained" = "이전 팩을 {path}에 저장했습니다."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Vision"
|
||||
|
||||
@@ -1582,6 +1582,10 @@ failed_fragment = " nepavyko {failed}"
|
||||
"commandpack.compat_header" = "§ePaketas '{pack}': turinys nepasiekiamas Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aPaketas '{pack}' nenaudoja turinio, kurio trūksta Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Atnaujinkite serverį į naujesnį Minecraft, kad atkurtumėte šį turinį, arba paskelbkite atsargas (dimensijos blockFallbacks, bloko backup)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cPasirinkite įkeltą Iris pasaulį. Studio pasauliams negalima taikyti generavimo atnaujinimų."
|
||||
"commandpack.update_world_staged" = "§aPasauliui '{world}' paruoštas {pack} v{version}. Tvarkingai paleidus iš naujo, paketas bus aktyvuotas su reljefo perėjimais."
|
||||
"commandpack.update_world_unchanged" = "§aPasaulis '{world}' jau naudoja {pack} v{version} su tais pačiais generavimo nustatymais. Paleisti iš naujo nereikia."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bATIDAROMA"
|
||||
@@ -1693,7 +1697,7 @@ chunk_failed = "Šriftas {x},{z} nepavyko: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§a„Iris“ paketas '{pack}' įdiegtas§8 | §f{transferred}§7 per §f{elapsed}"
|
||||
"progress.unchanged" = "§e„Iris“ paketas '{pack}' jau įdiegtas."
|
||||
"progress.unchanged" = "§e„Iris“ paketas '{pack}' jau įdiegtas. Naudokite overwrite=true, kad sukurtumėte atsarginę kopiją ir jį pakeistumėte."
|
||||
"progress.failed" = "§cNepavyko atsisiųsti „Iris“ paketo.§7 Peržiūrėkite aukščiau pateiktą atsisiuntimo informaciją ir bandykite dar kartą."
|
||||
"progress.failed_detail" = "§cNepavyko atsisiųsti „Iris“ paketo.§7 {error}"
|
||||
"progress.cancelled" = "§e„Iris“ paketo atsisiuntimas atšauktas prieš publikavimą."
|
||||
@@ -1719,14 +1723,17 @@ check_github = "Patikrinkite, ar jis yra GitHub ir pranešti apie tai darbuotoja
|
||||
invalid_dimension = "Netinkamas dimensijos aplankas pagal dimensions/."
|
||||
importing = "Importuojama {name} ({key})"
|
||||
dimension_key_conflict = "Kitas matmuo paketų aplanke jau naudoja raktą {key}. Importuoti nepavyko!"
|
||||
pack_key_conflict = "Kita pakuotė naudoja raktą {key}. Importuoti nepavyko!"
|
||||
"pack_key_conflict" = "„Iris“ paketas '{key}' jau įdiegtas. Naudokite overwrite=true, kad sukurtumėte atsarginę kopiją ir jį pakeistumėte."
|
||||
acquired = "Sėkmingai įgyta {name}."
|
||||
already_installed = "Paketas {key} jau įdiegtas, atsisiuntimas praleidžiamas."
|
||||
"already_installed" = "Paketas {key} jau įdiegtas, atsisiuntimas praleidžiamas. Naudokite overwrite=true, kad sukurtumėte atsarginę kopiją ir jį pakeistumėte."
|
||||
in_progress = "Jau vyksta kitas „Iris“ paketo atsisiuntimas. Palaukite, kol jis bus baigtas, ir bandykite dar kartą."
|
||||
validation_failed = "Pakuotė \"{pack}\"nepavyko patvirtinimas; pasaulio ir Studio kūrimas bus atsisakyta. Motyvai:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Pakuotė \"{pack}\"patvirtinta {count} įspėjimai.", one = "Pakuotė \"{pack}\"patvirtinta {count} įspėjimas." }
|
||||
validated = "Pakuotė \"{pack}\"patvirtintas."
|
||||
"close_studio" = "Prieš pakeisdami įdiegtą paketą, uždarykite aktyvų Studio projektą."
|
||||
"backup_retained" = "Ankstesnis paketas išsaugotas čia: {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Regėjimas"
|
||||
|
||||
@@ -1577,6 +1577,10 @@ failed_fragment = " mislukt {failed}"
|
||||
"commandpack.compat_header" = "§ePack '{pack}': inhoud niet beschikbaar op Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aPack '{pack}' gebruikt geen inhoud die ontbreekt in Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Werk de server bij naar een nieuwere Minecraft om deze inhoud te herstellen, of declareer terugvallen (blockFallbacks van de dimensie, backup van het blok)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cKies een geladen Iris-wereld. Studio-werelden kunnen geen generatie-updates ontvangen."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} klaargezet voor wereld '{world}'. Een nette herstart activeert het pakket met terreinovergangen."
|
||||
"commandpack.update_world_unchanged" = "§aWereld '{world}' gebruikt al {pack} v{version} met dezelfde generatie-instellingen. Herstarten is niet nodig."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bOPENEN"
|
||||
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk. {x},{z} mislukt: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris-pack '{pack}' geïnstalleerd§8 | §f{transferred}§7 in §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris-pack '{pack}' is al geïnstalleerd."
|
||||
"progress.unchanged" = "§eIris-pack '{pack}' is al geïnstalleerd. Gebruik overwrite=true om een reservekopie te maken en het te vervangen."
|
||||
"progress.failed" = "§cDownload van het Iris-pack mislukt.§7 Bekijk de downloadgegevens hierboven en probeer het opnieuw."
|
||||
"progress.failed_detail" = "§cDownload van het Iris-pack mislukt.§7 {error}"
|
||||
"progress.cancelled" = "§eDownload van het Iris-pack geannuleerd vóór publicatie."
|
||||
@@ -1714,14 +1718,17 @@ check_github = "Controleer of het aanwezig is op GitHub En meld dit aan het pers
|
||||
invalid_dimension = "Ongeldige dimensiemap onder dimensions/."
|
||||
importing = "Importeren {name} ({key})"
|
||||
dimension_key_conflict = "Een andere dimensie in de packs-map gebruikt al de sleutel {key}. Importeren mislukt!"
|
||||
pack_key_conflict = "Een ander pakje gebruikt de sleutel {key}. Importeren mislukt!"
|
||||
"pack_key_conflict" = "Iris-pack '{key}' is al geïnstalleerd. Gebruik overwrite=true om een reservekopie te maken en het te vervangen."
|
||||
acquired = "Succesvol verworven {name}."
|
||||
already_installed = "Pack {key} is al geïnstalleerd, download wordt overgeslagen."
|
||||
"already_installed" = "Pack {key} is al geïnstalleerd, download wordt overgeslagen. Gebruik overwrite=true om een reservekopie te maken en het te vervangen."
|
||||
in_progress = "Er wordt al een ander Iris-pack gedownload. Wacht tot dit is voltooid voordat je het opnieuw probeert."
|
||||
validation_failed = "Verpakking{pack}' mislukte validatie; wereld en Studio creatie zal worden geweigerd. Motivering:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Verpakking{pack}' gevalideerd met {count} waarschuwingen.", one = "Verpakking{pack}' gevalideerd met {count} waarschuwing." }
|
||||
validated = "Verpakking{pack}' gevalideerd."
|
||||
"close_studio" = "Sluit het actieve Studio-project voordat je een geïnstalleerd pakket vervangt."
|
||||
"backup_retained" = "Vorig pakket opgeslagen in {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Gezicht"
|
||||
|
||||
@@ -1582,6 +1582,10 @@ failed_fragment = " nieudany {failed}"
|
||||
"commandpack.compat_header" = "§ePaczka '{pack}': zawartość niedostępna w Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aPaczka '{pack}' nie używa zawartości brakującej w Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Zaktualizuj serwer do nowszego Minecraft, aby przywrócić tę zawartość, albo zadeklaruj zamienniki (blockFallbacks wymiaru, backup bloku)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cWybierz wczytany świat Iris. Światy Studio nie mogą otrzymywać aktualizacji generowania."
|
||||
"commandpack.update_world_staged" = "§aPrzygotowano {pack} v{version} dla świata '{world}'. Prawidłowy restart aktywuje pakiet z przejściami terenu."
|
||||
"commandpack.update_world_unchanged" = "§aŚwiat '{world}' już używa {pack} v{version} z tymi samymi ustawieniami generowania. Restart nie jest potrzebny."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bOTWIERANIE"
|
||||
@@ -1693,7 +1697,7 @@ chunk_failed = "Chunk Przewodniczący {x},{z} niepowodzenie: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aPakiet Iris '{pack}' zainstalowany§8 | §f{transferred}§7 w §f{elapsed}"
|
||||
"progress.unchanged" = "§ePakiet Iris '{pack}' jest już zainstalowany."
|
||||
"progress.unchanged" = "§ePakiet Iris '{pack}' jest już zainstalowany. Użyj overwrite=true, aby utworzyć kopię zapasową i go zastąpić."
|
||||
"progress.failed" = "§cPobieranie pakietu Iris nie powiodło się.§7 Sprawdź powyższe szczegóły pobierania i spróbuj ponownie."
|
||||
"progress.failed_detail" = "§cPobieranie pakietu Iris nie powiodło się.§7 {error}"
|
||||
"progress.cancelled" = "§ePobieranie pakietu Iris anulowano przed publikacją."
|
||||
@@ -1719,14 +1723,17 @@ check_github = "Sprawdź, czy jest obecny na GitHub i zgłosić do do personelu!
|
||||
invalid_dimension = "Nieprawidłowy folder wymiarów pod dimensions/."
|
||||
importing = "Przywóz {name} ({key})"
|
||||
dimension_key_conflict = "Inny wymiar w folderze pakietów już używa klucza {key}. Import nie powiódł się!"
|
||||
pack_key_conflict = "Inny pakiet używa klucza {key}. Import nie powiódł się!"
|
||||
"pack_key_conflict" = "Pakiet Iris '{key}' jest już zainstalowany. Użyj overwrite=true, aby utworzyć kopię zapasową i go zastąpić."
|
||||
acquired = "Udane nabycie {name}."
|
||||
already_installed = "Pakiet {key} jest już zainstalowany, pomijanie pobierania."
|
||||
"already_installed" = "Pakiet {key} jest już zainstalowany, pomijanie pobierania. Użyj overwrite=true, aby utworzyć kopię zapasową i go zastąpić."
|
||||
in_progress = "Trwa już pobieranie innego pakietu Iris. Poczekaj na jego zakończenie, zanim spróbujesz ponownie."
|
||||
validation_failed = "Paczka \"{pack}\"nieudaną walidację; świat i tworzenie studia zostaną odrzucone. Uzasadnienie:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Paczka \"{pack}\"zatwierdzone {count} Ostrzeżenia.", one = "Paczka \"{pack}\"zatwierdzone {count} Ostrzeżenie." }
|
||||
validated = "Paczka \"{pack}\"zatwierdzone."
|
||||
"close_studio" = "Zamknij aktywny projekt Studio przed zastąpieniem zainstalowanego pakietu."
|
||||
"backup_retained" = "Poprzedni pakiet zapisano w {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Wizja"
|
||||
|
||||
@@ -1577,6 +1577,10 @@ failed_fragment = " falhou {failed}"
|
||||
"commandpack.compat_header" = "§ePack '{pack}': conteúdo indisponível no Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aO pack '{pack}' não usa conteúdo em falta no Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Atualize o servidor para um Minecraft mais recente para restaurar este conteúdo, ou declare alternativas (blockFallbacks da dimensão, backup do bloco)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cEscolhe um mundo Iris carregado. Os mundos Studio não podem receber atualizações de geração."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} preparado para o mundo '{world}'. Um reinício normal ativará o pacote com transição do terreno."
|
||||
"commandpack.update_world_unchanged" = "§aO mundo '{world}' já utiliza {pack} v{version} com as mesmas definições de geração. Não é necessário reiniciar."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bA ABRIR"
|
||||
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk. {x},{z} falhou: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aPack Iris '{pack}' instalado§8 | §f{transferred}§7 em §f{elapsed}"
|
||||
"progress.unchanged" = "§eO pack Iris '{pack}' já está instalado."
|
||||
"progress.unchanged" = "§eO pack Iris '{pack}' já está instalado. Usa overwrite=true para criar uma cópia de segurança e substituí-lo."
|
||||
"progress.failed" = "§cA transferência do pack Iris falhou.§7 Reveja os detalhes da transferência acima e tente novamente."
|
||||
"progress.failed_detail" = "§cA transferência do pack Iris falhou.§7 {error}"
|
||||
"progress.cancelled" = "§eA transferência do pack Iris foi cancelada antes da publicação."
|
||||
@@ -1714,14 +1718,17 @@ check_github = "Verifique se está presente em GitHub E reporta isto ao pessoal!
|
||||
invalid_dimension = "Pasta de dimensões inválida sob dimensions/."
|
||||
importing = "Importação {name} ({key})"
|
||||
dimension_key_conflict = "Outra dimensão na pasta pacotes já está usando a chave {key}. A importação falhou!"
|
||||
pack_key_conflict = "Outro pacote está usando a chave {key}. A importação falhou!"
|
||||
"pack_key_conflict" = "O pack Iris '{key}' já está instalado. Usa overwrite=true para criar uma cópia de segurança e substituí-lo."
|
||||
acquired = "Adquirido com sucesso {name}."
|
||||
already_installed = "O pack {key} já está instalado, download ignorado."
|
||||
"already_installed" = "O pack {key} já está instalado, download ignorado. Usa overwrite=true para criar uma cópia de segurança e substituí-lo."
|
||||
in_progress = "Já está em curso a transferência de outro pack Iris. Aguarde que termine antes de tentar novamente."
|
||||
validation_failed = "Embalar '{pack}' validação falhada; a criação de mundo e estúdio será recusada. Motivos:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Embalar '{pack}' validado com {count} avisos.", one = "Embalar '{pack}' validado com {count} Aviso." }
|
||||
validated = "Embalar '{pack}' validado."
|
||||
"close_studio" = "Fecha o projeto Studio ativo antes de substituir um pacote instalado."
|
||||
"backup_retained" = "Pacote anterior guardado em {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Visão"
|
||||
|
||||
@@ -1582,6 +1582,10 @@ failed_fragment = " неудачный {failed}"
|
||||
"commandpack.compat_header" = "§eПак '{pack}': содержимое недоступно в Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aПак '{pack}' не использует содержимое, которого нет в Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Обновите сервер до более новой версии Minecraft, чтобы вернуть это содержимое, или объявите замены (blockFallbacks измерения, backup блока)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cВыберите загруженный мир Iris. Миры Studio не могут получать обновления генерации."
|
||||
"commandpack.update_world_staged" = "§a{pack} v{version} подготовлен для мира '{world}'. Штатный перезапуск активирует пакет с плавным переходом рельефа."
|
||||
"commandpack.update_world_unchanged" = "§aМир '{world}' уже использует {pack} v{version} с теми же настройками генерации. Перезапуск не требуется."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bОТКРЫТИЕ"
|
||||
@@ -1693,7 +1697,7 @@ chunk_failed = "Кусок {x},{z} не удалось: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aПак Iris «{pack}» установлен§8 | §f{transferred}§7 за §f{elapsed}"
|
||||
"progress.unchanged" = "§eПак Iris «{pack}» уже установлен."
|
||||
"progress.unchanged" = "§eПак Iris «{pack}» уже установлен. Используйте overwrite=true, чтобы создать резервную копию и заменить его."
|
||||
"progress.failed" = "§cНе удалось загрузить пак Iris.§7 Проверьте сведения о загрузке выше и повторите попытку."
|
||||
"progress.failed_detail" = "§cНе удалось загрузить пак Iris.§7 {error}"
|
||||
"progress.cancelled" = "§eЗагрузка пака Iris отменена до публикации."
|
||||
@@ -1719,14 +1723,17 @@ check_github = "Убедитесь, что он присутствует на Gi
|
||||
invalid_dimension = "Недействительная размерная папка под dimensions/."
|
||||
importing = "импортировать {name} ({key})"
|
||||
dimension_key_conflict = "Еще одно измерение в папке пакетов уже использует ключ {key}. Импорт провалился!"
|
||||
pack_key_conflict = "Другой пакет использует ключ. {key}. Импорт провалился!"
|
||||
"pack_key_conflict" = "Пак Iris «{key}» уже установлен. Используйте overwrite=true, чтобы создать резервную копию и заменить его."
|
||||
acquired = "Успешно приобретенный {name}."
|
||||
already_installed = "Пак {key} уже установлен, загрузка пропущена."
|
||||
"already_installed" = "Пак {key} уже установлен, загрузка пропущена. Используйте overwrite=true, чтобы создать резервную копию и заменить его."
|
||||
in_progress = "Уже выполняется загрузка другого пака Iris. Дождитесь её завершения, прежде чем повторить попытку."
|
||||
validation_failed = "Пакуй.{pack}Неудачная проверка; мир и создание студии будут отклонены. Причины:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Пакуй.{pack}подтвержденный {count} предупреждения.", one = "Пакуй.{pack}подтвержденный {count} предупреждение." }
|
||||
validated = "Пакуй.{pack}Подтверждено."
|
||||
"close_studio" = "Закройте активный проект Studio перед заменой установленного пакета."
|
||||
"backup_retained" = "Предыдущий пакет сохранён в {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Видение"
|
||||
|
||||
@@ -1582,6 +1582,10 @@ failed_fragment = " Başarısız oldu {failed}"
|
||||
"commandpack.compat_header" = "§ePaket '{pack}': içerik Minecraft {version} sürümünde yok"
|
||||
"commandpack.compat_none" = "§aPaket '{pack}' Minecraft {version} sürümünde eksik olan içerik kullanmıyor."
|
||||
"commandpack.compat_remedy" = "§7Bu içeriği geri kazanmak için sunucuyu daha yeni bir Minecraft sürümüne güncelleyin veya yedekler tanımlayın (boyutun blockFallbacks, bloğun backup)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cYüklü bir Iris dünyası seçin. Studio dünyaları üretim güncellemeleri alamaz."
|
||||
"commandpack.update_world_staged" = "§a'{world}' dünyası için {pack} v{version} hazırlandı. Düzgün bir yeniden başlatma, paketi arazi geçişleriyle etkinleştirir."
|
||||
"commandpack.update_world_unchanged" = "§a'{world}' dünyası aynı üretim ayarlarıyla zaten {pack} v{version} kullanıyor. Yeniden başlatma gerekmiyor."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bAÇILIYOR"
|
||||
@@ -1693,7 +1697,7 @@ chunk_failed = "Chunk. {x},{z} başarısız: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris paketi '{pack}' kuruldu§8 | §f{transferred}§7, süre: §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris paketi '{pack}' zaten kurulu."
|
||||
"progress.unchanged" = "§eIris paketi '{pack}' zaten kurulu. Yedeklemek ve değiştirmek için overwrite=true kullanın."
|
||||
"progress.failed" = "§cIris paketi indirilemedi.§7 Yukarıdaki indirme ayrıntılarını inceleyip yeniden deneyin."
|
||||
"progress.failed_detail" = "§cIris paketi indirilemedi.§7 {error}"
|
||||
"progress.cancelled" = "§eIris paketi indirme işlemi yayınlanmadan önce iptal edildi."
|
||||
@@ -1719,14 +1723,17 @@ check_github = "GitHub'de mevcut olduğunu kontrol edin ve bunu personele rapor
|
||||
invalid_dimension = "geçersiz boyut klasörü altında dimensions/."
|
||||
importing = "İthalat {name} ({key})"
|
||||
dimension_key_conflict = "Paketlerdeki başka bir boyut zaten anahtarı kullanıyor {key}. İthalat başarısız oldu!"
|
||||
pack_key_conflict = "Başka bir paket anahtarı kullanıyor {key}. İthalat başarısız oldu!"
|
||||
"pack_key_conflict" = "Iris paketi '{key}' zaten kurulu. Yedeklemek ve değiştirmek için overwrite=true kullanın."
|
||||
acquired = "Başarılı bir şekilde satın alındı {name}."
|
||||
already_installed = "{key} paketi zaten kurulu, indirme atlanıyor."
|
||||
"already_installed" = "{key} paketi zaten kurulu, indirme atlanıyor. Yedeklemek ve değiştirmek için overwrite=true kullanın."
|
||||
in_progress = "Başka bir Iris paketi indirme işlemi zaten devam ediyor. Yeniden denemeden önce tamamlanmasını bekleyin."
|
||||
validation_failed = "Pack \"{pack}“Başarısız doğrulama; dünya ve Stüdyo yaratımı reddedilecektir. Sebepler:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Pack \"{pack}\" ile doğrulandı\" {count} Uyarılar.", one = "Pack \"{pack}\" ile doğrulandı\" {count} Uyarı." }
|
||||
validated = "Pack \"{pack}\"Doğrulandı."
|
||||
"close_studio" = "Yüklü bir paketi değiştirmeden önce etkin Studio projesini kapatın."
|
||||
"backup_retained" = "Önceki paket {path} konumuna kaydedildi."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Vizyon"
|
||||
|
||||
@@ -1580,6 +1580,10 @@ failed_fragment = " bị lỗi {failed}"
|
||||
"commandpack.compat_header" = "§eGói '{pack}': nội dung không có trên Minecraft {version}"
|
||||
"commandpack.compat_none" = "§aGói '{pack}' không dùng nội dung nào thiếu trên Minecraft {version}."
|
||||
"commandpack.compat_remedy" = "§7Cập nhật máy chủ lên Minecraft mới hơn để khôi phục nội dung này, hoặc khai báo phương án dự phòng (blockFallbacks của chiều, backup của khối)."
|
||||
"commandpack.update_world_requires_persistent_world" = "§cChọn một thế giới Iris đã được tải. Thế giới Studio không thể nhận bản cập nhật tạo địa hình."
|
||||
"commandpack.update_world_staged" = "§aĐã chuẩn bị {pack} v{version} cho thế giới '{world}'. Khởi động lại đúng cách sẽ kích hoạt gói với chuyển tiếp địa hình."
|
||||
"commandpack.update_world_unchanged" = "§aThế giới '{world}' đã dùng {pack} v{version} với cùng thiết lập tạo địa hình. Không cần khởi động lại."
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6Studio §bĐANG MỞ"
|
||||
@@ -1691,7 +1695,7 @@ chunk_failed = "Chunk. {x},{z} thất bại: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aĐã cài đặt gói Iris '{pack}'§8 | §f{transferred}§7 trong §f{elapsed}"
|
||||
"progress.unchanged" = "§eGói Iris '{pack}' đã được cài đặt."
|
||||
"progress.unchanged" = "§eGói Iris '{pack}' đã được cài đặt. Dùng overwrite=true để sao lưu và thay thế gói."
|
||||
"progress.failed" = "§cKhông thể tải gói Iris.§7 Hãy xem lại thông tin tải xuống ở trên rồi thử lại."
|
||||
"progress.failed_detail" = "§cKhông thể tải gói Iris.§7 {error}"
|
||||
"progress.cancelled" = "§eĐã hủy tải gói Iris trước khi xuất bản."
|
||||
@@ -1717,14 +1721,17 @@ check_github = "Kiểm tra xem nó có ở trên không GitHub và báo cáo v
|
||||
invalid_dimension = "Thư mục chiều không hợp lệ nằm dưới dimensions/."
|
||||
importing = "Nhập {name} ({key})"
|
||||
dimension_key_conflict = "Một chiều không gian khác trong thư mục gói là đã sử dụng phím {key}. Nhập thất bại!"
|
||||
pack_key_conflict = "Name {key}. Nhập thất bại!"
|
||||
"pack_key_conflict" = "Gói Iris '{key}' đã được cài đặt. Dùng overwrite=true để sao lưu và thay thế gói."
|
||||
acquired = "Được thành công {name}."
|
||||
already_installed = "Gói {key} đã được cài đặt, bỏ qua tải xuống."
|
||||
"already_installed" = "Gói {key} đã được cài đặt, bỏ qua tải xuống. Dùng overwrite=true để sao lưu và thay thế gói."
|
||||
in_progress = "Một gói Iris khác đang được tải xuống. Hãy chờ quá trình này hoàn tất trước khi thử lại."
|
||||
validation_failed = "Gói '{pack}'Đã thất bại trong việc xác nhận; thế giới và phòng thu sẽ bị từ chối. Lý do:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "Gói '{pack}Xác nhận với {count} Cảnh báo.", one = "Gói '{pack}Xác nhận với {count} Cảnh báo." }
|
||||
validated = "Gói '{pack}Xác nhận."
|
||||
"close_studio" = "Đóng dự án Studio đang hoạt động trước khi thay thế gói đã cài đặt."
|
||||
"backup_retained" = "Đã lưu gói trước đó tại {path}."
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris Tầm nhìn"
|
||||
|
||||
@@ -1580,6 +1580,10 @@ failed_fragment = " 失败 {failed}"
|
||||
"commandpack.compat_header" = "§e资源包 '{pack}': 内容在 Minecraft {version} 中不可用"
|
||||
"commandpack.compat_none" = "§a资源包 '{pack}' 未使用 Minecraft {version} 中缺失的内容。"
|
||||
"commandpack.compat_remedy" = "§7将服务器升级到更新的 Minecraft 以恢复这些内容,或声明回退 (维度的 blockFallbacks、方块的 backup)。"
|
||||
"commandpack.update_world_requires_persistent_world" = "§c请选择已加载的 Iris 世界。Studio 世界无法接收生成更新。"
|
||||
"commandpack.update_world_staged" = "§a已为世界 '{world}' 暂存 {pack} v{version}。正常重启后将启用该包,并进行地形融合。"
|
||||
"commandpack.update_world_unchanged" = "§a世界 '{world}' 已使用 {pack} v{version},且生成设置相同。无需重启。"
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6工作室 §b开幕"
|
||||
@@ -1691,7 +1695,7 @@ chunk_failed = "块 {x},{z} 失败: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris 包“{pack}”已安装§8 | §f{transferred}§7,用时 §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris 包“{pack}”已安装。"
|
||||
"progress.unchanged" = "§eIris 包“{pack}”已安装。 使用 overwrite=true 备份并替换该包。"
|
||||
"progress.failed" = "§cIris 包下载失败。§7 请查看上方的下载详情后重试。"
|
||||
"progress.failed_detail" = "§cIris 包下载失败。§7 {error}"
|
||||
"progress.cancelled" = "§eIris 包下载已在发布前取消。"
|
||||
@@ -1717,14 +1721,17 @@ check_github = "检查它是否在 GitHub 把这个报告给工作人员!"
|
||||
invalid_dimension = "无效的维度文件夹 dimensions/."
|
||||
importing = "导入 {name} ({key})"
|
||||
dimension_key_conflict = "包文件夹中的另一个维度已经使用密钥 {key}. 导入失败 !"
|
||||
pack_key_conflict = "另一个包是用钥匙 {key}. 导入失败 !"
|
||||
"pack_key_conflict" = "Iris 包“{key}”已安装。 使用 overwrite=true 备份并替换该包。"
|
||||
acquired = "已成功获取 {name}."
|
||||
already_installed = "包 {key} 已安装,跳过下载。"
|
||||
"already_installed" = "包 {key} 已安装,跳过下载。 使用 overwrite=true 备份并替换该包。"
|
||||
in_progress = "另一个 Iris 包下载已在进行中。请等待其完成后再试。"
|
||||
validation_failed = "包{pack}' 验证失败; 世界和工作室的创建将被拒绝. 原因:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "包{pack}'经验证 {count} 警告! 警告你们!", one = "包{pack}'经验证 {count} 警告,你应该知道" }
|
||||
validated = "包{pack}已确认。"
|
||||
"close_studio" = "替换已安装的包之前,请关闭当前 Studio 项目。"
|
||||
"backup_retained" = "旧包已保存到 {path}。"
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris 愿景"
|
||||
|
||||
@@ -1580,6 +1580,10 @@ failed_fragment = " 失敗 {failed}"
|
||||
"commandpack.compat_header" = "§e資源包 '{pack}': 內容在 Minecraft {version} 中無法使用"
|
||||
"commandpack.compat_none" = "§a資源包 '{pack}' 未使用 Minecraft {version} 中缺少的內容。"
|
||||
"commandpack.compat_remedy" = "§7將伺服器升級到較新的 Minecraft 以還原這些內容,或宣告備援 (維度的 blockFallbacks、方塊的 backup)。"
|
||||
"commandpack.update_world_requires_persistent_world" = "§c請選擇已載入的 Iris 世界。Studio 世界無法接收生成更新。"
|
||||
"commandpack.update_world_staged" = "§a已為世界 '{world}' 暫存 {pack} v{version}。正常重新啟動後將啟用此套件,並進行地形融合。"
|
||||
"commandpack.update_world_unchanged" = "§a世界 '{world}' 已使用 {pack} v{version},且生成設定相同。無需重新啟動。"
|
||||
|
||||
|
||||
[iris.runtime.studio]
|
||||
opening = "§6工作室 §b開幕"
|
||||
@@ -1691,7 +1695,7 @@ chunk_failed = "塊 {x},{z} 失敗: {type}"
|
||||
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
|
||||
"progress.detail" = "§8 - §7{detail}"
|
||||
"progress.complete" = "§aIris 套件「{pack}」已安裝§8 | §f{transferred}§7,用時 §f{elapsed}"
|
||||
"progress.unchanged" = "§eIris 套件「{pack}」已安裝。"
|
||||
"progress.unchanged" = "§eIris 套件「{pack}」已安裝。 使用 overwrite=true 備份並替換此套件。"
|
||||
"progress.failed" = "§cIris 套件下載失敗。§7 請檢視上方的下載詳細資訊後重試。"
|
||||
"progress.failed_detail" = "§cIris 套件下載失敗。§7 {error}"
|
||||
"progress.cancelled" = "§eIris 套件下載已在發佈前取消。"
|
||||
@@ -1717,14 +1721,17 @@ check_github = "檢查它是否在 GitHub 把這個報告給工作人員!"
|
||||
invalid_dimension = "無效的維度資料夾 dimensions/."
|
||||
importing = "匯入 {name} ({key})"
|
||||
dimension_key_conflict = "包資料夾中的另一個維度已經使用金鑰 {key}. 匯入失敗 !"
|
||||
pack_key_conflict = "另一個包是用鑰匙 {key}. 匯入失敗 !"
|
||||
"pack_key_conflict" = "Iris 套件「{key}」已安裝。 使用 overwrite=true 備份並替換此套件。"
|
||||
acquired = "已成功獲取 {name}."
|
||||
already_installed = "套件 {key} 已安裝,跳過下載。"
|
||||
"already_installed" = "套件 {key} 已安裝,跳過下載。 使用 overwrite=true 備份並替換此套件。"
|
||||
in_progress = "另一個 Iris 套件下載已在進行中。請等待其完成後再試。"
|
||||
validation_failed = "包{pack}' 驗證失敗; 世界和工作室的建立將被拒絕. 原因:"
|
||||
validation_reason = " - {reason}"
|
||||
validated_with_warnings = { other = "包{pack}'經驗證 {count} 警告! 警告你們!", one = "包{pack}'經驗證 {count} 警告,你應該知道" }
|
||||
validated = "包{pack}已確認。"
|
||||
"close_studio" = "替換已安裝的套件之前,請關閉目前的 Studio 專案。"
|
||||
"backup_retained" = "舊套件已儲存至 {path}。"
|
||||
|
||||
|
||||
[iris.client.vision]
|
||||
title = "Iris 願景"
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
package art.arcane.iris.diagnostics.splash;
|
||||
|
||||
import art.arcane.iris.pack.BuiltInPackUpdates;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IrisSplashComposerTest {
|
||||
@Test
|
||||
public void packLinesShowUpdatesAndKeepCustomPacks() {
|
||||
List<IrisSplashPackScanner.SplashPackMetadata> packs = List.of(
|
||||
new IrisSplashPackScanner.SplashPackMetadata("custom", "7"),
|
||||
new IrisSplashPackScanner.SplashPackMetadata("overworld", "4009"),
|
||||
new IrisSplashPackScanner.SplashPackMetadata("underworld", "1012"));
|
||||
assertEquals(List.of("Custom Dimensions: 3", " custom v7", " overworld v4009 -> v4010 available",
|
||||
" underworld v1012", "Update overworld: /iris download pack=overworld overwrite=true"),
|
||||
IrisSplashComposer.composePackLines(packs, Map.of(
|
||||
"overworld", new BuiltInPackUpdates.Update("4010"),
|
||||
"underworld", new BuiltInPackUpdates.Update("1012"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composeInfoUsesCurrentVersionWithoutStaleReleaseTag() {
|
||||
String[] info = IrisSplashComposer.composeInfo("4.0.0-26.2", "Paper 26.2", IrisSplashComposer.InfoStyle.PLAIN);
|
||||
|
||||
@@ -18,6 +18,31 @@ public class AtomicDirectoryPublisherTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void retainedBackupSurvivesCommitAndRemainsAvailableForRollback() throws Exception {
|
||||
Path root = temporaryFolder.getRoot().toPath();
|
||||
Path target = Files.createDirectory(root.resolve("target"));
|
||||
Files.writeString(target.resolve("value.txt"), "old");
|
||||
Path staged = Files.createDirectory(root.resolve("stage"));
|
||||
Files.writeString(staged.resolve("value.txt"), "new");
|
||||
Path retained = root.resolve("retained");
|
||||
|
||||
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publish(staged, target)) {
|
||||
assertTrue(publication.retainBackup(retained));
|
||||
}
|
||||
|
||||
assertEquals("old", Files.readString(target.resolve("value.txt")));
|
||||
assertFalse(Files.exists(retained));
|
||||
Files.createDirectory(staged);
|
||||
Files.writeString(staged.resolve("value.txt"), "new");
|
||||
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publish(staged, target)) {
|
||||
assertTrue(publication.retainBackup(retained));
|
||||
publication.commit();
|
||||
}
|
||||
assertEquals("old", Files.readString(retained.resolve("value.txt")));
|
||||
assertEquals("new", Files.readString(target.resolve("value.txt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitPublishesStagedDirectoryAndRemovesBackup() throws Exception {
|
||||
Path root = temporaryFolder.getRoot().toPath();
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package art.arcane.iris.pack;
|
||||
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BuiltInPackUpdatesTest {
|
||||
@Test
|
||||
public void comparesNumericVersionsWithoutDowngradeNotices() {
|
||||
BuiltInPackUpdates.Update update = new BuiltInPackUpdates.Update("4010");
|
||||
assertEquals(" -> v4010 available", update.suffix("999"));
|
||||
assertEquals("", update.suffix("4010"));
|
||||
assertEquals("", update.suffix("5000"));
|
||||
assertEquals(" (latest v4010)", update.suffix("custom"));
|
||||
assertFalse(update.newerThan("custom"));
|
||||
assertEquals(" (update check unavailable)", BuiltInPackUpdates.Update.unavailable().suffix("4010"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checksOnlyBuiltInPacksAndKeepsSuccessfulResultsOnPartialFailure() throws Exception {
|
||||
AtomicInteger requests = new AtomicInteger();
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
server.createContext("/", exchange -> {
|
||||
requests.incrementAndGet();
|
||||
boolean overworld = exchange.getRequestURI().getPath().equals("/overworld");
|
||||
byte[] body = (overworld ? release("v4010", "overworld.zip", false) : "rate limited")
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(overworld ? 200 : 429, body.length);
|
||||
exchange.getResponseBody().write(body);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
||||
Map<String, BuiltInPackUpdates.Update> updates = BuiltInPackUpdates.check(
|
||||
List.of("overworld", "underworld", "custom", "overworld"), client,
|
||||
pack -> URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/" + pack))
|
||||
.get(10, TimeUnit.SECONDS);
|
||||
assertEquals(2, requests.get());
|
||||
assertEquals("4010", updates.get("overworld").version());
|
||||
assertEquals(BuiltInPackUpdates.Update.unavailable(), updates.get("underworld"));
|
||||
assertFalse(updates.containsKey("custom"));
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMalformedPrereleaseAndMissingArchiveMetadata() throws Exception {
|
||||
for (String body : List.of("invalid", "{}", release("4010", "source.zip", false),
|
||||
release("4010", "overworld.zip", true), release("not-a-version", "overworld.zip", false))) {
|
||||
assertEquals(BuiltInPackUpdates.Update.unavailable(), checkResponse(body));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsStableReleaseWithMatchingArchive() throws Exception {
|
||||
assertTrue(checkResponse(release("V+4010", "overworld.zip", false)).newerThan("4009"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boundsReleaseMetadataSize() throws Exception {
|
||||
assertEquals(BuiltInPackUpdates.Update.unavailable(), checkResponse(" ".repeat(1024 * 1024 + 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timesOutWithoutResponseHeaders() throws Exception {
|
||||
assertStalledResponseUnavailable(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timesOutWhenResponseBodyStalls() throws Exception {
|
||||
assertStalledResponseUnavailable(true);
|
||||
}
|
||||
|
||||
private void assertStalledResponseUnavailable(boolean sendHeaders) throws Exception {
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
server.createContext("/", exchange -> {
|
||||
if (sendHeaders) {
|
||||
exchange.sendResponseHeaders(200, 100);
|
||||
exchange.getResponseBody().write('{');
|
||||
exchange.getResponseBody().flush();
|
||||
}
|
||||
});
|
||||
server.start();
|
||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
||||
assertEquals(BuiltInPackUpdates.Update.unavailable(), BuiltInPackUpdates.check(
|
||||
List.of("overworld"), client,
|
||||
pack -> URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/"))
|
||||
.get(8, TimeUnit.SECONDS).get("overworld"));
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private BuiltInPackUpdates.Update checkResponse(String response) throws Exception {
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
server.createContext("/", exchange -> {
|
||||
byte[] body = response.getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(200, body.length);
|
||||
exchange.getResponseBody().write(body);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
||||
return BuiltInPackUpdates.check(List.of("overworld"), client,
|
||||
pack -> URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/"))
|
||||
.get(10, TimeUnit.SECONDS).get("overworld");
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static String release(String version, String asset, boolean prerelease) {
|
||||
return "{\"tag_name\":\"" + version + "\",\"draft\":false,\"prerelease\":" + prerelease
|
||||
+ ",\"assets\":[{\"name\":\"" + asset + "\"}]}";
|
||||
}
|
||||
}
|
||||
@@ -606,6 +606,13 @@ public class PackDownloaderTest {
|
||||
assertTrue(result.restartRequired());
|
||||
assertEquals("new", Files.readString(target.toPath().resolve("state.txt"), StandardCharsets.UTF_8));
|
||||
assertFalse(Files.exists(target.toPath().resolve("old-only.txt")));
|
||||
File[] backups = new File(packsFolder, ".backups").listFiles();
|
||||
assertNotNull(backups);
|
||||
assertEquals(1, backups.length);
|
||||
assertEquals("old", Files.readString(backups[0].toPath().resolve("state.txt")));
|
||||
assertEquals("old", Files.readString(backups[0].toPath().resolve("old-only.txt")));
|
||||
assertTrue(feedback.stream().anyMatch(message -> message.contains(backups[0].getPath())));
|
||||
assertFalse(PackDirectoryResolver.isVisiblePackDirectory(new File(packsFolder, ".backups")));
|
||||
assertTransactionStateClean(packsFolder);
|
||||
assertEquals(0, PackDownloader.downloadLockCount());
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ import art.arcane.iris.pack.BrokenPackException;
|
||||
import art.arcane.iris.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.pack.PackValidationResult;
|
||||
import art.arcane.iris.generation.runtime.PreservationRegistry;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.iris.world.history.GenerationHistory;
|
||||
import art.arcane.iris.world.task.J;
|
||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import org.junit.Assume;
|
||||
import org.junit.After;
|
||||
@@ -35,6 +40,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class StudioSVCWorldPackPublishTest {
|
||||
@ClassRule
|
||||
@@ -54,6 +60,34 @@ public class StudioSVCWorldPackPublishTest {
|
||||
IrisServices.remove(PreservationRegistry.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void brokenSourceIsRejectedBeforeOpeningOrChangingWorldHistory() throws Exception {
|
||||
Path source = temporaryFolder.newFolder("invalid-generation-update").toPath();
|
||||
writeValidPack(source);
|
||||
Files.writeString(source.resolve("biomes/biome.json"), "{");
|
||||
Path world = temporaryFolder.newFolder("untouched-generation-world").toPath();
|
||||
Files.writeString(world.resolve("level.dat"), "world-state");
|
||||
IrisData sourceData = mock(IrisData.class);
|
||||
when(sourceData.getDataFolder()).thenReturn(source.toFile());
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
dimension.setLoadKey("main");
|
||||
dimension.setLoader(sourceData);
|
||||
VolmitSender sender = mock(VolmitSender.class);
|
||||
|
||||
try (MockedStatic<J> scheduler = mockStatic(J.class);
|
||||
MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class);
|
||||
MockedStatic<GenerationHistory> history = mockStatic(GenerationHistory.class);
|
||||
MockedStatic<ServerConfigurator> configurator = mockStatic(ServerConfigurator.class)) {
|
||||
assertNull(new StudioSVC().replaceIntoWorld(sender, dimension, world.toFile(), 42L));
|
||||
|
||||
history.verifyNoInteractions();
|
||||
configurator.verifyNoInteractions();
|
||||
assertFalse(Files.exists(world.resolve("iris")));
|
||||
assertEquals("world-state", Files.readString(world.resolve("level.dat")));
|
||||
assertTrue(PackValidationRegistry.isBroken(source));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void copiesToStageAndPublishesTheCompletePack() throws IOException {
|
||||
Path root = temporaryFolder.newFolder("world").toPath();
|
||||
|
||||
Reference in New Issue
Block a user