diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index b98042c45..a5cc9dde0 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -2,6 +2,8 @@ package com.simibubi.create; import java.util.Random; +import net.minecraftforge.fml.loading.FMLLoader; + import org.slf4j.Logger; import com.google.gson.Gson; @@ -152,6 +154,9 @@ public class Create { // FIXME: this is not thread-safe Mods.CURIOS.executeIfInstalled(() -> () -> Curios.init(modEventBus, forgeEventBus)); + + if (FMLLoader.getDist().isDedicatedServer()) + SCHEMATIC_RECEIVER.computeHashes(); } public static void init(final FMLCommonSetupEvent event) { diff --git a/src/main/java/com/simibubi/create/content/schematics/SchematicFile.java b/src/main/java/com/simibubi/create/content/schematics/SchematicFile.java new file mode 100644 index 000000000..be9e35945 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/schematics/SchematicFile.java @@ -0,0 +1,3 @@ +package com.simibubi.create.content.schematics; + +public record SchematicFile(String playerName, String schematicName) {} diff --git a/src/main/java/com/simibubi/create/content/schematics/SchematicItem.java b/src/main/java/com/simibubi/create/content/schematics/SchematicItem.java index 9bcffbcb5..9d7841cba 100644 --- a/src/main/java/com/simibubi/create/content/schematics/SchematicItem.java +++ b/src/main/java/com/simibubi/create/content/schematics/SchematicItem.java @@ -57,6 +57,10 @@ public class SchematicItem extends Item { super(properties); } + public static ItemStack create(HolderGetter lookup, SchematicFile schematicFile) { + return create(lookup, schematicFile.schematicName(), schematicFile.playerName()); + } + public static ItemStack create(HolderGetter lookup, String schematic, String owner) { ItemStack blueprint = AllItems.SCHEMATIC.asStack(); diff --git a/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java b/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java index 741274fe0..089243aa0 100644 --- a/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java +++ b/src/main/java/com/simibubi/create/content/schematics/ServerSchematicLoader.java @@ -1,10 +1,13 @@ package com.simibubi.create.content.schematics; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -24,21 +27,27 @@ import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.infrastructure.config.AllConfigs; import com.simibubi.create.infrastructure.config.CSchematics; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.ChatFormatting; +import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import org.apache.commons.codec.digest.DigestUtils; +import org.jetbrains.annotations.Nullable; + public class ServerSchematicLoader { - private Map activeUploads; + private Map activeUploads = new HashMap<>(); - public class SchematicUploadEntry { + private Map sumToSchematic = new Object2ReferenceOpenHashMap<>(); + + public static class SchematicUploadEntry { public Level world; public BlockPos tablePos; public OutputStream stream; @@ -56,19 +65,42 @@ public class ServerSchematicLoader { } } - public ServerSchematicLoader() { - activeUploads = new HashMap<>(); - } - public String getSchematicPath() { return "schematics/uploaded"; } private final ObjectArrayList deadEntries = ObjectArrayList.of(); + @Nullable + public SchematicFile getSchematicFileFromSum(String sum) { + return sumToSchematic.get(sum); + } + + public void computeHashes() { + Util.ioPool().submit(() -> { + try (Stream filePaths = Files.find(Path.of(getSchematicPath()), 2, + (filePath, attributes) -> filePath.toString().endsWith(".nbt"))) { + for (Path path : filePaths.toList()) { + try (InputStream stream = new FileInputStream(path.toFile())) { + String[] pathSplit = path.toString() + .replace("schematics/uploaded/", "") + .replace(".nbt", "") + .split("/"); + String playerName = pathSplit[0]; + String schematicName = pathSplit[1]; + String schematicMd5Hex = DigestUtils.md5Hex(stream); + + sumToSchematic.computeIfAbsent(schematicMd5Hex, k -> new SchematicFile(playerName, schematicName)); + } + } + } catch (IOException ignored) {} + }); + } + public void tick() { // Detect Timed out Uploads int timeout = getConfig().schematicIdleTimeout.get(); + for (String upload : activeUploads.keySet()) { SchematicUploadEntry entry = activeUploads.get(upload); @@ -82,6 +114,7 @@ public class ServerSchematicLoader { for (String toRemove : deadEntries) { this.cancelUpload(toRemove); } + deadEntries.clear(); } @@ -240,17 +273,22 @@ public class ServerSchematicLoader { table.finishUpload(); } + // Use when the schematic already exists on the server + public void useLocalFile(Level level, BlockPos pos, SchematicFile schematicFile) { + SchematicTableBlockEntity table = getTable(level, pos); + if (table != null) { + table.finishUpload(); + table.inventory.setStackInSlot(1, SchematicItem.create(level.holderLookup(Registries.BLOCK), schematicFile)); + } + } + public SchematicTableBlockEntity getTable(Level world, BlockPos pos) { - BlockEntity be = world.getBlockEntity(pos); - if (!(be instanceof SchematicTableBlockEntity)) - return null; - SchematicTableBlockEntity table = (SchematicTableBlockEntity) be; - return table; + return world.getBlockEntity(pos) instanceof SchematicTableBlockEntity table ? table : null; } public void handleFinishedUpload(ServerPlayer player, String schematic) { - String playerSchematicId = player.getGameProfile() - .getName() + "/" + schematic; + String playerName = player.getGameProfile().getName(); + String playerSchematicId = playerName + "/" + schematic; if (activeUploads.containsKey(playerSchematicId)) { try { @@ -259,6 +297,11 @@ public class ServerSchematicLoader { Level world = removed.world; BlockPos pos = removed.tablePos; + // It'll be fine:tm: + try (InputStream stream = Files.newInputStream(Path.of(playerSchematicId), StandardOpenOption.READ)) { + sumToSchematic.computeIfAbsent(DigestUtils.md5Hex(stream), k -> new SchematicFile(playerName, schematic)); + } catch (IOException ignored) {} + Create.LOGGER.info("New Schematic Uploaded: " + playerSchematicId); if (pos == null) return; @@ -271,9 +314,7 @@ public class ServerSchematicLoader { if (table == null) return; table.finishUpload(); - table.inventory.setStackInSlot(1, SchematicItem.create(world.holderLookup(Registries.BLOCK), schematic, player.getGameProfile() - .getName())); - + table.inventory.setStackInSlot(1, SchematicItem.create(world.holderLookup(Registries.BLOCK), schematic, playerName)); } catch (IOException e) { Create.LOGGER.error("Exception Thrown when finishing Upload: " + playerSchematicId); e.printStackTrace(); diff --git a/src/main/java/com/simibubi/create/content/schematics/client/ClientSchematicLoader.java b/src/main/java/com/simibubi/create/content/schematics/client/ClientSchematicLoader.java index ea40b1def..3aa0f4cf3 100644 --- a/src/main/java/com/simibubi/create/content/schematics/client/ClientSchematicLoader.java +++ b/src/main/java/com/simibubi/create/content/schematics/client/ClientSchematicLoader.java @@ -28,6 +28,8 @@ import net.minecraft.network.chat.Component; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import org.apache.commons.codec.digest.DigestUtils; + @OnlyIn(Dist.CLIENT) public class ClientSchematicLoader { @@ -73,7 +75,11 @@ public class ClientSchematicLoader { in = Files.newInputStream(path, StandardOpenOption.READ); activeUploads.put(schematic, in); - AllPackets.getChannel().sendToServer(SchematicUploadPacket.begin(schematic, size)); + + try (InputStream stream = Files.newInputStream(path, StandardOpenOption.READ)) { + String md5 = DigestUtils.md5Hex(stream); + AllPackets.getChannel().sendToServer(SchematicUploadPacket.begin(schematic, size, md5)); + } } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/com/simibubi/create/content/schematics/packet/SchematicUploadPacket.java b/src/main/java/com/simibubi/create/content/schematics/packet/SchematicUploadPacket.java index 69c19a5c2..29960bafe 100644 --- a/src/main/java/com/simibubi/create/content/schematics/packet/SchematicUploadPacket.java +++ b/src/main/java/com/simibubi/create/content/schematics/packet/SchematicUploadPacket.java @@ -1,6 +1,7 @@ package com.simibubi.create.content.schematics.packet; import com.simibubi.create.Create; +import com.simibubi.create.content.schematics.SchematicFile; import com.simibubi.create.content.schematics.table.SchematicTableMenu; import com.simibubi.create.foundation.networking.SimplePacketBase; @@ -9,6 +10,8 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent.Context; +import java.io.File; + public class SchematicUploadPacket extends SimplePacketBase { public static final int BEGIN = 0; @@ -20,14 +23,17 @@ public class SchematicUploadPacket extends SimplePacketBase { private String schematic; private byte[] data; + private String md5Hex; + public SchematicUploadPacket(int code, String schematic) { this.code = code; this.schematic = schematic; } - public static SchematicUploadPacket begin(String schematic, long size) { + public static SchematicUploadPacket begin(String schematic, long size, String md5Hex) { SchematicUploadPacket pkt = new SchematicUploadPacket(BEGIN, schematic); pkt.size = size; + pkt.md5Hex = md5Hex; return pkt; } @@ -45,8 +51,10 @@ public class SchematicUploadPacket extends SimplePacketBase { code = buffer.readInt(); schematic = buffer.readUtf(256); - if (code == BEGIN) + if (code == BEGIN) { size = buffer.readLong(); + md5Hex = buffer.readUtf(32); + } if (code == WRITE) data = buffer.readByteArray(); } @@ -56,8 +64,10 @@ public class SchematicUploadPacket extends SimplePacketBase { buffer.writeInt(code); buffer.writeUtf(schematic); - if (code == BEGIN) + if (code == BEGIN) { buffer.writeLong(size); + buffer.writeUtf(md5Hex); + } if (code == WRITE) buffer.writeByteArray(data); } @@ -69,9 +79,29 @@ public class SchematicUploadPacket extends SimplePacketBase { if (player == null) return; if (code == BEGIN) { - BlockPos pos = ((SchematicTableMenu) player.containerMenu).contentHolder - .getBlockPos(); - Create.SCHEMATIC_RECEIVER.handleNewUpload(player, schematic, size, pos); + boolean usedLocalFile = false; + + BlockPos pos = ((SchematicTableMenu) player.containerMenu).contentHolder.getBlockPos(); + + SchematicFile schematicFile = Create.SCHEMATIC_RECEIVER.getSchematicFileFromSum(md5Hex); + + if (schematicFile != null) { + String filePath = String.format( + "%s/%s/%s", + Create.SCHEMATIC_RECEIVER.getSchematicPath(), + schematicFile.playerName(), + schematicFile.schematicName() + ); + + // Check if the file exists + if (new File(filePath).isFile()) { + Create.SCHEMATIC_RECEIVER.useLocalFile(player.level(), pos, schematicFile); + usedLocalFile = true; + } + } + + if (!usedLocalFile) + Create.SCHEMATIC_RECEIVER.handleNewUpload(player, schematic, size, pos); } if (code == WRITE) Create.SCHEMATIC_RECEIVER.handleWriteRequest(player, schematic, data);