*WIP* Prepared Folder Syncing - Filelist exchange

This commit is contained in:
Frank 2021-08-15 15:06:36 +02:00
parent 5df6de1e3a
commit 1f239baeb9
9 changed files with 323 additions and 190 deletions

View file

@ -11,7 +11,7 @@ loader_version= 0.11.6
fabric_version = 0.36.1+1.17
# Mod Properties
mod_version = 0.4.0
mod_version = 0.4.1
maven_group = ru.bclib
archives_base_name = bclib

View file

@ -39,6 +39,7 @@ public class BCLib implements ModInitializer {
TagAPI.init();
CraftingRecipes.init();
WorldDataAPI.registerModCache(MOD_ID);
DataExchangeAPI.registerMod(MOD_ID);
DataFixerAPI.registerPatch(() -> new BCLibPatch());
DataExchangeAPI.registerDescriptors(List.of(
HelloClient.DESCRIPTOR,

View file

@ -8,7 +8,6 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.datafixer.DataFixerAPI;
import java.io.File;
@ -20,7 +19,7 @@ import java.util.function.Consumer;
/**
* Mod-specifix data-storage for a world.
*
* <p>
* This class provides the ability for mod to store persistent data inside a world. The Storage for the world is
* currently initialized as part of the {@link DataFixerAPI} in {@link DataFixerAPI#fixData(LevelStorageAccess, boolean, Consumer)}
* or {@link DataFixerAPI#initializeWorldData(File, boolean)}
@ -32,45 +31,47 @@ public class WorldDataAPI {
public static void load(File dataDir) {
WorldDataAPI.dataDir = dataDir;
MODS.stream().parallel().forEach(modID -> {
File file = new File(dataDir, modID + ".nbt");
CompoundTag root = new CompoundTag();
if (file.exists()) {
try {
root = NbtIo.readCompressed(file);
}
catch (IOException e) {
BCLib.LOGGER.error("World data loading failed", e);
}
}
else {
Optional<ModContainer> optional = FabricLoader.getInstance().getModContainer(modID);
if (optional.isPresent()) {
ModContainer modContainer = optional.get();
if (BCLib.isDevEnvironment()) {
root.putString("version", "255.255.9999");
MODS.stream()
.parallel()
.forEach(modID -> {
File file = new File(dataDir, modID + ".nbt");
CompoundTag root = new CompoundTag();
if (file.exists()) {
try {
root = NbtIo.readCompressed(file);
}
else {
root.putString("version", modContainer.getMetadata().getVersion().toString());
catch (IOException e) {
BCLib.LOGGER.error("World data loading failed", e);
}
}
else {
Optional<ModContainer> optional = FabricLoader.getInstance()
.getModContainer(modID);
if (optional.isPresent()) {
ModContainer modContainer = optional.get();
if (BCLib.isDevEnvironment()) {
root.putString("version", "255.255.9999");
}
else {
root.putString("version", modContainer.getMetadata()
.getVersion()
.toString());
}
saveFile(modID);
}
saveFile(modID);
}
}
TAGS.put(modID, root);
});
TAGS.put(modID, root);
});
}
/**
* Register mod cache, world cache is located in world data folder.
* <p>
* Will also register the Mod for the {@link DataExchangeAPI} using {@link DataExchangeAPI#registerMod(String)}
*
* @param modID - {@link String} modID.
*/
public static void registerModCache(String modID) {
MODS.add(modID);
DataExchangeAPI.registerMod(modID);
}
/**
@ -117,7 +118,7 @@ public class WorldDataAPI {
*/
public static void saveFile(String modID) {
try {
if (!dataDir.exists()){
if (!dataDir.exists()) {
dataDir.mkdirs();
}
NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt"));

View file

@ -13,157 +13,181 @@ import java.util.List;
import java.util.function.BiConsumer;
public class DataExchangeAPI extends DataExchange {
private final static List<String> MODS = Lists.newArrayList();
private final static List<String> MODS = Lists.newArrayList();
/**
* You should never need to create a custom instance of this Object.
*/
public DataExchangeAPI() {
super();
}
/**
* You should never need to create a custom instance of this Object.
*/
public DataExchangeAPI() {
super();
}
@Environment(EnvType.CLIENT)
protected ConnectorClientside clientSupplier(DataExchange api) {
return new ConnectorClientside(api);
}
@Environment(EnvType.CLIENT)
protected ConnectorClientside clientSupplier(DataExchange api) {
return new ConnectorClientside(api);
}
protected ConnectorServerside serverSupplier(DataExchange api) {
return new ConnectorServerside(api);
}
protected ConnectorServerside serverSupplier(DataExchange api) {
return new ConnectorServerside(api);
}
/**
* Register a mod to participate in the DataExchange.
*
* @param modID - {@link String} modID.
*/
public static void registerMod(String modID) {
if (!MODS.contains(modID))
MODS.add(modID);
}
/**
* Register a mod to participate in the DataExchange.
*
* @param modID - {@link String} modID.
*/
public static void registerMod(String modID) {
if (!MODS.contains(modID)) MODS.add(modID);
}
/**
* Returns the IDs of all registered Mods.
*
* @return List of modIDs
*/
public static List<String> registeredMods() {
return MODS;
}
/**
* Returns the IDs of all registered Mods.
*
* @return List of modIDs
*/
public static List<String> registeredMods() {
return MODS;
}
/**
* Add a new Descriptor for a {@link DataHandler}.
*
* @param desc The Descriptor you want to add.
*/
public static void registerDescriptor(DataHandlerDescriptor desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors().add(desc);
}
/**
* Add a new Descriptor for a {@link DataHandler}.
*
* @param desc The Descriptor you want to add.
*/
public static void registerDescriptor(DataHandlerDescriptor desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors()
.add(desc);
}
/**
* Bulk-Add a Descriptors for your {@link DataHandler}-Objects.
*
* @param desc The Descriptors you want to add.
*/
public static void registerDescriptors(List<DataHandlerDescriptor> desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors().addAll(desc);
}
/**
* Bulk-Add a Descriptors for your {@link DataHandler}-Objects.
*
* @param desc The Descriptors you want to add.
*/
public static void registerDescriptors(List<DataHandlerDescriptor> desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors()
.addAll(desc);
}
/**
* Sends the Handler.
* <p>
* Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server
* to the client (if {@code true}) or the other way around.
* <p>
* The method {@link DataHandler#serializeData(FriendlyByteBuf)} is called just before the data is sent. You should
* use this method to add the Data you need to the communication.
*
* @param h The Data that you want to send
*/
public static void send(DataHandler h) {
if (h.getOriginatesOnServer()) {
DataExchangeAPI.getInstance().server.sendToClient(h);
} else {
DataExchangeAPI.getInstance().client.sendToServer(h);
}
}
/**
* Sends the Handler.
* <p>
* Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server
* to the client (if {@code true}) or the other way around.
* <p>
* The method {@link DataHandler#serializeData(FriendlyByteBuf)} is called just before the data is sent. You should
* use this method to add the Data you need to the communication.
*
* @param h The Data that you want to send
*/
public static void send(DataHandler h) {
if (h.getOriginatesOnServer()) {
DataExchangeAPI.getInstance().server.sendToClient(h);
}
else {
DataExchangeAPI.getInstance().client.sendToServer(h);
}
}
/**
* Registers a File for automatic client syncing.
*
* @param modID The ID of the calling Mod
* @param fileName The name of the File
*/
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 fileName The name of the File
*/
public static void addAutoSyncFile(String modID, File fileName) {
getInstance().addAutoSyncFileData(modID, fileName, false, FileHash.NEED_TRANSFER);
}
/**
* Registers a File for automatic client syncing.
* <p>
* The file is synced of the {@link FileHash} on client and server are not equal. This method will not copy the
* configs content from the client to the server.
*
* @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.
* <p>
* The file is synced of the {@link FileHash} on client and server are not equal. This method will not copy the
* configs content from the client to the server.
*
* @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.
* <p>
* 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 modID The ID of the calling Mod
* @param fileName The name of the File
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
*/
public static void addAutoSyncFile(String modID, File fileName, NeedTransferPredicate needTransfer) {
getInstance().addAutoSyncFileData(modID, fileName, true, needTransfer);
}
/**
* Registers a File for automatic client syncing.
* <p>
* 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 modID The ID of the calling Mod
* @param fileName The name of the File
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
*/
public static void addAutoSyncFile(String modID, File fileName, NeedTransferPredicate needTransfer) {
getInstance().addAutoSyncFileData(modID, fileName, true, needTransfer);
}
/**
* Registers a File for automatic client syncing.
* <p>
* 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 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 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, NeedTransferPredicate needTransfer) {
getInstance().addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer);
}
/**
* Registers a File for automatic client syncing.
* <p>
* 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 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 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, NeedTransferPredicate needTransfer) {
getInstance().addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer);
}
/**
* Register a function that is called whenever the client receives a file from the server and replaced toe local
* file with the new content.
* <p>
* This callback is usefull if you need to reload the new content before the game is quit.
* @param callback A Function that receives the AutoSyncID as well as the Filename.
*/
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
onWriteCallbacks.add(callback);
}
/**
* Register a function that is called whenever the client receives a file from the server and replaced toe local
* file with the new content.
* <p>
* This callback is usefull if you need to reload the new content before the game is quit.
*
* @param callback A Function that receives the AutoSyncID as well as the Filename.
*/
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
onWriteCallbacks.add(callback);
}
static {
addOnWriteCallback(Config::reloadSyncedConfig);
}
/**
* Returns the sync-folder for a given Mod.
* <p>
* BCLib will ensure that the contents of sync-folder on the client is the same as the one on the server.
*
* @param modID ID of the Mod
* @return The path to the sync-folder
*/
public static File getSyncFolder(String modID) {
File fl = SYNC_FOLDER.resolve(modID.replace(".", "-")
.replace(":", "-")
.replace("\\", "-")
.replace("/", "-"))
.toFile();
if (!fl.exists()){
fl.mkdirs();
}
return fl;
}
static {
addOnWriteCallback(Config::reloadSyncedConfig);
}
}

View file

@ -104,6 +104,7 @@ class AutoFileSyncEntry extends AutoSyncID {
return data;
}
public static AutoFileSyncEntry findMatching(FileHash hash) {
return findMatching(hash.modID, hash.uniqueID);
}
@ -115,7 +116,7 @@ class AutoFileSyncEntry extends AutoSyncID {
public static AutoFileSyncEntry findMatching(String modID, String uniqueID) {
return DataExchange
.getInstance()
.autoSyncFiles
.getAutoSyncFiles()
.stream()
.filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID))
.findFirst()

View file

@ -4,14 +4,17 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
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.config.Configs;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -19,6 +22,12 @@ import java.util.Set;
import java.util.function.BiConsumer;
abstract public class DataExchange {
public final static Path SYNC_FOLDER = FabricLoader.getInstance()
.getGameDir()
.resolve("bclib-sync")
.toAbsolutePath();
public final static String SYNC_FOLDER_ID = "BCLIB-SYNC";
@FunctionalInterface
public interface NeedTransferPredicate {
public boolean test(FileHash clientHash, FileHash serverHash, FileContentWrapper content);
@ -54,7 +63,9 @@ abstract public class DataExchange {
protected ConnectorServerside server;
protected ConnectorClientside client;
protected final Set<DataHandlerDescriptor> descriptors;
protected final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
private final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
private boolean didLoadSyncFolder = false;
abstract protected ConnectorClientside clientSupplier(DataExchange api);
abstract protected ConnectorServerside serverSupplier(DataExchange api);
@ -65,6 +76,10 @@ abstract public class DataExchange {
public Set<DataHandlerDescriptor> getDescriptors() { return descriptors; }
public List<AutoFileSyncEntry> getAutoSyncFiles(){
return autoSyncFiles;
}
@Environment(EnvType.CLIENT)
protected void initClientside(){
if (client!=null) return;
@ -177,4 +192,43 @@ abstract public class DataExchange {
static void didReceiveFile(AutoSyncID aid, File file){
onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file));
}
private List<String> syncFolderContent;
protected List<String> getSyncFolderContent(){
if (syncFolderContent==null){
return new ArrayList<>(0);
}
return syncFolderContent;
}
//we call this from HelloServer to prepare transfer
protected void loadSyncFolder() {
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offserSyncFolder", true))
{
final File syncPath = SYNC_FOLDER.toFile();
if (!syncPath.exists()) {
syncPath.mkdirs();
}
if (syncFolderContent == null) {
syncFolderContent = new ArrayList<>(8);
addFilesForSyncFolder(syncPath);
}
}
}
private void addFilesForSyncFolder(File path){
for (final File f : path.listFiles()) {
if (f.isDirectory()) {
addFilesForSyncFolder(f);
} else if (f.isFile()) {
if (!f.getName().startsWith(".")) {
Path p = f.toPath();
p = SYNC_FOLDER.relativize(p);
syncFolderContent.add(p.toString());
}
}
}
}
}

View file

@ -77,11 +77,11 @@ public class HelloClient extends DataHandler {
buf.writeInt(0);
}
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offerConfigs", true)) {
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offerFiles", true)) {
//do only include files that exist on the server
final List<AutoFileSyncEntry> existingAutoSyncFiles = DataExchange
.getInstance()
.autoSyncFiles
.getAutoSyncFiles()
.stream()
.filter(e -> e.fileName.exists())
.collect(Collectors.toList());
@ -93,9 +93,22 @@ public class HelloClient extends DataHandler {
BCLib.LOGGER.info(" - Offering File " + entry);
}
} else {
BCLib.LOGGER.info("Server will not offer Configs.");
BCLib.LOGGER.info("Server will not offer Files.");
buf.writeInt(0);
}
//for the moment this is only hardcoded for the sync-folder offered by BCLIB, but it can be extended in future
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offserSyncFolder", true)) {
buf.writeInt(1); //currently we do only sync a single folder
writeString(buf, DataExchange.SYNC_FOLDER_ID); //the UID of the Folder
final List<String> fileNames = DataExchange.getInstance().getSyncFolderContent();
buf.writeInt(fileNames.size());
fileNames.forEach(fl -> writeString(buf, fl));
} else {
BCLib.LOGGER.info("Server will not offer Sync Folders.");
buf.writeInt(0);
}
Configs.MAIN_CONFIG.saveChanges();
}
String bclibVersion ="0.0.0";
@ -124,6 +137,25 @@ public class HelloClient extends DataHandler {
autoSyncedFiles.add(t);
//System.out.println(t.first);
}
//since this version we also send the sync folders
if (DataFixerAPI.isLargerOrEqualVersion(bclibVersion, "0.4.1")) {
final int folderCount = buf.readInt();
for (int i=0; i<folderCount; i++){
final String folderID = readString(buf);
final int entries = buf.readInt();
List<String> files = new ArrayList<>(entries);
for (int j=0; j<entries; j++){
files.add(readString(buf));
}
if (folderID.equals(DataExchange.SYNC_FOLDER_ID)) {
//TODO: implement the syncing here
} else {
BCLib.LOGGER.warning("Unknown Sync-Folder '"+folderID+"'");
}
}
}
}

View file

@ -84,5 +84,7 @@ public class HelloServer extends DataHandler {
} else {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
}
DataExchange.getInstance().loadSyncFolder();
}
}

View file

@ -537,4 +537,22 @@ public class DataFixerAPI {
return String.format(Locale.ROOT, "%d.%d.%d", a, b, c);
}
/**
* {@code true} if the version v1 is larger than v2
* @param v1 A Version string
* @param v2 Another Version string
* @return v1 &gt; v2
*/
public static boolean isLargerVersion(String v1, String v2){
return getModVersion(v1) > getModVersion(v2);
}
/**
* {@code true} if the version v1 is larger or equal v2
* @param v1 A Version string
* @param v2 Another Version string
* @return v1 &ge; v2
*/
public static boolean isLargerOrEqualVersion(String v1, String v2){
return getModVersion(v1) >= getModVersion(v2);
}
}