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 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; abstract public class DataExchange { @FunctionalInterface public interface NeedTransferPredicate { public boolean test(FileHash clientHash, FileHash serverHash, byte[] content); } 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); } } 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 static void prepareClientside(){ DataExchange api = DataExchange.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 = DataExchange.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 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(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)); } }