diff --git a/common/src/main/java/me/lucko/luckperms/common/actionlog/ActionJsonSerializer.java b/common/src/main/java/me/lucko/luckperms/common/actionlog/ActionJsonSerializer.java new file mode 100644 index 000000000..8efac649b --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/actionlog/ActionJsonSerializer.java @@ -0,0 +1,105 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.actionlog; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import me.lucko.luckperms.api.actionlog.Action; +import me.lucko.luckperms.common.util.gson.JObject; + +import java.time.Instant; +import java.util.UUID; + +public final class ActionJsonSerializer { + private ActionJsonSerializer() {} + + public static JsonObject serialize(Action logEntry) { + return new JObject() + .add("timestamp", new JsonPrimitive(logEntry.getTimestamp().getEpochSecond())) + .add("source", new JObject() + .add("uniqueId", new JsonPrimitive(logEntry.getSource().getUniqueId().toString())) + .add("name", new JsonPrimitive(logEntry.getSource().getName())) + ) + .add("target", new JObject() + .add("type", new JsonPrimitive(logEntry.getTarget().getType().name()))) + .consume(obj -> { + if (logEntry.getTarget().getUniqueId().isPresent()) { + obj.add("uniqueId", new JsonPrimitive(logEntry.getTarget().getUniqueId().get().toString())); + } + }) + .add("name", new JsonPrimitive(logEntry.getTarget().getName()) + ) + .add("description", new JsonPrimitive(logEntry.getDescription())) + .toJson(); + } + + public static LoggedAction deserialize(JsonElement element) { + Preconditions.checkArgument(element.isJsonObject()); + JsonObject data = element.getAsJsonObject(); + + LoggedAction.Builder builder = LoggedAction.build(); + + if (data.has("timestamp")) { // sigh - this wasn't included in the first implementations + builder.timestamp(Instant.ofEpochSecond(data.get("timestamp").getAsLong())); + } + + if (data.has("source")) { + JsonObject source = data.get("source").getAsJsonObject(); + builder.source(UUID.fromString(source.get("uniqueId").getAsString())); + builder.sourceName(source.get("name").getAsString()); + } else { + builder.source(UUID.fromString(data.get("actor").getAsString())); + builder.sourceName(data.get("actorName").getAsString()); + } + + if (data.has("target")) { + JsonObject target = data.get("target").getAsJsonObject(); + builder.targetType(LoggedAction.parseType(target.get("type").getAsString())); + if (target.has("uniqueId")) { + builder.target(UUID.fromString(target.get("uniqueId").getAsString())); + } + builder.targetName(target.get("name").getAsString()); + } else { + builder.targetType(LoggedAction.parseType(data.get("type").getAsString())); + if (data.has("acted")) { + builder.target(UUID.fromString(data.get("acted").getAsString())); + } + builder.targetName(data.get("actedName").getAsString()); + } + + if (data.has("description")) { + builder.description(data.get("description").getAsString()); + } else { + builder.description(data.get("action").getAsString()); + } + + return builder.build(); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/actionlog/LogEntryJsonSerializer.java b/common/src/main/java/me/lucko/luckperms/common/actionlog/LogEntryJsonSerializer.java deleted file mode 100644 index b376fec01..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/actionlog/LogEntryJsonSerializer.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.lucko.luckperms.common.actionlog; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import me.lucko.luckperms.api.actionlog.Action; - -import java.time.Instant; -import java.util.UUID; - -public final class LogEntryJsonSerializer { - private LogEntryJsonSerializer() {} - - public static JsonObject serialize(Action logEntry) { - JsonObject data = new JsonObject(); - data.add("timestamp", new JsonPrimitive(logEntry.getTimestamp().getEpochSecond())); - data.add("actor", new JsonPrimitive(logEntry.getSource().getUniqueId().toString())); - data.add("actorName", new JsonPrimitive(logEntry.getSource().getName())); - data.add("type", new JsonPrimitive(logEntry.getTarget().getType().name())); - if (logEntry.getTarget().getUniqueId().isPresent()) { - data.add("acted", new JsonPrimitive(logEntry.getTarget().getUniqueId().get().toString())); - } - data.add("actedName", new JsonPrimitive(logEntry.getTarget().getName())); - data.add("action", new JsonPrimitive(logEntry.getDescription())); - - return data; - } - - public static LoggedAction deserialize(JsonElement element) { - Preconditions.checkArgument(element.isJsonObject()); - JsonObject data = element.getAsJsonObject(); - - LoggedAction.Builder builder = LoggedAction.build(); - - if (data.has("timestamp")) { // sigh - this wasn't included in the first implementations - builder.timestamp(Instant.ofEpochSecond(data.get("timestamp").getAsLong())); - } - - builder.source(UUID.fromString(data.get("actor").getAsString())); - builder.sourceName(data.get("actorName").getAsString()); - builder.targetType(LoggedAction.parseType(data.get("type").getAsString())); - if (data.has("acted")) { - builder.source(UUID.fromString(data.get("acted").getAsString())); - } - builder.targetName(data.get("actedName").getAsString()); - builder.description(data.get("action").getAsString()); - - return builder.build(); - } - -} diff --git a/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java b/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java index 5bb5fcc8b..1ea63f02b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java +++ b/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java @@ -86,21 +86,18 @@ public class LoggedAction implements Action { this.action = description; } - @NonNull @Override - public Instant getTimestamp() { + public @NonNull Instant getTimestamp() { return Instant.ofEpochSecond(this.timestamp); } - @NonNull @Override - public Source getSource() { + public @NonNull Source getSource() { return this.source; } - @NonNull @Override - public Target getTarget() { + public @NonNull Target getTarget() { return this.target; } diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/message/ActionLogMessageImpl.java b/common/src/main/java/me/lucko/luckperms/common/messaging/message/ActionLogMessageImpl.java index 3850ec322..4ece3dca6 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/message/ActionLogMessageImpl.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/message/ActionLogMessageImpl.java @@ -29,7 +29,7 @@ import com.google.gson.JsonElement; import me.lucko.luckperms.api.actionlog.Action; import me.lucko.luckperms.api.messenger.message.type.ActionLogMessage; -import me.lucko.luckperms.common.actionlog.LogEntryJsonSerializer; +import me.lucko.luckperms.common.actionlog.ActionJsonSerializer; import me.lucko.luckperms.common.messaging.LuckPermsMessagingService; import org.checkerframework.checker.nullness.qual.NonNull; @@ -45,7 +45,7 @@ public class ActionLogMessageImpl extends AbstractMessage implements ActionLogMe throw new IllegalStateException("Missing content"); } - return new ActionLogMessageImpl(id, LogEntryJsonSerializer.deserialize(content)); + return new ActionLogMessageImpl(id, ActionJsonSerializer.deserialize(content)); } private final Action logEntry; @@ -63,7 +63,7 @@ public class ActionLogMessageImpl extends AbstractMessage implements ActionLogMe @Override public @NonNull String asEncodedString() { return LuckPermsMessagingService.encodeMessageAsString( - TYPE, getId(), LogEntryJsonSerializer.serialize(this.logEntry) + TYPE, getId(), ActionJsonSerializer.serialize(this.logEntry) ); } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java index d8aa36d4d..e253020fd 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java @@ -154,7 +154,7 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio this.uuidDataFile = MoreFiles.createFileIfNotExists(this.dataDirectory.resolve("uuidcache.txt")); this.uuidCache.load(this.uuidDataFile); - this.actionLogger.init(this.dataDirectory.resolve("actions.json")); + this.actionLogger.init(this.dataDirectory.resolve("actions.txt"), this.dataDirectory.resolve("actions.json")); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java index cf5e68ff4..bd0225762 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java @@ -28,19 +28,22 @@ package me.lucko.luckperms.common.storage.implementation.file; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import me.lucko.luckperms.api.actionlog.Action; +import me.lucko.luckperms.common.actionlog.ActionJsonSerializer; import me.lucko.luckperms.common.actionlog.Log; -import me.lucko.luckperms.common.actionlog.LogEntryJsonSerializer; import me.lucko.luckperms.common.cache.BufferedRequest; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.util.gson.GsonProvider; +import java.io.BufferedReader; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; @@ -69,8 +72,33 @@ public class FileActionLogger { this.saveBuffer = new SaveBuffer(plugin); } - public void init(Path contentFile) { + public void init(Path contentFile, Path legacyFile) { this.contentFile = contentFile; + + if (Files.exists(legacyFile)) { + // migrate + JsonArray array; + + try (JsonReader reader = new JsonReader(Files.newBufferedReader(legacyFile, StandardCharsets.UTF_8))) { + array = GsonProvider.parser().parse(reader).getAsJsonArray(); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + for (JsonElement element : array) { + this.entryQueue.add(ActionJsonSerializer.deserialize(element)); + } + + flush(); + + try { + Files.delete(legacyFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + } public void logAction(Action entry) { @@ -87,30 +115,14 @@ public class FileActionLogger { } try { - // read existing array data into memory - JsonArray array; - - if (Files.exists(this.contentFile)) { - try (JsonReader reader = new JsonReader(Files.newBufferedReader(this.contentFile, StandardCharsets.UTF_8))) { - array = GsonProvider.parser().parse(reader).getAsJsonArray(); - } catch (IOException e) { - e.printStackTrace(); - array = new JsonArray(); - } - } else { - array = new JsonArray(); - } + List toWrite = new ArrayList<>(this.entryQueue.size()); // poll the queue for new entries for (Action e; (e = this.entryQueue.poll()) != null; ) { - array.add(LogEntryJsonSerializer.serialize(e)); + toWrite.add(GsonProvider.normal().toJson(ActionJsonSerializer.serialize(e))); } - // write the full content back to the file - try (JsonWriter writer = new JsonWriter(Files.newBufferedWriter(this.contentFile, StandardCharsets.UTF_8))) { - writer.setIndent(" "); - GsonProvider.normal().toJson(array, writer); - } + Files.write(this.contentFile, toWrite, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (IOException e) { e.printStackTrace(); } @@ -120,13 +132,24 @@ public class FileActionLogger { } public Log getLog() throws IOException { + if (!Files.exists(this.contentFile)) { + return Log.empty(); + } + Log.Builder log = Log.builder(); - try (JsonReader reader = new JsonReader(Files.newBufferedReader(this.contentFile, StandardCharsets.UTF_8))) { - JsonArray array = GsonProvider.parser().parse(reader).getAsJsonArray(); - for (JsonElement element : array) { - log.add(LogEntryJsonSerializer.deserialize(element)); + + try (BufferedReader reader = Files.newBufferedReader(this.contentFile, StandardCharsets.UTF_8)) { + String line; + while ((line = reader.readLine()) != null) { + try { + JsonElement parsed = GsonProvider.parser().parse(line); + log.add(ActionJsonSerializer.deserialize(parsed)); + } catch (Exception e) { + e.printStackTrace(); + } } } + return log.build(); } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java index b10ee4f95..7db4a3ea4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java @@ -166,18 +166,25 @@ public class MongoStorage implements StorageImplementation { @Override public void logAction(Action entry) { MongoCollection c = this.database.getCollection(this.prefix + "action"); + Document doc = new Document() .append("timestamp", entry.getTimestamp().getEpochSecond()) - .append("actor", entry.getSource().getUniqueId()) - .append("actorName", entry.getSource().getName()) - .append("type", Character.toString(LoggedAction.getTypeCharacter(entry.getTarget().getType()))) - .append("actedName", entry.getTarget().getName()) - .append("action", entry.getDescription()); + .append("source", new Document() + .append("uniqueId", entry.getSource().getUniqueId()) + .append("name", entry.getSource().getName()) + ); + + Document target = new Document() + .append("type", entry.getTarget().getType().name()) + .append("name", entry.getTarget().getName()); if (entry.getTarget().getUniqueId().isPresent()) { - doc.append("acted", entry.getTarget().getUniqueId().get()); + target.append("uniqueId", entry.getTarget().getUniqueId().get()); } + doc.append("target", target); + doc.append("description", entry.getDescription()); + c.insertOne(doc); } @@ -189,22 +196,46 @@ public class MongoStorage implements StorageImplementation { while (cursor.hasNext()) { Document d = cursor.next(); - UUID actedUuid = null; - if (d.containsKey("acted")) { - actedUuid = d.get("acted", UUID.class); + if (d.containsKey("source")) { + // new format + Document source = d.get("source", Document.class); + Document target = d.get("target", Document.class); + + UUID targetUniqueId = null; + if (target.containsKey("uniqueId")) { + targetUniqueId = target.get("uniqueId", UUID.class); + } + + LoggedAction e = LoggedAction.build() + .timestamp(Instant.ofEpochSecond(d.getLong("timestamp"))) + .source(source.get("uniqueId", UUID.class)) + .sourceName(source.getString("name")) + .targetType(LoggedAction.parseType(target.getString("type"))) + .target(targetUniqueId) + .targetName(target.getString("name")) + .description(d.getString("description")) + .build(); + + log.add(e); + } else { + // old format + UUID actedUuid = null; + if (d.containsKey("acted")) { + actedUuid = d.get("acted", UUID.class); + } + + LoggedAction e = LoggedAction.build() + .timestamp(Instant.ofEpochSecond(d.getLong("timestamp"))) + .source(d.get("actor", UUID.class)) + .sourceName(d.getString("actorName")) + .targetType(LoggedAction.parseTypeCharacter(d.getString("type").charAt(0))) + .target(actedUuid) + .targetName(d.getString("actedName")) + .description(d.getString("action")) + .build(); + + log.add(e); } - - LoggedAction e = LoggedAction.build() - .timestamp(Instant.ofEpochSecond(d.getLong("timestamp"))) - .source(d.get("actor", UUID.class)) - .sourceName(d.getString("actorName")) - .targetType(LoggedAction.parseTypeCharacter(d.getString("type").charAt(0))) - .target(actedUuid) - .targetName(d.getString("actedName")) - .description(d.getString("action")) - .build(); - - log.add(e); } } return log.build();