Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the dump command #19

Open
wants to merge 1 commit into
base: 1.19.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/main/java/xyz/nucleoid/creator_tools/CreatorTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.util.Identifier;
import xyz.nucleoid.creator_tools.command.DumpCommand;
import xyz.nucleoid.creator_tools.command.MapManageCommand;
import xyz.nucleoid.creator_tools.command.MapMetadataCommand;
import xyz.nucleoid.creator_tools.item.CreatorToolsItems;
import xyz.nucleoid.creator_tools.workspace.MapWorkspaceManager;
import xyz.nucleoid.creator_tools.workspace.WorkspaceTraveler;
import xyz.nucleoid.creator_tools.workspace.editor.WorkspaceNetworking;

import java.util.Locale;

public final class CreatorTools implements ModInitializer {
public static final String ID = "nucleoid_creator_tools";

Expand All @@ -27,6 +31,7 @@ public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated, environment) -> {
MapManageCommand.register(dispatcher);
MapMetadataCommand.register(dispatcher);
DumpCommand.register(dispatcher);
});

ServerTickEvents.START_SERVER_TICK.register(server -> {
Expand All @@ -42,4 +47,15 @@ public void onInitialize() {
public static Identifier identifier(String path) {
return new Identifier(ID, path);
}

public static Identifier getSourceNameIdentifier(ServerCommandSource source, Identifier identifier) {
if (identifier.getNamespace().equals("minecraft")) {
var sourceName = source.getName()
.toLowerCase(Locale.ROOT)
.replaceAll("\\s", "_");
return new Identifier(sourceName, identifier.getPath());
}

return identifier;
}
}
59 changes: 59 additions & 0 deletions src/main/java/xyz/nucleoid/creator_tools/command/DumpCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package xyz.nucleoid.creator_tools.command;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.server.command.DataCommand;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.command.DataCommand.ObjectType;
import net.minecraft.text.Text;
import xyz.nucleoid.creator_tools.CreatorTools;
import xyz.nucleoid.creator_tools.exporter.DumpExporter;

import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;

public final class DumpCommand {
// @formatter:off
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
var builder = literal("dump").requires(source -> source.hasPermissionLevel(4));

for (var objectType : DataCommand.TARGET_OBJECT_TYPES) {
objectType.addArgumentsToBuilder(builder, builderx -> {
return builderx
.then(argument("path", IdentifierArgumentType.identifier())
.executes(context -> DumpCommand.dump(context, objectType)));
});
}

dispatcher.register(builder);
}
// @formatter:on

private static int dump(CommandContext<ServerCommandSource> context, ObjectType objectType) throws CommandSyntaxException {
var source = context.getSource();

var object = objectType.getObject(context);
var nbt = object.getNbt();

var givenIdentifier = IdentifierArgumentType.getIdentifier(context, "path");
var identifier = CreatorTools.getSourceNameIdentifier(source, givenIdentifier);

var future = DumpExporter.saveToExport(source.getServer(), nbt, identifier);

future.handle((v, throwable) -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whenCompleteAsync(..., server)? Sending messages off-thread has potential to explode 😅

if (throwable == null) {
source.sendFeedback(Text.translatable("text.nucleoid_creator_tools.dump.success", identifier), false);
} else {
CreatorTools.LOGGER.error("Failed to export object to '{}'", identifier, throwable);
source.sendError(Text.translatable("text.nucleoid_creator_tools.dump.error"));
}
return null;
});

return Command.SINGLE_SUCCESS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.dimension.DimensionTypes;
import xyz.nucleoid.creator_tools.CreatorTools;
import xyz.nucleoid.creator_tools.MapTemplateExporter;
import xyz.nucleoid.creator_tools.exporter.MapTemplateExporter;
import xyz.nucleoid.creator_tools.workspace.MapWorkspaceManager;
import xyz.nucleoid.creator_tools.workspace.WorkspaceTraveler;
import xyz.nucleoid.fantasy.RuntimeWorldConfig;
Expand All @@ -36,7 +36,6 @@
import xyz.nucleoid.map_templates.MapTemplateSerializer;

import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;

import static net.minecraft.server.command.CommandManager.argument;
Expand Down Expand Up @@ -125,16 +124,7 @@ private static int openWorkspace(CommandContext<ServerCommandSource> context, Ru
var source = context.getSource();

var givenIdentifier = IdentifierArgumentType.getIdentifier(context, "workspace");

Identifier identifier;
if (givenIdentifier.getNamespace().equals("minecraft")) {
var sourceName = context.getSource().getName()
.toLowerCase(Locale.ROOT)
.replaceAll("\\s", "_");
identifier = new Identifier(sourceName, givenIdentifier.getPath());
} else {
identifier = givenIdentifier;
}
var identifier = CreatorTools.getSourceNameIdentifier(source, givenIdentifier);

var workspaceManager = MapWorkspaceManager.get(source.getServer());
if (workspaceManager.byId(identifier) != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package xyz.nucleoid.creator_tools.exporter;

import net.minecraft.data.DataProvider;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.PathUtil;
import net.minecraft.util.Util;
import net.minecraft.util.WorldSavePath;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

import com.google.gson.stream.JsonWriter;
import com.mojang.serialization.JsonOps;

public final class DumpExporter {
private static final String DATA_DIRECTORY = "data";
private static final String FILE_EXTENSION = ".json";

private DumpExporter() {
}

public static CompletableFuture<Void> saveToExport(MinecraftServer server, NbtCompound nbt, Identifier identifier) {
return CompletableFuture.supplyAsync(() -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: CompletableFuture.runAsync(() -> {})

try {
var generatedPath = server.getSavePath(WorldSavePath.GENERATED);
var path = getAndCheckDataPath(generatedPath, identifier, FILE_EXTENSION);

var json = NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, nbt);
System.out.println(json);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be removed?


Files.createDirectories(path.getParent());

try (var output = Files.newOutputStream(path)) {
var jsonWriter = new JsonWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
Comment on lines +44 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
try (var output = Files.newOutputStream(path)) {
var jsonWriter = new JsonWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
try (var writer = new JsonWriter(Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {


jsonWriter.setSerializeNulls(false);
jsonWriter.setIndent(" ");

JsonHelper.writeSorted(jsonWriter, json, DataProvider.JSON_KEY_SORTING_COMPARATOR);
jsonWriter.flush();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be needed if including the JsonWriter in the try-with-resources

}

return null;
} catch (IOException | InvalidIdentifierException e) {
throw new CompletionException(e);
}
}, Util.getIoWorkerExecutor());
}

public static Path getDataPath(Path path, Identifier identifier, String extension) {
try {
Path namespacePath = path.resolve(identifier.getNamespace());
Path dataPath = namespacePath.resolve(DATA_DIRECTORY);

return PathUtil.getResourcePath(dataPath, identifier.getPath(), extension);
} catch (InvalidPathException e) {
throw new InvalidIdentifierException("Invalid resource path: " + identifier, e);
}
}

private static Path getAndCheckDataPath(Path path, Identifier identifier, String extension) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we should probably apply this to the map template exporter, too 😅

if (identifier.getPath().contains("//")) {
throw new InvalidIdentifierException("Invalid resource path: " + identifier);
}

Path dataPath = getDataPath(path, identifier, extension);
if (!(dataPath.startsWith(path) && PathUtil.isNormal(dataPath) && PathUtil.isAllowedName(dataPath))) {
throw new InvalidIdentifierException("Invalid resource path: " + dataPath);
}

return dataPath;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package xyz.nucleoid.creator_tools;
package xyz.nucleoid.creator_tools.exporter;

import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import xyz.nucleoid.creator_tools.CreatorTools;
import xyz.nucleoid.map_templates.MapTemplate;
import xyz.nucleoid.map_templates.MapTemplateSerializer;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"item.nucleoid_creator_tools.region_visibility_filter.set_filter": "Set the region visibility filter",
"text.nucleoid_creator_tools.chunk_generator.generator_not_found": "Chunk generator with id '%s' was not found!",
"text.nucleoid_creator_tools.dimension_options.dimension_not_found": "Dimension with id '%s' was not found!",
"text.nucleoid_creator_tools.dump.error": "Failed to dump data! An unexpected exception was thrown",
"text.nucleoid_creator_tools.dump.success": "Dumped data for '%s'",
"text.nucleoid_creator_tools.map_workspace.workspace_not_found": "Map with id '%s' was not found!",
"text.nucleoid_creator_tools.map.bounds.get": "The bounds for the workspace are %s to %s",
"text.nucleoid_creator_tools.map.bounds.set": "Updated bounds for workspace",
Expand Down