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();
WorldDataAPI.registerModCache(MOD_ID);
DataExchangeAPI.registerMod(MOD_ID);
DataExchangeAPI.registerModDependency("wunderreich");
DataFixerAPI.registerPatch(() -> new BCLibPatch());
DataExchangeAPI.registerDescriptors(List.of(
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.handler.autosync.AutoSync.NeedTransferPredicate;
import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
import ru.bclib.api.datafixer.DataFixerAPI;
import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil;
import ru.bclib.util.PathUtil.ModInfo;
@ -54,34 +55,41 @@ class AutoFileSyncEntry extends AutoSyncID {
}
static class ForModFileRequest extends AutoFileSyncEntry {
public static Path getLocalPathForID(String modID){
ModInfo mi = PathUtil.getModInfo(modID);
public static File getLocalPathForID(String modID, boolean matchLocalVersion){
ModInfo mi = PathUtil.getModInfo(modID, matchLocalVersion);
if (mi!=null){
return mi.jarPath;
return mi.jarPath.toFile();
}
return null;
}
ForModFileRequest(String modID) {
super(modID, AutoSyncID.ForModFileRequest.UNIQUE_ID, getLocalPathForID(modID).toFile(), false, (a, b, c) -> false);
if (this.fileName == null){
public final String version;
ForModFileRequest(String modID, boolean matchLocalVersion, String version) {
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+"'.");
}
if (version==null)
this.version = PathUtil.getModVersion(modID);
else
this.version = version;
}
@Override
public int serializeContent(FriendlyByteBuf buf) {
final int res = super.serializeContent(buf);
buf.writeInt(DataFixerAPI.getModVersion(version));
return res;
}
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
public String toString() {
return modID;
return "Mod " + modID + " (v" + version + ")";
}
}
@ -137,7 +145,7 @@ class AutoFileSyncEntry extends AutoSyncID {
entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
}
else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) {
entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(uniqueID, buf);
entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(modID, buf);
}
else {
entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
@ -205,6 +213,9 @@ class AutoFileSyncEntry extends AutoSyncID {
}
}
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);
}

View file

@ -18,6 +18,11 @@ public class AutoSyncID {
this.contentWrapper = contentWrapper;
this.localFile = localFile;
}
@Override
public String toString() {
return super.toString() + " (Content override)";
}
}
static class ForDirectFileRequest extends AutoSyncID {
@ -39,6 +44,11 @@ public class AutoSyncID {
final File fl = new File(DataHandler.readString(buf));
return new ForDirectFileRequest(uniqueID, fl);
}
@Override
public String toString() {
return super.uniqueID + " (" + this.relFile + ")";
}
}
static class ForModFileRequest extends AutoSyncID {
@ -59,6 +69,11 @@ public class AutoSyncID {
final String version = DataFixerAPI.getModVersion(buf.readInt());
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.gui.screens.SyncFilesScreen;
import ru.bclib.gui.screens.WarnBCLibVersionMismatch;
import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil;
import ru.bclib.util.PathUtil.ModInfo;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -69,10 +73,23 @@ public class HelloClient extends DataHandler.FromServer {
//write Plugin Versions
buf.writeInt(mods.size());
for (String modID : mods) {
writeString(buf, 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));
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver);
buf.writeInt(size);
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " ("+PathUtil.humanReadableFileSize(size)+")");
}
}
else {
@ -114,7 +131,7 @@ public class HelloClient extends DataHandler.FromServer {
}
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<SyncFolderDescriptor> autoSynFolders = null;
@ -123,14 +140,23 @@ public class HelloClient extends DataHandler.FromServer {
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
//read BCLibVersion (=protocol version)
bclibVersion = DataFixerAPI.getModVersion(buf.readInt());
final boolean protocolVersion_0_4_1 = DataFixerAPI.isLargerOrEqualVersion(bclibVersion, "0.4.1");
//read Plugin Versions
modVersion = new HashMap<>();
int count = buf.readInt();
for (int i = 0; i < count; i++) {
String id = readString(buf);
String version = DataFixerAPI.getModVersion(buf.readInt());
modVersion.put(id, version);
final String id = readString(buf);
final String version = DataFixerAPI.getModVersion(buf.readInt());
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
@ -146,7 +172,7 @@ public class HelloClient extends DataHandler.FromServer {
autoSynFolders = new ArrayList<>(1);
//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();
for (int i = 0; i < folderCount; i++) {
SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
@ -262,14 +288,14 @@ public class HelloClient extends DataHandler.FromServer {
@Environment(EnvType.CLIENT)
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 serverVersion = e.getValue();
final boolean requestMod = !serverVersion.equals(localVersion);
final Pair<String, Integer> serverInfo = e.getValue();
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){
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.gui.screens.ConfirmRestartScreen;
import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil;
import ru.bclib.util.Triple;
import java.io.File;
@ -75,7 +76,7 @@ public class SendFiles extends DataHandler.FromServer {
BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
for (AutoFileSyncEntry entry : existingFiles) {
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);
if (p.first != null) {
receivedFiles.add(p);
BCLib.LOGGER.info(" - " + p.first + " (" + p.second.length + " Bytes)");
BCLib.LOGGER.info(" - " + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
}
else {
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)
public static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
Path path = fileName.toPath();
BCLib.LOGGER.info(" - Writing " + path + " (" + data.length + " Bytes)");
if (!PathUtil.MOD_BAK_FOLDER.toFile().exists()){
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 {
final File parentFile = path.getParent()
.toFile();
@ -139,7 +166,23 @@ public class SendFiles extends DataHandler.FromServer {
parentFile.mkdirs();
}
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);
}
catch (IOException ioException) {
BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);

View file

@ -30,6 +30,9 @@ public class PathUtil {
.resolve("mods")
.normalize();
public final static Path MOD_BAK_FOLDER = MOD_FOLDER.resolve("_bclib_deactivated")
.normalize();
/**
* Tests if a path is a child-path.
* <p>
@ -95,7 +98,13 @@ public class PathUtil {
@Override
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,6 +124,7 @@ public class PathUtil {
* calling {@link #invalidateCachedMods()}
* <p>
* 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})
*/
public static Map<String, ModInfo> getMods() {
@ -150,18 +160,24 @@ public class PathUtil {
* <p>
* 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.
*
* @param modID The mod ID to query
* @return A {@link ModInfo}-Object for the querried Mod.
*/
public static ModInfo getModInfo(String modID) {
return getModInfo(modID, true);
}
public static ModInfo getModInfo(String modID, boolean matchVersion) {
getMods();
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;
}
/**
* Local Mod Version for the queried Mod
*
* @param modID The mod ID to query
* @return The version of the locally installed Mod
*/
@ -176,4 +192,28 @@ public class PathUtil {
}
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]);
}
}