Merge branch 'feature/networking' of github.com-quiqueck:quiqueck/BCLib into feature/networking
This commit is contained in:
commit
ae344c48ac
21 changed files with 744 additions and 185 deletions
|
@ -1,16 +1,18 @@
|
||||||
package ru.bclib.api.dataexchange;
|
package ru.bclib.api.dataexchange;
|
||||||
|
|
||||||
|
import ru.bclib.api.dataexchange.handler.DataExchange;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
abstract class Connector {
|
abstract class Connector {
|
||||||
protected final DataExchangeAPI api;
|
protected final DataExchange api;
|
||||||
|
|
||||||
Connector(DataExchangeAPI api) {
|
Connector(DataExchange api) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
}
|
}
|
||||||
public abstract boolean onClient();
|
public abstract boolean onClient();
|
||||||
|
|
||||||
protected Set<DataHandlerDescriptor> getDescriptors(){
|
protected Set<DataHandlerDescriptor> getDescriptors(){
|
||||||
return api.descriptors;
|
return api.getDescriptors();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,15 @@ import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import ru.bclib.BCLib;
|
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)
|
@Environment(EnvType.CLIENT)
|
||||||
class ConnectorClientside extends Connector {
|
public class ConnectorClientside extends Connector {
|
||||||
private Minecraft client;
|
private Minecraft client;
|
||||||
ConnectorClientside(DataExchangeAPI api) {
|
ConnectorClientside(DataExchange api) {
|
||||||
super(api);
|
super(api);
|
||||||
this.client = null;
|
this.client = null;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +27,7 @@ class ConnectorClientside extends Connector {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPlayInit(ClientPacketListener handler, Minecraft client){
|
public void onPlayInit(ClientPacketListener handler, Minecraft client){
|
||||||
if (this.client!=null && this.client != client){
|
if (this.client!=null && this.client != client){
|
||||||
BCLib.LOGGER.warning("Client changed!");
|
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()){
|
for(DataHandlerDescriptor desc : getDescriptors()){
|
||||||
if (desc.sendOnJoin){
|
if (desc.sendOnJoin){
|
||||||
DataHandler h = desc.JOIN_INSTANCE.get();
|
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()) {
|
for(DataHandlerDescriptor desc : getDescriptors()) {
|
||||||
ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER);
|
ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +61,7 @@ class ConnectorClientside extends Connector {
|
||||||
h.receiveFromServer(client, handler, buf, responseSender);
|
h.receiveFromServer(client, handler, buf, responseSender);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendToServer(DataHandler h){
|
public void sendToServer(DataHandler h){
|
||||||
if (client==null){
|
if (client==null){
|
||||||
throw new RuntimeException("[internal error] Client not initialized yet!");
|
throw new RuntimeException("[internal error] Client not initialized yet!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,14 @@ import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||||
import ru.bclib.BCLib;
|
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;
|
private MinecraftServer server;
|
||||||
ConnectorServerside(DataExchangeAPI api) {
|
ConnectorServerside(DataExchange api) {
|
||||||
super(api);
|
super(api);
|
||||||
server = null;
|
server = null;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +24,7 @@ class ConnectorServerside extends Connector {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server){
|
public void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server){
|
||||||
if (this.server!=null && this.server != server){
|
if (this.server!=null && this.server != server){
|
||||||
BCLib.LOGGER.warning("Server changed!");
|
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()){
|
for(DataHandlerDescriptor desc : getDescriptors()){
|
||||||
if (desc.sendOnJoin){
|
if (desc.sendOnJoin){
|
||||||
DataHandler h = desc.JOIN_INSTANCE.get();
|
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()){
|
for(DataHandlerDescriptor desc : getDescriptors()){
|
||||||
ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER);
|
ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER);
|
||||||
}
|
}
|
||||||
|
@ -54,7 +58,7 @@ class ConnectorServerside extends Connector {
|
||||||
h.receiveFromClient(server, player, handler, buf, responseSender);
|
h.receiveFromClient(server, player, handler, buf, responseSender);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendToClient(DataHandler h){
|
public void sendToClient(DataHandler h){
|
||||||
if (server==null){
|
if (server==null){
|
||||||
throw new RuntimeException("[internal error] Server not initialized yet!");
|
throw new RuntimeException("[internal error] Server not initialized yet!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,56 +6,33 @@ import net.fabricmc.api.Environment;
|
||||||
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents;
|
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents;
|
||||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
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.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
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<String> MODS = Lists.newArrayList();
|
private final static List<String> MODS = Lists.newArrayList();
|
||||||
private static DataExchangeAPI instance;
|
|
||||||
private ConnectorServerside server;
|
/**
|
||||||
private ConnectorClientside client;
|
* You should never need to create a custom instance of this Object.
|
||||||
protected final Set<DataHandlerDescriptor> descriptors;
|
*/
|
||||||
|
public DataExchangeAPI() {
|
||||||
|
super((api) -> new ConnectorClientside(api), (api) -> new ConnectorServerside(api));
|
||||||
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.
|
* Register a mod to participate in the DataExchange.
|
||||||
*
|
*
|
||||||
|
@ -78,34 +55,10 @@ public class DataExchangeAPI {
|
||||||
* @param desc The Descriptor you want to add.
|
* @param desc The Descriptor you want to add.
|
||||||
*/
|
*/
|
||||||
public static void registerDescriptor(DataHandlerDescriptor desc){
|
public static void registerDescriptor(DataHandlerDescriptor desc){
|
||||||
DataExchangeAPI api = DataExchangeAPI.getInstance();
|
DataExchange api = DataExchange.getInstance();
|
||||||
api.descriptors.add(desc);
|
api.getDescriptors().add(desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes all datastructures that need to exist in the client component.
|
|
||||||
* <p>
|
|
||||||
* 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.
|
|
||||||
* <p>
|
|
||||||
* 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.
|
* Sends the Handler.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -123,22 +76,62 @@ public class DataExchangeAPI {
|
||||||
DataExchangeAPI.getInstance().client.sendToServer(h);
|
DataExchangeAPI.getInstance().client.sendToServer(h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically called before the player enters the world.
|
* Registers a File for automatic client syncing.
|
||||||
* <p>
|
*
|
||||||
* This will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
|
* @param modID The ID of the calling Mod
|
||||||
* {@Code true},
|
* @param fileName The name of the File
|
||||||
*/
|
*/
|
||||||
@Environment(EnvType.CLIENT)
|
public static void addAutoSyncFile(String modID, File fileName){
|
||||||
public static void sendOnEnter(){
|
getInstance().addAutoSyncFileData(modID, fileName, false, FileHash.NEED_TRANSFER);
|
||||||
getInstance().descriptors.forEach((desc)-> {
|
}
|
||||||
if (desc.sendBeforeEnter){
|
|
||||||
DataHandler h = desc.JOIN_INSTANCE.get();
|
/**
|
||||||
if (!h.getOriginatesOnServer()) {
|
* Registers a File for automatic client syncing.
|
||||||
getInstance().client.sendToServer(h);
|
*
|
||||||
}
|
* @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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,16 +48,16 @@ public abstract class DataHandler {
|
||||||
deserializeFromIncomingData(buf, responseSender, false);
|
deserializeFromIncomingData(buf, responseSender, false);
|
||||||
server.execute(() -> runOnGameThread(null, server, false));
|
server.execute(() -> runOnGameThread(null, server, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void serializeData(FriendlyByteBuf buf) {
|
||||||
|
}
|
||||||
|
|
||||||
protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient){
|
protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient){
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient){
|
protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient){
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void serializeData(FriendlyByteBuf buf) {
|
|
||||||
}
|
|
||||||
|
|
||||||
final protected boolean reply(DataHandler message, MinecraftServer server){
|
final protected boolean reply(DataHandler message, MinecraftServer server){
|
||||||
if (lastMessageSender==null) return false;
|
if (lastMessageSender==null) return false;
|
||||||
message.sendToClient(server, lastMessageSender);
|
message.sendToClient(server, lastMessageSender);
|
||||||
|
|
212
src/main/java/ru/bclib/api/dataexchange/FileHash.java
Normal file
212
src/main/java/ru/bclib/api/dataexchange/FileHash.java
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a hash based on the contents of a File.
|
||||||
|
* <p>
|
||||||
|
* A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
|
||||||
|
* <p>
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Unique ID for the referenced File.
|
||||||
|
* <p>
|
||||||
|
* 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
|
||||||
|
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 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, uniqueID, modID);
|
||||||
|
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 serialize(FriendlyByteBuf buf) {
|
||||||
|
buf.writeInt(size);
|
||||||
|
buf.writeInt(value);
|
||||||
|
buf.writeByteArray(md5);
|
||||||
|
DataHandler.writeString(buf, modID);
|
||||||
|
DataHandler.writeString(buf, uniqueID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Deserialize a Buffer to a new {@link FileHash}-Object
|
||||||
|
* @param buf Thea buffer to read from
|
||||||
|
* @return The received String
|
||||||
|
*/
|
||||||
|
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(modID, uniqueID, 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}.
|
||||||
|
* <p>
|
||||||
|
* 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. Will return {@code null} when an error occurs or the File does not exist
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
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(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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
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<FileHash, byte[], DataExchange.AutoFileSyncEntry>{
|
||||||
|
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<FileHash, byte[]> e = deserialize(buf);
|
||||||
|
AutoFileSyncEntry match = findMatching(e.first);
|
||||||
|
return new AutoSyncTriple(e.first, e.second, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pair<FileHash, byte[]> 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<DataHandlerDescriptor> descriptors;
|
||||||
|
protected final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
|
||||||
|
|
||||||
|
private final Function<DataExchange, ConnectorClientside> clientSupplier;
|
||||||
|
private final Function<DataExchange, ConnectorServerside> serverSupplier;
|
||||||
|
|
||||||
|
protected DataExchange(Function<DataExchange, ConnectorClientside> client, Function<DataExchange, ConnectorServerside> server){
|
||||||
|
descriptors = new HashSet<>();
|
||||||
|
this.clientSupplier = client;
|
||||||
|
this.serverSupplier = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<DataHandlerDescriptor> 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.fabricmc.loader.api.ModContainer;
|
import net.fabricmc.loader.api.ModContainer;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.client.gui.screens.worldselection.EditWorldScreen;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
@ -15,10 +14,12 @@ import ru.bclib.BCLib;
|
||||||
import ru.bclib.api.dataexchange.DataExchangeAPI;
|
import ru.bclib.api.dataexchange.DataExchangeAPI;
|
||||||
import ru.bclib.api.dataexchange.DataHandler;
|
import ru.bclib.api.dataexchange.DataHandler;
|
||||||
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
|
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
|
||||||
|
import ru.bclib.api.dataexchange.FileHash;
|
||||||
import ru.bclib.api.datafixer.DataFixerAPI;
|
import ru.bclib.api.datafixer.DataFixerAPI;
|
||||||
import ru.bclib.gui.screens.ConfirmFixScreen;
|
|
||||||
import ru.bclib.gui.screens.WarnBCLibVersionMismatch;
|
import ru.bclib.gui.screens.WarnBCLibVersionMismatch;
|
||||||
|
import ru.bclib.util.Triple;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -26,6 +27,11 @@ import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent from the Server to the Client.
|
||||||
|
* <p>
|
||||||
|
* For Details refer to {@link HelloServer}
|
||||||
|
*/
|
||||||
public class HelloClient extends DataHandler {
|
public class HelloClient extends DataHandler {
|
||||||
public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_client"), HelloClient::new, false, false);
|
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(){
|
static String getBCLibVersion(){
|
||||||
return getModVersion(BCLib.MOD_ID);
|
return getModVersion(BCLib.MOD_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void serializeData(FriendlyByteBuf buf) {
|
||||||
|
final List<String> 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<DataExchange.AutoFileSyncEntry> 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";
|
String bclibVersion ="0.0.0";
|
||||||
Map<String, String> modVersion = new HashMap<>();
|
Map<String, String> modVersion = new HashMap<>();
|
||||||
|
List<DataExchange.AutoSyncTriple> autoSyncedFiles = null;
|
||||||
@Override
|
@Override
|
||||||
protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) {
|
protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) {
|
||||||
|
//read BCLibVersion (=protocol version)
|
||||||
bclibVersion = DataFixerAPI.getModVersion(buf.readInt());
|
bclibVersion = DataFixerAPI.getModVersion(buf.readInt());
|
||||||
|
|
||||||
|
//read Plugin Versions
|
||||||
modVersion = new HashMap<>();
|
modVersion = new HashMap<>();
|
||||||
|
|
||||||
int count = buf.readInt();
|
int count = buf.readInt();
|
||||||
for (int i=0; i< count; i++){
|
for (int i=0; i< count; i++) {
|
||||||
String id = readString(buf);
|
String id = readString(buf);
|
||||||
String version = DataFixerAPI.getModVersion(buf.readInt());
|
String version = DataFixerAPI.getModVersion(buf.readInt());
|
||||||
modVersion.put(id, version);
|
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
|
@Override
|
||||||
|
@ -75,17 +117,13 @@ public class HelloClient extends DataHandler {
|
||||||
String ver = getModVersion(e.getKey());
|
String ver = getModVersion(e.getKey());
|
||||||
BCLib.LOGGER.info(" - " + e.getKey() + " (client="+ver+", server="+ver+")");
|
BCLib.LOGGER.info(" - " + e.getKey() + " (client="+ver+", server="+ver+")");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
for (DataExchange.AutoSyncTriple e : autoSyncedFiles) {
|
||||||
@Override
|
if (e.third == null) {
|
||||||
protected void serializeData(FriendlyByteBuf buf) {
|
BCLib.LOGGER.info(" - File " + e.first.modID + "." + e.first.uniqueID + ": Does not exist on client.");
|
||||||
final List<String> mods = DataExchangeAPI.registeredMods();
|
} else if (e.third.needTransfer.test(e.third.getFileHash(), e.first, e.second)) {
|
||||||
buf.writeInt(DataFixerAPI.getModVersion(getBCLibVersion()));
|
BCLib.LOGGER.info(" - File " + e.first.modID + "." + e.first.uniqueID + ": Needs Transfer");
|
||||||
|
}
|
||||||
buf.writeInt(mods.size());
|
|
||||||
for (String modID : mods) {
|
|
||||||
writeString(buf, modID);
|
|
||||||
buf.writeInt(DataFixerAPI.getModVersion(getModVersion(modID)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,34 @@ import ru.bclib.api.dataexchange.DataHandler;
|
||||||
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
|
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
|
||||||
import ru.bclib.api.datafixer.DataFixerAPI;
|
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.
|
||||||
|
* <p>
|
||||||
|
* <tab<le>
|
||||||
|
* <tr>
|
||||||
|
* <th>Server</th>
|
||||||
|
* <th></th>
|
||||||
|
* <th>Client</th>
|
||||||
|
* <th></th>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td colspan="4">Player enters World</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td></td>
|
||||||
|
* <td><--</td>
|
||||||
|
* <td>{@link HelloServer}</td>
|
||||||
|
* <td>Sends the current BLib-Version installed on the Client</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>{@link HelloClient}</td>
|
||||||
|
* <td>--></td>
|
||||||
|
* <td></td>
|
||||||
|
* <td>Sends the current BClIb-Version and the Version of all Plugins on the Server</td>
|
||||||
|
* </tr>
|
||||||
|
* </table>
|
||||||
|
*/
|
||||||
public class HelloServer extends DataHandler {
|
public class HelloServer extends DataHandler {
|
||||||
public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_server"), HelloServer::new, false, true);
|
public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_server"), HelloServer::new, false, true);
|
||||||
|
|
||||||
|
@ -19,6 +47,11 @@ public class HelloServer extends DataHandler {
|
||||||
public HelloServer() {
|
public HelloServer() {
|
||||||
super(DESCRIPTOR.IDENTIFIER, false);
|
super(DESCRIPTOR.IDENTIFIER, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void serializeData(FriendlyByteBuf buf) {
|
||||||
|
buf.writeInt(DataFixerAPI.getModVersion(HelloClient.getBCLibVersion()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) {
|
protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) {
|
||||||
|
@ -31,9 +64,4 @@ public class HelloServer extends DataHandler {
|
||||||
BCLib.LOGGER.info("Received Hello from Client. (server="+localBclibVersion+", client="+bclibVersion+")");
|
BCLib.LOGGER.info("Received Hello from Client. (server="+localBclibVersion+", client="+bclibVersion+")");
|
||||||
reply(new HelloClient(), server);
|
reply(new HelloClient(), server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void serializeData(FriendlyByteBuf buf) {
|
|
||||||
buf.writeInt(DataFixerAPI.getModVersion(HelloClient.getBCLibVersion()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,6 +254,13 @@ public class DataFixerAPI {
|
||||||
players.parallelStream().forEach((file) -> fixPlayer(profile, state, file));
|
players.parallelStream().forEach((file) -> fixPlayer(profile, state, file));
|
||||||
|
|
||||||
fixLevel(profile, state, new File(dir, "level.dat"));
|
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) {
|
if (!state.didFail) {
|
||||||
profile.markApplied();
|
profile.markApplied();
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ru.bclib.api.datafixer;
|
||||||
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import ru.bclib.api.WorldDataAPI;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -11,10 +12,11 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
class MigrationProfile {
|
public class MigrationProfile {
|
||||||
final Set<String> mods;
|
final Set<String> mods;
|
||||||
final Map<String, String> idReplacements;
|
final Map<String, String> idReplacements;
|
||||||
final List<PatchFunction<CompoundTag, Boolean>> levelPatchers;
|
final List<PatchFunction<CompoundTag, Boolean>> levelPatchers;
|
||||||
|
final List<Patch> worldDataPatchers;
|
||||||
|
|
||||||
private final CompoundTag config;
|
private final CompoundTag config;
|
||||||
|
|
||||||
|
@ -28,6 +30,7 @@ class MigrationProfile {
|
||||||
|
|
||||||
HashMap<String, String> replacements = new HashMap<String, String>();
|
HashMap<String, String> replacements = new HashMap<String, String>();
|
||||||
List<PatchFunction<CompoundTag, Boolean>> levelPatches = new LinkedList<>();
|
List<PatchFunction<CompoundTag, Boolean>> levelPatches = new LinkedList<>();
|
||||||
|
List<Patch> worldDataPatches = new LinkedList<>();
|
||||||
for (String modID : mods) {
|
for (String modID : mods) {
|
||||||
Patch.getALL()
|
Patch.getALL()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -37,6 +40,8 @@ class MigrationProfile {
|
||||||
replacements.putAll(patch.getIDReplacements());
|
replacements.putAll(patch.getIDReplacements());
|
||||||
if (patch.getLevelDatPatcher()!=null)
|
if (patch.getLevelDatPatcher()!=null)
|
||||||
levelPatches.add(patch.getLevelDatPatcher());
|
levelPatches.add(patch.getLevelDatPatcher());
|
||||||
|
if (patch.getWorldDataPatcher()!=null)
|
||||||
|
worldDataPatches.add(patch);
|
||||||
DataFixerAPI.LOGGER.info("Applying " + patch);
|
DataFixerAPI.LOGGER.info("Applying " + patch);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -47,6 +52,7 @@ class MigrationProfile {
|
||||||
|
|
||||||
this.idReplacements = Collections.unmodifiableMap(replacements);
|
this.idReplacements = Collections.unmodifiableMap(replacements);
|
||||||
this.levelPatchers = Collections.unmodifiableList(levelPatches);
|
this.levelPatchers = Collections.unmodifiableList(levelPatches);
|
||||||
|
this.worldDataPatchers = Collections.unmodifiableList(worldDataPatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
final public void markApplied() {
|
final public void markApplied() {
|
||||||
|
@ -66,7 +72,7 @@ class MigrationProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasAnyFixes() {
|
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) {
|
public boolean replaceStringFromIDs(@NotNull CompoundTag tag, @NotNull String key) {
|
||||||
|
@ -87,8 +93,18 @@ class MigrationProfile {
|
||||||
public boolean patchLevelDat(@NotNull CompoundTag level) throws PatchDidiFailException {
|
public boolean patchLevelDat(@NotNull CompoundTag level) throws PatchDidiFailException {
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
for (PatchFunction<CompoundTag, Boolean> f : levelPatchers) {
|
for (PatchFunction<CompoundTag, Boolean> f : levelPatchers) {
|
||||||
changed |= f.apply(level);
|
changed |= f.apply(level, this);
|
||||||
}
|
}
|
||||||
return changed;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,14 +124,25 @@ public abstract class Patch {
|
||||||
* Return a {@link PatchFunction} that is called with the content of <i>level.dat</i>.
|
* Return a {@link PatchFunction} that is called with the content of <i>level.dat</i>.
|
||||||
* <p>
|
* <p>
|
||||||
* The function needs to return {@code true}, if changes were made to the data.
|
* 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.
|
* The default implementation of this method returns null.
|
||||||
*
|
*
|
||||||
* @return The returned function is called a {@code CompoundTag} that contains the
|
* @return {@code true} if changes were applied and we need to save the data
|
||||||
* contents of <i>level.dat</i>
|
|
||||||
*/
|
*/
|
||||||
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() { return null; }
|
public PatchFunction<CompoundTag, Boolean> 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<CompoundTag, Boolean> getWorldDataPatcher() { return null; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates ready to use data for all currently registered patches. The list of
|
* Generates ready to use data for all currently registered patches. The list of
|
||||||
|
|
|
@ -2,5 +2,5 @@ package ru.bclib.api.datafixer;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface PatchFunction<T, R> {
|
public interface PatchFunction<T, R> {
|
||||||
R apply(T t) throws PatchDidiFailException;
|
R apply(T t, MigrationProfile profile) throws PatchDidiFailException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ru.bclib.config;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import ru.bclib.BCLib;
|
import ru.bclib.BCLib;
|
||||||
|
import ru.bclib.api.dataexchange.DataExchangeAPI;
|
||||||
import ru.bclib.config.ConfigKeeper.BooleanEntry;
|
import ru.bclib.config.ConfigKeeper.BooleanEntry;
|
||||||
import ru.bclib.config.ConfigKeeper.Entry;
|
import ru.bclib.config.ConfigKeeper.Entry;
|
||||||
import ru.bclib.config.ConfigKeeper.FloatEntry;
|
import ru.bclib.config.ConfigKeeper.FloatEntry;
|
||||||
|
@ -13,16 +14,19 @@ import java.io.File;
|
||||||
|
|
||||||
public abstract class Config {
|
public abstract class Config {
|
||||||
protected final ConfigKeeper keeper;
|
protected final ConfigKeeper keeper;
|
||||||
|
protected final boolean autoSync;
|
||||||
protected abstract void registerEntries();
|
protected abstract void registerEntries();
|
||||||
|
|
||||||
public Config(String modID, String group) {
|
protected Config(String modID, String group) {
|
||||||
this(modID, group, null);
|
this(modID, group, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Config(String modID, String group, File path) {
|
protected Config(String modID, String group, boolean autoSync) {
|
||||||
this.keeper = new ConfigKeeper(modID, group, path);
|
this.keeper = new ConfigKeeper(modID, group);
|
||||||
this.registerEntries();
|
this.registerEntries();
|
||||||
|
this.autoSync = autoSync;
|
||||||
|
|
||||||
|
DataExchangeAPI.addAutoSyncFile(BCLib.MOD_ID, "CONFIG_"+modID+"_"+group, keeper.getConfigFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveChanges() {
|
public void saveChanges() {
|
||||||
|
|
|
@ -22,13 +22,13 @@ public final class ConfigKeeper {
|
||||||
private boolean changed = false;
|
private boolean changed = false;
|
||||||
|
|
||||||
public ConfigKeeper(String modID, String group) {
|
public ConfigKeeper(String modID, String group) {
|
||||||
this(modID, group, null);
|
this.writer = new ConfigWriter(modID, group);
|
||||||
}
|
|
||||||
|
|
||||||
protected ConfigKeeper(String modID, String group, File path) {
|
|
||||||
this.writer = new ConfigWriter(modID, group, path);
|
|
||||||
this.configObject = writer.load();
|
this.configObject = writer.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File getConfigFile(){
|
||||||
|
return this.writer.getConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
if (!changed) return;
|
if (!changed) return;
|
||||||
|
|
|
@ -15,20 +15,17 @@ public class ConfigWriter {
|
||||||
private JsonObject configObject;
|
private JsonObject configObject;
|
||||||
|
|
||||||
public ConfigWriter(String modID, String configFile) {
|
public ConfigWriter(String modID, String configFile) {
|
||||||
this(modID, configFile, null);
|
this.configFile = new File(GAME_CONFIG_DIR.resolve(modID).toFile() , configFile + ".json");
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
File parent = this.configFile.getParentFile();
|
File parent = this.configFile.getParentFile();
|
||||||
if (!parent.exists()) {
|
if (!parent.exists()) {
|
||||||
parent.mkdirs();
|
parent.mkdirs();
|
||||||
}
|
}
|
||||||
this.load();
|
this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File getConfigFile(){
|
||||||
|
return this.configFile;
|
||||||
|
}
|
||||||
|
|
||||||
public JsonObject getConfig() {
|
public JsonObject getConfig() {
|
||||||
return configObject;
|
return configObject;
|
||||||
|
|
|
@ -8,13 +8,13 @@ import ru.bclib.config.ConfigKeeper.IntegerRange;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class PathConfig extends Config {
|
public class PathConfig extends Config {
|
||||||
|
|
||||||
public PathConfig(String modID, String group) {
|
public PathConfig(String modID, String group, boolean autoSync) {
|
||||||
this(modID, group, null);
|
super(modID, group, autoSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PathConfig(String modID, String group, File path) {
|
public PathConfig(String modID, String group) {
|
||||||
super(modID, group, path);
|
super(modID, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,6 +13,6 @@ public class GameMixin {
|
||||||
|
|
||||||
@Inject(method="onStartGameSession", at=@At("TAIL"))
|
@Inject(method="onStartGameSession", at=@At("TAIL"))
|
||||||
public void bcliv_onStart(CallbackInfo ci){
|
public void bcliv_onStart(CallbackInfo ci){
|
||||||
DataExchangeAPI.send(new HelloServer());
|
DataExchangeAPI.sendOnEnter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/main/java/ru/bclib/util/Pair.java
Normal file
11
src/main/java/ru/bclib/util/Pair.java
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package ru.bclib.util;
|
||||||
|
|
||||||
|
public class Pair <A, B>{
|
||||||
|
public final A first;
|
||||||
|
public final B second;
|
||||||
|
|
||||||
|
public Pair(A first, B second) {
|
||||||
|
this.first = first;
|
||||||
|
this.second = second;
|
||||||
|
}
|
||||||
|
}
|
10
src/main/java/ru/bclib/util/Triple.java
Normal file
10
src/main/java/ru/bclib/util/Triple.java
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package ru.bclib.util;
|
||||||
|
|
||||||
|
public class Triple <A, B, C> extends Pair<A, B>{
|
||||||
|
public final C third;
|
||||||
|
|
||||||
|
public Triple(A first, B second, C third) {
|
||||||
|
super(first, second);
|
||||||
|
this.third = third;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue