From 589151af815c395614085bc4821fc8f3f5de77cb Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 5 Aug 2021 18:43:44 +0200 Subject: [PATCH 1/6] Call DataFixer on WorldData --- .../ru/bclib/api/datafixer/DataFixerAPI.java | 7 ++++++ .../bclib/api/datafixer/MigrationProfile.java | 22 ++++++++++++++++--- .../java/ru/bclib/api/datafixer/Patch.java | 17 +++++++++++--- .../ru/bclib/api/datafixer/PatchFunction.java | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java b/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java index dfdec8d7..b98a8931 100644 --- a/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java +++ b/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java @@ -254,6 +254,13 @@ public class DataFixerAPI { players.parallelStream().forEach((file) -> fixPlayer(profile, state, file)); fixLevel(profile, state, new File(dir, "level.dat")); + + try { + profile.patchWorldData(); + } catch (PatchDidiFailException e){ + state.didFail = true; + BCLib.LOGGER.error(e.getMessage()); + } if (!state.didFail) { profile.markApplied(); diff --git a/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java b/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java index 8ad695b6..d58f793c 100644 --- a/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java +++ b/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java @@ -2,6 +2,7 @@ package ru.bclib.api.datafixer; import net.minecraft.nbt.CompoundTag; import org.jetbrains.annotations.NotNull; +import ru.bclib.api.WorldDataAPI; import java.util.Collections; import java.util.HashMap; @@ -11,10 +12,11 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -class MigrationProfile { +public class MigrationProfile { final Set mods; final Map idReplacements; final List> levelPatchers; + final List worldDataPatchers; private final CompoundTag config; @@ -28,6 +30,7 @@ class MigrationProfile { HashMap replacements = new HashMap(); List> levelPatches = new LinkedList<>(); + List worldDataPatches = new LinkedList<>(); for (String modID : mods) { Patch.getALL() .stream() @@ -37,6 +40,8 @@ class MigrationProfile { replacements.putAll(patch.getIDReplacements()); if (patch.getLevelDatPatcher()!=null) levelPatches.add(patch.getLevelDatPatcher()); + if (patch.getWorldDataPatcher()!=null) + worldDataPatches.add(patch); DataFixerAPI.LOGGER.info("Applying " + patch); } else { @@ -47,6 +52,7 @@ class MigrationProfile { this.idReplacements = Collections.unmodifiableMap(replacements); this.levelPatchers = Collections.unmodifiableList(levelPatches); + this.worldDataPatchers = Collections.unmodifiableList(worldDataPatches); } final public void markApplied() { @@ -66,7 +72,7 @@ class MigrationProfile { } public boolean hasAnyFixes() { - return idReplacements.size() > 0 || levelPatchers.size() > 0; + return idReplacements.size() > 0 || levelPatchers.size() > 0 || worldDataPatchers.size() > 0; } public boolean replaceStringFromIDs(@NotNull CompoundTag tag, @NotNull String key) { @@ -87,8 +93,18 @@ class MigrationProfile { public boolean patchLevelDat(@NotNull CompoundTag level) throws PatchDidiFailException { boolean changed = false; for (PatchFunction f : levelPatchers) { - changed |= f.apply(level); + changed |= f.apply(level, this); } return changed; } + + public void patchWorldData() throws PatchDidiFailException { + for (Patch patch : worldDataPatchers) { + CompoundTag root = WorldDataAPI.getRootTag(patch.modID); + boolean changed = patch.getWorldDataPatcher().apply(root, this); + if (changed) { + WorldDataAPI.saveFile(patch.modID); + } + } + } } diff --git a/src/main/java/ru/bclib/api/datafixer/Patch.java b/src/main/java/ru/bclib/api/datafixer/Patch.java index 30db9b4c..e687f3a6 100644 --- a/src/main/java/ru/bclib/api/datafixer/Patch.java +++ b/src/main/java/ru/bclib/api/datafixer/Patch.java @@ -124,14 +124,25 @@ public abstract class Patch { * Return a {@link PatchFunction} that is called with the content of level.dat. *

* The function needs to return {@code true}, if changes were made to the data. - * If an error occurs, the method shoudl throw a {@link PatchDidiFailException} + * If an error occurs, the method should throw a {@link PatchDidiFailException} * * The default implementation of this method returns null. * - * @return The returned function is called a {@code CompoundTag} that contains the - * contents of level.dat + * @return {@code true} if changes were applied and we need to save the data */ public PatchFunction getLevelDatPatcher() { return null; } + + /** + * Return a {@link PatchFunction} that is called with the content from the + * {@link ru.bclib.api.WorldDataAPI} for this Mod. + * The function needs to return {@code true}, if changes were made to the data. + * If an error occurs, the method should throw a {@link PatchDidiFailException} + * + * The default implementation of this method returns null. + * + * @return {@code true} if changes were applied and we need to save the data + */ + public PatchFunction getWorldDataPatcher() { return null; } /** * Generates ready to use data for all currently registered patches. The list of diff --git a/src/main/java/ru/bclib/api/datafixer/PatchFunction.java b/src/main/java/ru/bclib/api/datafixer/PatchFunction.java index 77c70f6b..7d429d68 100644 --- a/src/main/java/ru/bclib/api/datafixer/PatchFunction.java +++ b/src/main/java/ru/bclib/api/datafixer/PatchFunction.java @@ -2,5 +2,5 @@ package ru.bclib.api.datafixer; @FunctionalInterface public interface PatchFunction { - R apply(T t) throws PatchDidiFailException; + R apply(T t, MigrationProfile profile) throws PatchDidiFailException; } From 16cbba6e76506664a8e0b72cb761671fba279044 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 5 Aug 2021 19:07:14 +0200 Subject: [PATCH 2/6] Preparing Config-Objects to automatically sync from Server to Client --- .../api/dataexchange/DataExchangeAPI.java | 25 ++++++++++++++++++- src/main/java/ru/bclib/config/Config.java | 16 +++++++----- .../java/ru/bclib/config/ConfigKeeper.java | 10 ++++---- .../java/ru/bclib/config/ConfigWriter.java | 13 ++++------ src/main/java/ru/bclib/config/PathConfig.java | 10 ++++---- .../java/ru/bclib/config/SessionConfig.java | 25 ------------------- 6 files changed, 49 insertions(+), 50 deletions(-) delete mode 100644 src/main/java/ru/bclib/config/SessionConfig.java diff --git a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java index f354928d..e5e53187 100644 --- a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java +++ b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java @@ -6,12 +6,14 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import net.minecraft.client.Minecraft; import net.minecraft.network.FriendlyByteBuf; +import java.io.File; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Predicate; public class DataExchangeAPI { private final static List MODS = Lists.newArrayList(); @@ -19,6 +21,17 @@ public class DataExchangeAPI { private ConnectorServerside server; private ConnectorClientside client; protected final Set descriptors; + + private static class AutoFileSyncEntry { + public final Predicate needTransfer; + public final File fileName; + + private AutoFileSyncEntry(Predicate needTransfer, File fileName) { + this.needTransfer = needTransfer; + this.fileName = fileName; + } + } + protected final List autoSyncFiles = new ArrayList<>(4); private DataExchangeAPI(){ @@ -141,4 +154,14 @@ public class DataExchangeAPI { } }); } + + /** + * Registers a File for automatic client syncing. + * + * @param needTransfer If the predicate returns true, the file needs to get copied to the server. + * @param fileName The name of the File + */ + public static void addAutoSyncFile(Predicate needTransfer, File fileName){ + DataExchangeAPI.getInstance().autoSyncFiles.add(new AutoFileSyncEntry(needTransfer, fileName)); + } } diff --git a/src/main/java/ru/bclib/config/Config.java b/src/main/java/ru/bclib/config/Config.java index d5672efe..d49d802f 100644 --- a/src/main/java/ru/bclib/config/Config.java +++ b/src/main/java/ru/bclib/config/Config.java @@ -2,6 +2,7 @@ package ru.bclib.config; import org.jetbrains.annotations.Nullable; import ru.bclib.BCLib; +import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.config.ConfigKeeper.BooleanEntry; import ru.bclib.config.ConfigKeeper.Entry; import ru.bclib.config.ConfigKeeper.FloatEntry; @@ -13,16 +14,19 @@ import java.io.File; public abstract class Config { protected final ConfigKeeper keeper; - + protected final boolean autoSync; protected abstract void registerEntries(); - - public Config(String modID, String group) { - this(modID, group, null); + + protected Config(String modID, String group) { + this(modID, group, true); } - protected Config(String modID, String group, File path) { - this.keeper = new ConfigKeeper(modID, group, path); + protected Config(String modID, String group, boolean autoSync) { + this.keeper = new ConfigKeeper(modID, group); this.registerEntries(); + this.autoSync = autoSync; + + DataExchangeAPI.addAutoSyncFile((content)->{return false;}, keeper.getConfigFile()); } public void saveChanges() { diff --git a/src/main/java/ru/bclib/config/ConfigKeeper.java b/src/main/java/ru/bclib/config/ConfigKeeper.java index 12c1e75e..6c905d05 100644 --- a/src/main/java/ru/bclib/config/ConfigKeeper.java +++ b/src/main/java/ru/bclib/config/ConfigKeeper.java @@ -22,13 +22,13 @@ public final class ConfigKeeper { private boolean changed = false; public ConfigKeeper(String modID, String group) { - this(modID, group, null); - } - - protected ConfigKeeper(String modID, String group, File path) { - this.writer = new ConfigWriter(modID, group, path); + this.writer = new ConfigWriter(modID, group); this.configObject = writer.load(); } + + File getConfigFile(){ + return this.writer.getConfigFile(); + } public void save() { if (!changed) return; diff --git a/src/main/java/ru/bclib/config/ConfigWriter.java b/src/main/java/ru/bclib/config/ConfigWriter.java index 35739a71..3320ae7b 100644 --- a/src/main/java/ru/bclib/config/ConfigWriter.java +++ b/src/main/java/ru/bclib/config/ConfigWriter.java @@ -15,20 +15,17 @@ public class ConfigWriter { private JsonObject configObject; public ConfigWriter(String modID, String configFile) { - this(modID, configFile, null); - } - - public ConfigWriter(String modID, String configFile, File configFolder) { - this.configFile = new File((configFolder == null ? GAME_CONFIG_DIR.resolve(modID).toFile() : new File( - configFolder, - modID - )), configFile + ".json"); + this.configFile = new File(GAME_CONFIG_DIR.resolve(modID).toFile() , configFile + ".json"); File parent = this.configFile.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } this.load(); } + + File getConfigFile(){ + return this.configFile; + } public JsonObject getConfig() { return configObject; diff --git a/src/main/java/ru/bclib/config/PathConfig.java b/src/main/java/ru/bclib/config/PathConfig.java index ca9d960e..b10388bf 100644 --- a/src/main/java/ru/bclib/config/PathConfig.java +++ b/src/main/java/ru/bclib/config/PathConfig.java @@ -8,13 +8,13 @@ import ru.bclib.config.ConfigKeeper.IntegerRange; import java.io.File; public class PathConfig extends Config { - - public PathConfig(String modID, String group) { - this(modID, group, null); + + public PathConfig(String modID, String group, boolean autoSync) { + super(modID, group, autoSync); } - protected PathConfig(String modID, String group, File path) { - super(modID, group, path); + public PathConfig(String modID, String group) { + super(modID, group); } @Override diff --git a/src/main/java/ru/bclib/config/SessionConfig.java b/src/main/java/ru/bclib/config/SessionConfig.java deleted file mode 100644 index 8ceff397..00000000 --- a/src/main/java/ru/bclib/config/SessionConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.bclib.config; - -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.storage.LevelStorageSource; -import ru.bclib.BCLib; - -import java.io.File; - -public class SessionConfig extends PathConfig { - private static File getWorldFolder(LevelStorageSource.LevelStorageAccess session, ServerLevel world) { - File dir = session.getDimensionPath(world.dimension()); - if (!new File(dir, "level.dat").exists()) { - dir = dir.getParentFile(); - } - return dir; - } - - public final File levelFolder; - - public SessionConfig(String modID, String group, LevelStorageSource.LevelStorageAccess session, ServerLevel world) { - super(modID, group, new File(getWorldFolder(session, world), BCLib.MOD_ID+"-config")); - - this.levelFolder = new File(getWorldFolder(session, world), BCLib.MOD_ID+"-config"); - } -} From 9b92c6cbf28f18bb7390c1f16848f89edeef9366 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 5 Aug 2021 19:09:34 +0200 Subject: [PATCH 3/6] call `sendOnEnter` --- src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java | 2 +- src/main/java/ru/bclib/mixin/client/GameMixin.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java index e5e53187..309dc291 100644 --- a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java +++ b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java @@ -140,7 +140,7 @@ public class DataExchangeAPI { /** * Automatically called before the player enters the world. *

- * This will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to* + * This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to* * {@Code true}, */ @Environment(EnvType.CLIENT) diff --git a/src/main/java/ru/bclib/mixin/client/GameMixin.java b/src/main/java/ru/bclib/mixin/client/GameMixin.java index 0753dd73..940090b7 100644 --- a/src/main/java/ru/bclib/mixin/client/GameMixin.java +++ b/src/main/java/ru/bclib/mixin/client/GameMixin.java @@ -13,6 +13,6 @@ public class GameMixin { @Inject(method="onStartGameSession", at=@At("TAIL")) public void bcliv_onStart(CallbackInfo ci){ - DataExchangeAPI.send(new HelloServer()); + DataExchangeAPI.sendOnEnter(); } } From feee3514a928e20263774722ce623a4d2ab4efd7 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 6 Aug 2021 08:23:23 +0200 Subject: [PATCH 4/6] Unpolute public API --- .../ru/bclib/api/dataexchange/Connector.java | 8 +- .../api/dataexchange/ConnectorClientside.java | 20 +-- .../api/dataexchange/ConnectorServerside.java | 20 +-- .../api/dataexchange/DataExchangeAPI.java | 106 ++------------ .../dataexchange/handler/DataExchange.java | 133 ++++++++++++++++++ 5 files changed, 172 insertions(+), 115 deletions(-) create mode 100644 src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java diff --git a/src/main/java/ru/bclib/api/dataexchange/Connector.java b/src/main/java/ru/bclib/api/dataexchange/Connector.java index 55a046a0..6f933489 100644 --- a/src/main/java/ru/bclib/api/dataexchange/Connector.java +++ b/src/main/java/ru/bclib/api/dataexchange/Connector.java @@ -1,16 +1,18 @@ package ru.bclib.api.dataexchange; +import ru.bclib.api.dataexchange.handler.DataExchange; + import java.util.Set; abstract class Connector { - protected final DataExchangeAPI api; + protected final DataExchange api; - Connector(DataExchangeAPI api) { + Connector(DataExchange api) { this.api = api; } public abstract boolean onClient(); protected Set getDescriptors(){ - return api.descriptors; + return api.getDescriptors(); } } diff --git a/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java b/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java index 2e34765f..3ed6fa1f 100644 --- a/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java +++ b/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java @@ -8,11 +8,15 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.network.FriendlyByteBuf; import ru.bclib.BCLib; +import ru.bclib.api.dataexchange.handler.DataExchange; +/** + * This is an internal class that handles a Clienetside players Connection to a Server + */ @Environment(EnvType.CLIENT) -class ConnectorClientside extends Connector { +public class ConnectorClientside extends Connector { private Minecraft client; - ConnectorClientside(DataExchangeAPI api) { + ConnectorClientside(DataExchange api) { super(api); this.client = null; } @@ -23,7 +27,7 @@ class ConnectorClientside extends Connector { return true; } - protected void onPlayInit(ClientPacketListener handler, Minecraft client){ + public void onPlayInit(ClientPacketListener handler, Minecraft client){ if (this.client!=null && this.client != client){ BCLib.LOGGER.warning("Client changed!"); } @@ -34,8 +38,8 @@ class ConnectorClientside extends Connector { }); } } - - void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client){ + + public void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client){ for(DataHandlerDescriptor desc : getDescriptors()){ if (desc.sendOnJoin){ DataHandler h = desc.JOIN_INSTANCE.get(); @@ -45,8 +49,8 @@ class ConnectorClientside extends Connector { } } } - - void onPlayDisconnect(ClientPacketListener handler, Minecraft client){ + + public void onPlayDisconnect(ClientPacketListener handler, Minecraft client){ for(DataHandlerDescriptor desc : getDescriptors()) { ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER); } @@ -57,7 +61,7 @@ class ConnectorClientside extends Connector { h.receiveFromServer(client, handler, buf, responseSender); } - void sendToServer(DataHandler h){ + public void sendToServer(DataHandler h){ if (client==null){ throw new RuntimeException("[internal error] Client not initialized yet!"); } diff --git a/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java b/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java index b5d7e920..0a42a147 100644 --- a/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java +++ b/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java @@ -7,10 +7,14 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; import ru.bclib.BCLib; +import ru.bclib.api.dataexchange.handler.DataExchange; -class ConnectorServerside extends Connector { +/** + * This is an internal class that handles a Serverside Connection to a Client-Player + */ +public class ConnectorServerside extends Connector { private MinecraftServer server; - ConnectorServerside(DataExchangeAPI api) { + ConnectorServerside(DataExchange api) { super(api); server = null; } @@ -20,7 +24,7 @@ class ConnectorServerside extends Connector { return false; } - protected void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server){ + public void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server){ if (this.server!=null && this.server != server){ BCLib.LOGGER.warning("Server changed!"); } @@ -31,8 +35,8 @@ class ConnectorServerside extends Connector { }); } } - - void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server){ + + public void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server){ for(DataHandlerDescriptor desc : getDescriptors()){ if (desc.sendOnJoin){ DataHandler h = desc.JOIN_INSTANCE.get(); @@ -42,8 +46,8 @@ class ConnectorServerside extends Connector { } } } - - void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server){ + + public void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server){ for(DataHandlerDescriptor desc : getDescriptors()){ ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER); } @@ -54,7 +58,7 @@ class ConnectorServerside extends Connector { h.receiveFromClient(server, player, handler, buf, responseSender); } - void sendToClient(DataHandler h){ + public void sendToClient(DataHandler h){ if (server==null){ throw new RuntimeException("[internal error] Server not initialized yet!"); } diff --git a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java index 309dc291..83d8ca53 100644 --- a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java +++ b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java @@ -7,68 +7,24 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.minecraft.network.FriendlyByteBuf; +import ru.bclib.api.dataexchange.handler.DataExchange; import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; -public class DataExchangeAPI { +public class DataExchangeAPI extends DataExchange { private final static List MODS = Lists.newArrayList(); - private static DataExchangeAPI instance; - private ConnectorServerside server; - private ConnectorClientside client; - protected final Set descriptors; - private static class AutoFileSyncEntry { - public final Predicate needTransfer; - public final File fileName; + protected DataExchangeAPI() { + super((api) -> new ConnectorClientside(api), (api) -> new ConnectorServerside(api)); + } + - private AutoFileSyncEntry(Predicate needTransfer, File fileName) { - this.needTransfer = needTransfer; - this.fileName = fileName; - } - } - protected final List autoSyncFiles = new ArrayList<>(4); - - - private DataExchangeAPI(){ - descriptors = new HashSet<>(); - } - - static DataExchangeAPI getInstance(){ - if (instance==null){ - instance = new DataExchangeAPI(); - } - return instance; - } - - @Environment(EnvType.CLIENT) - private void initClientside(){ - if (client!=null) return; - client = new ConnectorClientside(this); - ClientLoginConnectionEvents.INIT.register((a, b) ->{ - System.out.println("INIT"); - }); - ClientLoginConnectionEvents.QUERY_START.register((a, b) ->{ - System.out.println("INIT"); - }); - ClientPlayConnectionEvents.INIT.register(client::onPlayInit); - ClientPlayConnectionEvents.JOIN.register(client::onPlayReady); - ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect); - } - - private void initServerSide(){ - if (server!=null) return; - server = new ConnectorServerside(this); - - ServerPlayConnectionEvents.INIT.register(server::onPlayInit); - ServerPlayConnectionEvents.JOIN.register(server::onPlayReady); - ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect); - } - /** * Register a mod to participate in the DataExchange. * @@ -91,34 +47,10 @@ public class DataExchangeAPI { * @param desc The Descriptor you want to add. */ public static void registerDescriptor(DataHandlerDescriptor desc){ - DataExchangeAPI api = DataExchangeAPI.getInstance(); + DataExchangeAPI api = DataExchange.getInstance(); api.descriptors.add(desc); } - - - /** - * Initializes all datastructures that need to exist in the client component. - *

- * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called - */ - @Environment(EnvType.CLIENT) - public static void prepareClientside(){ - DataExchangeAPI api = DataExchangeAPI.getInstance(); - api.initClientside(); - - } - - /** - * Initializes all datastructures that need to exist in the server component. - *

- * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called - */ - public static void prepareServerside(){ - DataExchangeAPI api = DataExchangeAPI.getInstance(); - api.initServerSide(); - } - - + /** * Sends the Handler. *

@@ -136,24 +68,6 @@ public class DataExchangeAPI { DataExchangeAPI.getInstance().client.sendToServer(h); } } - - /** - * Automatically called before the player enters the world. - *

- * This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to* - * {@Code true}, - */ - @Environment(EnvType.CLIENT) - public static void sendOnEnter(){ - getInstance().descriptors.forEach((desc)-> { - if (desc.sendBeforeEnter){ - DataHandler h = desc.JOIN_INSTANCE.get(); - if (!h.getOriginatesOnServer()) { - getInstance().client.sendToServer(h); - } - } - }); - } /** * Registers a File for automatic client syncing. @@ -162,6 +76,6 @@ public class DataExchangeAPI { * @param fileName The name of the File */ public static void addAutoSyncFile(Predicate needTransfer, File fileName){ - DataExchangeAPI.getInstance().autoSyncFiles.add(new AutoFileSyncEntry(needTransfer, fileName)); + DataExchangeAPI.getInstance().addAutoSyncFileData(needTransfer, fileName); } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java b/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java new file mode 100644 index 00000000..138aedc9 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java @@ -0,0 +1,133 @@ +package ru.bclib.api.dataexchange.handler; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import ru.bclib.api.dataexchange.ConnectorClientside; +import ru.bclib.api.dataexchange.ConnectorServerside; +import ru.bclib.api.dataexchange.DataExchangeAPI; +import ru.bclib.api.dataexchange.DataHandler; +import ru.bclib.api.dataexchange.DataHandlerDescriptor; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +abstract public class DataExchange { + private static class AutoFileSyncEntry { + public final Predicate needTransfer; + public final File fileName; + + private AutoFileSyncEntry(Predicate needTransfer, File fileName) { + this.needTransfer = needTransfer; + this.fileName = fileName; + } + } + + private static DataExchangeAPI instance; + protected static DataExchangeAPI getInstance(){ + if (instance==null){ + instance = new DataExchangeAPI(); + } + return instance; + } + + protected ConnectorServerside server; + protected ConnectorClientside client; + protected final Set descriptors; + protected final List autoSyncFiles = new ArrayList<>(4); + + private final Function clientSupplier; + private final Function serverSupplier; + + protected DataExchange(Function client, Function server){ + descriptors = new HashSet<>(); + this.clientSupplier = client; + this.serverSupplier = server; + } + + public Set getDescriptors() { return descriptors; } + + @Environment(EnvType.CLIENT) + protected void initClientside(){ + if (client!=null) return; + client = clientSupplier.apply(this); + ClientLoginConnectionEvents.INIT.register((a, b) ->{ + System.out.println("INIT"); + }); + ClientLoginConnectionEvents.QUERY_START.register((a, b) ->{ + System.out.println("INIT"); + }); + ClientPlayConnectionEvents.INIT.register(client::onPlayInit); + ClientPlayConnectionEvents.JOIN.register(client::onPlayReady); + ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect); + } + + protected void initServerSide(){ + if (server!=null) return; + server = serverSupplier.apply(this); + + ServerPlayConnectionEvents.INIT.register(server::onPlayInit); + ServerPlayConnectionEvents.JOIN.register(server::onPlayReady); + ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect); + } + + /** + * Initializes all datastructures that need to exist in the client component. + *

+ * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called + */ + @Environment(EnvType.CLIENT) + public void prepareClientside(){ + DataExchangeAPI api = DataExchangeAPI.getInstance(); + api.initClientside(); + + } + + /** + * Initializes all datastructures that need to exist in the server component. + *

+ * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called + */ + public static void prepareServerside(){ + DataExchange api = DataExchangeAPI.getInstance(); + api.initServerSide(); + } + + + + /** + * Automatically called before the player enters the world. + *

+ * This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to* + * {@Code true}, + */ + @Environment(EnvType.CLIENT) + public static void sendOnEnter(){ + getInstance().descriptors.forEach((desc)-> { + if (desc.sendBeforeEnter){ + DataHandler h = desc.JOIN_INSTANCE.get(); + if (!h.getOriginatesOnServer()) { + getInstance().client.sendToServer(h); + } + } + }); + } + + /** + * Registers a File for automatic client syncing. + * + * @param needTransfer If the predicate returns true, the file needs to get copied to the server. + * @param fileName The name of the File + */ + protected void addAutoSyncFileData(Predicate needTransfer, File fileName){ + autoSyncFiles.add(new AutoFileSyncEntry(needTransfer, fileName)); + } +} From 8397ef7ccab910fc4e5f2b0c899b48ab9c6d37b4 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 6 Aug 2021 09:33:11 +0200 Subject: [PATCH 5/6] Added FileHash Object --- .../ru/bclib/api/dataexchange/FileHash.java | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/main/java/ru/bclib/api/dataexchange/FileHash.java diff --git a/src/main/java/ru/bclib/api/dataexchange/FileHash.java b/src/main/java/ru/bclib/api/dataexchange/FileHash.java new file mode 100644 index 00000000..867aff49 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/FileHash.java @@ -0,0 +1,151 @@ +package ru.bclib.api.dataexchange; + +import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; +import ru.bclib.BCLib; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Objects; + +/** + * Calculates a hash based on the contents of a File. + *

+ * A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions + *

+ * You can compare instances using {@link #equals(Object)} to determine if two files are + * identical. + */ +public class FileHash { + /** + * The md5-hash of the file + */ + @NotNull + public final byte[] md5; + + /** + * The size (in bytes) of the input. + */ + public final int size; + + /** + * a value that is directly calculated from defined byte positions. + */ + public final int value; + + FileHash(byte[] md5, int size, int value) { + Objects.nonNull(md5); + this.md5 = md5; + this.size = size; + this.value = value; + } + + @Override + public String toString() { + return String.format("%08x", size) + + "-" + + String.format("%08x", value) + + "-" + + getMd5String(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FileHash that = (FileHash) o; + return size == that.size && value == that.value && Arrays.equals(md5, that.md5); + } + + @Override + public int hashCode() { + int result = Objects.hash(size, value); + result = 31 * result + Arrays.hashCode(md5); + return result; + } + + /** + * Convert the md5-hash to a human readable string + * @return The converted String + */ + public String getMd5String(){ + return toHexString(md5); + } + /** + * Serializes the Object to a buffer + * @param buf The buffer to write to + */ + public void writeString(FriendlyByteBuf buf) { + buf.writeInt(size); + buf.writeInt(value); + buf.writeByteArray(md5); + } + + /** + *Deserialize a Buffer to a new {@link FileHash}-Object + * @param buf Thea buffer to read from + * @return The received String + */ + public static FileHash readString(FriendlyByteBuf buf){ + final int size = buf.readInt(); + final int value = buf.readInt(); + final byte[] md5 = buf.readByteArray(); + + return new FileHash(md5, size, value); + } + + /** + * Converts a byte-array to a hex-string representation + * @param bytes The source array + * @return The resulting string, or an empty String if the input was {@code null} + */ + public static String toHexString(byte[] bytes) { + if (bytes==null) return ""; + + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * Create a new {@link FileHash}. + * @param file The input file + * @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are + * identical. + */ + public static FileHash createFromBinary(File file){ + if (!file.exists()) return null; + final Path path = file.toPath(); + + int size = 0; + byte[] md5 = new byte[0]; + int value = 0; + + try { + byte[] data = Files.readAllBytes(path); + + size = data.length; + + value = size>0 ? (data[size/3] | (data [size/2]<<8) | (data [size/5]<<16)) : -1; + if (size>20) value |= data[20]<<24; + + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(data); + md5 = md.digest(); + + return new FileHash(md5, size, value); + } catch (IOException e) { + BCLib.LOGGER.error("Failed to read file: " + file); + return null; + } catch (NoSuchAlgorithmException e) { + BCLib.LOGGER.error("Unable to build hash for file: " + file); + } + } +} From 119e94520c8cf2ad36df067cbfd2e1301ce670ac Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 6 Aug 2021 17:40:46 +0200 Subject: [PATCH 6/6] Compare files on client/server --- .../api/dataexchange/DataExchangeAPI.java | 68 ++++++++- .../bclib/api/dataexchange/DataHandler.java | 10 +- .../ru/bclib/api/dataexchange/FileHash.java | 83 +++++++++-- .../dataexchange/handler/DataExchange.java | 134 ++++++++++++++++-- .../api/dataexchange/handler/HelloClient.java | 68 +++++++-- .../api/dataexchange/handler/HelloServer.java | 38 ++++- src/main/java/ru/bclib/config/Config.java | 2 +- src/main/java/ru/bclib/util/Pair.java | 11 ++ src/main/java/ru/bclib/util/Triple.java | 10 ++ 9 files changed, 371 insertions(+), 53 deletions(-) create mode 100644 src/main/java/ru/bclib/util/Pair.java create mode 100644 src/main/java/ru/bclib/util/Triple.java diff --git a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java index 83d8ca53..e74a6b92 100644 --- a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java +++ b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java @@ -7,9 +7,14 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.minecraft.network.FriendlyByteBuf; +import ru.bclib.BCLib; import ru.bclib.api.dataexchange.handler.DataExchange; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -20,7 +25,10 @@ import java.util.function.Predicate; public class DataExchangeAPI extends DataExchange { private final static List MODS = Lists.newArrayList(); - protected DataExchangeAPI() { + /** + * You should never need to create a custom instance of this Object. + */ + public DataExchangeAPI() { super((api) -> new ConnectorClientside(api), (api) -> new ConnectorServerside(api)); } @@ -47,8 +55,8 @@ public class DataExchangeAPI extends DataExchange { * @param desc The Descriptor you want to add. */ public static void registerDescriptor(DataHandlerDescriptor desc){ - DataExchangeAPI api = DataExchange.getInstance(); - api.descriptors.add(desc); + DataExchange api = DataExchange.getInstance(); + api.getDescriptors().add(desc); } /** @@ -72,10 +80,58 @@ public class DataExchangeAPI extends DataExchange { /** * Registers a File for automatic client syncing. * - * @param needTransfer If the predicate returns true, the file needs to get copied to the server. + * @param modID The ID of the calling Mod * @param fileName The name of the File */ - public static void addAutoSyncFile(Predicate needTransfer, File fileName){ - DataExchangeAPI.getInstance().addAutoSyncFileData(needTransfer, fileName); + public static void addAutoSyncFile(String modID, File fileName){ + getInstance().addAutoSyncFileData(modID, fileName, false, FileHash.NEED_TRANSFER); + } + + /** + * Registers a File for automatic client syncing. + * + * @param modID The ID of the calling Mod + * @param uniqueID A unique Identifier for the File. (see {@link ru.bclib.api.dataexchange.FileHash#uniqueID} for + * Details + * @param fileName The name of the File + */ + public static void addAutoSyncFile(String modID, String uniqueID, File fileName){ + getInstance().addAutoSyncFileData(modID, uniqueID, fileName, false, FileHash.NEED_TRANSFER); + } + + /** + * Registers a File for automatic client syncing. + * + * @param modID The ID of the calling Mod + * @param fileName The name of the File + * @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the + * entire file from the client to the server. + *

+ * You should only use this option, if you need to compare parts of the file in order to decide + * If the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash} + * for comparison is sufficient. + * @param needTransfer If the predicate returns true, the file needs to get copied to the server. + */ + public static void addAutoSyncFile(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer){ + getInstance().addAutoSyncFileData(modID, fileName, requestContent, needTransfer); + } + + /** + * Registers a File for automatic client syncing. + * + * @param modID The ID of the calling Mod + * @param uniqueID A unique Identifier for the File. (see {@link ru.bclib.api.dataexchange.FileHash#uniqueID} for + * Details + * @param fileName The name of the File + * @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the + * entire file from the client to the server. + *

+ * You should only use this option, if you need to compare parts of the file in order to decide + * If the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash} + * for comparison is sufficient. + * @param needTransfer If the predicate returns true, the file needs to get copied to the server. + */ + public static void addAutoSyncFile(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer){ + getInstance().addAutoSyncFileData(modID, uniqueID, fileName, requestContent, needTransfer); } } diff --git a/src/main/java/ru/bclib/api/dataexchange/DataHandler.java b/src/main/java/ru/bclib/api/dataexchange/DataHandler.java index fb11bd25..c9912843 100644 --- a/src/main/java/ru/bclib/api/dataexchange/DataHandler.java +++ b/src/main/java/ru/bclib/api/dataexchange/DataHandler.java @@ -48,16 +48,16 @@ public abstract class DataHandler { deserializeFromIncomingData(buf, responseSender, false); server.execute(() -> runOnGameThread(null, server, false)); } - + + protected void serializeData(FriendlyByteBuf buf) { + } + protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient){ } protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient){ } - - protected void serializeData(FriendlyByteBuf buf) { - } - + final protected boolean reply(DataHandler message, MinecraftServer server){ if (lastMessageSender==null) return false; message.sendToClient(server, lastMessageSender); diff --git a/src/main/java/ru/bclib/api/dataexchange/FileHash.java b/src/main/java/ru/bclib/api/dataexchange/FileHash.java index 867aff49..44296b85 100644 --- a/src/main/java/ru/bclib/api/dataexchange/FileHash.java +++ b/src/main/java/ru/bclib/api/dataexchange/FileHash.java @@ -3,6 +3,7 @@ package ru.bclib.api.dataexchange; import net.minecraft.network.FriendlyByteBuf; import org.jetbrains.annotations.NotNull; import ru.bclib.BCLib; +import ru.bclib.api.dataexchange.handler.DataExchange; import java.io.File; import java.io.IOException; @@ -12,6 +13,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Objects; +import java.util.function.Predicate; /** * Calculates a hash based on the contents of a File. @@ -38,11 +40,47 @@ public class FileHash { */ public final int value; - FileHash(byte[] md5, int size, int value) { + /** + * A Unique ID for the referenced File. + *

+ * Files with the same {@link #modID} need to have a unique IDs. Normally the filename from {@link #FileHash(String, File, byte[], int, int)} + * is used to generated that ID, but you can directly specify one using {@link #FileHash(String, String, byte[], int, int)}. + */ + @NotNull + public final String uniqueID; + + /** + * The ID of the Mod that is registering the File + */ + @NotNull + public final String modID; + + FileHash(String modID, File file, byte[] md5, int size, int value) { + this(modID, file.getName(), md5, size, value); + } + + FileHash(String modID, String uniqueID, byte[] md5, int size, int value) { + Objects.nonNull(modID); + Objects.nonNull(uniqueID); Objects.nonNull(md5); + this.md5 = md5; this.size = size; this.value = value; + this.modID = modID; + this.uniqueID = uniqueID; + } + + private static int ERR_DOES_NOT_EXIST = -10; + private static int ERR_IO_ERROR = -20; + static FileHash createForEmpty(String modID, String uniqueID, int errCode){ + return new FileHash(modID, uniqueID, new byte[0], 0, errCode); + } + + final static DataExchange.NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content)-> !clientHash.equals(serverHash); + + public boolean noFile() { + return md5.length == 0; } @Override @@ -58,13 +96,13 @@ public class FileHash { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - FileHash that = (FileHash) o; - return size == that.size && value == that.value && Arrays.equals(md5, that.md5); + FileHash fileHash = (FileHash) o; + return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5) && uniqueID.equals(fileHash.uniqueID) && modID.equals(fileHash.modID); } @Override public int hashCode() { - int result = Objects.hash(size, value); + int result = Objects.hash(size, value, uniqueID, modID); result = 31 * result + Arrays.hashCode(md5); return result; } @@ -76,14 +114,17 @@ public class FileHash { public String getMd5String(){ return toHexString(md5); } + /** * Serializes the Object to a buffer * @param buf The buffer to write to */ - public void writeString(FriendlyByteBuf buf) { + public void serialize(FriendlyByteBuf buf) { buf.writeInt(size); buf.writeInt(value); buf.writeByteArray(md5); + DataHandler.writeString(buf, modID); + DataHandler.writeString(buf, uniqueID); } /** @@ -91,12 +132,14 @@ public class FileHash { * @param buf Thea buffer to read from * @return The received String */ - public static FileHash readString(FriendlyByteBuf buf){ + public static FileHash deserialize(FriendlyByteBuf buf){ final int size = buf.readInt(); final int value = buf.readInt(); final byte[] md5 = buf.readByteArray(); + final String modID = DataHandler.readString(buf); + final String uniqueID = DataHandler.readString(buf); - return new FileHash(md5, size, value); + return new FileHash(modID, uniqueID, md5, size, value); } /** @@ -116,12 +159,28 @@ public class FileHash { /** * Create a new {@link FileHash}. + *

+ * Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}. + * @param modID ID of the calling Mod * @param file The input file + * * @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are - * identical. + * identical. Will return {@code null} when an error occurs or the File does not exist */ - public static FileHash createFromBinary(File file){ - if (!file.exists()) return null; + public static FileHash create(String modID, File file){ + return create(modID, file, file.getName()); + } + + /** + * Create a new {@link FileHash}. + * @param modID ID of the calling Mod + * @param file The input file + * @param uniqueID The unique ID that is used for this File (see {@link FileHash#uniqueID} for Details. + * @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are + * identical. Will return {@code null} when an error occurs or the File does not exist + */ + public static FileHash create(String modID, File file, String uniqueID){ + if (!file.exists()) return createForEmpty(modID, uniqueID, ERR_DOES_NOT_EXIST); final Path path = file.toPath(); int size = 0; @@ -140,12 +199,14 @@ public class FileHash { md.update(data); md5 = md.digest(); - return new FileHash(md5, size, value); + return new FileHash(modID, uniqueID, md5, size, value); } catch (IOException e) { BCLib.LOGGER.error("Failed to read file: " + file); return null; } catch (NoSuchAlgorithmException e) { BCLib.LOGGER.error("Unable to build hash for file: " + file); } + + return createForEmpty(modID, uniqueID, ERR_IO_ERROR); } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java b/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java index 138aedc9..c0b87c11 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java @@ -5,29 +5,117 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.network.FriendlyByteBuf; import ru.bclib.api.dataexchange.ConnectorClientside; import ru.bclib.api.dataexchange.ConnectorServerside; import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandlerDescriptor; +import ru.bclib.api.dataexchange.FileHash; +import ru.bclib.util.Pair; +import ru.bclib.util.Triple; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.Supplier; abstract public class DataExchange { - private static class AutoFileSyncEntry { - public final Predicate needTransfer; - public final File fileName; + @FunctionalInterface + public interface NeedTransferPredicate { + public boolean test(FileHash clientHash, FileHash serverHash, byte[] content); + } - private AutoFileSyncEntry(Predicate needTransfer, File fileName) { + final static class AutoSyncTriple extends Triple{ + public AutoSyncTriple(FileHash first, byte[] second, AutoFileSyncEntry third) { + super(first, second, third); + } + } + static class AutoFileSyncEntry { + public final NeedTransferPredicate needTransfer; + public final File fileName; + public final String modID; + public final String uniqueID; + public final boolean requestContent; + private FileHash hash; + + AutoFileSyncEntry(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) { + this(modID, fileName.getName(), fileName, requestContent, needTransfer); + } + + AutoFileSyncEntry(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) { this.needTransfer = needTransfer; this.fileName = fileName; + this.modID = modID; + this.uniqueID = uniqueID; + this.requestContent = requestContent; + } + + public FileHash getFileHash(){ + if (hash == null) { + hash = FileHash.create(modID, fileName, uniqueID); + } + return hash; + } + + public byte[] getContent(){ + if (!fileName.exists()) return new byte[0]; + final Path path = fileName.toPath(); + + try { + return Files.readAllBytes(path); + } catch (IOException e) { + + } + return new byte[0]; + } + + + public void serialize(FriendlyByteBuf buf){ + getFileHash().serialize(buf); + buf.writeBoolean(requestContent); + + if (requestContent) { + byte[] content = getContent(); + buf.writeInt(content.length); + buf.writeByteArray(content); + } + } + + public static AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf){ + Pair e = deserialize(buf); + AutoFileSyncEntry match = findMatching(e.first); + return new AutoSyncTriple(e.first, e.second, match); + } + + public static Pair deserialize(FriendlyByteBuf buf){ + FileHash hash = FileHash.deserialize(buf); + boolean withContent = buf.readBoolean(); + byte[] data = null; + if (withContent) { + int size = buf.readInt(); + data = buf.readByteArray(size); + } + + return new Pair(hash, data); + } + + public static AutoFileSyncEntry findMatching(FileHash hash){ + return DataExchange + .getInstance() + .autoSyncFiles + .stream() + .filter(asf -> asf.modID.equals(hash.modID) && asf.uniqueID.equals(hash.uniqueID)) + .findFirst() + .orElse(null); } } @@ -85,8 +173,8 @@ abstract public class DataExchange { * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called */ @Environment(EnvType.CLIENT) - public void prepareClientside(){ - DataExchangeAPI api = DataExchangeAPI.getInstance(); + public static void prepareClientside(){ + DataExchange api = DataExchange.getInstance(); api.initClientside(); } @@ -97,7 +185,7 @@ abstract public class DataExchange { * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called */ public static void prepareServerside(){ - DataExchange api = DataExchangeAPI.getInstance(); + DataExchange api = DataExchange.getInstance(); api.initServerSide(); } @@ -124,10 +212,36 @@ abstract public class DataExchange { /** * Registers a File for automatic client syncing. * + * @param modID The ID of the calling Mod * @param needTransfer If the predicate returns true, the file needs to get copied to the server. * @param fileName The name of the File + * @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the + * entire file from the client to the server. + *

+ * You should only use this option, if you need to compare parts of the file in order to decide + * If the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash} + * for comparison is sufficient. */ - protected void addAutoSyncFileData(Predicate needTransfer, File fileName){ - autoSyncFiles.add(new AutoFileSyncEntry(needTransfer, fileName)); + protected void addAutoSyncFileData(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer){ + autoSyncFiles.add(new AutoFileSyncEntry(modID, fileName, requestContent, needTransfer)); + } + + /** + * Registers a File for automatic client syncing. + * + * @param modID The ID of the calling Mod + * @param uniqueID A unique Identifier for the File. (see {@link ru.bclib.api.dataexchange.FileHash#uniqueID} for + * Details + * @param needTransfer If the predicate returns true, the file needs to get copied to the server. + * @param fileName The name of the File + * @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the + * entire file from the client to the server. + *

+ * You should only use this option, if you need to compare parts of the file in order to decide + * If the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash} + * for comparison is sufficient. + */ + protected void addAutoSyncFileData(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer){ + autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer)); } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/HelloClient.java b/src/main/java/ru/bclib/api/dataexchange/handler/HelloClient.java index 6bdccd89..758ed4ae 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/HelloClient.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/HelloClient.java @@ -7,7 +7,6 @@ import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.worldselection.EditWorldScreen; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; @@ -15,10 +14,12 @@ import ru.bclib.BCLib; import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandlerDescriptor; +import ru.bclib.api.dataexchange.FileHash; import ru.bclib.api.datafixer.DataFixerAPI; -import ru.bclib.gui.screens.ConfirmFixScreen; import ru.bclib.gui.screens.WarnBCLibVersionMismatch; +import ru.bclib.util.Triple; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,6 +27,11 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.function.Consumer; +/** + * Sent from the Server to the Client. + *

+ * For Details refer to {@link HelloServer} + */ public class HelloClient extends DataHandler { public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_client"), HelloClient::new, false, false); @@ -45,20 +51,56 @@ public class HelloClient extends DataHandler { static String getBCLibVersion(){ return getModVersion(BCLib.MOD_ID); } + + @Override + protected void serializeData(FriendlyByteBuf buf) { + final List mods = DataExchangeAPI.registeredMods(); + + //write BCLibVersion (=protocol version) + buf.writeInt(DataFixerAPI.getModVersion(getBCLibVersion())); + + //write Plugin Versions + buf.writeInt(mods.size()); + for (String modID : mods) { + writeString(buf, modID); + buf.writeInt(DataFixerAPI.getModVersion(getModVersion(modID))); + } + + //send config Data + final List autoSyncFiles = DataExchange.getInstance().autoSyncFiles; + buf.writeInt(autoSyncFiles.size()); + for (DataExchange.AutoFileSyncEntry entry : autoSyncFiles) { + System.out.println("Serializing " + entry.getFileHash()); + entry.serialize(buf); + } + } String bclibVersion ="0.0.0"; Map modVersion = new HashMap<>(); + List autoSyncedFiles = null; @Override protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) { + //read BCLibVersion (=protocol version) bclibVersion = DataFixerAPI.getModVersion(buf.readInt()); + + //read Plugin Versions modVersion = new HashMap<>(); - int count = buf.readInt(); - for (int i=0; i< count; i++){ + for (int i=0; i< count; i++) { String id = readString(buf); String version = DataFixerAPI.getModVersion(buf.readInt()); modVersion.put(id, version); } + + //read config Data + count = buf.readInt(); + autoSyncedFiles = new ArrayList<>(count); + for (int i=0; i< count; i++) { + System.out.println("Deserializing "); + DataExchange.AutoSyncTriple t = DataExchange.AutoFileSyncEntry.deserializeAndMatch(buf); + autoSyncedFiles.add(t); + System.out.println(t.first); + } } @Override @@ -75,17 +117,13 @@ public class HelloClient extends DataHandler { String ver = getModVersion(e.getKey()); BCLib.LOGGER.info(" - " + e.getKey() + " (client="+ver+", server="+ver+")"); } - } - - @Override - protected void serializeData(FriendlyByteBuf buf) { - final List mods = DataExchangeAPI.registeredMods(); - buf.writeInt(DataFixerAPI.getModVersion(getBCLibVersion())); - - buf.writeInt(mods.size()); - for (String modID : mods) { - writeString(buf, modID); - buf.writeInt(DataFixerAPI.getModVersion(getModVersion(modID))); + + for (DataExchange.AutoSyncTriple e : autoSyncedFiles) { + if (e.third == null) { + BCLib.LOGGER.info(" - File " + e.first.modID + "." + e.first.uniqueID + ": Does not exist on client."); + } else if (e.third.needTransfer.test(e.third.getFileHash(), e.first, e.second)) { + BCLib.LOGGER.info(" - File " + e.first.modID + "." + e.first.uniqueID + ": Needs Transfer"); + } } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java b/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java index 481139d5..411de2b1 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java @@ -10,6 +10,34 @@ import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandlerDescriptor; import ru.bclib.api.datafixer.DataFixerAPI; +/** + * This message is sent once a player enters the world. It initiates a sequence of Messages that will sync files between both + * client and server. + *

+ * + * + * Server + * + * Client + * + * + * + * Player enters World + * + * + * + * <-- + * {@link HelloServer} + * Sends the current BLib-Version installed on the Client + * + * + * {@link HelloClient} + * --> + * + * Sends the current BClIb-Version and the Version of all Plugins on the Server + * + * + */ public class HelloServer extends DataHandler { public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_server"), HelloServer::new, false, true); @@ -17,6 +45,11 @@ public class HelloServer extends DataHandler { public HelloServer() { super(DESCRIPTOR.IDENTIFIER, false); } + + @Override + protected void serializeData(FriendlyByteBuf buf) { + buf.writeInt(DataFixerAPI.getModVersion(HelloClient.getBCLibVersion())); + } @Override protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) { @@ -29,9 +62,4 @@ public class HelloServer extends DataHandler { BCLib.LOGGER.info("Received Hello from Client. (server="+localBclibVersion+", client="+bclibVersion+")"); reply(new HelloClient(), server); } - - @Override - protected void serializeData(FriendlyByteBuf buf) { - buf.writeInt(DataFixerAPI.getModVersion(HelloClient.getBCLibVersion())); - } } diff --git a/src/main/java/ru/bclib/config/Config.java b/src/main/java/ru/bclib/config/Config.java index d49d802f..d2280eb9 100644 --- a/src/main/java/ru/bclib/config/Config.java +++ b/src/main/java/ru/bclib/config/Config.java @@ -26,7 +26,7 @@ public abstract class Config { this.registerEntries(); this.autoSync = autoSync; - DataExchangeAPI.addAutoSyncFile((content)->{return false;}, keeper.getConfigFile()); + DataExchangeAPI.addAutoSyncFile(BCLib.MOD_ID, "CONFIG_"+modID+"_"+group, keeper.getConfigFile()); } public void saveChanges() { diff --git a/src/main/java/ru/bclib/util/Pair.java b/src/main/java/ru/bclib/util/Pair.java new file mode 100644 index 00000000..61ceb6eb --- /dev/null +++ b/src/main/java/ru/bclib/util/Pair.java @@ -0,0 +1,11 @@ +package ru.bclib.util; + +public class Pair { + public final A first; + public final B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } +} diff --git a/src/main/java/ru/bclib/util/Triple.java b/src/main/java/ru/bclib/util/Triple.java new file mode 100644 index 00000000..df88cf75 --- /dev/null +++ b/src/main/java/ru/bclib/util/Triple.java @@ -0,0 +1,10 @@ +package ru.bclib.util; + +public class Triple extends Pair{ + public final C third; + + public Triple(A first, B second, C third) { + super(first, second); + this.third = third; + } +}