Implemented content based syncing
This commit is contained in:
parent
f80b55aa50
commit
5df6de1e3a
7 changed files with 194 additions and 49 deletions
|
@ -10,7 +10,7 @@ public class FileContentWrapper {
|
||||||
private byte[] rawContent;
|
private byte[] rawContent;
|
||||||
private ByteArrayOutputStream outputStream;
|
private ByteArrayOutputStream outputStream;
|
||||||
|
|
||||||
FileContentWrapper(byte[] content){
|
FileContentWrapper(byte[] content) {
|
||||||
this.rawContent = content;
|
this.rawContent = content;
|
||||||
this.outputStream = null;
|
this.outputStream = null;
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,14 @@ public class FileContentWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getRawContent() {
|
public byte[] getRawContent() {
|
||||||
if (outputStream!=null){
|
if (outputStream != null) {
|
||||||
return outputStream.toByteArray();
|
return outputStream.toByteArray();
|
||||||
}
|
}
|
||||||
return rawContent;
|
return rawContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invalidateOutputStream(){
|
private void invalidateOutputStream() {
|
||||||
if (this.outputStream!=null){
|
if (this.outputStream != null) {
|
||||||
try {
|
try {
|
||||||
this.outputStream.close();
|
this.outputStream.close();
|
||||||
}
|
}
|
||||||
|
@ -43,25 +43,33 @@ public class FileContentWrapper {
|
||||||
invalidateOutputStream();
|
invalidateOutputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void syncWithOutputStream(){
|
public void syncWithOutputStream() {
|
||||||
if (outputStream!=null){
|
if (outputStream != null) {
|
||||||
|
try {
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
BCLib.LOGGER.error(e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
setRawContent(getRawContent());
|
setRawContent(getRawContent());
|
||||||
|
invalidateOutputStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteArrayInputStream getInputStream(){
|
public ByteArrayInputStream getInputStream() {
|
||||||
if (rawContent==null) return new ByteArrayInputStream(new byte[0]);
|
if (rawContent == null) return new ByteArrayInputStream(new byte[0]);
|
||||||
return new ByteArrayInputStream(rawContent);
|
return new ByteArrayInputStream(rawContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteArrayOutputStream getOrCreateOutputStream(){
|
public ByteArrayOutputStream getOrCreateOutputStream() {
|
||||||
if (this.outputStream == null){
|
if (this.outputStream == null) {
|
||||||
return this.getEmptyOutputStream();
|
return this.getEmptyOutputStream();
|
||||||
}
|
}
|
||||||
return this.outputStream;
|
return this.outputStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteArrayOutputStream getEmptyOutputStream(){
|
public ByteArrayOutputStream getEmptyOutputStream() {
|
||||||
invalidateOutputStream();
|
invalidateOutputStream();
|
||||||
this.outputStream = new ByteArrayOutputStream(this.rawContent.length);
|
this.outputStream = new ByteArrayOutputStream(this.rawContent.length);
|
||||||
return this.outputStream;
|
return this.outputStream;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package ru.bclib.config;
|
package ru.bclib.config;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import ru.bclib.BCLib;
|
import ru.bclib.BCLib;
|
||||||
import ru.bclib.api.dataexchange.DataExchangeAPI;
|
import ru.bclib.api.dataexchange.DataExchangeAPI;
|
||||||
|
@ -13,9 +12,7 @@ import ru.bclib.config.ConfigKeeper.FloatEntry;
|
||||||
import ru.bclib.config.ConfigKeeper.IntegerEntry;
|
import ru.bclib.config.ConfigKeeper.IntegerEntry;
|
||||||
import ru.bclib.config.ConfigKeeper.RangeEntry;
|
import ru.bclib.config.ConfigKeeper.RangeEntry;
|
||||||
import ru.bclib.config.ConfigKeeper.StringEntry;
|
import ru.bclib.config.ConfigKeeper.StringEntry;
|
||||||
import ru.bclib.util.JsonFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -24,46 +21,46 @@ public abstract class Config {
|
||||||
protected final static Map<AutoSyncID, Config> autoSyncConfigs = new HashMap<>();
|
protected final static Map<AutoSyncID, Config> autoSyncConfigs = new HashMap<>();
|
||||||
protected final ConfigKeeper keeper;
|
protected final ConfigKeeper keeper;
|
||||||
protected final boolean autoSync;
|
protected final boolean autoSync;
|
||||||
|
|
||||||
protected abstract void registerEntries();
|
protected abstract void registerEntries();
|
||||||
|
|
||||||
protected Config(String modID, String group) {
|
protected Config(String modID, String group) {
|
||||||
this(modID, group, true, false);
|
this(modID, group, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Config(String modID, String group, boolean autoSync){
|
protected Config(String modID, String group, boolean autoSync) {
|
||||||
this(modID, group, autoSync, false);
|
this(modID, group, autoSync, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Config(String modID, String group, boolean autoSync, boolean diffContent) {
|
protected Config(String modID, String group, boolean autoSync, boolean diffContent) {
|
||||||
BCLib.LOGGER.info("Registered Config " + modID+"."+group+" ("+autoSync+")");
|
BCLib.LOGGER.info("Registered Config " + modID + "." + group + " (" + autoSync + ")");
|
||||||
this.keeper = new ConfigKeeper(modID, group);
|
this.keeper = new ConfigKeeper(modID, group);
|
||||||
this.registerEntries();
|
this.registerEntries();
|
||||||
this.autoSync = autoSync;
|
this.autoSync = autoSync;
|
||||||
|
|
||||||
if (autoSync) {
|
if (autoSync) {
|
||||||
final String uid = "CONFIG_" + modID + "_" + group;
|
final String uid = "CONFIG_" + modID + "_" + group;
|
||||||
final AutoSyncID aid = new AutoSyncID(BCLib.MOD_ID, uid);
|
final AutoSyncID aid = new AutoSyncID(BCLib.MOD_ID, uid);
|
||||||
DataExchangeAPI.addAutoSyncFile(aid.modID, aid.uniqueID, keeper.getConfigFile(), this::compareForSync );
|
DataExchangeAPI.addAutoSyncFile(aid.modID, aid.uniqueID, keeper.getConfigFile(), this::compareForSync);
|
||||||
autoSyncConfigs.put(aid, this);
|
autoSyncConfigs.put(aid, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean compareForSync(FileHash fileHash, FileHash fileHash1, FileContentWrapper content) {
|
private boolean compareForSync(FileHash fileHash, FileHash fileHash1, FileContentWrapper content) {
|
||||||
ByteArrayInputStream inputStream = content.getInputStream();
|
return keeper.compareAndUpdateForSync(content);
|
||||||
final JsonObject other = JsonFactory.getJsonObject(inputStream);
|
|
||||||
return this.keeper.compareAndUpdateForSync(other);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveChanges() {
|
public void saveChanges() {
|
||||||
this.keeper.save();
|
this.keeper.save();
|
||||||
}
|
}
|
||||||
public static void reloadSyncedConfig(AutoSyncID aid, File file){
|
|
||||||
Config cfg = autoSyncConfigs.get(aid);
|
public static void reloadSyncedConfig(AutoSyncID aid, File file) {
|
||||||
if (cfg!=null) {
|
Config cfg = autoSyncConfigs.get(aid);
|
||||||
|
if (cfg != null) {
|
||||||
cfg.reload();
|
cfg.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reload() {
|
public void reload() {
|
||||||
this.keeper.reload();
|
this.keeper.reload();
|
||||||
BCLib.LOGGER.info("Did Reload " + keeper.getConfigFile());
|
BCLib.LOGGER.info("Did Reload " + keeper.getConfigFile());
|
||||||
|
@ -200,6 +197,6 @@ public abstract class Config {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,13 @@ import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import net.minecraft.util.GsonHelper;
|
import net.minecraft.util.GsonHelper;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import ru.bclib.api.dataexchange.handler.FileContentWrapper;
|
||||||
import ru.bclib.util.JsonFactory;
|
import ru.bclib.util.JsonFactory;
|
||||||
|
import ru.bclib.util.Pair;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -25,24 +29,97 @@ public final class ConfigKeeper {
|
||||||
this.writer = new ConfigWriter(modID, group);
|
this.writer = new ConfigWriter(modID, group);
|
||||||
this.configObject = writer.load();
|
this.configObject = writer.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
File getConfigFile(){
|
File getConfigFile() {
|
||||||
return this.writer.getConfigFile();
|
return this.writer.getConfigFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean compareAndUpdateForSync(JsonObject other) {
|
boolean compareAndUpdateForSync(FileContentWrapper content) {
|
||||||
final JsonObject me = this.configObject;
|
ByteArrayInputStream inputStream = content.getInputStream();
|
||||||
return true;
|
final JsonObject other = JsonFactory.getJsonObject(inputStream);
|
||||||
|
|
||||||
|
boolean changed = this.compareAndUpdateForSync(other);
|
||||||
|
if (changed) {
|
||||||
|
OutputStream outStream = content.getEmptyOutputStream();
|
||||||
|
JsonFactory.storeJson(outStream, this.configObject);
|
||||||
|
content.syncWithOutputStream();
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean compareAndUpdateForSync(JsonObject other) {
|
||||||
|
return compareAndUpdateForSync(this.configObject, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<JsonElement, Pair<String, String>> find(JsonObject json, Pair<String, String> key) {
|
||||||
|
for (var entry : json.entrySet()) {
|
||||||
|
final Pair<String, String> otherKey = ConfigKey.realKey(entry.getKey());
|
||||||
|
if (otherKey.first.equals(key.first)) return new Pair<>(entry.getValue(), otherKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for content based auto-sync.
|
||||||
|
*
|
||||||
|
* @param me - When called in AutoSync this represents the content of the client.
|
||||||
|
* @param other - When called in AutoSync, this represents the content of the server
|
||||||
|
* @return {@code true} if content was changed
|
||||||
|
*/
|
||||||
|
static boolean compareAndUpdateForSync(JsonObject me, JsonObject other) {
|
||||||
|
boolean changed = false;
|
||||||
|
for (var otherEntry : other.entrySet()) {
|
||||||
|
final Pair<String, String> otherKey = ConfigKey.realKey(otherEntry.getKey());
|
||||||
|
final JsonElement otherValue = otherEntry.getValue();
|
||||||
|
|
||||||
|
Pair<JsonElement, Pair<String, String>> temp = find(me, otherKey);
|
||||||
|
//we already have an entry
|
||||||
|
if (temp != null) {
|
||||||
|
final Pair<String, String> myKey = temp.second;
|
||||||
|
final JsonElement myValue = temp.first;
|
||||||
|
|
||||||
|
if ((otherValue.isJsonNull() && !myValue.isJsonNull()) || (otherValue.isJsonPrimitive() && !myValue.isJsonPrimitive()) || (otherValue.isJsonObject() && !myValue.isJsonObject()) || (otherValue.isJsonArray() && !myValue.isJsonArray())) {
|
||||||
|
//types are different => replace with "server"-version in other
|
||||||
|
changed = true;
|
||||||
|
me.add(myKey.first + myKey.second, otherValue);
|
||||||
|
}
|
||||||
|
else if (otherValue.isJsonPrimitive()) {
|
||||||
|
if (!otherValue.equals(myValue)) {
|
||||||
|
changed = true;
|
||||||
|
me.add(myKey.first + myKey.second, otherValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
if (!changed) return;
|
if (!changed) return;
|
||||||
this.writer.save();
|
this.writer.save();
|
||||||
this.changed = false;
|
this.changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reload() {
|
void reload() {
|
||||||
this.configObject = this.writer.reload();
|
this.configObject = this.writer.reload();
|
||||||
|
this.configEntries.clear();
|
||||||
this.changed = false;
|
this.changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package ru.bclib.config;
|
package ru.bclib.config;
|
||||||
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import ru.bclib.util.Pair;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -84,4 +86,15 @@ public class ConfigKey {
|
||||||
throw new IndexOutOfBoundsException("Config key must be not empty!");
|
throw new IndexOutOfBoundsException("Config key must be not empty!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Pair<String, String> realKey(@NotNull String key) {
|
||||||
|
String[] parts = key.split("\\[default:", 2);
|
||||||
|
if (parts.length == 1) {
|
||||||
|
return new Pair(parts[0].trim(), "");
|
||||||
|
}
|
||||||
|
else if (parts.length == 2) {
|
||||||
|
return new Pair(parts[0].trim(), " " + ("[default:" + parts[1]).trim());
|
||||||
|
}
|
||||||
|
return new Pair(key, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ import java.io.OutputStreamWriter;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
|
||||||
public class JsonFactory {
|
public class JsonFactory {
|
||||||
public final static Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
public final static Gson GSON = new GsonBuilder().setPrettyPrinting()
|
||||||
|
.create();
|
||||||
|
|
||||||
public static JsonObject getJsonObject(InputStream stream) {
|
public static JsonObject getJsonObject(InputStream stream) {
|
||||||
try {
|
try {
|
||||||
|
@ -61,7 +62,8 @@ public class JsonFactory {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Environment(EnvType.CLIENT)
|
@Environment(EnvType.CLIENT)
|
||||||
public static JsonObject getJsonObject(ResourceLocation location) {
|
public static JsonObject getJsonObject(ResourceLocation location) {
|
||||||
ResourceManager manager = Minecraft.getInstance().getResourceManager();
|
ResourceManager manager = Minecraft.getInstance()
|
||||||
|
.getResourceManager();
|
||||||
JsonObject obj = null;
|
JsonObject obj = null;
|
||||||
try {
|
try {
|
||||||
Resource resource = manager.getResource(location);
|
Resource resource = manager.getResource(location);
|
||||||
|
@ -108,6 +110,13 @@ public class JsonFactory {
|
||||||
public static void storeJson(OutputStream outStream, JsonElement jsonObject) {
|
public static void storeJson(OutputStream outStream, JsonElement jsonObject) {
|
||||||
OutputStreamWriter writer = new OutputStreamWriter(outStream);
|
OutputStreamWriter writer = new OutputStreamWriter(outStream);
|
||||||
GSON.toJson(jsonObject, writer);
|
GSON.toJson(jsonObject, writer);
|
||||||
|
try {
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
BCLib.LOGGER.error(e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getInt(JsonObject object, String member, int def) {
|
public static int getInt(JsonObject object, String member, int def) {
|
||||||
|
|
|
@ -1,11 +1,31 @@
|
||||||
package ru.bclib.util;
|
package ru.bclib.util;
|
||||||
|
|
||||||
public class Pair <A, B>{
|
import java.util.Objects;
|
||||||
public final A first;
|
|
||||||
public final B second;
|
|
||||||
|
|
||||||
public Pair(A first, B second) {
|
public class Pair<A, B> {
|
||||||
this.first = first;
|
public final A first;
|
||||||
this.second = second;
|
public final B second;
|
||||||
}
|
|
||||||
|
public Pair(A first, B second) {
|
||||||
|
this.first = first;
|
||||||
|
this.second = second;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Pair{" + "first=" + first + ", second=" + second + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Pair)) return false;
|
||||||
|
Pair<?, ?> pair = (Pair<?, ?>) o;
|
||||||
|
return Objects.equals(first, pair.first) && Objects.equals(second, pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(first, second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,31 @@
|
||||||
package ru.bclib.util;
|
package ru.bclib.util;
|
||||||
|
|
||||||
public class Triple <A, B, C> extends Pair<A, B>{
|
import java.util.Objects;
|
||||||
public final C third;
|
|
||||||
|
|
||||||
public Triple(A first, B second, C third) {
|
public class Triple<A, B, C> extends Pair<A, B> {
|
||||||
super(first, second);
|
public final C third;
|
||||||
this.third = third;
|
|
||||||
}
|
public Triple(A first, B second, C third) {
|
||||||
|
super(first, second);
|
||||||
|
this.third = third;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Triple{" + "first=" + first + ", second=" + second + ", third=" + third + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Triple)) return false;
|
||||||
|
if (!super.equals(o)) return false;
|
||||||
|
Triple<?, ?, ?> triple = (Triple<?, ?, ?>) o;
|
||||||
|
return Objects.equals(third, triple.third);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(super.hashCode(), third);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue