Skip to content

Commit

Permalink
Manually load and save PersistentData, and make it more robust to mal…
Browse files Browse the repository at this point in the history
…formed data
  • Loading branch information
williewillus committed Jun 22, 2022
1 parent 4640df3 commit e355d85
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 78 deletions.
140 changes: 111 additions & 29 deletions Xplat/src/main/java/vazkii/patchouli/client/base/PersistentData.java
Original file line number Diff line number Diff line change
@@ -1,74 +1,156 @@
package vazkii.patchouli.client.base;

import com.google.gson.annotations.SerializedName;
import com.google.common.base.Charsets;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;

import vazkii.patchouli.api.PatchouliAPI;
import vazkii.patchouli.client.book.BookEntry;
import vazkii.patchouli.common.book.Book;
import vazkii.patchouli.common.util.SerializationUtil;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class PersistentData {

private static File saveFile;
private static final Path saveFile = Paths.get("patchouli_data.json");

public static DataHolder data;
public static DataHolder data = new DataHolder(new JsonObject());

public static void setup() {
saveFile = new File("patchouli_data.json");
load();
}

public static void load() {
data = SerializationUtil.loadFromFile(saveFile, DataHolder.class, DataHolder::new);
try (var r = Files.newBufferedReader(saveFile, Charsets.UTF_8)) {
var root = SerializationUtil.RAW_GSON.fromJson(r, JsonObject.class);
data = new DataHolder(root);
} catch (IOException e) {
if (!(e instanceof NoSuchFileException)) {
PatchouliAPI.LOGGER.warn("Unable to load patchouli_data.json, replacing with default", e);
}
data = new DataHolder(new JsonObject());
save();
} catch (Exception e) {
PatchouliAPI.LOGGER.warn("Corrupted patchouli_data.json, replacing with default", e);
data = new DataHolder(new JsonObject());
save();
}
}

public static void save() {
SerializationUtil.saveToFile(SerializationUtil.PRETTY_GSON, saveFile, DataHolder.class, data);
var json = data.serialize();
try (var w = Files.newBufferedWriter(saveFile, Charsets.UTF_8)) {
SerializationUtil.PRETTY_GSON.toJson(json, w);
} catch (IOException e) {
PatchouliAPI.LOGGER.warn("Unable to save patchouli_data.json", e);
}
}

public static final class DataHolder {
public int bookGuiScale = 0;
public boolean clickedVisualize = false;
public int bookGuiScale;
public boolean clickedVisualize;

Map<String, PersistentData.BookData> bookData = new HashMap<>();
private final Map<ResourceLocation, PersistentData.BookData> bookData = new HashMap<>();

public PersistentData.BookData getBookData(Book book) {
String res = book.id.toString();
if (!bookData.containsKey(res)) {
bookData.put(res, new PersistentData.BookData());
public DataHolder(JsonObject root) {
this.bookGuiScale = GsonHelper.getAsInt(root, "bookGuiScale", 0);
this.clickedVisualize = GsonHelper.getAsBoolean(root, "clickedVisualize", false);
var obj = GsonHelper.getAsJsonObject(root, "bookData", new JsonObject());

for (var e : obj.entrySet()) {
this.bookData.put(new ResourceLocation(e.getKey()), new BookData(e.getValue().getAsJsonObject()));
}
}

return bookData.get(res);
public PersistentData.BookData getBookData(Book book) {
return bookData.computeIfAbsent(book.id, k -> new BookData(new JsonObject()));
}

public JsonObject serialize() {
var ret = new JsonObject();
ret.addProperty("bookGuiScale", this.bookGuiScale);
ret.addProperty("clickedVisualize", this.clickedVisualize);

var books = new JsonObject();
for (var e : bookData.entrySet()) {
books.add(e.getKey().toString(), e.getValue().serialize());
}
ret.add("bookData", books);
return ret;
}
}

public static final class Bookmark {
public String entry;
// Serialized as page for legacy reasons
@SerializedName("page") public int spread;
public final ResourceLocation entry;
public final int spread;

public Bookmark(String entry, int spread) {
public Bookmark(ResourceLocation entry, int spread) {
this.entry = entry;
this.spread = spread;
}

public Bookmark(JsonObject root) {
this.entry = new ResourceLocation(GsonHelper.getAsString(root, "entry"));
this.spread = GsonHelper.getAsInt(root, "page"); // Serialized as page for legacy reasons
}

public BookEntry getEntry(Book book) {
ResourceLocation res = new ResourceLocation(entry);
return book.getContents().entries.get(res);
return book.getContents().entries.get(entry);
}

public JsonObject serialize() {
var ret = new JsonObject();
ret.addProperty("entry", this.entry.toString());
ret.addProperty("page", this.spread); // Serialized as page for legacy reasons
return ret;
}
}

public static final class BookData {
public List<String> viewedEntries = new ArrayList<>();
public List<Bookmark> bookmarks = new ArrayList<>();
public List<String> history = new ArrayList<>();
public List<String> completedManualQuests = new ArrayList<>();
public final List<ResourceLocation> viewedEntries = new ArrayList<>();
public final List<Bookmark> bookmarks = new ArrayList<>();
public final List<ResourceLocation> history = new ArrayList<>();
public final List<ResourceLocation> completedManualQuests = new ArrayList<>();

public BookData(JsonObject root) {
var emptyArray = new JsonArray();
for (var e : GsonHelper.getAsJsonArray(root, "viewedEntries", emptyArray)) {
viewedEntries.add(new ResourceLocation(e.getAsString()));
}
for (var e : GsonHelper.getAsJsonArray(root, "bookmarks", emptyArray)) {
bookmarks.add(new Bookmark(e.getAsJsonObject()));
}
for (var e : GsonHelper.getAsJsonArray(root, "history", emptyArray)) {
history.add(new ResourceLocation(e.getAsString()));
}
for (var e : GsonHelper.getAsJsonArray(root, "completedManualQuests", emptyArray)) {
completedManualQuests.add(new ResourceLocation(e.getAsString()));
}
}

public JsonObject serialize() {
var ret = new JsonObject();
var viewed = new JsonArray();
this.viewedEntries.stream().map(Object::toString).forEach(viewed::add);
ret.add("viewedEntries", viewed);

var bookmarks = new JsonArray();
this.bookmarks.stream().map(Bookmark::serialize).forEach(bookmarks::add);
ret.add("bookmarks", bookmarks);

var completed = new JsonArray();
this.completedManualQuests.stream().map(Object::toString).forEach(completed::add);
ret.add("completedManualQuests", completed);

return ret;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ public boolean isExtension() {
@Override
protected EntryDisplayState computeReadState() {
BookData data = PersistentData.data.getBookData(book);
if (data != null && getId() != null && !readByDefault && !isLocked() && !data.viewedEntries.contains(getId().toString())) {
if (data != null && getId() != null && !readByDefault && !isLocked() && !data.viewedEntries.contains(getId())) {
return EntryDisplayState.UNREAD;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void onFirstOpened() {
super.onFirstOpened();

boolean dirty = false;
String key = entry.getId().toString();
var key = entry.getId();

BookData data = PersistentData.data.getBookData(book);

Expand Down Expand Up @@ -193,7 +193,7 @@ boolean isBookmarkedAlready() {

@Override
public void bookmarkThis() {
String entryKey = entry.getId().toString();
var entryKey = entry.getId();
BookData data = PersistentData.data.getBookData(book);
data.bookmarks.add(new Bookmark(entryKey, spread));
PersistentData.save();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import net.minecraft.client.resources.language.I18n;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;

import vazkii.patchouli.client.base.PersistentData;
import vazkii.patchouli.client.base.PersistentData.BookData;
Expand Down Expand Up @@ -38,7 +37,6 @@ protected Collection<BookEntry> getEntries() {
BookData data = PersistentData.data.getBookData(book);

return data.history.stream()
.map(ResourceLocation::new)
.map((res) -> book.getContents().entries.get(res))
.filter((e) -> e != null && !e.isLocked())
.collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private void markCategoryAsRead(BookEntry entry, BookCategory category, int maxR

private void markEntry(BookEntry entry) {
boolean dirty = false;
String key = entry.getId().toString();
var key = entry.getId();

if (!entry.isLocked() && entry.getReadState().equals(EntryDisplayState.UNREAD)) {
PersistentData.BookData data = PersistentData.data.getBookData(book);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void render(PoseStack ms, int mouseX, int mouseY, float pticks) {
}

public void handleButtonVisualize(Button button) {
String entryKey = parent.getEntry().getId().toString();
var entryKey = parent.getEntry().getId();
Bookmark bookmark = new Bookmark(entryKey, pageNum / 2);
MultiblockVisualizationHandler.setMultiblock(multiblockObj, i18nText(name), bookmark, true);
parent.addBookmarkButtons();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void build(BookEntry entry, BookContentsBuilder builder, int pageNum) {

public boolean isCompleted(Book book) {
return isManual
? PersistentData.data.getBookData(book).completedManualQuests.contains(entry.getId().toString())
? PersistentData.data.getBookData(book).completedManualQuests.contains(entry.getId())
: trigger != null && ClientAdvancements.hasDone(trigger.toString());
}

Expand All @@ -62,7 +62,7 @@ private void updateButtonText(Button button) {
}

protected void questButtonClicked(Button button) {
String res = entry.getId().toString();
var res = entry.getId();
BookData data = PersistentData.data.getBookData(parent.book);

if (data.completedManualQuests.contains(res)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
import net.minecraft.util.GsonHelper;

import vazkii.patchouli.api.IVariable;
import vazkii.patchouli.api.PatchouliAPI;

import javax.annotation.Nullable;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.function.Supplier;

public final class SerializationUtil {

Expand All @@ -27,43 +24,6 @@ public final class SerializationUtil {

private SerializationUtil() {}

public static <T> T loadFromFile(File f, Class<? extends T> clazz, Supplier<T> baseCase) {
return loadFromFile(RAW_GSON, f, clazz, baseCase);
}

public static <T> T loadFromFile(Gson gson, File f, Class<? extends T> clazz, Supplier<T> baseCase) {
try {
if (!f.exists()) {
T t = baseCase.get();
saveToFile(gson, f, clazz, t);
return t;
}

FileInputStream in = new FileInputStream(f);
return gson.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), clazz);
} catch (IOException e) {
PatchouliAPI.LOGGER.error("Failed to load file", e);
return null;
}
}

public static <T> void saveToFile(File f, Class<? extends T> clazz, T obj) {
saveToFile(RAW_GSON, f, clazz, obj);
}

public static <T> void saveToFile(Gson gson, File f, Class<? extends T> clazz, T obj) {
String json = gson.toJson(obj, clazz);
try {
f.createNewFile();

try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
writer.write(json);
}
} catch (IOException e) {
PatchouliAPI.LOGGER.error("Failed to save file", e);
}
}

public static ResourceLocation getAsResourceLocation(JsonObject object, String key, @Nullable ResourceLocation fallback) {
if (object.has(key)) {
return new ResourceLocation(GsonHelper.convertToString(object.get(key), key));
Expand Down

0 comments on commit e355d85

Please sign in to comment.