[Ready for Testing] Folder-Syncing
This commit is contained in:
parent
9cd0ef1f04
commit
0b9d6093a0
11 changed files with 1034 additions and 608 deletions
|
@ -98,22 +98,22 @@ 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, FileHash.NEED_TRANSFER);
|
||||
getInstance().addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
* <p>
|
||||
* The file is synced of the {@link FileHash} on client and server are not equal. This method will not copy the
|
||||
* The file is synced of the {@link SyncFileHash} on client and server are not equal. This method will not copy the
|
||||
* configs content from the client to the server.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link ru.bclib.api.dataexchange.FileHash#uniqueID} for
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param fileName The name of the File
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID, String uniqueID, File fileName) {
|
||||
getInstance().addAutoSyncFileData(modID, uniqueID, fileName, false, FileHash.NEED_TRANSFER);
|
||||
getInstance().addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,7 +123,7 @@ public class DataExchangeAPI extends DataExchange {
|
|||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* if the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash}
|
||||
* if the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
|
@ -141,11 +141,11 @@ public class DataExchangeAPI extends DataExchange {
|
|||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* if the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash}
|
||||
* if the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link ru.bclib.api.dataexchange.FileHash#uniqueID} for
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param fileName The name of the File
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
|
@ -174,14 +174,14 @@ public class DataExchangeAPI extends DataExchange {
|
|||
* @param modID ID of the Mod
|
||||
* @return The path to the sync-folder
|
||||
*/
|
||||
public static File getSyncFolder(String modID) {
|
||||
File fl = SYNC_FOLDER.resolve(modID.replace(".", "-")
|
||||
.replace(":", "-")
|
||||
.replace("\\", "-")
|
||||
.replace("/", "-"))
|
||||
.toFile();
|
||||
public static File getModSyncFolder(String modID) {
|
||||
File fl = SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-")
|
||||
.replace(":", "-")
|
||||
.replace("\\", "-")
|
||||
.replace("/", "-"))
|
||||
.toFile();
|
||||
|
||||
if (!fl.exists()){
|
||||
if (!fl.exists()) {
|
||||
fl.mkdirs();
|
||||
}
|
||||
return fl;
|
||||
|
|
|
@ -3,8 +3,6 @@ package ru.bclib.api.dataexchange;
|
|||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import ru.bclib.BCLib;
|
||||
import ru.bclib.api.dataexchange.handler.AutoSyncID;
|
||||
import ru.bclib.api.dataexchange.handler.DataExchange;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -15,182 +13,149 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Calculates a hash based on the contents of a File.
|
||||
* <p>
|
||||
* A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
|
||||
* <p>
|
||||
* You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical.
|
||||
*/
|
||||
public class FileHash extends AutoSyncID {
|
||||
/**
|
||||
* The md5-hash of the file
|
||||
*/
|
||||
@NotNull
|
||||
public final byte[] md5;
|
||||
|
||||
/**
|
||||
* The size (in bytes) of the input.
|
||||
*/
|
||||
public final int size;
|
||||
|
||||
/**
|
||||
* a value that is directly calculated from defined byte positions.
|
||||
*/
|
||||
public final int value;
|
||||
|
||||
|
||||
|
||||
FileHash(String modID, File file, byte[] md5, int size, int value) {
|
||||
this(modID, file.getName(), md5, size, value);
|
||||
}
|
||||
|
||||
FileHash(String modID, String uniqueID, byte[] md5, int size, int value) {
|
||||
super(modID, uniqueID);
|
||||
Objects.nonNull(md5);
|
||||
|
||||
this.md5 = md5;
|
||||
this.size = size;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private static int ERR_DOES_NOT_EXIST = -10;
|
||||
private static int ERR_IO_ERROR = -20;
|
||||
static FileHash createForEmpty(String modID, String uniqueID, int errCode){
|
||||
return new FileHash(modID, uniqueID, new byte[0], 0, errCode);
|
||||
}
|
||||
|
||||
final static DataExchange.NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content)-> !clientHash.equals(serverHash);
|
||||
|
||||
public boolean noFile() {
|
||||
return md5.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()+": "+String.format("%08x", size)
|
||||
+ "-"
|
||||
+ String.format("%08x", value)
|
||||
+ "-"
|
||||
+ getMd5String();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FileHash fileHash = (FileHash) o;
|
||||
return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5) && uniqueID.equals(fileHash.uniqueID) && modID.equals(fileHash.modID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(size, value, uniqueID, modID);
|
||||
result = 31 * result + Arrays.hashCode(md5);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the md5-hash to a human readable string
|
||||
* @return The converted String
|
||||
*/
|
||||
public String getMd5String(){
|
||||
return toHexString(md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Object to a buffer
|
||||
* @param buf The buffer to write to
|
||||
*/
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
buf.writeInt(size);
|
||||
buf.writeInt(value);
|
||||
buf.writeByteArray(md5);
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
*Deserialize a Buffer to a new {@link FileHash}-Object
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static FileHash deserialize(FriendlyByteBuf buf){
|
||||
final int size = buf.readInt();
|
||||
final int value = buf.readInt();
|
||||
final byte[] md5 = buf.readByteArray();
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
|
||||
return new FileHash(modID, uniqueID, md5, size, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte-array to a hex-string representation
|
||||
* @param bytes The source array
|
||||
* @return The resulting string, or an empty String if the input was {@code null}
|
||||
*/
|
||||
public static String toHexString(byte[] bytes) {
|
||||
if (bytes==null) return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FileHash}.
|
||||
* <p>
|
||||
* Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}.
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
*
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static FileHash create(String modID, File file){
|
||||
return create(modID, file, file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FileHash}.
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
* @param uniqueID The unique ID that is used for this File (see {@link FileHash#uniqueID} for Details.
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static FileHash create(String modID, File file, String uniqueID){
|
||||
if (!file.exists()) return createForEmpty(modID, uniqueID, ERR_DOES_NOT_EXIST);
|
||||
final Path path = file.toPath();
|
||||
|
||||
int size = 0;
|
||||
byte[] md5 = new byte[0];
|
||||
int value = 0;
|
||||
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(path);
|
||||
|
||||
size = data.length;
|
||||
|
||||
value = size>0 ? (data[size/3] | (data [size/2]<<8) | (data [size/5]<<16)) : -1;
|
||||
if (size>20) value |= data[20]<<24;
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(data);
|
||||
md5 = md.digest();
|
||||
|
||||
return new FileHash(modID, uniqueID, md5, size, value);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error("Failed to read file: " + file);
|
||||
return null;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
BCLib.LOGGER.error("Unable to build hash for file: " + file);
|
||||
}
|
||||
|
||||
return createForEmpty(modID, uniqueID, ERR_IO_ERROR);
|
||||
}
|
||||
public class FileHash {
|
||||
private static int ERR_DOES_NOT_EXIST = -10;
|
||||
private static int ERR_IO_ERROR = -20;
|
||||
|
||||
/**
|
||||
* The md5-hash of the file
|
||||
*/
|
||||
@NotNull
|
||||
public final byte[] md5;
|
||||
|
||||
/**
|
||||
* The size (in bytes) of the input.
|
||||
*/
|
||||
public final int size;
|
||||
|
||||
/**
|
||||
* a value that is directly calculated from defined byte positions.
|
||||
*/
|
||||
public final int value;
|
||||
|
||||
FileHash(byte[] md5, int size, int value) {
|
||||
Objects.nonNull(md5);
|
||||
|
||||
this.md5 = md5;
|
||||
this.size = size;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static FileHash createForEmpty(int errCode) {
|
||||
return new FileHash(new byte[0], 0, errCode);
|
||||
}
|
||||
|
||||
public boolean noFile() {
|
||||
return md5.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Object to a buffer
|
||||
*
|
||||
* @param buf The buffer to write to
|
||||
*/
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
buf.writeInt(size);
|
||||
buf.writeInt(value);
|
||||
buf.writeByteArray(md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
|
||||
*
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static FileHash deserialize(FriendlyByteBuf buf) {
|
||||
final int size = buf.readInt();
|
||||
final int value = buf.readInt();
|
||||
final byte[] md5 = buf.readByteArray();
|
||||
|
||||
return new FileHash(md5, size, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the md5-hash to a human readable string
|
||||
*
|
||||
* @return The converted String
|
||||
*/
|
||||
public String getMd5String() {
|
||||
return toHexString(md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte-array to a hex-string representation
|
||||
*
|
||||
* @param bytes The source array
|
||||
* @return The resulting string, or an empty String if the input was {@code null}
|
||||
*/
|
||||
public static String toHexString(byte[] bytes) {
|
||||
if (bytes == null) return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FileHash}.
|
||||
*
|
||||
* @param file The input file
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static FileHash create(File file) {
|
||||
if (!file.exists()) return createForEmpty(ERR_DOES_NOT_EXIST);
|
||||
final Path path = file.toPath();
|
||||
|
||||
int size = 0;
|
||||
byte[] md5 = new byte[0];
|
||||
int value = 0;
|
||||
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(path);
|
||||
|
||||
size = data.length;
|
||||
|
||||
value = size > 0 ? (data[size / 3] | (data[size / 2] << 8) | (data[size / 5] << 16)) : -1;
|
||||
if (size > 20) value |= data[20] << 24;
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(data);
|
||||
md5 = md.digest();
|
||||
|
||||
return new FileHash(md5, size, value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
BCLib.LOGGER.error("Failed to read file: " + file);
|
||||
return null;
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
BCLib.LOGGER.error("Unable to build hash for file: " + file);
|
||||
}
|
||||
|
||||
return createForEmpty(ERR_IO_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof FileHash)) return false;
|
||||
FileHash fileHash = (FileHash) o;
|
||||
return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(size, value);
|
||||
result = 31 * result + Arrays.hashCode(md5);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%08x", size) + "-" + String.format("%08x", value) + "-" + getMd5String();
|
||||
}
|
||||
}
|
||||
|
|
108
src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java
Normal file
108
src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
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 java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Calculates a hash based on the contents of a File.
|
||||
* <p>
|
||||
* A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
|
||||
* <p>
|
||||
* You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical.
|
||||
*/
|
||||
public class SyncFileHash extends AutoSyncID {
|
||||
public final FileHash hash;
|
||||
|
||||
SyncFileHash(String modID, File file, byte[] md5, int size, int value) {
|
||||
this(modID, file.getName(), md5, size, value);
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, String uniqueID, byte[] md5, int size, int value) {
|
||||
this(modID, uniqueID, new FileHash(md5, size, value));
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, File file, FileHash hash) {
|
||||
this(modID, file.getName(), hash);
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, String uniqueID, FileHash hash) {
|
||||
super(modID, uniqueID);
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
|
||||
final static DataExchange.NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content)-> !clientHash.equals(serverHash);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()+": "+hash.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SyncFileHash)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
SyncFileHash that = (SyncFileHash) o;
|
||||
return hash.equals(that.hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Object to a buffer
|
||||
* @param buf The buffer to write to
|
||||
*/
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
hash.serialize(buf);
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
*Deserialize a Buffer to a new {@link SyncFileHash}-Object
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static SyncFileHash deserialize(FriendlyByteBuf buf){
|
||||
final FileHash hash = FileHash.deserialize(buf);
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
|
||||
return new SyncFileHash(modID, uniqueID, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SyncFileHash}.
|
||||
* <p>
|
||||
* Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}.
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
*
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static SyncFileHash create(String modID, File file){
|
||||
return create(modID, file, file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SyncFileHash}.
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
* @param uniqueID The unique ID that is used for this File (see {@link SyncFileHash#uniqueID} for Details.
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static SyncFileHash create(String modID, File file, String uniqueID){
|
||||
return new SyncFileHash(modID, uniqueID, FileHash.create(file));
|
||||
}
|
||||
}
|
|
@ -2,7 +2,9 @@ package ru.bclib.api.dataexchange.handler;
|
|||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import ru.bclib.api.dataexchange.DataHandler;
|
||||
import ru.bclib.api.dataexchange.FileHash;
|
||||
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.util.Pair;
|
||||
import ru.bclib.util.Triple;
|
||||
|
||||
|
@ -12,114 +14,157 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
|
||||
class AutoFileSyncEntry extends AutoSyncID {
|
||||
public final DataExchange.NeedTransferPredicate needTransfer;
|
||||
public final File fileName;
|
||||
public final boolean requestContent;
|
||||
private FileHash hash;
|
||||
|
||||
AutoFileSyncEntry(String modID, File fileName, boolean requestContent, DataExchange.NeedTransferPredicate needTransfer) {
|
||||
this(modID, fileName.getName(), fileName, requestContent, needTransfer);
|
||||
}
|
||||
|
||||
AutoFileSyncEntry(String modID, String uniqueID, File fileName, boolean requestContent, DataExchange.NeedTransferPredicate needTransfer) {
|
||||
super(modID, uniqueID);
|
||||
this.needTransfer = needTransfer;
|
||||
this.fileName = fileName;
|
||||
this.requestContent = requestContent;
|
||||
}
|
||||
|
||||
public FileHash getFileHash() {
|
||||
if (hash == null)
|
||||
{
|
||||
hash = FileHash.create(modID, fileName, uniqueID);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
if (!fileName.exists()) return new byte[0];
|
||||
final Path path = fileName.toPath();
|
||||
|
||||
try {
|
||||
return Files.readAllBytes(path);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
return serializeFileContent(buf);
|
||||
}
|
||||
|
||||
public static Triple<AutoFileSyncEntry, byte[], AutoSyncID> deserializeContent(FriendlyByteBuf buf) {
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
byte[] data = deserializeFileContent(buf);
|
||||
|
||||
AutoFileSyncEntry entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
|
||||
return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
|
||||
}
|
||||
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
getFileHash().serialize(buf);
|
||||
buf.writeBoolean(requestContent);
|
||||
|
||||
if (requestContent) {
|
||||
serializeFileContent(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public static DataExchange.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) {
|
||||
Pair<FileHash, byte[]> e = deserialize(buf);
|
||||
AutoFileSyncEntry match = findMatching(e.first);
|
||||
return new DataExchange.AutoSyncTriple(e.first, e.second, match);
|
||||
}
|
||||
|
||||
public static Pair<FileHash, byte[]> deserialize(FriendlyByteBuf buf) {
|
||||
FileHash hash = FileHash.deserialize(buf);
|
||||
boolean withContent = buf.readBoolean();
|
||||
byte[] data = null;
|
||||
if (withContent) {
|
||||
data = deserializeFileContent(buf);
|
||||
}
|
||||
|
||||
return new Pair(hash, data);
|
||||
}
|
||||
|
||||
private int serializeFileContent(FriendlyByteBuf buf) {
|
||||
byte[] content = getContent();
|
||||
buf.writeInt(content.length);
|
||||
buf.writeByteArray(content);
|
||||
return content.length;
|
||||
}
|
||||
|
||||
private static byte[] deserializeFileContent(FriendlyByteBuf buf) {
|
||||
byte[] data;
|
||||
int size = buf.readInt();
|
||||
data = buf.readByteArray(size);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public static AutoFileSyncEntry findMatching(FileHash hash) {
|
||||
return findMatching(hash.modID, hash.uniqueID);
|
||||
}
|
||||
|
||||
public static AutoFileSyncEntry findMatching(AutoSyncID aid) {
|
||||
return findMatching(aid.modID, aid.uniqueID);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
static class ForDirectFileRequest extends AutoFileSyncEntry {
|
||||
final File relFile;
|
||||
|
||||
ForDirectFileRequest(String syncID, File relFile, File absFile) {
|
||||
super(AutoSyncID.ForDirectFileRequest.MOD_ID, syncID, absFile, false, (a, b, c) -> false);
|
||||
this.relFile = relFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
int res = super.serializeContent(buf);
|
||||
DataHandler.writeString(buf, relFile.toString());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static AutoFileSyncEntry.ForDirectFileRequest finishDeserializeContent(String syncID, FriendlyByteBuf buf){
|
||||
final String relFile = DataHandler.readString(buf);
|
||||
SyncFolderDescriptor desc = DataExchange.getSyncFolderDescriptor(syncID);
|
||||
if (desc!=null) {
|
||||
return new AutoFileSyncEntry.ForDirectFileRequest(syncID, new File(relFile), desc.localFolder.resolve(relFile)
|
||||
.toFile());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public final DataExchange.NeedTransferPredicate needTransfer;
|
||||
public final File fileName;
|
||||
public final boolean requestContent;
|
||||
private SyncFileHash hash;
|
||||
|
||||
AutoFileSyncEntry(String modID, File fileName, boolean requestContent, DataExchange.NeedTransferPredicate needTransfer) {
|
||||
this(modID, fileName.getName(), fileName, requestContent, needTransfer);
|
||||
}
|
||||
|
||||
AutoFileSyncEntry(String modID, String uniqueID, File fileName, boolean requestContent, DataExchange.NeedTransferPredicate needTransfer) {
|
||||
super(modID, uniqueID);
|
||||
this.needTransfer = needTransfer;
|
||||
this.fileName = fileName;
|
||||
this.requestContent = requestContent;
|
||||
}
|
||||
|
||||
public SyncFileHash getFileHash() {
|
||||
if (hash == null) {
|
||||
hash = SyncFileHash.create(modID, fileName, uniqueID);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
if (!fileName.exists()) return new byte[0];
|
||||
final Path path = fileName.toPath();
|
||||
|
||||
try {
|
||||
return Files.readAllBytes(path);
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
return serializeFileContent(buf);
|
||||
}
|
||||
|
||||
public static Triple<AutoFileSyncEntry, byte[], AutoSyncID> deserializeContent(FriendlyByteBuf buf) {
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
byte[] data = deserializeFileContent(buf);
|
||||
|
||||
AutoFileSyncEntry entry;
|
||||
if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)){
|
||||
entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
|
||||
} else {
|
||||
entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
|
||||
}
|
||||
return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
|
||||
}
|
||||
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
getFileHash().serialize(buf);
|
||||
buf.writeBoolean(requestContent);
|
||||
|
||||
if (requestContent) {
|
||||
serializeFileContent(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public static DataExchange.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) {
|
||||
Pair<SyncFileHash, byte[]> e = deserialize(buf);
|
||||
AutoFileSyncEntry match = findMatching(e.first);
|
||||
return new DataExchange.AutoSyncTriple(e.first, e.second, match);
|
||||
}
|
||||
|
||||
public static Pair<SyncFileHash, byte[]> deserialize(FriendlyByteBuf buf) {
|
||||
SyncFileHash hash = SyncFileHash.deserialize(buf);
|
||||
boolean withContent = buf.readBoolean();
|
||||
byte[] data = null;
|
||||
if (withContent) {
|
||||
data = deserializeFileContent(buf);
|
||||
}
|
||||
|
||||
return new Pair(hash, data);
|
||||
}
|
||||
|
||||
private int serializeFileContent(FriendlyByteBuf buf) {
|
||||
byte[] content = getContent();
|
||||
buf.writeInt(content.length);
|
||||
buf.writeByteArray(content);
|
||||
return content.length;
|
||||
}
|
||||
|
||||
private static byte[] deserializeFileContent(FriendlyByteBuf buf) {
|
||||
byte[] data;
|
||||
int size = buf.readInt();
|
||||
data = buf.readByteArray(size);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public static AutoFileSyncEntry findMatching(SyncFileHash hash) {
|
||||
return findMatching(hash.modID, hash.uniqueID);
|
||||
}
|
||||
|
||||
public static AutoFileSyncEntry findMatching(AutoSyncID aid) {
|
||||
if (aid instanceof AutoSyncID.ForDirectFileRequest) {
|
||||
AutoSyncID.ForDirectFileRequest freq = (AutoSyncID.ForDirectFileRequest) aid;
|
||||
SyncFolderDescriptor desc = DataExchange.getSyncFolderDescriptor(freq.uniqueID);
|
||||
if (desc != null) {
|
||||
SubFile subFile = desc.getLocalSubFile(freq.relFile.toString());
|
||||
if (subFile != null) {
|
||||
final File absPath = desc.localFolder.resolve(subFile.relPath)
|
||||
.toFile();
|
||||
return new AutoFileSyncEntry.ForDirectFileRequest(freq.uniqueID, new File(subFile.relPath), absPath);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return findMatching(aid.modID, aid.uniqueID);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package ru.bclib.api.dataexchange.handler;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import ru.bclib.api.dataexchange.DataHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
@ -16,6 +18,27 @@ public class AutoSyncID {
|
|||
this.localFile = localFile;
|
||||
}
|
||||
}
|
||||
|
||||
static class ForDirectFileRequest extends AutoSyncID {
|
||||
public final static String MOD_ID = "bclib::FILE";
|
||||
final File relFile;
|
||||
|
||||
ForDirectFileRequest(String syncID, File relFile) {
|
||||
super(ForDirectFileRequest.MOD_ID, syncID);
|
||||
this.relFile = relFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
super.serializeData(buf);
|
||||
DataHandler.writeString(buf, relFile.toString());
|
||||
}
|
||||
|
||||
static ForDirectFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf){
|
||||
final File fl = new File(DataHandler.readString(buf));
|
||||
return new ForDirectFileRequest(uniqueID, fl);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A Unique ID for the referenced File.
|
||||
* <p>
|
||||
|
@ -56,4 +79,20 @@ public class AutoSyncID {
|
|||
public int hashCode() {
|
||||
return Objects.hash(uniqueID, modID);
|
||||
}
|
||||
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
}
|
||||
|
||||
static AutoSyncID deserializeData(FriendlyByteBuf buf){
|
||||
String modID = DataHandler.readString(buf);
|
||||
String uID = DataHandler.readString(buf);
|
||||
|
||||
if (ForDirectFileRequest.MOD_ID.equals(modID)){
|
||||
return ForDirectFileRequest.finishDeserialize(modID, uID, buf);
|
||||
} else {
|
||||
return new AutoSyncID(modID, uID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,230 +5,426 @@ 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 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.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
abstract public class DataExchange {
|
||||
public final static Path SYNC_FOLDER = FabricLoader.getInstance()
|
||||
.getGameDir()
|
||||
.resolve("bclib-sync")
|
||||
.toAbsolutePath();
|
||||
public final static String SYNC_FOLDER_ID = "BCLIB-SYNC";
|
||||
|
||||
@FunctionalInterface
|
||||
public interface NeedTransferPredicate {
|
||||
public boolean test(FileHash clientHash, FileHash serverHash, FileContentWrapper content);
|
||||
}
|
||||
|
||||
protected final static List<BiConsumer<AutoSyncID, File>> onWriteCallbacks = new ArrayList<>(2);
|
||||
|
||||
final static class AutoSyncTriple {
|
||||
public final FileHash serverHash;
|
||||
public final byte[] serverContent;
|
||||
public final AutoFileSyncEntry localMatch;
|
||||
|
||||
public AutoSyncTriple(FileHash 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;
|
||||
protected static DataExchangeAPI getInstance(){
|
||||
if (instance==null){
|
||||
instance = new DataExchangeAPI();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected ConnectorServerside server;
|
||||
protected ConnectorClientside client;
|
||||
protected final Set<DataHandlerDescriptor> descriptors;
|
||||
private final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
|
||||
|
||||
private boolean didLoadSyncFolder = false;
|
||||
|
||||
abstract protected ConnectorClientside clientSupplier(DataExchange api);
|
||||
abstract protected ConnectorServerside serverSupplier(DataExchange api);
|
||||
|
||||
protected DataExchange(){
|
||||
descriptors = new HashSet<>();
|
||||
}
|
||||
|
||||
public Set<DataHandlerDescriptor> getDescriptors() { return descriptors; }
|
||||
|
||||
public List<AutoFileSyncEntry> getAutoSyncFiles(){
|
||||
return autoSyncFiles;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void initClientside(){
|
||||
if (client!=null) return;
|
||||
client = clientSupplier(this);
|
||||
public final static SyncFolderDescriptor SYNC_FOLDER = new SyncFolderDescriptor("BCLIB-SYNC", FabricLoader.getInstance()
|
||||
.getGameDir()
|
||||
.resolve("bclib-sync")
|
||||
.toAbsolutePath(), true);
|
||||
final List<SyncFolderDescriptor> 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<SubFile> 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);
|
||||
fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(localFolder.relativize(p)
|
||||
.toString(), FileHash.create(p.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<SubFile> relativeFilesStream() {
|
||||
loadCache();
|
||||
return fileCache.stream();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface NeedTransferPredicate {
|
||||
public boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content);
|
||||
}
|
||||
|
||||
protected final static List<BiConsumer<AutoSyncID, File>> 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;
|
||||
|
||||
protected static DataExchangeAPI getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new DataExchangeAPI();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected ConnectorServerside server;
|
||||
protected ConnectorClientside client;
|
||||
protected final Set<DataHandlerDescriptor> descriptors;
|
||||
private final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
|
||||
|
||||
private boolean didLoadSyncFolder = false;
|
||||
|
||||
abstract protected ConnectorClientside clientSupplier(DataExchange api);
|
||||
|
||||
abstract protected ConnectorServerside serverSupplier(DataExchange api);
|
||||
|
||||
protected DataExchange() {
|
||||
descriptors = new HashSet<>();
|
||||
}
|
||||
|
||||
public Set<DataHandlerDescriptor> getDescriptors() { return descriptors; }
|
||||
|
||||
public List<AutoFileSyncEntry> getAutoSyncFiles() {
|
||||
return autoSyncFiles;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void initClientside() {
|
||||
if (client != null) return;
|
||||
client = clientSupplier(this);
|
||||
/*ClientLoginConnectionEvents.INIT.register((a, b) ->{
|
||||
System.out.println("INIT");
|
||||
});
|
||||
ClientLoginConnectionEvents.QUERY_START.register((a, b) ->{
|
||||
System.out.println("INIT");
|
||||
});*/
|
||||
ClientPlayConnectionEvents.INIT.register(client::onPlayInit);
|
||||
ClientPlayConnectionEvents.JOIN.register(client::onPlayReady);
|
||||
ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect);
|
||||
}
|
||||
|
||||
protected void initServerSide(){
|
||||
if (server!=null) return;
|
||||
server = serverSupplier(this);
|
||||
|
||||
ServerPlayConnectionEvents.INIT.register(server::onPlayInit);
|
||||
ServerPlayConnectionEvents.JOIN.register(server::onPlayReady);
|
||||
ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the client component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void prepareClientside(){
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initClientside();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the server component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
public static void prepareServerside(){
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initServerSide();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Automatically called before the player enters the world.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
|
||||
* {@code true},
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void sendOnEnter(){
|
||||
getInstance().descriptors.forEach((desc)-> {
|
||||
if (desc.sendBeforeEnter){
|
||||
DataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (!h.getOriginatesOnServer()) {
|
||||
getInstance().client.sendToServer(h);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
* @param fileName The name of the File
|
||||
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash}
|
||||
* for comparison is sufficient.
|
||||
*/
|
||||
protected void addAutoSyncFileData(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer){
|
||||
autoSyncFiles.add(new AutoFileSyncEntry(modID, fileName, requestContent, needTransfer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link ru.bclib.api.dataexchange.FileHash#uniqueID} for
|
||||
* Details
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
* @param fileName The name of the File
|
||||
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link ru.bclib.api.dataexchange.FileHash}
|
||||
* for comparison is sufficient.
|
||||
*/
|
||||
protected void addAutoSyncFileData(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer){
|
||||
autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem.
|
||||
* <p>
|
||||
* 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<String> syncFolderContent;
|
||||
protected List<String> getSyncFolderContent(){
|
||||
if (syncFolderContent==null){
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return syncFolderContent;
|
||||
}
|
||||
|
||||
//we call this from HelloServer to prepare transfer
|
||||
protected void loadSyncFolder() {
|
||||
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offserSyncFolder", true))
|
||||
{
|
||||
final File syncPath = SYNC_FOLDER.toFile();
|
||||
if (!syncPath.exists()) {
|
||||
syncPath.mkdirs();
|
||||
}
|
||||
|
||||
if (syncFolderContent == null) {
|
||||
syncFolderContent = new ArrayList<>(8);
|
||||
addFilesForSyncFolder(syncPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFilesForSyncFolder(File path){
|
||||
for (final File f : path.listFiles()) {
|
||||
if (f.isDirectory()) {
|
||||
addFilesForSyncFolder(f);
|
||||
} else if (f.isFile()) {
|
||||
if (!f.getName().startsWith(".")) {
|
||||
Path p = f.toPath();
|
||||
p = SYNC_FOLDER.relativize(p);
|
||||
syncFolderContent.add(p.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
ClientPlayConnectionEvents.INIT.register(client::onPlayInit);
|
||||
ClientPlayConnectionEvents.JOIN.register(client::onPlayReady);
|
||||
ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect);
|
||||
}
|
||||
|
||||
protected void initServerSide() {
|
||||
if (server != null) return;
|
||||
server = serverSupplier(this);
|
||||
|
||||
ServerPlayConnectionEvents.INIT.register(server::onPlayInit);
|
||||
ServerPlayConnectionEvents.JOIN.register(server::onPlayReady);
|
||||
ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the client component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void prepareClientside() {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initClientside();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the server component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
public static void prepareServerside() {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initServerSide();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically called before the player enters the world.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
|
||||
* {@code true},
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void sendOnEnter() {
|
||||
getInstance().descriptors.forEach((desc) -> {
|
||||
if (desc.sendBeforeEnter) {
|
||||
DataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (!h.getOriginatesOnServer()) {
|
||||
getInstance().client.sendToServer(h);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
* @param fileName The name of the File
|
||||
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link 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.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link 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.
|
||||
* <p>
|
||||
* 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<String> syncFolderContent;
|
||||
|
||||
protected List<String> getSyncFolderContent() {
|
||||
if (syncFolderContent == null) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return syncFolderContent;
|
||||
}
|
||||
|
||||
//we call this from HelloServer to prepare transfer
|
||||
protected void loadSyncFolder() {
|
||||
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A simple directory walker that ignores dot-files
|
||||
*
|
||||
* @param path The path where you want to start
|
||||
* @param pathConsumer The consumer called for each valid file. The consumer will get an absolute {@link Path}-Object
|
||||
* for each visited file
|
||||
*/
|
||||
public static void fileWalker(File path, Consumer<Path> pathConsumer) {
|
||||
if (!path.exists()) return;
|
||||
for (final File f : path.listFiles()) {
|
||||
if (f.getName()
|
||||
.startsWith(".")) continue;
|
||||
if (f.isDirectory()) {
|
||||
fileWalker(f, pathConsumer);
|
||||
}
|
||||
else if (f.isFile()) {
|
||||
pathConsumer.accept(f.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,14 @@ 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.datafixer.DataFixerAPI;
|
||||
import ru.bclib.config.Configs;
|
||||
import ru.bclib.gui.screens.SyncFilesScreen;
|
||||
import ru.bclib.gui.screens.WarnBCLibVersionMismatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -41,28 +44,31 @@ public class HelloClient extends DataHandler {
|
|||
super(DESCRIPTOR.IDENTIFIER, true);
|
||||
}
|
||||
|
||||
public static String getModVersion(String modID){
|
||||
Optional<ModContainer> optional = FabricLoader.getInstance().getModContainer(modID);
|
||||
public static String getModVersion(String modID) {
|
||||
Optional<ModContainer> optional = FabricLoader.getInstance()
|
||||
.getModContainer(modID);
|
||||
if (optional.isPresent()) {
|
||||
ModContainer modContainer = optional.get();
|
||||
return modContainer.getMetadata().getVersion().toString();
|
||||
return modContainer.getMetadata()
|
||||
.getVersion()
|
||||
.toString();
|
||||
}
|
||||
return "0.0.0";
|
||||
}
|
||||
|
||||
static String getBCLibVersion(){
|
||||
static String getBCLibVersion() {
|
||||
return getModVersion(BCLib.MOD_ID);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void serializeData(FriendlyByteBuf buf) {
|
||||
final String vbclib = getBCLibVersion();
|
||||
BCLib.LOGGER.info("Sending Hello to Client. (server="+vbclib+")");
|
||||
BCLib.LOGGER.info("Sending Hello to Client. (server=" + vbclib + ")");
|
||||
final List<String> mods = DataExchangeAPI.registeredMods();
|
||||
|
||||
|
||||
//write BCLibVersion (=protocol version)
|
||||
buf.writeInt(DataFixerAPI.getModVersion(vbclib));
|
||||
|
||||
|
||||
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offerMods", true)) {
|
||||
//write Plugin Versions
|
||||
buf.writeInt(mods.size());
|
||||
|
@ -72,128 +78,162 @@ public class HelloClient extends DataHandler {
|
|||
buf.writeInt(DataFixerAPI.getModVersion(ver));
|
||||
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
BCLib.LOGGER.info("Server will not list Mods.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
|
||||
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offerFiles", true)) {
|
||||
//do only include files that exist on the server
|
||||
final List<AutoFileSyncEntry> existingAutoSyncFiles = DataExchange
|
||||
.getInstance()
|
||||
.getAutoSyncFiles()
|
||||
.stream()
|
||||
.filter(e -> e.fileName.exists())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<AutoFileSyncEntry> existingAutoSyncFiles = DataExchange.getInstance()
|
||||
.getAutoSyncFiles()
|
||||
.stream()
|
||||
.filter(e -> e.fileName.exists())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
//send config Data
|
||||
buf.writeInt(existingAutoSyncFiles.size());
|
||||
for (AutoFileSyncEntry entry : existingAutoSyncFiles) {
|
||||
entry.serialize(buf);
|
||||
BCLib.LOGGER.info(" - Offering File " + entry);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
BCLib.LOGGER.info("Server will not offer Files.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
//for the moment this is only hardcoded for the sync-folder offered by BCLIB, but it can be extended in future
|
||||
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offserSyncFolder", true)) {
|
||||
buf.writeInt(1); //currently we do only sync a single folder
|
||||
writeString(buf, DataExchange.SYNC_FOLDER_ID); //the UID of the Folder
|
||||
final List<String> fileNames = DataExchange.getInstance().getSyncFolderContent();
|
||||
buf.writeInt(fileNames.size());
|
||||
fileNames.forEach(fl -> writeString(buf, fl));
|
||||
} else {
|
||||
if (Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "offersSyncFolders", true)) {
|
||||
buf.writeInt(((DataExchange) DataExchange.getInstance()).syncFolderDescriptions.size());
|
||||
((DataExchange) DataExchange.getInstance()).syncFolderDescriptions.forEach(desc -> {
|
||||
BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete="+desc.removeAdditionalFiles+")");
|
||||
desc.serialize(buf);
|
||||
});
|
||||
}
|
||||
else {
|
||||
BCLib.LOGGER.info("Server will not offer Sync Folders.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
Configs.MAIN_CONFIG.saveChanges();
|
||||
}
|
||||
|
||||
String bclibVersion ="0.0.0";
|
||||
String bclibVersion = "0.0.0";
|
||||
Map<String, String> modVersion = new HashMap<>();
|
||||
List<DataExchange.AutoSyncTriple> autoSyncedFiles = null;
|
||||
List<SyncFolderDescriptor> autoSynFolders = null;
|
||||
|
||||
@Override
|
||||
protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) {
|
||||
//read BCLibVersion (=protocol version)
|
||||
bclibVersion = DataFixerAPI.getModVersion(buf.readInt());
|
||||
|
||||
|
||||
//read Plugin Versions
|
||||
modVersion = new HashMap<>();
|
||||
int count = buf.readInt();
|
||||
for (int i=0; i< count; i++) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
String id = readString(buf);
|
||||
String version = DataFixerAPI.getModVersion(buf.readInt());
|
||||
modVersion.put(id, version);
|
||||
}
|
||||
|
||||
|
||||
//read config Data
|
||||
count = buf.readInt();
|
||||
autoSyncedFiles = new ArrayList<>(count);
|
||||
for (int i=0; i< count; i++) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
//System.out.println("Deserializing ");
|
||||
DataExchange.AutoSyncTriple t = AutoFileSyncEntry.deserializeAndMatch(buf);
|
||||
autoSyncedFiles.add(t);
|
||||
//System.out.println(t.first);
|
||||
}
|
||||
|
||||
//since this version we also send the sync folders
|
||||
|
||||
autoSynFolders = new ArrayList<>(1);
|
||||
//since v0.4.1 we also send the sync folders
|
||||
if (DataFixerAPI.isLargerOrEqualVersion(bclibVersion, "0.4.1")) {
|
||||
final int folderCount = buf.readInt();
|
||||
for (int i=0; i<folderCount; i++){
|
||||
final String folderID = readString(buf);
|
||||
final int entries = buf.readInt();
|
||||
List<String> files = new ArrayList<>(entries);
|
||||
for (int j=0; j<entries; j++){
|
||||
files.add(readString(buf));
|
||||
}
|
||||
|
||||
if (folderID.equals(DataExchange.SYNC_FOLDER_ID)) {
|
||||
//TODO: implement the syncing here
|
||||
} else {
|
||||
BCLib.LOGGER.warning("Unknown Sync-Folder '"+folderID+"'");
|
||||
}
|
||||
for (int i = 0; i < folderCount; i++) {
|
||||
SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
|
||||
autoSynFolders.add(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient) {
|
||||
final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "debugHashes", false);
|
||||
final String localBclibVersion = getBCLibVersion();
|
||||
BCLib.LOGGER.info("Received Hello from Server. (client="+localBclibVersion+", server="+bclibVersion+")");
|
||||
|
||||
// if (DataFixerAPI.getModVersion(localBclibVersion) != DataFixerAPI.getModVersion(bclibVersion)){
|
||||
// showBCLibError(client);
|
||||
// return;
|
||||
// }
|
||||
|
||||
final List<AutoSyncID> filesToRequest = new ArrayList<>(2);
|
||||
|
||||
|
||||
|
||||
for (Entry<String, String> e : modVersion.entrySet()){
|
||||
String ver = getModVersion(e.getKey());
|
||||
BCLib.LOGGER.info(" - " + e.getKey() + " (client="+ver+", server="+ver+")");
|
||||
private void processAutoSyncFolder(final List<AutoSyncID> filesToRequest, final List<AutoSyncID.ForDirectFileRequest> filesToRemove) {
|
||||
if (!Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "syncFolders", true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoSyncedFiles.size()>0) {
|
||||
|
||||
if (autoSynFolders.size() > 0) {
|
||||
BCLib.LOGGER.info("Folders offered by Server:");
|
||||
}
|
||||
|
||||
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);
|
||||
if (localDescriptor != null) {
|
||||
BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")");
|
||||
localDescriptor.invalidateCache();
|
||||
|
||||
if (desc.removeAdditionalFiles) {
|
||||
List<AutoSyncID.ForDirectFileRequest> additionalFiles = localDescriptor.relativeFilesStream()
|
||||
.filter(subFile -> !desc.hasRelativeFile(subFile))
|
||||
.map(subFile -> new AutoSyncID.ForDirectFileRequest(desc.folderID, desc.localFolder.resolve(subFile.relPath).toFile()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * Removing " + aid.relFile));
|
||||
filesToRemove.addAll(additionalFiles);
|
||||
}
|
||||
|
||||
desc.relativeFilesStream().forEach(subFile -> {
|
||||
SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath);
|
||||
if (localSubFile != null){
|
||||
//the file exists locally, check if the hashes match
|
||||
if (!localSubFile.hash.equals(subFile.hash)){
|
||||
BCLib.LOGGER.info(" * Changed " + subFile.relPath);
|
||||
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID, new File(subFile.relPath)));
|
||||
}
|
||||
} else {
|
||||
//the file is missing locally
|
||||
BCLib.LOGGER.info(" * Missing " + subFile.relPath);
|
||||
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID, new File(subFile.relPath)));
|
||||
}
|
||||
});
|
||||
|
||||
//free some memory
|
||||
localDescriptor.invalidateCache();
|
||||
}
|
||||
else {
|
||||
BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void processSingleFileSync(final List<AutoSyncID> filesToRequest) {
|
||||
final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "debugHashes", false);
|
||||
|
||||
if (autoSyncedFiles.size() > 0) {
|
||||
BCLib.LOGGER.info("Files offered by Server:");
|
||||
}
|
||||
|
||||
//Handle single sync files
|
||||
//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) {
|
||||
String actionString = "";
|
||||
FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent);
|
||||
if (e.localMatch == null) {
|
||||
actionString = "(new, prepare update)";
|
||||
filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
|
||||
} else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
|
||||
}
|
||||
else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
|
||||
actionString = "(prepare update)";
|
||||
//we did not yet receive the new content
|
||||
if (contentWrapper.getRawContent() == null) {
|
||||
filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
filesToRequest.add(new AutoSyncID.WithContentOverride(e.serverHash.modID, e.serverHash.uniqueID, contentWrapper, e.localMatch.fileName));
|
||||
}
|
||||
}
|
||||
|
@ -205,20 +245,48 @@ public class HelloClient extends DataHandler {
|
|||
BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null));
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToRequest.size()>0 && SendFiles.acceptFiles()) {
|
||||
showDownloadConfigs(client, filesToRequest);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient) {
|
||||
final String localBclibVersion = getBCLibVersion();
|
||||
BCLib.LOGGER.info("Received Hello from Server. (client=" + localBclibVersion + ", server=" + bclibVersion + ")");
|
||||
|
||||
// if (DataFixerAPI.getModVersion(localBclibVersion) != DataFixerAPI.getModVersion(bclibVersion)){
|
||||
// showBCLibError(client);
|
||||
// return;
|
||||
// }
|
||||
|
||||
final List<AutoSyncID> filesToRequest = new ArrayList<>(2);
|
||||
final List<AutoSyncID.ForDirectFileRequest> filesToRemove = new ArrayList<>(2);
|
||||
|
||||
for (Entry<String, String> e : modVersion.entrySet()) {
|
||||
String ver = getModVersion(e.getKey());
|
||||
BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + ver + ", server=" + ver + ")");
|
||||
}
|
||||
|
||||
processSingleFileSync(filesToRequest);
|
||||
processAutoSyncFolder(filesToRequest, filesToRemove);
|
||||
|
||||
//Handle folder sync
|
||||
//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()) {
|
||||
showDownloadConfigs(client, filesToRequest, filesToRemove);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showBCLibError(Minecraft client){
|
||||
protected void showBCLibError(Minecraft client) {
|
||||
BCLib.LOGGER.error("BCLib differs on client and server.");
|
||||
client.setScreen(new WarnBCLibVersionMismatch((download) -> {
|
||||
Minecraft.getInstance().setScreen((Screen)null);
|
||||
if (download){
|
||||
requestBCLibDownload((hadErrors)->{
|
||||
Minecraft.getInstance()
|
||||
.setScreen((Screen) null);
|
||||
if (download) {
|
||||
requestBCLibDownload((hadErrors) -> {
|
||||
client.stop();
|
||||
});
|
||||
}
|
||||
|
@ -226,38 +294,45 @@ public class HelloClient extends DataHandler {
|
|||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showDownloadConfigs(Minecraft client, List<AutoSyncID> files){
|
||||
protected void showDownloadConfigs(Minecraft client, List<AutoSyncID> files, final List<AutoSyncID.ForDirectFileRequest> filesToRemove) {
|
||||
client.setScreen(new SyncFilesScreen((download) -> {
|
||||
Minecraft.getInstance().setScreen((Screen)null);
|
||||
if (download){
|
||||
Minecraft.getInstance()
|
||||
.setScreen((Screen) null);
|
||||
if (download) {
|
||||
BCLib.LOGGER.info("Updating local Files:");
|
||||
List<AutoSyncID.WithContentOverride> localChanges = new ArrayList<>(files.toArray().length);
|
||||
List<AutoSyncID> requestFiles = new ArrayList<>(files.toArray().length);
|
||||
|
||||
files.forEach(aid -> {
|
||||
if (aid instanceof WithContentOverride) {
|
||||
final WithContentOverride aidc = (WithContentOverride)aid;
|
||||
final WithContentOverride aidc = (WithContentOverride) aid;
|
||||
BCLib.LOGGER.info(" - " + aid + " (updating Content)");
|
||||
|
||||
SendFiles.writeSyncedFile(aid, aidc.contentWrapper.getRawContent(), aidc.localFile);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
requestFiles.add(aid);
|
||||
BCLib.LOGGER.info(" - " + aid + " (requesting)");
|
||||
}
|
||||
});
|
||||
|
||||
filesToRemove.forEach(aid -> {
|
||||
BCLib.LOGGER.info(" - " + aid.relFile + " (removing)");
|
||||
aid.relFile.delete();
|
||||
});
|
||||
|
||||
requestFileDownloads(requestFiles);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void requestBCLibDownload(Consumer<Boolean> whenFinished){
|
||||
private void requestBCLibDownload(Consumer<Boolean> whenFinished) {
|
||||
BCLib.LOGGER.warning("Starting download of BCLib");
|
||||
whenFinished.accept(true);
|
||||
}
|
||||
|
||||
private void requestFileDownloads(List<AutoSyncID> files){
|
||||
private void requestFileDownloads(List<AutoSyncID> files) {
|
||||
BCLib.LOGGER.info("Starting download of Files:" + files.size());
|
||||
DataExchangeAPI.send(new RequestFiles(files));
|
||||
}
|
||||
|
|
|
@ -36,8 +36,7 @@ public class RequestFiles extends DataHandler {
|
|||
buf.writeInt(files.size());
|
||||
|
||||
for (AutoSyncID a : files){
|
||||
writeString(buf, a.modID);
|
||||
writeString(buf, a.uniqueID);
|
||||
a.serializeData(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,9 +49,7 @@ public class RequestFiles extends DataHandler {
|
|||
|
||||
BCLib.LOGGER.info("Client requested " + size + " Files:");
|
||||
for (int i=0; i<size; i++){
|
||||
String modID = readString(buf);
|
||||
String uID = readString(buf);
|
||||
AutoSyncID asid = new AutoSyncID(modID, uID);
|
||||
AutoSyncID asid = AutoSyncID.deserializeData(buf);
|
||||
files.add(asid);
|
||||
BCLib.LOGGER.info(" - " + asid);
|
||||
}
|
||||
|
|
|
@ -108,6 +108,11 @@ public class SendFiles extends DataHandler {
|
|||
Path path = fileName.toPath();
|
||||
BCLib.LOGGER.info(" - Writing " + path + " (" + data.length + " Bytes)");
|
||||
try {
|
||||
final File parentFile = path.getParent()
|
||||
.toFile();
|
||||
if (!parentFile.exists()){
|
||||
parentFile.mkdirs();
|
||||
}
|
||||
Files.write(path, data);
|
||||
DataExchange.didReceiveFile(e, fileName);
|
||||
} catch (IOException ioException) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package ru.bclib.config;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
import ru.bclib.BCLib;
|
||||
import ru.bclib.api.dataexchange.DataExchangeAPI;
|
||||
import ru.bclib.api.dataexchange.FileHash;
|
||||
import ru.bclib.api.dataexchange.SyncFileHash;
|
||||
import ru.bclib.api.dataexchange.handler.AutoSyncID;
|
||||
import ru.bclib.api.dataexchange.handler.FileContentWrapper;
|
||||
import ru.bclib.config.ConfigKeeper.BooleanEntry;
|
||||
|
@ -50,7 +50,7 @@ public abstract class Config {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean compareForSync(FileHash fileHash, FileHash fileHash1, FileContentWrapper content) {
|
||||
private boolean compareForSync(SyncFileHash syncFileHash, SyncFileHash syncFileHash1, FileContentWrapper content) {
|
||||
return keeper.compareAndUpdateForSync(content);
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ public final class ConfigKeeper {
|
|||
changed = true;
|
||||
me.add(myKey.first + myKey.second, otherValue);
|
||||
}
|
||||
else if (otherValue.isJsonPrimitive()) {
|
||||
else if (otherValue.isJsonPrimitive() || otherValue.isJsonArray() || otherValue.isJsonNull()) {
|
||||
if (!otherValue.equals(myValue)) {
|
||||
changed = true;
|
||||
me.add(myKey.first + myKey.second, otherValue);
|
||||
|
@ -93,17 +93,13 @@ public final class ConfigKeeper {
|
|||
else if (otherValue.isJsonObject()) {
|
||||
changed |= compareAndUpdateForSync(myValue.getAsJsonObject(), otherValue.getAsJsonObject());
|
||||
}
|
||||
else if (otherValue.isJsonArray()) {
|
||||
if (!otherValue.equals(myValue)) {
|
||||
changed = true;
|
||||
me.add(myKey.first + myKey.second, otherValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else { //no entry, just copy the value from other
|
||||
changed = true;
|
||||
me.add(otherKey.first + otherKey.second, otherValue);
|
||||
if (!otherValue.isJsonNull()) {
|
||||
changed = true;
|
||||
temp = find(me, otherKey);
|
||||
me.add(otherKey.first + otherKey.second, otherValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue