Prepared sync pipeline for content-based config sync

This commit is contained in:
Frank Bauer 2021-08-15 11:30:39 +02:00
parent 895b605523
commit f80b55aa50
14 changed files with 242 additions and 90 deletions

1
.gitignore vendored
View file

@ -27,5 +27,6 @@ bin/
# fabric # fabric
run/ run/
run-client/
output/ output/
*.log *.log

View file

@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import ru.bclib.api.dataexchange.handler.AutoSyncID;
import ru.bclib.api.dataexchange.handler.DataExchange; import ru.bclib.api.dataexchange.handler.DataExchange;
import ru.bclib.config.Config; import ru.bclib.config.Config;

View file

@ -3,6 +3,7 @@ package ru.bclib.api.dataexchange;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import ru.bclib.BCLib; import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.handler.AutoSyncID;
import ru.bclib.api.dataexchange.handler.DataExchange; import ru.bclib.api.dataexchange.handler.DataExchange;
import java.io.File; import java.io.File;
@ -22,7 +23,7 @@ import java.util.Objects;
* You can compare instances using {@link #equals(Object)} to determine if two files are * You can compare instances using {@link #equals(Object)} to determine if two files are
* identical. * identical.
*/ */
public class FileHash extends DataExchange.AutoSyncID { public class FileHash extends AutoSyncID {
/** /**
* The md5-hash of the file * The md5-hash of the file
*/ */

View file

@ -11,7 +11,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
class AutoFileSyncEntry extends DataExchange.AutoSyncID { class AutoFileSyncEntry extends AutoSyncID {
public final DataExchange.NeedTransferPredicate needTransfer; public final DataExchange.NeedTransferPredicate needTransfer;
public final File fileName; public final File fileName;
public final boolean requestContent; public final boolean requestContent;
@ -54,13 +54,13 @@ class AutoFileSyncEntry extends DataExchange.AutoSyncID {
return serializeFileContent(buf); return serializeFileContent(buf);
} }
public static Triple<AutoFileSyncEntry, byte[], DataExchange.AutoSyncID> deserializeContent(FriendlyByteBuf buf) { public static Triple<AutoFileSyncEntry, byte[], AutoSyncID> deserializeContent(FriendlyByteBuf buf) {
final String modID = DataHandler.readString(buf); final String modID = DataHandler.readString(buf);
final String uniqueID = DataHandler.readString(buf); final String uniqueID = DataHandler.readString(buf);
byte[] data = deserializeFileContent(buf); byte[] data = deserializeFileContent(buf);
AutoFileSyncEntry entry = AutoFileSyncEntry.findMatching(modID, uniqueID); AutoFileSyncEntry entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
return new Triple<>(entry, data, new DataExchange.AutoSyncID(modID, uniqueID)); return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
} }
@ -108,7 +108,7 @@ class AutoFileSyncEntry extends DataExchange.AutoSyncID {
return findMatching(hash.modID, hash.uniqueID); return findMatching(hash.modID, hash.uniqueID);
} }
public static AutoFileSyncEntry findMatching(DataExchange.AutoSyncID aid) { public static AutoFileSyncEntry findMatching(AutoSyncID aid) {
return findMatching(aid.modID, aid.uniqueID); return findMatching(aid.modID, aid.uniqueID);
} }

View file

@ -0,0 +1,59 @@
package ru.bclib.api.dataexchange.handler;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Objects;
public class AutoSyncID {
static class WithContentOverride extends AutoSyncID {
final FileContentWrapper contentWrapper;
final File localFile;
WithContentOverride(String modID, String uniqueID, FileContentWrapper contentWrapper, File localFile) {
super(modID, uniqueID);
this.contentWrapper = contentWrapper;
this.localFile = localFile;
}
}
/**
* A Unique ID for the referenced File.
* <p>
* Files with the same {@link #modID} need to have a unique IDs. Normally the filename from FileHash(String, File, byte[], int, int)
* is used to generated that ID, but you can directly specify one using FileHash(String, String, byte[], int, int).
*/
@NotNull
public final String uniqueID;
/**
* The ID of the Mod that is registering the File
*/
@NotNull
public final String modID;
public AutoSyncID(String modID, String uniqueID) {
Objects.nonNull(modID);
Objects.nonNull(uniqueID);
this.modID = modID;
this.uniqueID = uniqueID;
}
@Override
public String toString() {
return modID + "." + uniqueID;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AutoSyncID)) return false;
AutoSyncID that = (AutoSyncID) o;
return uniqueID.equals(that.uniqueID) && modID.equals(that.modID);
}
@Override
public int hashCode() {
return Objects.hash(uniqueID, modID);
}
}

View file

@ -4,82 +4,42 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import org.jetbrains.annotations.NotNull;
import ru.bclib.api.dataexchange.ConnectorClientside; import ru.bclib.api.dataexchange.ConnectorClientside;
import ru.bclib.api.dataexchange.ConnectorServerside; import ru.bclib.api.dataexchange.ConnectorServerside;
import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor; import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.FileHash; import ru.bclib.api.dataexchange.FileHash;
import ru.bclib.util.Triple;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
abstract public class DataExchange { abstract public class DataExchange {
@FunctionalInterface @FunctionalInterface
public interface NeedTransferPredicate { public interface NeedTransferPredicate {
public boolean test(FileHash clientHash, FileHash serverHash, byte[] content); public boolean test(FileHash clientHash, FileHash serverHash, FileContentWrapper content);
} }
public static class AutoSyncID {
/**
* A Unique ID for the referenced File.
* <p>
* Files with the same {@link #modID} need to have a unique IDs. Normally the filename from FileHash(String, File, byte[], int, int)
* is used to generated that ID, but you can directly specify one using FileHash(String, String, byte[], int, int).
*/
@NotNull
public final String uniqueID;
/**
* The ID of the Mod that is registering the File
*/
@NotNull
public final String modID;
public AutoSyncID(String modID, String uniqueID) {
Objects.nonNull(modID);
Objects.nonNull(uniqueID);
this.modID = modID;
this.uniqueID = uniqueID;
}
@Override
public String toString() {
return modID+"."+uniqueID;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AutoSyncID)) return false;
AutoSyncID that = (AutoSyncID) o;
return uniqueID.equals(that.uniqueID) && modID.equals(that.modID);
}
@Override
public int hashCode() {
return Objects.hash(uniqueID, modID);
}
}
protected final static List<BiConsumer<AutoSyncID, File>> onWriteCallbacks = new ArrayList<>(2); protected final static List<BiConsumer<AutoSyncID, File>> onWriteCallbacks = new ArrayList<>(2);
final static class AutoSyncTriple extends Triple<FileHash, byte[], AutoFileSyncEntry>{ final static class AutoSyncTriple {
public AutoSyncTriple(FileHash first, byte[] second, AutoFileSyncEntry third) { public final FileHash serverHash;
super(first, second, third); 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 @Override
public String toString() { public String toString() {
return first.modID+"."+first.uniqueID; return serverHash.modID+"."+serverHash.uniqueID;
} }
} }

View file

@ -0,0 +1,69 @@
package ru.bclib.api.dataexchange.handler;
import ru.bclib.BCLib;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class FileContentWrapper {
private byte[] rawContent;
private ByteArrayOutputStream outputStream;
FileContentWrapper(byte[] content){
this.rawContent = content;
this.outputStream = null;
}
public byte[] getOriginalContent() {
return rawContent;
}
public byte[] getRawContent() {
if (outputStream!=null){
return outputStream.toByteArray();
}
return rawContent;
}
private void invalidateOutputStream(){
if (this.outputStream!=null){
try {
this.outputStream.close();
}
catch (IOException e) {
BCLib.LOGGER.debug(e);
}
}
this.outputStream = null;
}
public void setRawContent(byte[] rawContent) {
this.rawContent = rawContent;
invalidateOutputStream();
}
public void syncWithOutputStream(){
if (outputStream!=null){
setRawContent(getRawContent());
}
}
public ByteArrayInputStream getInputStream(){
if (rawContent==null) return new ByteArrayInputStream(new byte[0]);
return new ByteArrayInputStream(rawContent);
}
public ByteArrayOutputStream getOrCreateOutputStream(){
if (this.outputStream == null){
return this.getEmptyOutputStream();
}
return this.outputStream;
}
public ByteArrayOutputStream getEmptyOutputStream(){
invalidateOutputStream();
this.outputStream = new ByteArrayOutputStream(this.rawContent.length);
return this.outputStream;
}
}

View file

@ -14,7 +14,7 @@ import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor; import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.handler.DataExchange.AutoSyncID; import ru.bclib.api.dataexchange.handler.AutoSyncID.WithContentOverride;
import ru.bclib.api.datafixer.DataFixerAPI; import ru.bclib.api.datafixer.DataFixerAPI;
import ru.bclib.config.Configs; import ru.bclib.config.Configs;
import ru.bclib.gui.screens.SyncFilesScreen; import ru.bclib.gui.screens.SyncFilesScreen;
@ -126,6 +126,7 @@ public class HelloClient extends DataHandler {
} }
} }
@Override @Override
protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient) { protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient) {
final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "debugHashes", false); final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(Configs.MAIN_SYNC_CATEGORY, "debugHashes", false);
@ -137,7 +138,9 @@ public class HelloClient extends DataHandler {
// return; // return;
// } // }
List<AutoSyncID> filesToRequest = new ArrayList<>(4); final List<AutoSyncID> filesToRequest = new ArrayList<>(2);
for (Entry<String, String> e : modVersion.entrySet()){ for (Entry<String, String> e : modVersion.entrySet()){
String ver = getModVersion(e.getKey()); String ver = getModVersion(e.getKey());
@ -147,26 +150,32 @@ public class HelloClient extends DataHandler {
if (autoSyncedFiles.size()>0) { if (autoSyncedFiles.size()>0) {
BCLib.LOGGER.info("Files offered by Server:"); BCLib.LOGGER.info("Files offered by Server:");
} }
final String requestText = SendFiles.acceptFiles()?"requesting":"differs";
for (DataExchange.AutoSyncTriple e : autoSyncedFiles) { for (DataExchange.AutoSyncTriple e : autoSyncedFiles) {
boolean willRequest = false; String actionString = "";
if (e.third == null) { FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent);
willRequest = true; if (e.localMatch == null) {
filesToRequest.add(new AutoSyncID(e.first.modID, e.first.uniqueID)); actionString = "(new, prepare update)";
} else if (e.third.needTransfer.test(e.third.getFileHash(), e.first, e.second)) { filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
willRequest = true; } else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
filesToRequest.add(new AutoSyncID(e.first.modID, e.first.uniqueID)); 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 {
filesToRequest.add(new AutoSyncID.WithContentOverride(e.serverHash.modID, e.serverHash.uniqueID, contentWrapper, e.localMatch.fileName));
}
} }
BCLib.LOGGER.info(" - " + e + ": " + (willRequest ? (" ("+requestText+")" ):"")); BCLib.LOGGER.info(" - " + e + ": " + actionString);
if (debugHashes) { if (debugHashes) {
BCLib.LOGGER.info(" * " + e.first + " (Server)"); BCLib.LOGGER.info(" * " + e.serverHash + " (Server)");
BCLib.LOGGER.info(" * " + e.third.getFileHash() + " (Client)"); BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)");
BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null));
} }
} }
if (filesToRequest.size()>0 && SendFiles.acceptFiles()) { if (filesToRequest.size()>0 && SendFiles.acceptFiles()) {
showDonwloadConfigs(client, filesToRequest); showDownloadConfigs(client, filesToRequest);
return; return;
} }
} }
@ -185,11 +194,27 @@ public class HelloClient extends DataHandler {
} }
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
protected void showDonwloadConfigs(Minecraft client, List<AutoSyncID> files){ protected void showDownloadConfigs(Minecraft client, List<AutoSyncID> files){
client.setScreen(new SyncFilesScreen((download) -> { client.setScreen(new SyncFilesScreen((download) -> {
Minecraft.getInstance().setScreen((Screen)null); Minecraft.getInstance().setScreen((Screen)null);
if (download){ if (download){
requestFileDownloads(files); 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;
BCLib.LOGGER.info(" - " + aid + " (updating Content)");
SendFiles.writeSyncedFile(aid, aidc.contentWrapper.getRawContent(), aidc.localFile);
} else {
requestFiles.add(aid);
BCLib.LOGGER.info(" - " + aid + " (requesting)");
}
});
requestFileDownloads(requestFiles);
} }
})); }));

View file

@ -8,7 +8,6 @@ import net.minecraft.server.MinecraftServer;
import ru.bclib.BCLib; import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataHandler; import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor; import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.handler.DataExchange.AutoSyncID;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View file

@ -16,6 +16,7 @@ import ru.bclib.gui.screens.ConfirmRestartScreen;
import ru.bclib.util.Pair; import ru.bclib.util.Pair;
import ru.bclib.util.Triple; import ru.bclib.util.Triple;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -77,7 +78,7 @@ public class SendFiles extends DataHandler {
receivedFiles = new ArrayList<>(size); receivedFiles = new ArrayList<>(size);
BCLib.LOGGER.info("Server sent " + size + " Files:"); BCLib.LOGGER.info("Server sent " + size + " Files:");
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Triple<AutoFileSyncEntry, byte[], DataExchange.AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf); Triple<AutoFileSyncEntry, byte[], AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf);
if (p.first != null) { if (p.first != null) {
receivedFiles.add(p); receivedFiles.add(p);
BCLib.LOGGER.info(" - " + p.first + " (" + p.second.length + " Bytes)"); BCLib.LOGGER.info(" - " + p.first + " (" + p.second.length + " Bytes)");
@ -95,20 +96,25 @@ public class SendFiles extends DataHandler {
for (Pair<AutoFileSyncEntry, byte[]> entry : receivedFiles) { for (Pair<AutoFileSyncEntry, byte[]> entry : receivedFiles) {
final AutoFileSyncEntry e = entry.first; final AutoFileSyncEntry e = entry.first;
final byte[] data = entry.second; final byte[] data = entry.second;
Path path = e.fileName.toPath();
BCLib.LOGGER.info(" - Writing " + path + " (" + data.length + " Bytes)"); writeSyncedFile(e, data, e.fileName);
try {
Files.write(path, data);
DataExchange.didReceiveFile(e, e.fileName);
} catch (IOException ioException) {
BCLib.LOGGER.error(" --> Writing " + e.fileName + " failed: " + ioException);
}
} }
showConfirmRestart(client); showConfirmRestart(client);
} }
} }
public static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
Path path = fileName.toPath();
BCLib.LOGGER.info(" - Writing " + path + " (" + data.length + " Bytes)");
try {
Files.write(path, data);
DataExchange.didReceiveFile(e, fileName);
} catch (IOException ioException) {
BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);
}
}
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
protected void showConfirmRestart(Minecraft client){ protected void showConfirmRestart(Minecraft client){
client.setScreen(new ConfirmRestartScreen(() -> { client.setScreen(new ConfirmRestartScreen(() -> {

View file

@ -1,31 +1,40 @@
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;
import ru.bclib.api.dataexchange.handler.DataExchange; import ru.bclib.api.dataexchange.FileHash;
import ru.bclib.api.dataexchange.handler.AutoSyncID;
import ru.bclib.api.dataexchange.handler.FileContentWrapper;
import ru.bclib.config.ConfigKeeper.BooleanEntry; import ru.bclib.config.ConfigKeeper.BooleanEntry;
import ru.bclib.config.ConfigKeeper.Entry; import ru.bclib.config.ConfigKeeper.Entry;
import ru.bclib.config.ConfigKeeper.FloatEntry; 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;
public abstract class Config { public abstract class Config {
protected final static Map<DataExchange.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); 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);
}
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();
@ -33,16 +42,22 @@ public abstract class Config {
if (autoSync) { if (autoSync) {
final String uid = "CONFIG_" + modID + "_" + group; final String uid = "CONFIG_" + modID + "_" + group;
final DataExchange.AutoSyncID aid = new DataExchange.AutoSyncID(BCLib.MOD_ID, uid); final AutoSyncID aid = new AutoSyncID(BCLib.MOD_ID, uid);
DataExchangeAPI.addAutoSyncFile(aid.modID, aid.uniqueID, keeper.getConfigFile()); 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) {
ByteArrayInputStream inputStream = content.getInputStream();
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(DataExchange.AutoSyncID aid, File file){ public static void reloadSyncedConfig(AutoSyncID aid, File file){
Config cfg = autoSyncConfigs.get(aid); Config cfg = autoSyncConfigs.get(aid);
if (cfg!=null) { if (cfg!=null) {
cfg.reload(); cfg.reload();

View file

@ -30,6 +30,11 @@ public final class ConfigKeeper {
return this.writer.getConfigFile(); return this.writer.getConfigFile();
} }
boolean compareAndUpdateForSync(JsonObject other) {
final JsonObject me = this.configObject;
return true;
}
public void save() { public void save() {
if (!changed) return; if (!changed) return;
this.writer.save(); this.writer.save();

View file

@ -7,6 +7,10 @@ import ru.bclib.config.ConfigKeeper.IntegerRange;
public class PathConfig extends Config { public class PathConfig extends Config {
public PathConfig(String modID, String group, boolean autoSync, boolean diffContent) {
super(modID, group, autoSync, diffContent);
}
public PathConfig(String modID, String group, boolean autoSync) { public PathConfig(String modID, String group, boolean autoSync) {
super(modID, group, autoSync); super(modID, group, autoSync);
} }

View file

@ -19,6 +19,8 @@ import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
public class JsonFactory { public class JsonFactory {
@ -103,6 +105,11 @@ public class JsonFactory {
} }
} }
public static void storeJson(OutputStream outStream, JsonElement jsonObject) {
OutputStreamWriter writer = new OutputStreamWriter(outStream);
GSON.toJson(jsonObject, writer);
}
public static int getInt(JsonObject object, String member, int def) { public static int getInt(JsonObject object, String member, int def) {
JsonElement elem = object.get(member); JsonElement elem = object.get(member);
return elem == null ? def : elem.getAsInt(); return elem == null ? def : elem.getAsInt();