Implemented ModSync

This commit is contained in:
Frank 2021-08-18 14:23:33 +02:00
parent 8588191556
commit 71ad055ea5
6 changed files with 165 additions and 29 deletions

View file

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

View file

@ -6,6 +6,7 @@ import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.SyncFileHash; import ru.bclib.api.dataexchange.SyncFileHash;
import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate; import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate;
import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile; import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
import ru.bclib.api.datafixer.DataFixerAPI;
import ru.bclib.util.Pair; import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil; import ru.bclib.util.PathUtil;
import ru.bclib.util.PathUtil.ModInfo; import ru.bclib.util.PathUtil.ModInfo;
@ -54,34 +55,41 @@ class AutoFileSyncEntry extends AutoSyncID {
} }
static class ForModFileRequest extends AutoFileSyncEntry { static class ForModFileRequest extends AutoFileSyncEntry {
public static Path getLocalPathForID(String modID){ public static File getLocalPathForID(String modID, boolean matchLocalVersion){
ModInfo mi = PathUtil.getModInfo(modID); ModInfo mi = PathUtil.getModInfo(modID, matchLocalVersion);
if (mi!=null){ if (mi!=null){
return mi.jarPath; return mi.jarPath.toFile();
} }
return null; return null;
} }
ForModFileRequest(String modID) { public final String version;
super(modID, AutoSyncID.ForModFileRequest.UNIQUE_ID, getLocalPathForID(modID).toFile(), false, (a, b, c) -> false); ForModFileRequest(String modID, boolean matchLocalVersion, String version) {
if (this.fileName == null){ super(modID, AutoSyncID.ForModFileRequest.UNIQUE_ID, getLocalPathForID(modID, matchLocalVersion), false, (a, b, c) -> false);
if (this.fileName == null && matchLocalVersion){
BCLib.LOGGER.error("Unknown mod '"+modID+"'."); BCLib.LOGGER.error("Unknown mod '"+modID+"'.");
} }
if (version==null)
this.version = PathUtil.getModVersion(modID);
else
this.version = version;
} }
@Override @Override
public int serializeContent(FriendlyByteBuf buf) { public int serializeContent(FriendlyByteBuf buf) {
final int res = super.serializeContent(buf); final int res = super.serializeContent(buf);
buf.writeInt(DataFixerAPI.getModVersion(version));
return res; return res;
} }
static AutoFileSyncEntry.ForModFileRequest finishDeserializeContent(String modID, FriendlyByteBuf buf) { static AutoFileSyncEntry.ForModFileRequest finishDeserializeContent(String modID, FriendlyByteBuf buf) {
return new AutoFileSyncEntry.ForModFileRequest(modID); final String version = DataFixerAPI.getModVersion(buf.readInt());
return new AutoFileSyncEntry.ForModFileRequest(modID, false, version);
} }
@Override @Override
public String toString() { public String toString() {
return modID; return "Mod " + modID + " (v" + version + ")";
} }
} }
@ -137,7 +145,7 @@ class AutoFileSyncEntry extends AutoSyncID {
entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf); entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
} }
else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) { else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) {
entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(uniqueID, buf); entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(modID, buf);
} }
else { else {
entry = AutoFileSyncEntry.findMatching(modID, uniqueID); entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
@ -205,6 +213,9 @@ class AutoFileSyncEntry extends AutoSyncID {
} }
} }
return null; return null;
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
AutoSyncID.ForModFileRequest mreq = (AutoSyncID.ForModFileRequest) aid;
return new AutoFileSyncEntry.ForModFileRequest(mreq.modID, true, null);
} }
return findMatching(aid.modID, aid.uniqueID); return findMatching(aid.modID, aid.uniqueID);
} }

View file

@ -18,6 +18,11 @@ public class AutoSyncID {
this.contentWrapper = contentWrapper; this.contentWrapper = contentWrapper;
this.localFile = localFile; this.localFile = localFile;
} }
@Override
public String toString() {
return super.toString() + " (Content override)";
}
} }
static class ForDirectFileRequest extends AutoSyncID { static class ForDirectFileRequest extends AutoSyncID {
@ -39,6 +44,11 @@ public class AutoSyncID {
final File fl = new File(DataHandler.readString(buf)); final File fl = new File(DataHandler.readString(buf));
return new ForDirectFileRequest(uniqueID, fl); return new ForDirectFileRequest(uniqueID, fl);
} }
@Override
public String toString() {
return super.uniqueID + " (" + this.relFile + ")";
}
} }
static class ForModFileRequest extends AutoSyncID { static class ForModFileRequest extends AutoSyncID {
@ -59,6 +69,11 @@ public class AutoSyncID {
final String version = DataFixerAPI.getModVersion(buf.readInt()); final String version = DataFixerAPI.getModVersion(buf.readInt());
return new ForModFileRequest(modID, version); return new ForModFileRequest(modID, version);
} }
@Override
public String toString() {
return super.modID + " (v" + this.version + ")";
}
} }
/** /**

View file

@ -18,9 +18,13 @@ import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
import ru.bclib.api.datafixer.DataFixerAPI; import ru.bclib.api.datafixer.DataFixerAPI;
import ru.bclib.gui.screens.SyncFilesScreen; import ru.bclib.gui.screens.SyncFilesScreen;
import ru.bclib.gui.screens.WarnBCLibVersionMismatch; import ru.bclib.gui.screens.WarnBCLibVersionMismatch;
import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil; import ru.bclib.util.PathUtil;
import ru.bclib.util.PathUtil.ModInfo;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -69,10 +73,23 @@ public class HelloClient extends DataHandler.FromServer {
//write Plugin Versions //write Plugin Versions
buf.writeInt(mods.size()); buf.writeInt(mods.size());
for (String modID : mods) { for (String modID : mods) {
writeString(buf, modID);
final String ver = PathUtil.getModVersion(modID); final String ver = PathUtil.getModVersion(modID);
final ModInfo mi = PathUtil.getModInfo(modID);
int size = 0;
if (mi!=null) {
try {
size = (int)Files.size(mi.jarPath);
}
catch (IOException e) {
BCLib.LOGGER.error("Unable to get File Size: " + e.getMessage());
}
}
writeString(buf, modID);
buf.writeInt(DataFixerAPI.getModVersion(ver)); buf.writeInt(DataFixerAPI.getModVersion(ver));
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver); buf.writeInt(size);
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " ("+PathUtil.humanReadableFileSize(size)+")");
} }
} }
else { else {
@ -114,7 +131,7 @@ public class HelloClient extends DataHandler.FromServer {
} }
String bclibVersion = "0.0.0"; String bclibVersion = "0.0.0";
Map<String, String> modVersion = new HashMap<>(); Map<String, Pair<String, Integer>> modVersion = new HashMap<>();
List<AutoSync.AutoSyncTriple> autoSyncedFiles = null; List<AutoSync.AutoSyncTriple> autoSyncedFiles = null;
List<SyncFolderDescriptor> autoSynFolders = null; List<SyncFolderDescriptor> autoSynFolders = null;
@ -123,14 +140,23 @@ public class HelloClient extends DataHandler.FromServer {
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) { protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
//read BCLibVersion (=protocol version) //read BCLibVersion (=protocol version)
bclibVersion = DataFixerAPI.getModVersion(buf.readInt()); bclibVersion = DataFixerAPI.getModVersion(buf.readInt());
final boolean protocolVersion_0_4_1 = DataFixerAPI.isLargerOrEqualVersion(bclibVersion, "0.4.1");
//read Plugin Versions //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); final String id = readString(buf);
String version = DataFixerAPI.getModVersion(buf.readInt()); final String version = DataFixerAPI.getModVersion(buf.readInt());
modVersion.put(id, version); final int size;
//since v0.4.1 we also send the size of the mod-File
if (protocolVersion_0_4_1) {
size = buf.readInt();
} else {
size = 0;
}
modVersion.put(id, new Pair<>(version, size));
} }
//read config Data //read config Data
@ -146,7 +172,7 @@ public class HelloClient extends DataHandler.FromServer {
autoSynFolders = new ArrayList<>(1); autoSynFolders = new ArrayList<>(1);
//since v0.4.1 we also send the sync folders //since v0.4.1 we also send the sync folders
if (DataFixerAPI.isLargerOrEqualVersion(bclibVersion, "0.4.1")) { if (protocolVersion_0_4_1) {
final int folderCount = buf.readInt(); final int folderCount = buf.readInt();
for (int i = 0; i < folderCount; i++) { for (int i = 0; i < folderCount; i++) {
SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf); SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
@ -262,14 +288,14 @@ public class HelloClient extends DataHandler.FromServer {
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
private void processModFileSync(final List<AutoSyncID> filesToRequest) { private void processModFileSync(final List<AutoSyncID> filesToRequest) {
for (Entry<String, String> e : modVersion.entrySet()) { for (Entry<String, Pair<String, Integer>> e : modVersion.entrySet()) {
final String localVersion = PathUtil.getModVersion(e.getKey()); final String localVersion = PathUtil.getModVersion(e.getKey());
final String serverVersion = e.getValue(); final Pair<String, Integer> serverInfo = e.getValue();
final boolean requestMod = !serverVersion.equals(localVersion); final boolean requestMod = !serverInfo.first.equals(localVersion) && serverInfo.second>0;
BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + localVersion + ", server=" + serverVersion + (requestMod?", requesting":"") +")"); BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + localVersion + ", server=" + serverInfo.first + ", size=" + PathUtil.humanReadableFileSize(serverInfo.second) + (requestMod?", requesting":"") +")");
if (requestMod){ if (requestMod){
filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverVersion)); filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverInfo.first));
} }
} }
} }

View file

@ -14,6 +14,7 @@ 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.AutoSync.Config;
import ru.bclib.gui.screens.ConfirmRestartScreen; import ru.bclib.gui.screens.ConfirmRestartScreen;
import ru.bclib.util.Pair; import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil;
import ru.bclib.util.Triple; import ru.bclib.util.Triple;
import java.io.File; import java.io.File;
@ -75,7 +76,7 @@ public class SendFiles extends DataHandler.FromServer {
BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:"); BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
for (AutoFileSyncEntry entry : existingFiles) { for (AutoFileSyncEntry entry : existingFiles) {
int length = entry.serializeContent(buf); int length = entry.serializeContent(buf);
BCLib.LOGGER.info(" - " + entry + " (" + length + " Bytes)"); BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")");
} }
} }
@ -101,7 +102,7 @@ public class SendFiles extends DataHandler.FromServer {
Triple<AutoFileSyncEntry, byte[], AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf); Triple<AutoFileSyncEntry, byte[], AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf);
if (p.first != null) { if (p.first != null) {
receivedFiles.add(p); receivedFiles.add(p);
BCLib.LOGGER.info(" - " + p.first + " (" + p.second.length + " Bytes)"); BCLib.LOGGER.info(" - " + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
} }
else { else {
BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client."); BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client.");
@ -128,10 +129,36 @@ public class SendFiles extends DataHandler.FromServer {
} }
} }
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) { public static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
Path path = fileName.toPath(); if (!PathUtil.MOD_BAK_FOLDER.toFile().exists()){
BCLib.LOGGER.info(" - Writing " + path + " (" + data.length + " Bytes)"); PathUtil.MOD_BAK_FOLDER.toFile().mkdirs();
}
Path path = fileName!=null?fileName.toPath():null;
Path removeAfter = null;
if (e instanceof AutoFileSyncEntry.ForModFileRequest){
AutoFileSyncEntry.ForModFileRequest mase = (AutoFileSyncEntry.ForModFileRequest)e;
removeAfter = path;
int count = 0;
String name = "bclib_synced_" + mase.modID + "_" + mase.version.replace(".", "_") + ".jar";
do {
if (path != null) {
//move to the same directory as the existing Mod
path = path.getParent()
.resolve(name);
}
else {
//move to the default mode location
path = PathUtil.MOD_FOLDER.resolve(name);
}
count++;
name = "bclib_synced_" + mase.modID + "_" + mase.version.replace(".", "_") + "__" + String.format("%03d", count) + ".jar";
} while (path.toFile().exists());
}
BCLib.LOGGER.info(" - Writing " + path + " (" + PathUtil.humanReadableFileSize(data.length) + ")");
try { try {
final File parentFile = path.getParent() final File parentFile = path.getParent()
.toFile(); .toFile();
@ -139,7 +166,23 @@ public class SendFiles extends DataHandler.FromServer {
parentFile.mkdirs(); parentFile.mkdirs();
} }
Files.write(path, data); Files.write(path, data);
if (removeAfter != null){
final String bakFileName = removeAfter.toFile().getName();
String collisionFreeName = bakFileName;
Path targetPath;
int count = 0;
do {
targetPath = PathUtil.MOD_BAK_FOLDER.resolve(collisionFreeName);
count++;
collisionFreeName = String.format("%03d", count) + "_" + bakFileName;
} while (targetPath.toFile().exists());
BCLib.LOGGER.info(" - Moving " + removeAfter + " to " +targetPath);
removeAfter.toFile().renameTo(targetPath.toFile());
}
AutoSync.didReceiveFile(e, fileName); AutoSync.didReceiveFile(e, fileName);
} }
catch (IOException ioException) { catch (IOException ioException) {
BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException); BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);

View file

@ -30,6 +30,9 @@ public class PathUtil {
.resolve("mods") .resolve("mods")
.normalize(); .normalize();
public final static Path MOD_BAK_FOLDER = MOD_FOLDER.resolve("_bclib_deactivated")
.normalize();
/** /**
* Tests if a path is a child-path. * Tests if a path is a child-path.
* <p> * <p>
@ -95,7 +98,13 @@ public class PathUtil {
@Override @Override
public String toString() { public String toString() {
return "ModInfo{" + "id=" + metadata.getId()+ "version=" + metadata.getVersion() + "jarPath=" + jarPath + '}'; return "ModInfo{" + "id=" + metadata.getId() + ", version=" + metadata.getVersion() + ", jarPath=" + jarPath + '}';
}
public String getVersion() {
if (metadata == null) return "0.0.0";
return metadata.getVersion()
.toString();
} }
} }
@ -115,10 +124,11 @@ public class PathUtil {
* calling {@link #invalidateCachedMods()} * calling {@link #invalidateCachedMods()}
* <p> * <p>
* An error message is printed if a mod fails to load, but the parsing will continue. * An error message is printed if a mod fails to load, but the parsing will continue.
*
* @return A map of all found mods. (key=ModID, value={@link ModInfo}) * @return A map of all found mods. (key=ModID, value={@link ModInfo})
*/ */
public static Map<String, ModInfo> getMods() { public static Map<String, ModInfo> getMods() {
if (mods!=null) return mods; if (mods != null) return mods;
mods = new HashMap<>(); mods = new HashMap<>();
org.apache.logging.log4j.Logger logger = LogManager.getFormatterLogger("BCLib|ModLoader"); org.apache.logging.log4j.Logger logger = LogManager.getFormatterLogger("BCLib|ModLoader");
@ -150,18 +160,24 @@ public class PathUtil {
* <p> * <p>
* The call will also return null if the mode-Version in the jar-File is not the same * The call will also return null if the mode-Version in the jar-File is not the same
* as the version of the loaded Mod. * as the version of the loaded Mod.
*
* @param modID The mod ID to query * @param modID The mod ID to query
* @return A {@link ModInfo}-Object for the querried Mod. * @return A {@link ModInfo}-Object for the querried Mod.
*/ */
public static ModInfo getModInfo(String modID){ public static ModInfo getModInfo(String modID) {
return getModInfo(modID, true);
}
public static ModInfo getModInfo(String modID, boolean matchVersion) {
getMods(); getMods();
final ModInfo mi = mods.get(modID); final ModInfo mi = mods.get(modID);
if (!getModVersion(modID).equals(mi.metadata.getVersion())) return null; if (mi == null || !getModVersion(modID).equals(mi.getVersion())) return null;
return mi; return mi;
} }
/** /**
* Local Mod Version for the queried Mod * Local Mod Version for the queried Mod
*
* @param modID The mod ID to query * @param modID The mod ID to query
* @return The version of the locally installed Mod * @return The version of the locally installed Mod
*/ */
@ -176,4 +192,28 @@ public class PathUtil {
} }
return "0.0.0"; return "0.0.0";
} }
/**
* Creates a human readable File-Size
*
* @param size Filesize in bytes
* @return A Human readable String
*/
public static String humanReadableFileSize(long size) {
final int threshold = 2;
final int factor = 1024;
if (size < 0) return "? Byte";
if (size < factor * threshold) {
return size + " Byte";
}
char[] units = {'K', 'M', 'G', 'T', 'P'};
int unitIndex = 0;
double fSize = size;
do {
unitIndex++;
fSize /= 1024;
} while (fSize > factor * threshold && unitIndex < units.length);
return String.format("%.1f %ciB", fSize, units[unitIndex - 1]);
}
} }