From 123a5e2dc4e4496efd885f891d15f6d55b363b1b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 17 Aug 2021 11:57:40 +0200 Subject: [PATCH] Refactored AutoSync --- src/main/java/ru/bclib/BCLib.java | 8 +- .../api/dataexchange/DataExchangeAPI.java | 16 +- .../bclib/api/dataexchange/SyncFileHash.java | 6 +- .../dataexchange/handler/DataExchange.java | 338 +----------------- .../{ => autosync}/AutoFileSyncEntry.java | 51 ++- .../handler/autosync/AutoSync.java | 210 +++++++++++ .../handler/{ => autosync}/AutoSyncID.java | 2 +- .../{ => autosync}/FileContentWrapper.java | 2 +- .../handler/{ => autosync}/HelloClient.java | 75 ++-- .../handler/{ => autosync}/HelloServer.java | 92 ++--- .../handler/{ => autosync}/RequestFiles.java | 40 ++- .../handler/{ => autosync}/SendFiles.java | 55 +-- .../autosync/SyncFolderDescriptor.java | 206 +++++++++++ src/main/java/ru/bclib/config/Config.java | 4 +- .../java/ru/bclib/config/ConfigKeeper.java | 2 +- src/main/java/ru/bclib/config/Configs.java | 1 - src/main/java/ru/bclib/util/PathUtil.java | 20 ++ 17 files changed, 610 insertions(+), 518 deletions(-) rename src/main/java/ru/bclib/api/dataexchange/handler/{ => autosync}/AutoFileSyncEntry.java (78%) create mode 100644 src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSync.java rename src/main/java/ru/bclib/api/dataexchange/handler/{ => autosync}/AutoSyncID.java (98%) rename src/main/java/ru/bclib/api/dataexchange/handler/{ => autosync}/FileContentWrapper.java (96%) rename src/main/java/ru/bclib/api/dataexchange/handler/{ => autosync}/HelloClient.java (82%) rename src/main/java/ru/bclib/api/dataexchange/handler/{ => autosync}/HelloServer.java (56%) rename src/main/java/ru/bclib/api/dataexchange/handler/{ => autosync}/RequestFiles.java (74%) rename src/main/java/ru/bclib/api/dataexchange/handler/{ => autosync}/SendFiles.java (84%) create mode 100644 src/main/java/ru/bclib/api/dataexchange/handler/autosync/SyncFolderDescriptor.java diff --git a/src/main/java/ru/bclib/BCLib.java b/src/main/java/ru/bclib/BCLib.java index 6ddbe5c7..92201974 100644 --- a/src/main/java/ru/bclib/BCLib.java +++ b/src/main/java/ru/bclib/BCLib.java @@ -7,10 +7,10 @@ import net.minecraft.resources.ResourceLocation; import ru.bclib.api.TagAPI; import ru.bclib.api.WorldDataAPI; import ru.bclib.api.dataexchange.DataExchangeAPI; -import ru.bclib.api.dataexchange.handler.HelloClient; -import ru.bclib.api.dataexchange.handler.HelloServer; -import ru.bclib.api.dataexchange.handler.RequestFiles; -import ru.bclib.api.dataexchange.handler.SendFiles; +import ru.bclib.api.dataexchange.handler.autosync.HelloClient; +import ru.bclib.api.dataexchange.handler.autosync.HelloServer; +import ru.bclib.api.dataexchange.handler.autosync.RequestFiles; +import ru.bclib.api.dataexchange.handler.autosync.SendFiles; import ru.bclib.api.datafixer.DataFixerAPI; import ru.bclib.config.Configs; import ru.bclib.recipes.CraftingRecipes; diff --git a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java index 3f43de8e..0af4a224 100644 --- a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java +++ b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java @@ -4,7 +4,9 @@ import com.google.common.collect.Lists; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.network.FriendlyByteBuf; -import ru.bclib.api.dataexchange.handler.AutoSyncID; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate; +import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID; import ru.bclib.api.dataexchange.handler.DataExchange; import ru.bclib.config.Config; @@ -98,7 +100,7 @@ public class DataExchangeAPI extends DataExchange { * @param fileName The name of the File */ public static void addAutoSyncFile(String modID, File fileName) { - getInstance().addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER); + AutoSync.addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER); } /** @@ -113,7 +115,7 @@ public class DataExchangeAPI extends DataExchange { * @param fileName The name of the File */ public static void addAutoSyncFile(String modID, String uniqueID, File fileName) { - getInstance().addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER); + AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER); } /** @@ -131,7 +133,7 @@ public class DataExchangeAPI extends DataExchange { * @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); + AutoSync.addAutoSyncFileData(modID, fileName, true, needTransfer); } /** @@ -151,7 +153,7 @@ public class DataExchangeAPI extends DataExchange { * @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); + AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer); } /** @@ -163,7 +165,7 @@ public class DataExchangeAPI extends DataExchange { * @param callback A Function that receives the AutoSyncID as well as the Filename. */ public static void addOnWriteCallback(BiConsumer callback) { - onWriteCallbacks.add(callback); + AutoSync.addOnWriteCallback(callback); } /** @@ -175,7 +177,7 @@ public class DataExchangeAPI extends DataExchange { * @return The path to the sync-folder */ public static File getModSyncFolder(String modID) { - File fl = SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-") + File fl = AutoSync.SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-") .replace(":", "-") .replace("\\", "-") .replace("/", "-")) diff --git a/src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java b/src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java index af8761e5..81a3c853 100644 --- a/src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java +++ b/src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java @@ -1,8 +1,8 @@ package ru.bclib.api.dataexchange; import net.minecraft.network.FriendlyByteBuf; -import ru.bclib.api.dataexchange.handler.AutoSyncID; -import ru.bclib.api.dataexchange.handler.DataExchange; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate; +import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID; import java.io.File; import java.util.Objects; @@ -36,7 +36,7 @@ public class SyncFileHash extends AutoSyncID { } - final static DataExchange.NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content)-> !clientHash.equals(serverHash); + final static NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content)-> !clientHash.equals(serverHash); @Override public String toString() { 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 bbd9d9c1..469a1047 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java @@ -4,255 +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 net.minecraft.network.FriendlyByteBuf; -import org.jetbrains.annotations.NotNull; -import ru.bclib.BCLib; 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.api.dataexchange.SyncFileHash; -import ru.bclib.api.dataexchange.handler.AutoSyncID.ForDirectFileRequest; -import ru.bclib.config.Configs; -import ru.bclib.util.PathUtil; -import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.function.BiConsumer; -import java.util.stream.Stream; abstract public class DataExchange { - public final static SyncFolderDescriptor SYNC_FOLDER = new SyncFolderDescriptor("BCLIB-SYNC", FabricLoader.getInstance() - .getGameDir() - .resolve("bclib-sync") - .normalize() - .toAbsolutePath(), true); - final List syncFolderDescriptions = Arrays.asList(SYNC_FOLDER); - - public static class SyncFolderDescriptor { - static class SubFile { - public final String relPath; - public final FileHash hash; - - - SubFile(String relPath, FileHash hash) { - this.relPath = relPath; - this.hash = hash; - } - - @Override - public String toString() { - return relPath; - } - - public void serialize(FriendlyByteBuf buf) { - DataHandler.writeString(buf, relPath); - hash.serialize(buf); - } - - public static SubFile deserialize(FriendlyByteBuf buf) { - final String relPath = DataHandler.readString(buf); - FileHash hash = FileHash.deserialize(buf); - return new SubFile(relPath, hash); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o instanceof String) return relPath.equals(o); - if (!(o instanceof SubFile)) return false; - SubFile subFile = (SubFile) o; - return relPath.equals(subFile.relPath); - } - - @Override - public int hashCode() { - return relPath.hashCode(); - } - } - - @NotNull - public final String folderID; - public final boolean removeAdditionalFiles; - @NotNull - public final Path localFolder; - - private List fileCache; - - SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) { - this.removeAdditionalFiles = removeAdditionalFiles; - this.folderID = folderID; - this.localFolder = localFolder; - fileCache = null; - } - - @Override - public String toString() { - return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + (fileCache == null ? "?" : fileCache.size()) + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o instanceof String) { - return folderID.equals(o); - } - if (o instanceof AutoSyncID.ForDirectFileRequest) { - return folderID.equals(((ForDirectFileRequest) o).uniqueID); - } - if (!(o instanceof SyncFolderDescriptor)) return false; - SyncFolderDescriptor that = (SyncFolderDescriptor) o; - return folderID.equals(that.folderID); - } - - @Override - public int hashCode() { - return folderID.hashCode(); - } - - public int fileCount() { - return fileCache == null ? 0 : fileCache.size(); - } - - public void invalidateCache() { - fileCache = null; - } - - public void loadCache() { - if (fileCache == null) { - fileCache = new ArrayList<>(8); - PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(localFolder.relativize(p) - .toString(), FileHash.create(p.toFile())))); - - /*//this tests if we can trick the system to load files that are not beneath the base-folder - if (!BCLib.isClient()) { - fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile()))); - }*/ - } - } - - public void serialize(FriendlyByteBuf buf) { - final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "debugHashes", false); - loadCache(); - - DataHandler.writeString(buf, folderID); - buf.writeBoolean(removeAdditionalFiles); - buf.writeInt(fileCache.size()); - fileCache.forEach(fl -> { - BCLib.LOGGER.info(" - " + fl.relPath); - if (debugHashes) { - BCLib.LOGGER.info(" " + fl.hash); - } - fl.serialize(buf); - }); - } - - public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) { - final String folderID = DataHandler.readString(buf); - final boolean remAddFiles = buf.readBoolean(); - final int count = buf.readInt(); - SyncFolderDescriptor localDescriptor = DataExchange.getInstance() - .getSyncFolderDescriptor(folderID); - - final SyncFolderDescriptor desc; - if (localDescriptor != null) { - desc = new SyncFolderDescriptor(folderID, localDescriptor.localFolder, remAddFiles); - desc.fileCache = new ArrayList<>(count); - } - else { - BCLib.LOGGER.warning(BCLib.isClient() ? "Client" : "Server" + " does not know Sync-Folder ID '" + folderID + "'"); - desc = null; - } - - for (int i = 0; i < count; i++) { - SubFile relPath = SubFile.deserialize(buf); - if (desc != null) desc.fileCache.add(relPath); - } - - return desc; - } - - //Note: make sure loadCache was called before using this - boolean hasRelativeFile(String relFile) { - return fileCache.stream() - .filter(sf -> sf.equals(relFile)) - .findFirst() - .isPresent(); - } - - //Note: make sure loadCache was called before using this - boolean hasRelativeFile(SubFile subFile) { - return hasRelativeFile(subFile.relPath); - } - - //Note: make sure loadCache was called before using this - SubFile getLocalSubFile(String relPath) { - return fileCache.stream() - .filter(sf -> sf.relPath.equals(relPath)) - .findFirst() - .orElse(null); - } - - Stream relativeFilesStream() { - loadCache(); - return fileCache.stream(); - } - - public Path mapAbsolute(String relPath) { - return this.localFolder.resolve(relPath) - .normalize(); - } - - public Path mapAbsolute(SubFile subFile) { - return this.localFolder.resolve(subFile.relPath) - .normalize(); - } - - public boolean acceptChildElements(Path absPath) { - return PathUtil.isChildOf(this.localFolder, absPath); - } - - public boolean acceptChildElements(SubFile subFile) { - return acceptChildElements(mapAbsolute(subFile)); - } - - public boolean discardChildElements(SubFile subFile) { - return !acceptChildElements(subFile); - } - } - - @FunctionalInterface - public interface NeedTransferPredicate { - public boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content); - } - - protected final static List> onWriteCallbacks = new ArrayList<>(2); - - final static class AutoSyncTriple { - public final SyncFileHash serverHash; - public final byte[] serverContent; - public final AutoFileSyncEntry localMatch; - - public AutoSyncTriple(SyncFileHash serverHash, byte[] serverContent, AutoFileSyncEntry localMatch) { - this.serverHash = serverHash; - this.serverContent = serverContent; - this.localMatch = localMatch; - } - - @Override - public String toString() { - return serverHash.modID + "." + serverHash.uniqueID; - } - } private static DataExchangeAPI instance; @@ -266,7 +28,7 @@ abstract public class DataExchange { protected ConnectorServerside server; protected ConnectorClientside client; protected final Set descriptors; - private final List autoSyncFiles = new ArrayList<>(4); + private boolean didLoadSyncFolder = false; @@ -280,9 +42,6 @@ abstract public class DataExchange { public Set getDescriptors() { return descriptors; } - public List getAutoSyncFiles() { - return autoSyncFiles; - } @Environment(EnvType.CLIENT) protected void initClientside() { @@ -344,101 +103,6 @@ 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 SyncFileHash} - * 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 SyncFileHash#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 SyncFileHash} - * 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)); - } - /** - * Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem. - *

- * This is the place where reload Code should go. - * - * @param aid The ID of the received File - * @param file The location of the FIle on the client - */ - static void didReceiveFile(AutoSyncID aid, File file) { - onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file)); - } - - private List syncFolderContent; - - protected List 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, "offersSyncFolders", true)) { - syncFolderDescriptions.forEach(desc -> desc.loadCache()); - } - } - - protected static SyncFolderDescriptor getSyncFolderDescriptor(String folderID) { - return ((DataExchange) getInstance()).syncFolderDescriptions.stream() - .filter(d -> d.equals(folderID)) - .findFirst() - .orElse(null); - } - - protected static Path localBasePathForFolderID(String folderID) { - final SyncFolderDescriptor desc = getSyncFolderDescriptor(folderID); - if (desc != null) { - return desc.localFolder; - } - else { - BCLib.LOGGER.warning("Unknown Sync-Folder ID '" + folderID + "'"); - return null; - } - } - - protected void registerSyncFolder(String folderID, Path localBaseFolder, boolean removeAdditionalFiles) { - localBaseFolder = localBaseFolder.normalize(); - if (PathUtil.isChildOf(PathUtil.GAME_FOLDER, localBaseFolder)) { - final SyncFolderDescriptor desc = new SyncFolderDescriptor(folderID, localBaseFolder, removeAdditionalFiles); - if (this.syncFolderDescriptions.contains(desc)) { - BCLib.LOGGER.warning("Tried to override Folder Sync '" + folderID + "' again."); - } - else { - this.syncFolderDescriptions.add(desc); - } - } - else { - BCLib.LOGGER.error(localBaseFolder + " (from " + folderID + ") is outside the game directory " + PathUtil.GAME_FOLDER + ". Sync is not allowed."); - } - } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/AutoFileSyncEntry.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoFileSyncEntry.java similarity index 78% rename from src/main/java/ru/bclib/api/dataexchange/handler/AutoFileSyncEntry.java rename to src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoFileSyncEntry.java index bf6002b5..92abed37 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/AutoFileSyncEntry.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoFileSyncEntry.java @@ -1,10 +1,10 @@ -package ru.bclib.api.dataexchange.handler; +package ru.bclib.api.dataexchange.handler.autosync; import net.minecraft.network.FriendlyByteBuf; import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.SyncFileHash; -import ru.bclib.api.dataexchange.handler.DataExchange.SyncFolderDescriptor; -import ru.bclib.api.dataexchange.handler.DataExchange.SyncFolderDescriptor.SubFile; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate; +import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile; import ru.bclib.util.Pair; import ru.bclib.util.Triple; @@ -14,7 +14,7 @@ import java.nio.file.Files; import java.nio.file.Path; class AutoFileSyncEntry extends AutoSyncID { - static class ForDirectFileRequest extends AutoFileSyncEntry { + static class ForDirectFileRequest extends AutoFileSyncEntry { final File relFile; ForDirectFileRequest(String syncID, File relFile, File absFile) { @@ -30,10 +30,10 @@ class AutoFileSyncEntry extends AutoSyncID { return res; } - static AutoFileSyncEntry.ForDirectFileRequest finishDeserializeContent(String syncID, FriendlyByteBuf buf){ + static AutoFileSyncEntry.ForDirectFileRequest finishDeserializeContent(String syncID, FriendlyByteBuf buf) { final String relFile = DataHandler.readString(buf); - SyncFolderDescriptor desc = DataExchange.getSyncFolderDescriptor(syncID); - if (desc!=null) { + SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(syncID); + if (desc != null) { //ensures that the file is not above the base-folder if (desc.acceptChildElements(desc.mapAbsolute(relFile))) { return new AutoFileSyncEntry.ForDirectFileRequest(syncID, new File(relFile), desc.localFolder.resolve(relFile) @@ -49,16 +49,17 @@ class AutoFileSyncEntry extends AutoSyncID { return uniqueID + " - " + relFile; } } - public final DataExchange.NeedTransferPredicate needTransfer; + + public final NeedTransferPredicate needTransfer; public final File fileName; public final boolean requestContent; private SyncFileHash hash; - AutoFileSyncEntry(String modID, File fileName, boolean requestContent, DataExchange.NeedTransferPredicate needTransfer) { + 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, DataExchange.NeedTransferPredicate needTransfer) { + AutoFileSyncEntry(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) { super(modID, uniqueID); this.needTransfer = needTransfer; this.fileName = fileName; @@ -97,9 +98,10 @@ class AutoFileSyncEntry extends AutoSyncID { byte[] data = deserializeFileContent(buf); AutoFileSyncEntry entry; - if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)){ + if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)) { entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf); - } else { + } + else { entry = AutoFileSyncEntry.findMatching(modID, uniqueID); } return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID)); @@ -115,10 +117,10 @@ class AutoFileSyncEntry extends AutoSyncID { } } - public static DataExchange.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) { + public static AutoSync.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) { Pair e = deserialize(buf); AutoFileSyncEntry match = findMatching(e.first); - return new DataExchange.AutoSyncTriple(e.first, e.second, match); + return new AutoSync.AutoSyncTriple(e.first, e.second, match); } public static Pair deserialize(FriendlyByteBuf buf) { @@ -154,15 +156,13 @@ class AutoFileSyncEntry extends AutoSyncID { public static AutoFileSyncEntry findMatching(AutoSyncID aid) { if (aid instanceof AutoSyncID.ForDirectFileRequest) { AutoSyncID.ForDirectFileRequest freq = (AutoSyncID.ForDirectFileRequest) aid; - SyncFolderDescriptor desc = DataExchange.getSyncFolderDescriptor(freq.uniqueID); + SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(freq.uniqueID); if (desc != null) { SubFile subFile = desc.getLocalSubFile(freq.relFile.toString()); if (subFile != null) { - final File absPath = desc - .localFolder - .resolve(subFile.relPath) - .normalize() - .toFile(); + final File absPath = desc.localFolder.resolve(subFile.relPath) + .normalize() + .toFile(); return new AutoFileSyncEntry.ForDirectFileRequest(freq.uniqueID, new File(subFile.relPath), absPath); } } @@ -172,11 +172,10 @@ class AutoFileSyncEntry extends AutoSyncID { } public static AutoFileSyncEntry findMatching(String modID, String uniqueID) { - return DataExchange.getInstance() - .getAutoSyncFiles() - .stream() - .filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID)) - .findFirst() - .orElse(null); + return AutoSync.getAutoSyncFiles() + .stream() + .filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID)) + .findFirst() + .orElse(null); } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSync.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSync.java new file mode 100644 index 00000000..29ce9336 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSync.java @@ -0,0 +1,210 @@ +package ru.bclib.api.dataexchange.handler.autosync; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import ru.bclib.BCLib; +import ru.bclib.api.dataexchange.SyncFileHash; +import ru.bclib.config.Configs; +import ru.bclib.util.PathUtil; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.BiConsumer; + +public class AutoSync { + public static final String MAIN_SYNC_CATEGORY = "client_sync"; + public final static SyncFolderDescriptor SYNC_FOLDER = new SyncFolderDescriptor("BCLIB-SYNC", FabricLoader.getInstance() + .getGameDir() + .resolve("bclib-sync") + .normalize() + .toAbsolutePath(), true); + + @FunctionalInterface + public interface NeedTransferPredicate { + public boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content); + } + + @Environment(EnvType.CLIENT) + public static class ClientConfig { + @Environment(EnvType.CLIENT) + static boolean shouldClientConfigPrintDebugHashes() { + return Configs.CLIENT_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "debugHashes", true); + } + + @Environment(EnvType.CLIENT) + static boolean isClientConfigAllowingAutoSync() { + return Configs.CLIENT_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "enabled", true); + } + + @Environment(EnvType.CLIENT) + static boolean isClientConfigAcceptingFiles() { + return Configs.CLIENT_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "acceptFiles", true) && isClientConfigAllowingAutoSync(); + } + + @Environment(EnvType.CLIENT) + static boolean isClientConfigAcceptingFolders() { + return Configs.CLIENT_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "acceptFolders", true) && isClientConfigAllowingAutoSync(); + } + } + + public static class Config { + static boolean isAllowingAutoSync() { + return Configs.MAIN_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "enabled", true); + } + + static boolean isOfferingFiles() { + return Configs.MAIN_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "offerFiles", true) && ClientConfig.isClientConfigAllowingAutoSync(); + } + + static boolean isOfferingFolders() { + return Configs.MAIN_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "offerFolders", true) && ClientConfig.isClientConfigAllowingAutoSync(); + } + + static boolean isOfferingMods() { + return Configs.MAIN_CONFIG.getBoolean(MAIN_SYNC_CATEGORY, "offerMods", true) && ClientConfig.isClientConfigAllowingAutoSync(); + } + } + + final static class AutoSyncTriple { + public final SyncFileHash serverHash; + public final byte[] serverContent; + public final AutoFileSyncEntry localMatch; + + public AutoSyncTriple(SyncFileHash serverHash, byte[] serverContent, AutoFileSyncEntry localMatch) { + this.serverHash = serverHash; + this.serverContent = serverContent; + this.localMatch = localMatch; + } + + @Override + public String toString() { + return serverHash.modID + "." + serverHash.uniqueID; + } + } + + + // ##### File Syncing + protected final static List> onWriteCallbacks = new ArrayList<>(2); + /** + * Register a function that is called whenever the client receives a file from the server and replaced toe local + * file with the new content. + *

+ * 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 callback) { + onWriteCallbacks.add(callback); + } + private static final List autoSyncFiles = new ArrayList<>(4); + + public static List getAutoSyncFiles() { + return autoSyncFiles; + } + + /** + * 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 SyncFileHash} + * for comparison is sufficient. + */ + public static 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 SyncFileHash#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 SyncFileHash} + * for comparison is sufficient. + */ + public static void addAutoSyncFileData(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) { + autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer)); + } + + /** + * Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem. + *

+ * This is the place where reload Code should go. + * + * @param aid The ID of the received File + * @param file The location of the FIle on the client + */ + static void didReceiveFile(AutoSyncID aid, File file) { + onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file)); + } + + + // ##### Folder Syncing + static final List syncFolderDescriptions = Arrays.asList(SYNC_FOLDER); + + private List syncFolderContent; + + protected List getSyncFolderContent() { + if (syncFolderContent == null) { + return new ArrayList<>(0); + } + return syncFolderContent; + } + + //we call this from HelloServer to prepare transfer + protected static void loadSyncFolder() { + if (Configs.MAIN_CONFIG.getBoolean(AutoSync.MAIN_SYNC_CATEGORY, "offersSyncFolders", true)) { + syncFolderDescriptions.forEach(desc -> desc.loadCache()); + } + } + + protected static SyncFolderDescriptor getSyncFolderDescriptor(String folderID) { + return syncFolderDescriptions.stream() + .filter(d -> d.equals(folderID)) + .findFirst() + .orElse(null); + } + + protected static Path localBasePathForFolderID(String folderID) { + final SyncFolderDescriptor desc = getSyncFolderDescriptor(folderID); + if (desc != null) { + return desc.localFolder; + } + else { + BCLib.LOGGER.warning("Unknown Sync-Folder ID '" + folderID + "'"); + return null; + } + } + + public static void registerSyncFolder(String folderID, Path localBaseFolder, boolean removeAdditionalFiles) { + localBaseFolder = localBaseFolder.normalize(); + if (PathUtil.isChildOf(PathUtil.GAME_FOLDER, localBaseFolder)) { + final SyncFolderDescriptor desc = new SyncFolderDescriptor(folderID, localBaseFolder, removeAdditionalFiles); + if (syncFolderDescriptions.contains(desc)) { + BCLib.LOGGER.warning("Tried to override Folder Sync '" + folderID + "' again."); + } + else { + syncFolderDescriptions.add(desc); + } + } + else { + BCLib.LOGGER.error(localBaseFolder + " (from " + folderID + ") is outside the game directory " + PathUtil.GAME_FOLDER + ". Sync is not allowed."); + } + } +} diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/AutoSyncID.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSyncID.java similarity index 98% rename from src/main/java/ru/bclib/api/dataexchange/handler/AutoSyncID.java rename to src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSyncID.java index f4a46209..ea4891ed 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/AutoSyncID.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSyncID.java @@ -1,4 +1,4 @@ -package ru.bclib.api.dataexchange.handler; +package ru.bclib.api.dataexchange.handler.autosync; import net.minecraft.network.FriendlyByteBuf; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/FileContentWrapper.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/FileContentWrapper.java similarity index 96% rename from src/main/java/ru/bclib/api/dataexchange/handler/FileContentWrapper.java rename to src/main/java/ru/bclib/api/dataexchange/handler/autosync/FileContentWrapper.java index f95b83fd..d2c4b7b2 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/FileContentWrapper.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/FileContentWrapper.java @@ -1,4 +1,4 @@ -package ru.bclib.api.dataexchange.handler; +package ru.bclib.api.dataexchange.handler.autosync; import ru.bclib.BCLib; diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/HelloClient.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloClient.java similarity index 82% rename from src/main/java/ru/bclib/api/dataexchange/handler/HelloClient.java rename to src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloClient.java index 2767ac8d..75d8234a 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/HelloClient.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloClient.java @@ -1,10 +1,8 @@ -package ru.bclib.api.dataexchange.handler; +package ru.bclib.api.dataexchange.handler.autosync; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.networking.v1.PacketSender; -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.network.FriendlyByteBuf; @@ -14,13 +12,15 @@ 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.handler.AutoSyncID.WithContentOverride; -import ru.bclib.api.dataexchange.handler.DataExchange.SyncFolderDescriptor; -import ru.bclib.api.dataexchange.handler.DataExchange.SyncFolderDescriptor.SubFile; +import ru.bclib.api.dataexchange.handler.DataExchange; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.ClientConfig; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.Config; +import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.WithContentOverride; +import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile; import ru.bclib.api.datafixer.DataFixerAPI; -import ru.bclib.config.Configs; import ru.bclib.gui.screens.SyncFilesScreen; import ru.bclib.gui.screens.WarnBCLibVersionMismatch; +import ru.bclib.util.PathUtil; import java.io.File; import java.util.ArrayList; @@ -28,7 +28,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -44,36 +43,18 @@ public class HelloClient extends DataHandler { super(DESCRIPTOR.IDENTIFIER, true); } - public static ModContainer getModContainer(String modID) { - Optional optional = FabricLoader.getInstance() - .getModContainer(modID); - return optional.orElse(null); - } - - public static String getModVersion(String modID) { - Optional optional = FabricLoader.getInstance() - .getModContainer(modID); - if (optional.isPresent()) { - ModContainer modContainer = optional.get(); - return modContainer.getMetadata() - .getVersion() - .toString(); - } - return "0.0.0"; - } - static String getBCLibVersion() { - return getModVersion(BCLib.MOD_ID); + return PathUtil.getModVersion(BCLib.MOD_ID); } @Override protected boolean prepareData(boolean isClient) { - if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "enabled", true)) { + if (!Config.isAllowingAutoSync()) { BCLib.LOGGER.info("Auto-Sync was disabled on the server."); return false; } - DataExchange.getInstance().loadSyncFolder(); + AutoSync.loadSyncFolder(); return true; } @@ -86,12 +67,12 @@ public class HelloClient extends DataHandler { //write BCLibVersion (=protocol version) buf.writeInt(DataFixerAPI.getModVersion(vbclib)); - if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offerMods", true)) { + if (Config.isOfferingMods()) { //write Plugin Versions buf.writeInt(mods.size()); for (String modID : mods) { writeString(buf, modID); - final String ver = getModVersion(modID); + final String ver = PathUtil.getModVersion(modID); buf.writeInt(DataFixerAPI.getModVersion(ver)); BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver); } @@ -101,9 +82,9 @@ public class HelloClient extends DataHandler { buf.writeInt(0); } - if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offerFiles", true)) { + if (Config.isOfferingFiles()) { //do only include files that exist on the server - final List existingAutoSyncFiles = DataExchange.getInstance() + final List existingAutoSyncFiles = AutoSync .getAutoSyncFiles() .stream() .filter(e -> e.fileName.exists()) @@ -121,9 +102,9 @@ public class HelloClient extends DataHandler { buf.writeInt(0); } - if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offersSyncFolders", true)) { - buf.writeInt(((DataExchange) DataExchange.getInstance()).syncFolderDescriptions.size()); - ((DataExchange) DataExchange.getInstance()).syncFolderDescriptions.forEach(desc -> { + if (Config.isOfferingFolders()) { + buf.writeInt(AutoSync.syncFolderDescriptions.size()); + AutoSync.syncFolderDescriptions.forEach(desc -> { BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")"); desc.serialize(buf); }); @@ -132,12 +113,11 @@ public class HelloClient extends DataHandler { BCLib.LOGGER.info("Server will not offer Sync Folders."); buf.writeInt(0); } - Configs.MAIN_CONFIG.saveChanges(); } String bclibVersion = "0.0.0"; Map modVersion = new HashMap<>(); - List autoSyncedFiles = null; + List autoSyncedFiles = null; List autoSynFolders = null; @Override @@ -159,7 +139,7 @@ public class HelloClient extends DataHandler { autoSyncedFiles = new ArrayList<>(count); for (int i = 0; i < count; i++) { //System.out.println("Deserializing "); - DataExchange.AutoSyncTriple t = AutoFileSyncEntry.deserializeAndMatch(buf); + AutoSync.AutoSyncTriple t = AutoFileSyncEntry.deserializeAndMatch(buf); autoSyncedFiles.add(t); //System.out.println(t.first); } @@ -177,7 +157,7 @@ public class HelloClient extends DataHandler { } private void processAutoSyncFolder(final List filesToRequest, final List filesToRemove) { - if (!Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "syncFolders", true)) { + if (!ClientConfig.isClientConfigAcceptingFolders()) { return; } @@ -187,7 +167,7 @@ public class HelloClient extends DataHandler { autoSynFolders.forEach(desc -> { //desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client - SyncFolderDescriptor localDescriptor = DataExchange.getSyncFolderDescriptor(desc.folderID); + SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID); if (localDescriptor != null) { BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")"); localDescriptor.invalidateCache(); @@ -220,7 +200,8 @@ public class HelloClient extends DataHandler { if (!localSubFile.hash.equals(subFile.hash)) { BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)"); filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID, new File(subFile.relPath))); - } else { + } + else { BCLib.LOGGER.info(" * " + subFile.relPath); } } @@ -241,7 +222,7 @@ public class HelloClient extends DataHandler { } private void processSingleFileSync(final List filesToRequest) { - final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "debugHashes", false); + final boolean debugHashes = ClientConfig.shouldClientConfigPrintDebugHashes(); if (autoSyncedFiles.size() > 0) { BCLib.LOGGER.info("Files offered by Server:"); @@ -251,7 +232,7 @@ public class HelloClient extends DataHandler { //Single files need to be registered for sync on both client and server //There are no restrictions to the target folder, but the client decides the final //location. - for (DataExchange.AutoSyncTriple e : autoSyncedFiles) { + for (AutoSync.AutoSyncTriple e : autoSyncedFiles) { String actionString = ""; FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent); if (e.localMatch == null) { @@ -281,7 +262,7 @@ public class HelloClient extends DataHandler { @Override protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient) { - if (!Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "enabled", true)) { + if (!ClientConfig.isClientConfigAllowingAutoSync()) { BCLib.LOGGER.info("Auto-Sync was disabled on the client."); return; } @@ -297,7 +278,7 @@ public class HelloClient extends DataHandler { final List filesToRemove = new ArrayList<>(2); for (Entry e : modVersion.entrySet()) { - String ver = getModVersion(e.getKey()); + String ver = PathUtil.getModVersion(e.getKey()); BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + ver + ", server=" + ver + ")"); } @@ -308,7 +289,7 @@ public class HelloClient extends DataHandler { //Both client and server need to know about the folder you want to sync //Files can only get placed within that folder - if ((filesToRequest.size() > 0 || filesToRemove.size() > 0) && SendFiles.acceptFiles()) { + if ((filesToRequest.size() > 0 || filesToRemove.size() > 0) && ClientConfig.isClientConfigAcceptingFiles()) { showDownloadConfigs(client, filesToRequest, filesToRemove); return; } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloServer.java similarity index 56% rename from src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java rename to src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloServer.java index 6b2d40a6..29ca7de4 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloServer.java @@ -1,4 +1,4 @@ -package ru.bclib.api.dataexchange.handler; +package ru.bclib.api.dataexchange.handler.autosync; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.minecraft.client.Minecraft; @@ -9,56 +9,58 @@ 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.handler.autosync.AutoSync.ClientConfig; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.Config; import ru.bclib.api.datafixer.DataFixerAPI; -import ru.bclib.config.Configs; import java.io.File; /** * 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. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Description
ServerClient
Player enters World
<--{@link HelloServer}Sends the current BLib-Version installed on the Client
{@link HelloClient}-->Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files - * ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server
<--{@link RequestFiles}Request missing or out of sync Files from the Server
{@link SendFiles}-->Send Files from the Server to the Client
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Description
ServerClient
Player enters World
<--{@link HelloServer}Sends the current BLib-Version installed on the Client
{@link HelloClient}-->Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files + * ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server
<--{@link RequestFiles}Request missing or out of sync Files from the Server
{@link SendFiles}-->Send Files from the Server to the Client
*/ public class HelloServer extends DataHandler { public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_server"), HelloServer::new, true, false); - protected String bclibVersion ="0.0.0"; + protected String bclibVersion = "0.0.0"; + public HelloServer() { super(DESCRIPTOR.IDENTIFIER, false); } @@ -66,7 +68,7 @@ public class HelloServer extends DataHandler { @Override protected boolean prepareData(boolean isClient) { - if (!Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "enabled", true)) { + if (!ClientConfig.isClientConfigAllowingAutoSync()) { BCLib.LOGGER.info("Auto-Sync was disabled on the client."); return false; } @@ -86,15 +88,15 @@ public class HelloServer extends DataHandler { @Override protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient) { - if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "enabled", true)) { + if (!Config.isAllowingAutoSync()) { BCLib.LOGGER.info("Auto-Sync was disabled on the server."); return; } String localBclibVersion = HelloClient.getBCLibVersion(); - BCLib.LOGGER.info("Received Hello from Client. (server="+localBclibVersion+", client="+bclibVersion+")"); - - if (!server.isPublished()){ + BCLib.LOGGER.info("Received Hello from Client. (server=" + localBclibVersion + ", client=" + bclibVersion + ")"); + + if (!server.isPublished()) { BCLib.LOGGER.info("Auto-Sync is disabled for Singleplayer worlds."); return; } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/RequestFiles.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/RequestFiles.java similarity index 74% rename from src/main/java/ru/bclib/api/dataexchange/handler/RequestFiles.java rename to src/main/java/ru/bclib/api/dataexchange/handler/autosync/RequestFiles.java index cd94db0b..c9033b9c 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/RequestFiles.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/RequestFiles.java @@ -1,4 +1,4 @@ -package ru.bclib.api.dataexchange.handler; +package ru.bclib.api.dataexchange.handler.autosync; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.minecraft.client.Minecraft; @@ -8,7 +8,8 @@ import net.minecraft.server.MinecraftServer; import ru.bclib.BCLib; import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandlerDescriptor; -import ru.bclib.config.Configs; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.ClientConfig; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.Config; import java.util.ArrayList; import java.util.List; @@ -20,7 +21,8 @@ public class RequestFiles extends DataHandler { static String currentToken = ""; protected List files; - private RequestFiles(){ + + private RequestFiles() { this(null); } @@ -31,7 +33,7 @@ public class RequestFiles extends DataHandler { @Override protected boolean prepareData(boolean isClient) { - if (!Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "enabled", true)) { + if (!ClientConfig.isClientConfigAllowingAutoSync()) { BCLib.LOGGER.info("Auto-Sync was disabled on the client."); return false; } @@ -42,15 +44,16 @@ public class RequestFiles extends DataHandler { protected void serializeData(FriendlyByteBuf buf, boolean isClient) { newToken(); writeString(buf, currentToken); - + buf.writeInt(files.size()); - for (AutoSyncID a : files){ + for (AutoSyncID a : files) { a.serializeData(buf); } } - + String receivedToken = ""; + @Override protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) { receivedToken = readString(buf); @@ -58,7 +61,7 @@ public class RequestFiles extends DataHandler { files = new ArrayList<>(size); BCLib.LOGGER.info("Client requested " + size + " Files:"); - for (int i=0; i syncEntries = files - .stream().map(asid -> AutoFileSyncEntry.findMatching(asid)) - .filter(e -> e!=null) - .collect(Collectors.toList()); - + List syncEntries = files.stream() + .map(asid -> AutoFileSyncEntry.findMatching(asid)) + .filter(e -> e != null) + .collect(Collectors.toList()); + reply(new SendFiles(syncEntries, receivedToken), server); } - - public static void newToken(){ - currentToken = UUID.randomUUID().toString(); + + public static void newToken() { + currentToken = UUID.randomUUID() + .toString(); } - + static { newToken(); } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/SendFiles.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java similarity index 84% rename from src/main/java/ru/bclib/api/dataexchange/handler/SendFiles.java rename to src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java index 411e699c..d19497b9 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/SendFiles.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java @@ -1,4 +1,4 @@ -package ru.bclib.api.dataexchange.handler; +package ru.bclib.api.dataexchange.handler.autosync; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -11,7 +11,9 @@ import net.minecraft.server.MinecraftServer; import ru.bclib.BCLib; import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandlerDescriptor; -import ru.bclib.config.Configs; +import ru.bclib.api.dataexchange.handler.DataExchange; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.ClientConfig; +import ru.bclib.api.dataexchange.handler.autosync.AutoSync.Config; import ru.bclib.gui.screens.ConfirmRestartScreen; import ru.bclib.util.Pair; import ru.bclib.util.Triple; @@ -26,26 +28,23 @@ import java.util.stream.Collectors; public class SendFiles extends DataHandler { public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "send_files"), SendFiles::new, false, false); - + protected List files; private String token = ""; - public SendFiles(){ + + public SendFiles() { this(null, ""); } + public SendFiles(List files, String token) { super(DESCRIPTOR.IDENTIFIER, true); this.files = files; this.token = token; } - - public static boolean acceptFiles() { - return Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "acceptFiles", true) - && Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "enabled", true); - } @Override protected boolean prepareData(boolean isClient) { - if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "enabled", true)) { + if (!Config.isAllowingAutoSync()) { BCLib.LOGGER.info("Auto-Sync was disabled on the server."); return false; } @@ -55,7 +54,9 @@ public class SendFiles extends DataHandler { @Override protected void serializeData(FriendlyByteBuf buf, boolean isClient) { - List existingFiles = files.stream().filter(e -> e.fileName.exists()).collect(Collectors.toList()); + List existingFiles = files.stream() + .filter(e -> e.fileName.exists()) + .collect(Collectors.toList()); /* //this will try to send a file that was not registered or requested by the client existingFiles.add(new AutoFileSyncEntry("none", new File("D:\\MinecraftPlugins\\BetterNether\\run\\server.properties"),true,(a, b, content) -> { @@ -72,18 +73,19 @@ public class SendFiles extends DataHandler { writeString(buf, token); buf.writeInt(existingFiles.size()); - + BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:"); for (AutoFileSyncEntry entry : existingFiles) { int length = entry.serializeContent(buf); BCLib.LOGGER.info(" - " + entry + " (" + length + " Bytes)"); } } - + private List> receivedFiles; + @Override protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) { - if (acceptFiles()) { + if (ClientConfig.isClientConfigAcceptingFiles()) { token = readString(buf); if (!token.equals(RequestFiles.currentToken)) { RequestFiles.newToken(); @@ -92,7 +94,7 @@ public class SendFiles extends DataHandler { return; } RequestFiles.newToken(); - + int size = buf.readInt(); receivedFiles = new ArrayList<>(size); BCLib.LOGGER.info("Server sent " + size + " Files:"); @@ -101,7 +103,8 @@ public class SendFiles extends DataHandler { if (p.first != null) { receivedFiles.add(p); BCLib.LOGGER.info(" - " + p.first + " (" + p.second.length + " Bytes)"); - } else { + } + else { BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client."); } } @@ -110,7 +113,7 @@ public class SendFiles extends DataHandler { @Override protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient) { - if (acceptFiles()) { + if (ClientConfig.isClientConfigAcceptingFiles()) { BCLib.LOGGER.info("Writing Files:"); //TODO: Reject files that were not in the last RequestFiles. @@ -120,7 +123,7 @@ public class SendFiles extends DataHandler { writeSyncedFile(e, data, e.fileName); } - + showConfirmRestart(client); } } @@ -130,23 +133,25 @@ public class SendFiles extends DataHandler { BCLib.LOGGER.info(" - Writing " + path + " (" + data.length + " Bytes)"); try { final File parentFile = path.getParent() - .toFile(); - if (!parentFile.exists()){ + .toFile(); + if (!parentFile.exists()) { parentFile.mkdirs(); } Files.write(path, data); - DataExchange.didReceiveFile(e, fileName); - } catch (IOException ioException) { + AutoSync.didReceiveFile(e, fileName); + } + catch (IOException ioException) { BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException); } } @Environment(EnvType.CLIENT) - protected void showConfirmRestart(Minecraft client){ + protected void showConfirmRestart(Minecraft client) { client.setScreen(new ConfirmRestartScreen(() -> { - Minecraft.getInstance().setScreen((Screen)null); + Minecraft.getInstance() + .setScreen((Screen) null); client.stop(); })); - + } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SyncFolderDescriptor.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SyncFolderDescriptor.java new file mode 100644 index 00000000..fd86004f --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SyncFolderDescriptor.java @@ -0,0 +1,206 @@ +package ru.bclib.api.dataexchange.handler.autosync; + +import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; +import ru.bclib.BCLib; +import ru.bclib.api.dataexchange.DataHandler; +import ru.bclib.api.dataexchange.FileHash; +import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.ForDirectFileRequest; +import ru.bclib.config.Configs; +import ru.bclib.util.PathUtil; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +public class SyncFolderDescriptor { + static class SubFile { + public final String relPath; + public final FileHash hash; + + + SubFile(String relPath, FileHash hash) { + this.relPath = relPath; + this.hash = hash; + } + + @Override + public String toString() { + return relPath; + } + + public void serialize(FriendlyByteBuf buf) { + DataHandler.writeString(buf, relPath); + hash.serialize(buf); + } + + public static SubFile deserialize(FriendlyByteBuf buf) { + final String relPath = DataHandler.readString(buf); + FileHash hash = FileHash.deserialize(buf); + return new SubFile(relPath, hash); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof String) return relPath.equals(o); + if (!(o instanceof SubFile)) return false; + SubFile subFile = (SubFile) o; + return relPath.equals(subFile.relPath); + } + + @Override + public int hashCode() { + return relPath.hashCode(); + } + } + + @NotNull + public final String folderID; + public final boolean removeAdditionalFiles; + @NotNull + public final Path localFolder; + + private List fileCache; + + public SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) { + this.removeAdditionalFiles = removeAdditionalFiles; + this.folderID = folderID; + this.localFolder = localFolder; + fileCache = null; + } + + @Override + public String toString() { + return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + (fileCache == null ? "?" : fileCache.size()) + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof String) { + return folderID.equals(o); + } + if (o instanceof ForDirectFileRequest) { + return folderID.equals(((ForDirectFileRequest) o).uniqueID); + } + if (!(o instanceof SyncFolderDescriptor)) return false; + SyncFolderDescriptor that = (SyncFolderDescriptor) o; + return folderID.equals(that.folderID); + } + + @Override + public int hashCode() { + return folderID.hashCode(); + } + + public int fileCount() { + return fileCache == null ? 0 : fileCache.size(); + } + + public void invalidateCache() { + fileCache = null; + } + + public void loadCache() { + if (fileCache == null) { + fileCache = new ArrayList<>(8); + PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(localFolder.relativize(p) + .toString(), FileHash.create(p.toFile())))); + + /*//this tests if we can trick the system to load files that are not beneath the base-folder + if (!BCLib.isClient()) { + fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile()))); + }*/ + } + } + + public void serialize(FriendlyByteBuf buf) { + final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(AutoSync.MAIN_SYNC_CATEGORY, "debugHashes", false); + loadCache(); + + DataHandler.writeString(buf, folderID); + buf.writeBoolean(removeAdditionalFiles); + buf.writeInt(fileCache.size()); + fileCache.forEach(fl -> { + BCLib.LOGGER.info(" - " + fl.relPath); + if (debugHashes) { + BCLib.LOGGER.info(" " + fl.hash); + } + fl.serialize(buf); + }); + } + + public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) { + final String folderID = DataHandler.readString(buf); + final boolean remAddFiles = buf.readBoolean(); + final int count = buf.readInt(); + SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(folderID); + + final SyncFolderDescriptor desc; + if (localDescriptor != null) { + desc = new SyncFolderDescriptor(folderID, localDescriptor.localFolder, remAddFiles); + desc.fileCache = new ArrayList<>(count); + } + else { + BCLib.LOGGER.warning(BCLib.isClient() ? "Client" : "Server" + " does not know Sync-Folder ID '" + folderID + "'"); + desc = null; + } + + for (int i = 0; i < count; i++) { + SubFile relPath = SubFile.deserialize(buf); + if (desc != null) desc.fileCache.add(relPath); + } + + return desc; + } + + //Note: make sure loadCache was called before using this + boolean hasRelativeFile(String relFile) { + return fileCache.stream() + .filter(sf -> sf.equals(relFile)) + .findFirst() + .isPresent(); + } + + //Note: make sure loadCache was called before using this + boolean hasRelativeFile(SubFile subFile) { + return hasRelativeFile(subFile.relPath); + } + + //Note: make sure loadCache was called before using this + SubFile getLocalSubFile(String relPath) { + return fileCache.stream() + .filter(sf -> sf.relPath.equals(relPath)) + .findFirst() + .orElse(null); + } + + Stream relativeFilesStream() { + loadCache(); + return fileCache.stream(); + } + + public Path mapAbsolute(String relPath) { + return this.localFolder.resolve(relPath) + .normalize(); + } + + public Path mapAbsolute(SubFile subFile) { + return this.localFolder.resolve(subFile.relPath) + .normalize(); + } + + public boolean acceptChildElements(Path absPath) { + return PathUtil.isChildOf(this.localFolder, absPath); + } + + public boolean acceptChildElements(SubFile subFile) { + return acceptChildElements(mapAbsolute(subFile)); + } + + public boolean discardChildElements(SubFile subFile) { + return !acceptChildElements(subFile); + } +} diff --git a/src/main/java/ru/bclib/config/Config.java b/src/main/java/ru/bclib/config/Config.java index 2d6fea64..82d76914 100644 --- a/src/main/java/ru/bclib/config/Config.java +++ b/src/main/java/ru/bclib/config/Config.java @@ -4,8 +4,8 @@ import org.jetbrains.annotations.Nullable; import ru.bclib.BCLib; import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.api.dataexchange.SyncFileHash; -import ru.bclib.api.dataexchange.handler.AutoSyncID; -import ru.bclib.api.dataexchange.handler.FileContentWrapper; +import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID; +import ru.bclib.api.dataexchange.handler.autosync.FileContentWrapper; import ru.bclib.config.ConfigKeeper.BooleanEntry; import ru.bclib.config.ConfigKeeper.Entry; import ru.bclib.config.ConfigKeeper.FloatEntry; diff --git a/src/main/java/ru/bclib/config/ConfigKeeper.java b/src/main/java/ru/bclib/config/ConfigKeeper.java index cb2aaa8e..57e5406d 100644 --- a/src/main/java/ru/bclib/config/ConfigKeeper.java +++ b/src/main/java/ru/bclib/config/ConfigKeeper.java @@ -6,7 +6,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.minecraft.util.GsonHelper; import org.jetbrains.annotations.Nullable; -import ru.bclib.api.dataexchange.handler.FileContentWrapper; +import ru.bclib.api.dataexchange.handler.autosync.FileContentWrapper; import ru.bclib.util.JsonFactory; import ru.bclib.util.Pair; diff --git a/src/main/java/ru/bclib/config/Configs.java b/src/main/java/ru/bclib/config/Configs.java index b603763a..2a638a9c 100644 --- a/src/main/java/ru/bclib/config/Configs.java +++ b/src/main/java/ru/bclib/config/Configs.java @@ -8,7 +8,6 @@ public class Configs { public static final PathConfig GENERATOR_CONFIG = new PathConfig(BCLib.MOD_ID, "generator"); public static final PathConfig MAIN_CONFIG = new PathConfig(BCLib.MOD_ID, "main"); public static final String MAIN_PATCH_CATEGORY = "patches"; - public static final String MAIN_SYNC_CATEGORY = "client_sync"; public static final PathConfig RECIPE_CONFIG = new PathConfig(BCLib.MOD_ID, "recipes"); diff --git a/src/main/java/ru/bclib/util/PathUtil.java b/src/main/java/ru/bclib/util/PathUtil.java index 1031ddb7..0653d3a6 100644 --- a/src/main/java/ru/bclib/util/PathUtil.java +++ b/src/main/java/ru/bclib/util/PathUtil.java @@ -1,6 +1,7 @@ package ru.bclib.util; import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.metadata.ModMetadata; import net.fabricmc.loader.metadata.ModMetadataParser; import net.fabricmc.loader.metadata.ParseMetadataException; @@ -15,6 +16,7 @@ import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import java.util.jar.JarFile; @@ -127,4 +129,22 @@ public class PathUtil { return mods; } + + public static String getModVersion(String modID) { + Optional optional = FabricLoader.getInstance() + .getModContainer(modID); + if (optional.isPresent()) { + ModContainer modContainer = optional.get(); + return modContainer.getMetadata() + .getVersion() + .toString(); + } + return "0.0.0"; + } + + public static ModContainer getModContainer(String modID) { + Optional optional = FabricLoader.getInstance() + .getModContainer(modID); + return optional.orElse(null); + } }