From 020aff66aaedfb413b4f093e6b0f727ae12579bf Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 7 Sep 2020 18:06:40 +0100 Subject: [PATCH] Refactor web editor session handling --- .../luckperms/common/backup/Exporter.java | 4 +- .../commands/generic/other/HolderEditor.java | 8 +- .../commands/misc/ApplyEditsCommand.java | 298 +------------- .../common/commands/misc/EditorCommand.java | 8 +- .../common/commands/misc/ImportCommand.java | 5 +- .../common/commands/misc/TreeCommand.java | 2 +- .../common/commands/misc/VerboseCommand.java | 2 +- .../{web => http}/AbstractHttpClient.java | 2 +- .../common/{web => http}/BytebinClient.java | 39 +- .../UnsuccessfulRequestException.java | 2 +- .../plugin/AbstractLuckPermsPlugin.java | 2 +- .../common/plugin/LuckPermsPlugin.java | 2 +- .../luckperms/common/treeview/TreeView.java | 6 +- .../luckperms/common/util/gson/JArray.java | 7 + .../common/verbose/VerboseListener.java | 6 +- .../WebEditorRequest.java} | 144 ++++--- .../common/webeditor/WebEditorResponse.java | 363 ++++++++++++++++++ 17 files changed, 505 insertions(+), 395 deletions(-) rename common/src/main/java/me/lucko/luckperms/common/{web => http}/AbstractHttpClient.java (98%) rename common/src/main/java/me/lucko/luckperms/common/{web => http}/BytebinClient.java (80%) rename common/src/main/java/me/lucko/luckperms/common/{web => http}/UnsuccessfulRequestException.java (97%) rename common/src/main/java/me/lucko/luckperms/common/{web/WebEditor.java => webeditor/WebEditorRequest.java} (63%) create mode 100644 common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorResponse.java diff --git a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java index 720d3f6e0..b2376626e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java +++ b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java @@ -27,6 +27,8 @@ package me.lucko.luckperms.common.backup; import com.google.gson.JsonObject; +import me.lucko.luckperms.common.http.AbstractHttpClient; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.Track; @@ -40,8 +42,6 @@ import me.lucko.luckperms.common.util.ProgressLogger; import me.lucko.luckperms.common.util.gson.GsonProvider; import me.lucko.luckperms.common.util.gson.JArray; import me.lucko.luckperms.common.util.gson.JObject; -import me.lucko.luckperms.common.web.AbstractHttpClient; -import me.lucko.luckperms.common.web.UnsuccessfulRequestException; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java index f7a85ee46..efe7c4e1f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/other/HolderEditor.java @@ -25,8 +25,6 @@ package me.lucko.luckperms.common.commands.generic.other; -import com.google.gson.JsonObject; - import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; import me.lucko.luckperms.common.command.access.ArgumentPermissions; @@ -49,7 +47,7 @@ import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.storage.misc.NodeEntry; import me.lucko.luckperms.common.util.Predicates; import me.lucko.luckperms.common.verbose.event.MetaCheckEvent; -import me.lucko.luckperms.common.web.WebEditor; +import me.lucko.luckperms.common.webeditor.WebEditorRequest; import net.luckperms.api.node.Node; import net.luckperms.api.query.QueryOptions; @@ -119,8 +117,8 @@ public class HolderEditor extends ChildCommand { Message.EDITOR_START.send(sender); - JsonObject payload = WebEditor.formPayload(holders, Collections.emptyList(), sender, label, plugin); - return WebEditor.post(payload, sender, plugin); + return WebEditorRequest.generate(holders, Collections.emptyList(), sender, label, plugin) + .createSession(plugin, sender); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java index 0bc5099ae..1c8031636 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ApplyEditsCommand.java @@ -25,49 +25,22 @@ package me.lucko.luckperms.common.commands.misc; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.SingleCommand; -import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.command.utils.MessageUtils; -import me.lucko.luckperms.common.command.utils.StorageAssistant; -import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.Track; -import me.lucko.luckperms.common.model.manager.group.GroupManager; -import me.lucko.luckperms.common.node.utils.NodeJsonSerializer; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.DurationFormatter; import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.Uuids; -import me.lucko.luckperms.common.web.UnsuccessfulRequestException; -import me.lucko.luckperms.common.web.WebEditor; - -import net.luckperms.api.actionlog.Action; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.event.cause.DeletionCause; -import net.luckperms.api.model.data.DataType; -import net.luckperms.api.node.Node; +import me.lucko.luckperms.common.webeditor.WebEditorResponse; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; public class ApplyEditsCommand extends SingleCommand { public ApplyEditsCommand(LocaleManager locale) { @@ -85,12 +58,12 @@ public class ApplyEditsCommand extends SingleCommand { JsonObject data; try { - data = WebEditor.readDataFromBytebin(plugin.getBytebin(), code); + data = plugin.getBytebin().getJsonContent(code).getAsJsonObject(); } catch (UnsuccessfulRequestException e) { Message.EDITOR_HTTP_REQUEST_FAILURE.send(sender, e.getResponse().code(), e.getResponse().message()); return CommandResult.STATE_ERROR; } catch (IOException e) { - new RuntimeException("Error uploading data to bytebin", e).printStackTrace(); + new RuntimeException("Error reading data from bytebin", e).printStackTrace(); Message.EDITOR_HTTP_UNKNOWN_FAILURE.send(sender); return CommandResult.STATE_ERROR; } @@ -100,271 +73,10 @@ public class ApplyEditsCommand extends SingleCommand { return CommandResult.FAILURE; } - boolean work = false; - if (data.has("changes")) { - JsonArray changes = data.get("changes").getAsJsonArray(); - for (JsonElement change : changes) { - if (readChanges(change.getAsJsonObject(), sender, plugin)) { - work = true; - } - } - } - if (data.has("groupDeletions")) { - JsonArray groupDeletions = data.get("groupDeletions").getAsJsonArray(); - for (JsonElement groupDeletion : groupDeletions) { - if (readGroupDeletion(groupDeletion, sender, plugin)) { - work = true; - } - } - } - if (data.has("trackDeletions")) { - JsonArray trackDeletions = data.get("trackDeletions").getAsJsonArray(); - for (JsonElement trackDeletion : trackDeletions) { - if (readTrackDeletion(trackDeletion, sender, plugin)) { - work = true; - } - } - } - - if (!work) { - Message.APPLY_EDITS_TARGET_NO_CHANGES_PRESENT.send(sender); - } - + new WebEditorResponse(data).apply(plugin, sender); return CommandResult.SUCCESS; } - private boolean readChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) { - String type = data.get("type").getAsString(); - - if (type.equals("user") || type.equals("group")) { - return readHolderChanges(data, sender, plugin); - } else if (type.equals("track")) { - return readTrackChanges(data, sender, plugin); - } else { - Message.APPLY_EDITS_UNKNOWN_TYPE.send(sender, type); - return false; - } - } - - private boolean readHolderChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) { - String type = data.get("type").getAsString(); - String id = data.get("id").getAsString(); - - PermissionHolder holder; - if (type.equals("user")) { - // user - UUID uuid = Uuids.parse(id); - if (uuid == null) { - Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(sender, id); - return false; - } - holder = plugin.getStorage().loadUser(uuid, null).join(); - if (holder == null) { - Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(sender, uuid.toString()); - return false; - } - } else { - // group - holder = plugin.getStorage().loadGroup(id).join().orElse(null); - if (holder == null) { - holder = plugin.getStorage().createAndLoadGroup(id, CreationCause.WEB_EDITOR).join(); - } - } - - if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), holder) || ArgumentPermissions.checkGroup(plugin, sender, holder, ImmutableContextSetImpl.EMPTY)) { - Message.COMMAND_NO_PERMISSION.send(sender); - return false; - } - - Set before = holder.normalData().asSet(); - Set after = new HashSet<>(NodeJsonSerializer.deserializeNodes(data.getAsJsonArray("nodes"))); - - Set diffAdded = getAdded(before, after); - Set diffRemoved = getRemoved(before, after); - - int additions = diffAdded.size(); - int deletions = diffRemoved.size(); - - if (additions == 0 && deletions == 0) { - return false; - } - - holder.setNodes(DataType.NORMAL, after); - - for (Node n : diffAdded) { - LoggedAction.build().source(sender).target(holder) - .description("webeditor", "add", n.getKey(), n.getValue(), n.getContexts()) - .build().submit(plugin, sender); - } - for (Node n : diffRemoved) { - LoggedAction.build().source(sender).target(holder) - .description("webeditor", "remove", n.getKey(), n.getValue(), n.getContexts()) - .build().submit(plugin, sender); - } - - String additionsSummary = "addition" + (additions == 1 ? "" : "s"); - String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s"); - - Message.APPLY_EDITS_SUCCESS.send(sender, type, holder.getFormattedDisplayName()); - Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary); - for (Node n : diffAdded) { - Message.APPLY_EDITS_DIFF_ADDED.send(sender, formatNode(plugin.getLocaleManager(), n)); - } - for (Node n : diffRemoved) { - Message.APPLY_EDITS_DIFF_REMOVED.send(sender, formatNode(plugin.getLocaleManager(), n)); - } - StorageAssistant.save(holder, sender, plugin); - return true; - } - - private boolean readTrackChanges(JsonObject data, Sender sender, LuckPermsPlugin plugin) { - String id = data.get("id").getAsString(); - - Track track = plugin.getStorage().loadTrack(id).join().orElse(null); - if (track == null) { - track = plugin.getStorage().createAndLoadTrack(id, CreationCause.WEB_EDITOR).join(); - } - - if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), track)) { - Message.COMMAND_NO_PERMISSION.send(sender); - return false; - } - - List before = track.getGroups(); - List after = new ArrayList<>(); - data.getAsJsonArray("groups").forEach(e -> after.add(e.getAsString())); - - if (before.equals(after)) { - return false; - } - - Set diffAdded = getAdded(before, after); - Set diffRemoved = getRemoved(before, after); - - int additions = diffAdded.size(); - int deletions = diffRemoved.size(); - - track.setGroups(after); - - if (hasBeenReordered(before, after, diffAdded, diffRemoved)) { - LoggedAction.build().source(sender).target(track) - .description("webeditor", "reorder", after) - .build().submit(plugin, sender); - } - for (String n : diffAdded) { - LoggedAction.build().source(sender).target(track) - .description("webeditor", "add", n) - .build().submit(plugin, sender); - } - for (String n : diffRemoved) { - LoggedAction.build().source(sender).target(track) - .description("webeditor", "remove", n) - .build().submit(plugin, sender); - } - - String additionsSummary = "addition" + (additions == 1 ? "" : "s"); - String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s"); - - Message.APPLY_EDITS_SUCCESS.send(sender, "track", track.getName()); - Message.APPLY_EDITS_SUCCESS_SUMMARY.send(sender, additions, additionsSummary, deletions, deletionsSummary); - Message.APPLY_EDITS_DIFF_REMOVED.send(sender, before); - Message.APPLY_EDITS_DIFF_ADDED.send(sender, after); - StorageAssistant.save(track, sender, plugin); - return true; - } - - private boolean readGroupDeletion(JsonElement data, Sender sender, LuckPermsPlugin plugin) { - String groupName = data.getAsString(); - - if (groupName.equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) { - Message.DELETE_GROUP_ERROR_DEFAULT.send(sender); - return true; - } - - Group group = plugin.getStorage().loadGroup(groupName).join().orElse(null); - if (group == null) { - return false; - } - - if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), group) || ArgumentPermissions.checkGroup(plugin, sender, group, ImmutableContextSetImpl.EMPTY)) { - Message.COMMAND_NO_PERMISSION.send(sender); - return false; - } - - try { - plugin.getStorage().deleteGroup(group, DeletionCause.COMMAND).get(); - } catch (Exception e) { - e.printStackTrace(); - Message.DELETE_ERROR.send(sender, group.getFormattedDisplayName()); - return true; - } - - Message.DELETE_SUCCESS.send(sender, group.getFormattedDisplayName()); - - LoggedAction.build().source(sender).targetName(groupName).targetType(Action.Target.Type.GROUP) - .description("webeditor", "delete") - .build().submit(plugin, sender); - - return true; - } - - private boolean readTrackDeletion(JsonElement data, Sender sender, LuckPermsPlugin plugin) { - String trackName = data.getAsString(); - - Track track = plugin.getStorage().loadTrack(trackName).join().orElse(null); - if (track == null) { - return false; - } - - if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), track)) { - Message.COMMAND_NO_PERMISSION.send(sender); - return false; - } - - try { - plugin.getStorage().deleteTrack(track, DeletionCause.COMMAND).get(); - } catch (Exception e) { - e.printStackTrace(); - Message.DELETE_ERROR.send(sender, track.getName()); - return true; - } - - Message.DELETE_SUCCESS.send(sender, trackName); - - LoggedAction.build().source(sender).targetName(trackName).targetType(Action.Target.Type.TRACK) - .description("webeditor", "delete") - .build().submit(plugin, sender); - - return true; - } - - private static String formatNode(LocaleManager localeManager, Node n) { - return n.getKey() + " &7(" + (n.getValue() ? "&a" : "&c") + n.getValue() + "&7)" + MessageUtils.getAppendableNodeContextString(localeManager, n) + - (n.hasExpiry() ? " &7(" + DurationFormatter.CONCISE.format(n.getExpiryDuration()) + ")" : ""); - } - - private static Set getAdded(Collection before, Collection after) { - Set added = new LinkedHashSet<>(after); - added.removeAll(before); - return added; - } - - private static Set getRemoved(Collection before, Collection after) { - Set removed = new LinkedHashSet<>(before); - removed.removeAll(after); - return removed; - } - - private static boolean hasBeenReordered(List before, List after, Collection diffAdded, Collection diffRemoved) { - after = new ArrayList<>(after); - before = new ArrayList<>(before); - - after.removeAll(diffAdded); - before.removeAll(diffRemoved); - - return !before.equals(after); - } - @Override public boolean shouldDisplay() { return false; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java index 0fea84e29..b717193ee 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/EditorCommand.java @@ -25,8 +25,6 @@ package me.lucko.luckperms.common.commands.misc; -import com.google.gson.JsonObject; - import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.SingleCommand; import me.lucko.luckperms.common.command.access.ArgumentPermissions; @@ -47,7 +45,7 @@ import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.storage.misc.NodeEntry; import me.lucko.luckperms.common.util.Predicates; import me.lucko.luckperms.common.verbose.event.MetaCheckEvent; -import me.lucko.luckperms.common.web.WebEditor; +import me.lucko.luckperms.common.webeditor.WebEditorRequest; import net.luckperms.api.node.Node; import net.luckperms.api.query.QueryOptions; @@ -171,8 +169,8 @@ public class EditorCommand extends SingleCommand { Message.EDITOR_START.send(sender); - JsonObject payload = WebEditor.formPayload(holders, tracks, sender, label, plugin); - return WebEditor.post(payload, sender, plugin); + return WebEditorRequest.generate(holders, tracks, sender, label, plugin) + .createSession(plugin, sender); } private enum Type { diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ImportCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ImportCommand.java index 50ca6c87d..907cadf1c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ImportCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ImportCommand.java @@ -32,6 +32,7 @@ import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.SingleCommand; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.utils.ArgumentList; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; @@ -39,8 +40,6 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.util.Predicates; import me.lucko.luckperms.common.util.gson.GsonProvider; -import me.lucko.luckperms.common.web.UnsuccessfulRequestException; -import me.lucko.luckperms.common.web.WebEditor; import java.io.BufferedReader; import java.io.IOException; @@ -118,7 +117,7 @@ public class ImportCommand extends SingleCommand { } try { - data = WebEditor.readDataFromBytebin(plugin.getBytebin(), code); + data = plugin.getBytebin().getJsonContent(code).getAsJsonObject(); } catch (UnsuccessfulRequestException e) { Message.IMPORT_HTTP_REQUEST_FAILURE.send(sender, e.getResponse().code(), e.getResponse().message()); return CommandResult.STATE_ERROR; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/TreeCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/TreeCommand.java index a38f5ee90..9d31a3a2c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/TreeCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/TreeCommand.java @@ -31,6 +31,7 @@ import me.lucko.luckperms.common.command.abstraction.SingleCommand; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.utils.ArgumentList; import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; @@ -40,7 +41,6 @@ import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.treeview.TreeView; import me.lucko.luckperms.common.util.Predicates; import me.lucko.luckperms.common.util.Uuids; -import me.lucko.luckperms.common.web.UnsuccessfulRequestException; import net.kyori.text.Component; import net.kyori.text.TextComponent; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java index fc721d8c6..018d8f21a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java @@ -32,6 +32,7 @@ import me.lucko.luckperms.common.command.tabcomplete.CompletionSupplier; import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; import me.lucko.luckperms.common.command.utils.ArgumentList; import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; @@ -42,7 +43,6 @@ import me.lucko.luckperms.common.verbose.InvalidFilterException; import me.lucko.luckperms.common.verbose.VerboseFilter; import me.lucko.luckperms.common.verbose.VerboseHandler; import me.lucko.luckperms.common.verbose.VerboseListener; -import me.lucko.luckperms.common.web.UnsuccessfulRequestException; import net.kyori.text.Component; import net.kyori.text.TextComponent; diff --git a/common/src/main/java/me/lucko/luckperms/common/web/AbstractHttpClient.java b/common/src/main/java/me/lucko/luckperms/common/http/AbstractHttpClient.java similarity index 98% rename from common/src/main/java/me/lucko/luckperms/common/web/AbstractHttpClient.java rename to common/src/main/java/me/lucko/luckperms/common/http/AbstractHttpClient.java index 62b90255d..76f8aa4c0 100644 --- a/common/src/main/java/me/lucko/luckperms/common/web/AbstractHttpClient.java +++ b/common/src/main/java/me/lucko/luckperms/common/http/AbstractHttpClient.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package me.lucko.luckperms.common.web; +package me.lucko.luckperms.common.http; import okhttp3.MediaType; import okhttp3.OkHttpClient; diff --git a/common/src/main/java/me/lucko/luckperms/common/web/BytebinClient.java b/common/src/main/java/me/lucko/luckperms/common/http/BytebinClient.java similarity index 80% rename from common/src/main/java/me/lucko/luckperms/common/web/BytebinClient.java rename to common/src/main/java/me/lucko/luckperms/common/http/BytebinClient.java index 265f61fc1..619452ef4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/web/BytebinClient.java +++ b/common/src/main/java/me/lucko/luckperms/common/http/BytebinClient.java @@ -23,15 +23,24 @@ * SOFTWARE. */ -package me.lucko.luckperms.common.web; +package me.lucko.luckperms.common.http; + +import com.google.gson.JsonElement; + +import me.lucko.luckperms.common.util.gson.GsonProvider; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; public class BytebinClient extends AbstractHttpClient { @@ -134,6 +143,34 @@ public class BytebinClient extends AbstractHttpClient { makeHttpRequest(request).close(); } + /** + * GETs json content from bytebin + * + * @param id the id of the content + * @return the data + * @throws IOException if an error occurs + */ + public JsonElement getJsonContent(String id) throws IOException, UnsuccessfulRequestException { + Request request = new Request.Builder() + .header("User-Agent", this.userAgent) + .url(this.url + id) + .build(); + + try (Response response = makeHttpRequest(request)) { + try (ResponseBody responseBody = response.body()) { + if (responseBody == null) { + throw new RuntimeException("No response"); + } + + try (InputStream inputStream = responseBody.byteStream()) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + return GsonProvider.normal().fromJson(reader, JsonElement.class); + } + } + } + } + } + public static final class Content { private final String key; private final boolean modifiable; diff --git a/common/src/main/java/me/lucko/luckperms/common/web/UnsuccessfulRequestException.java b/common/src/main/java/me/lucko/luckperms/common/http/UnsuccessfulRequestException.java similarity index 97% rename from common/src/main/java/me/lucko/luckperms/common/web/UnsuccessfulRequestException.java rename to common/src/main/java/me/lucko/luckperms/common/http/UnsuccessfulRequestException.java index c9ed18c4e..e6fc37990 100644 --- a/common/src/main/java/me/lucko/luckperms/common/web/UnsuccessfulRequestException.java +++ b/common/src/main/java/me/lucko/luckperms/common/http/UnsuccessfulRequestException.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package me.lucko.luckperms.common.web; +package me.lucko.luckperms.common.http; import okhttp3.Response; diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java index b9da8fe2c..0ad4ac73b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java @@ -39,6 +39,7 @@ import me.lucko.luckperms.common.event.AbstractEventBus; import me.lucko.luckperms.common.event.EventDispatcher; import me.lucko.luckperms.common.event.gen.GeneratedEventClass; import me.lucko.luckperms.common.extension.SimpleExtensionManager; +import me.lucko.luckperms.common.http.BytebinClient; import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.message.Message; @@ -53,7 +54,6 @@ import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher import me.lucko.luckperms.common.tasks.SyncTask; import me.lucko.luckperms.common.treeview.PermissionRegistry; import me.lucko.luckperms.common.verbose.VerboseHandler; -import me.lucko.luckperms.common.web.BytebinClient; import net.luckperms.api.LuckPerms; diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java index 4b707309f..559f0352a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java @@ -35,6 +35,7 @@ import me.lucko.luckperms.common.context.ContextManager; import me.lucko.luckperms.common.dependencies.DependencyManager; import me.lucko.luckperms.common.event.EventDispatcher; import me.lucko.luckperms.common.extension.SimpleExtensionManager; +import me.lucko.luckperms.common.http.BytebinClient; import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.messaging.InternalMessagingService; @@ -53,7 +54,6 @@ import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher import me.lucko.luckperms.common.tasks.SyncTask; import me.lucko.luckperms.common.treeview.PermissionRegistry; import me.lucko.luckperms.common.verbose.VerboseHandler; -import me.lucko.luckperms.common.web.BytebinClient; import net.luckperms.api.query.QueryOptions; diff --git a/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java b/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java index d16cd0d4e..331b8ac0a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java +++ b/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java @@ -29,14 +29,14 @@ import com.google.common.base.Splitter; import com.google.gson.JsonObject; import me.lucko.luckperms.common.cacheddata.type.PermissionCache; +import me.lucko.luckperms.common.http.AbstractHttpClient; +import me.lucko.luckperms.common.http.BytebinClient; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.util.gson.GsonProvider; import me.lucko.luckperms.common.util.gson.JObject; import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent; -import me.lucko.luckperms.common.web.AbstractHttpClient; -import me.lucko.luckperms.common.web.BytebinClient; -import me.lucko.luckperms.common.web.UnsuccessfulRequestException; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/common/src/main/java/me/lucko/luckperms/common/util/gson/JArray.java b/common/src/main/java/me/lucko/luckperms/common/util/gson/JArray.java index 730c2d661..b85904b3c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/util/gson/JArray.java +++ b/common/src/main/java/me/lucko/luckperms/common/util/gson/JArray.java @@ -57,6 +57,13 @@ public class JArray implements JElement { return this; } + public JArray addAll(Iterable iterable) { + for (String s : iterable) { + add(s); + } + return this; + } + public JArray add(JElement value) { if (value == null) { return add(JsonNull.INSTANCE); diff --git a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java index a330c4562..7ae1f2bce 100644 --- a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java +++ b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java @@ -29,6 +29,9 @@ import com.google.gson.JsonObject; import me.lucko.luckperms.common.calculator.result.TristateResult; import me.lucko.luckperms.common.command.utils.MessageUtils; +import me.lucko.luckperms.common.http.AbstractHttpClient; +import me.lucko.luckperms.common.http.BytebinClient; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.util.DurationFormatter; @@ -40,9 +43,6 @@ import me.lucko.luckperms.common.util.gson.JObject; import me.lucko.luckperms.common.verbose.event.MetaCheckEvent; import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent; import me.lucko.luckperms.common.verbose.event.VerboseEvent; -import me.lucko.luckperms.common.web.AbstractHttpClient; -import me.lucko.luckperms.common.web.BytebinClient; -import me.lucko.luckperms.common.web.UnsuccessfulRequestException; import net.kyori.text.TextComponent; import net.kyori.text.event.HoverEvent; diff --git a/common/src/main/java/me/lucko/luckperms/common/web/WebEditor.java b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java similarity index 63% rename from common/src/main/java/me/lucko/luckperms/common/web/WebEditor.java rename to common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java index 3aa54c293..835a91b9a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/web/WebEditor.java +++ b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package me.lucko.luckperms.common.web; +package me.lucko.luckperms.common.webeditor; import com.google.common.base.Preconditions; import com.google.gson.JsonObject; @@ -32,6 +32,8 @@ import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.context.ContextSetJsonSerializer; import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; +import me.lucko.luckperms.common.http.AbstractHttpClient; +import me.lucko.luckperms.common.http.UnsuccessfulRequestException; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.Track; @@ -49,15 +51,8 @@ import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; import net.luckperms.api.context.ImmutableContextSet; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; - -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; @@ -65,27 +60,21 @@ import java.util.List; import java.util.zip.GZIPOutputStream; /** - * Utility methods for interacting with the LuckPerms web permission editor. + * Encapsulates a request to the web editor. */ -public final class WebEditor { - private WebEditor() {} +public class WebEditorRequest { - private static JObject writeData(PermissionHolder holder) { - return new JObject() - .add("type", holder.getType().toString()) - .add("id", holder.getObjectName()) - .add("displayName", holder.getPlainDisplayName()) - .add("nodes", NodeJsonSerializer.serializeNodes(holder.normalData().asList())); - } - - private static JObject writeData(Track track) { - return new JObject() - .add("type", "track") - .add("id", track.getName()) - .add("groups", new JArray().consume(a -> track.getGroups().forEach(a::add))); - } - - public static JsonObject formPayload(List holders, List tracks, Sender sender, String cmdLabel, LuckPermsPlugin plugin) { + /** + * Generates a web editor request payload. + * + * @param holders the holders to edit + * @param tracks the tracks to edit + * @param sender the sender who is creating the session + * @param cmdLabel the command label used by LuckPerms + * @param plugin the plugin + * @return a payload + */ + public static WebEditorRequest generate(List holders, List tracks, Sender sender, String cmdLabel, LuckPermsPlugin plugin) { Preconditions.checkArgument(!holders.isEmpty(), "holders is empty"); ImmutableContextSet.Builder potentialContexts = new ImmutableContextSetImpl.BuilderImpl(); @@ -95,55 +84,83 @@ public final class WebEditor { } // form the payload data - return new JObject() - .add("metadata", new JObject() - .add("commandAlias", cmdLabel) - .add("uploader", new JObject() - .add("name", sender.getNameWithLocation()) - .add("uuid", sender.getUniqueId().toString()) - ) - .add("time", System.currentTimeMillis()) - .add("pluginVersion", plugin.getBootstrap().getVersion()) - ) + return new WebEditorRequest(holders, tracks, sender, cmdLabel, potentialContexts.build(), plugin); + } + + /** + * The encoded json object this payload is made up of + */ + private final JsonObject payload; + + private WebEditorRequest(List holders, List tracks, Sender sender, String cmdLabel, ImmutableContextSet potentialContexts, LuckPermsPlugin plugin) { + this.payload = new JObject() + .add("metadata", formMetadata(sender, cmdLabel, plugin.getBootstrap().getVersion())) .add("permissionHolders", new JArray() .consume(arr -> { for (PermissionHolder holder : holders) { - arr.add(writeData(holder)); + arr.add(formPermissionHolder(holder)); } }) ) .add("tracks", new JArray() .consume(arr -> { for (Track track : tracks) { - arr.add(writeData(track)); + arr.add(formTrack(track)); } }) ) - .add("knownPermissions", new JArray() - .consume(arr -> { - for (String perm : plugin.getPermissionRegistry().rootAsList()) { - arr.add(perm); - } - }) - ) - .consume(o -> { - o.add("potentialContexts", ContextSetJsonSerializer.serialize(potentialContexts.build())); - }) + .add("knownPermissions", new JArray().addAll(plugin.getPermissionRegistry().rootAsList())) + .add("potentialContexts", ContextSetJsonSerializer.serialize(potentialContexts)) .toJson(); } - public static CommandResult post(JsonObject payload, Sender sender, LuckPermsPlugin plugin) { - // upload the payload data to gist + private static JObject formMetadata(Sender sender, String cmdLabel, String pluginVersion) { + return new JObject() + .add("commandAlias", cmdLabel) + .add("uploader", new JObject() + .add("name", sender.getNameWithLocation()) + .add("uuid", sender.getUniqueId().toString()) + ) + .add("time", System.currentTimeMillis()) + .add("pluginVersion", pluginVersion); + } + + private static JObject formPermissionHolder(PermissionHolder holder) { + return new JObject() + .add("type", holder.getType().toString()) + .add("id", holder.getObjectName()) + .add("displayName", holder.getPlainDisplayName()) + .add("nodes", NodeJsonSerializer.serializeNodes(holder.normalData().asList())); + } + + private static JObject formTrack(Track track) { + return new JObject() + .add("type", "track") + .add("id", track.getName()) + .add("groups", new JArray().addAll(track.getGroups())); + } + + public byte[] encode() { ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); try (Writer writer = new OutputStreamWriter(new GZIPOutputStream(bytesOut), StandardCharsets.UTF_8)) { - GsonProvider.prettyPrinting().toJson(payload, writer); + GsonProvider.prettyPrinting().toJson(this.payload, writer); } catch (IOException e) { e.printStackTrace(); } + return bytesOut.toByteArray(); + } + /** + * Creates a web editor session, and sends the URL to the sender. + * + * @param plugin the plugin + * @param sender the sender creating the session + * @return the command result + */ + public CommandResult createSession(LuckPermsPlugin plugin, Sender sender) { String pasteId; try { - pasteId = plugin.getBytebin().postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE, false).key(); + pasteId = plugin.getBytebin().postContent(encode(), AbstractHttpClient.JSON_TYPE, false).key(); } catch (UnsuccessfulRequestException e) { Message.EDITOR_HTTP_REQUEST_FAILURE.send(sender, e.getResponse().code(), e.getResponse().message()); return CommandResult.STATE_ERROR; @@ -167,25 +184,4 @@ public final class WebEditor { return CommandResult.SUCCESS; } - public static JsonObject readDataFromBytebin(BytebinClient bytebin, String id) throws IOException, UnsuccessfulRequestException { - Request request = new Request.Builder() - .header("User-Agent", bytebin.getUserAgent()) - .url(bytebin.getUrl() + id) - .build(); - - try (Response response = bytebin.makeHttpRequest(request)) { - try (ResponseBody responseBody = response.body()) { - if (responseBody == null) { - throw new RuntimeException("No response"); - } - - try (InputStream inputStream = responseBody.byteStream()) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - return GsonProvider.normal().fromJson(reader, JsonObject.class); - } - } - } - } - } - } diff --git a/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorResponse.java b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorResponse.java new file mode 100644 index 000000000..29b4155c1 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorResponse.java @@ -0,0 +1,363 @@ +/* + * 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.webeditor; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import me.lucko.luckperms.common.actionlog.LoggedAction; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; +import me.lucko.luckperms.common.command.access.CommandPermission; +import me.lucko.luckperms.common.command.utils.MessageUtils; +import me.lucko.luckperms.common.command.utils.StorageAssistant; +import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; +import me.lucko.luckperms.common.locale.LocaleManager; +import me.lucko.luckperms.common.locale.message.Message; +import me.lucko.luckperms.common.model.Group; +import me.lucko.luckperms.common.model.PermissionHolder; +import me.lucko.luckperms.common.model.Track; +import me.lucko.luckperms.common.model.manager.group.GroupManager; +import me.lucko.luckperms.common.node.utils.NodeJsonSerializer; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.common.util.DurationFormatter; +import me.lucko.luckperms.common.util.Uuids; + +import net.luckperms.api.actionlog.Action; +import net.luckperms.api.event.cause.CreationCause; +import net.luckperms.api.event.cause.DeletionCause; +import net.luckperms.api.model.data.DataType; +import net.luckperms.api.node.Node; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +/** + * Encapsulates a response from the web editor. + */ +public class WebEditorResponse { + + /** + * The encoded json object this payload is made up of + */ + private final JsonObject payload; + + public WebEditorResponse(JsonObject payload) { + this.payload = payload; + } + + /** + * Applies the response to storage, sending appropriate informational messages to the sender. + * + * @param plugin the plugin + * @param sender the sender who is applying the session + */ + public void apply(LuckPermsPlugin plugin, Sender sender) { + Session session = new Session(plugin, sender); + boolean work = false; + + if (this.payload.has("changes")) { + JsonArray changes = this.payload.get("changes").getAsJsonArray(); + for (JsonElement change : changes) { + if (session.applyChange(change.getAsJsonObject())) { + work = true; + } + } + } + if (this.payload.has("groupDeletions")) { + JsonArray groupDeletions = this.payload.get("groupDeletions").getAsJsonArray(); + for (JsonElement groupDeletion : groupDeletions) { + if (session.applyGroupDelete(groupDeletion)) { + work = true; + } + } + } + if (this.payload.has("trackDeletions")) { + JsonArray trackDeletions = this.payload.get("trackDeletions").getAsJsonArray(); + for (JsonElement trackDeletion : trackDeletions) { + if (session.applyTrackDelete(trackDeletion)) { + work = true; + } + } + } + + if (!work) { + Message.APPLY_EDITS_TARGET_NO_CHANGES_PRESENT.send(sender); + } + } + + /** + * Represents the application of a given editor session on this platform. + */ + private static class Session { + private final LuckPermsPlugin plugin; + private final Sender sender; + + Session(LuckPermsPlugin plugin, Sender sender) { + this.plugin = plugin; + this.sender = sender; + } + + private boolean applyChange(JsonObject changeInfo) { + String type = changeInfo.get("type").getAsString(); + + if (type.equals("user") || type.equals("group")) { + return applyHolderChange(changeInfo); + } else if (type.equals("track")) { + return applyTrackChange(changeInfo); + } else { + Message.APPLY_EDITS_UNKNOWN_TYPE.send(this.sender, type); + return false; + } + } + + private boolean applyHolderChange(JsonObject changeInfo) { + String type = changeInfo.get("type").getAsString(); + String id = changeInfo.get("id").getAsString(); + + PermissionHolder holder; + if (type.equals("user")) { + // user + UUID uuid = Uuids.parse(id); + if (uuid == null) { + Message.APPLY_EDITS_TARGET_USER_NOT_UUID.send(this.sender, id); + return false; + } + holder = this.plugin.getStorage().loadUser(uuid, null).join(); + if (holder == null) { + Message.APPLY_EDITS_TARGET_USER_UNABLE_TO_LOAD.send(this.sender, uuid.toString()); + return false; + } + } else { + // group + holder = this.plugin.getStorage().loadGroup(id).join().orElse(null); + if (holder == null) { + holder = this.plugin.getStorage().createAndLoadGroup(id, CreationCause.WEB_EDITOR).join(); + } + } + + if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, holder) || ArgumentPermissions.checkGroup(this.plugin, this.sender, holder, ImmutableContextSetImpl.EMPTY)) { + Message.COMMAND_NO_PERMISSION.send(this.sender); + return false; + } + + Set before = holder.normalData().asSet(); + Set after = new HashSet<>(NodeJsonSerializer.deserializeNodes(changeInfo.getAsJsonArray("nodes"))); + + Set diffAdded = getAdded(before, after); + Set diffRemoved = getRemoved(before, after); + + int additions = diffAdded.size(); + int deletions = diffRemoved.size(); + + if (additions == 0 && deletions == 0) { + return false; + } + + holder.setNodes(DataType.NORMAL, after); + + for (Node n : diffAdded) { + LoggedAction.build().source(this.sender).target(holder) + .description("webeditor", "add", n.getKey(), n.getValue(), n.getContexts()) + .build().submit(this.plugin, this.sender); + } + for (Node n : diffRemoved) { + LoggedAction.build().source(this.sender).target(holder) + .description("webeditor", "remove", n.getKey(), n.getValue(), n.getContexts()) + .build().submit(this.plugin, this.sender); + } + + String additionsSummary = "addition" + (additions == 1 ? "" : "s"); + String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s"); + + Message.APPLY_EDITS_SUCCESS.send(this.sender, type, holder.getFormattedDisplayName()); + Message.APPLY_EDITS_SUCCESS_SUMMARY.send(this.sender, additions, additionsSummary, deletions, deletionsSummary); + for (Node n : diffAdded) { + Message.APPLY_EDITS_DIFF_ADDED.send(this.sender, formatNode(this.plugin.getLocaleManager(), n)); + } + for (Node n : diffRemoved) { + Message.APPLY_EDITS_DIFF_REMOVED.send(this.sender, formatNode(this.plugin.getLocaleManager(), n)); + } + StorageAssistant.save(holder, this.sender, this.plugin); + return true; + } + + private boolean applyTrackChange(JsonObject changeInfo) { + String id = changeInfo.get("id").getAsString(); + + Track track = this.plugin.getStorage().loadTrack(id).join().orElse(null); + if (track == null) { + track = this.plugin.getStorage().createAndLoadTrack(id, CreationCause.WEB_EDITOR).join(); + } + + if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, track)) { + Message.COMMAND_NO_PERMISSION.send(this.sender); + return false; + } + + List before = track.getGroups(); + List after = new ArrayList<>(); + changeInfo.getAsJsonArray("groups").forEach(e -> after.add(e.getAsString())); + + if (before.equals(after)) { + return false; + } + + Set diffAdded = getAdded(before, after); + Set diffRemoved = getRemoved(before, after); + + int additions = diffAdded.size(); + int deletions = diffRemoved.size(); + + track.setGroups(after); + + if (hasBeenReordered(before, after, diffAdded, diffRemoved)) { + LoggedAction.build().source(this.sender).target(track) + .description("webeditor", "reorder", after) + .build().submit(this.plugin, this.sender); + } + for (String n : diffAdded) { + LoggedAction.build().source(this.sender).target(track) + .description("webeditor", "add", n) + .build().submit(this.plugin, this.sender); + } + for (String n : diffRemoved) { + LoggedAction.build().source(this.sender).target(track) + .description("webeditor", "remove", n) + .build().submit(this.plugin, this.sender); + } + + String additionsSummary = "addition" + (additions == 1 ? "" : "s"); + String deletionsSummary = "deletion" + (deletions == 1 ? "" : "s"); + + Message.APPLY_EDITS_SUCCESS.send(this.sender, "track", track.getName()); + Message.APPLY_EDITS_SUCCESS_SUMMARY.send(this.sender, additions, additionsSummary, deletions, deletionsSummary); + Message.APPLY_EDITS_DIFF_REMOVED.send(this.sender, before); + Message.APPLY_EDITS_DIFF_ADDED.send(this.sender, after); + StorageAssistant.save(track, this.sender, this.plugin); + return true; + } + + private boolean applyGroupDelete(JsonElement changeInfo) { + String groupName = changeInfo.getAsString(); + + if (groupName.equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) { + Message.DELETE_GROUP_ERROR_DEFAULT.send(this.sender); + return true; + } + + Group group = this.plugin.getStorage().loadGroup(groupName).join().orElse(null); + if (group == null) { + return false; + } + + if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, group) || ArgumentPermissions.checkGroup(this.plugin, this.sender, group, ImmutableContextSetImpl.EMPTY)) { + Message.COMMAND_NO_PERMISSION.send(this.sender); + return false; + } + + try { + this.plugin.getStorage().deleteGroup(group, DeletionCause.COMMAND).get(); + } catch (Exception e) { + e.printStackTrace(); + Message.DELETE_ERROR.send(this.sender, group.getFormattedDisplayName()); + return true; + } + + Message.DELETE_SUCCESS.send(this.sender, group.getFormattedDisplayName()); + + LoggedAction.build().source(this.sender).targetName(groupName).targetType(Action.Target.Type.GROUP) + .description("webeditor", "delete") + .build().submit(this.plugin, this.sender); + + return true; + } + + private boolean applyTrackDelete(JsonElement changeInfo) { + String trackName = changeInfo.getAsString(); + + Track track = this.plugin.getStorage().loadTrack(trackName).join().orElse(null); + if (track == null) { + return false; + } + + if (ArgumentPermissions.checkModifyPerms(this.plugin, this.sender, CommandPermission.APPLY_EDITS, track)) { + Message.COMMAND_NO_PERMISSION.send(this.sender); + return false; + } + + try { + this.plugin.getStorage().deleteTrack(track, DeletionCause.COMMAND).get(); + } catch (Exception e) { + e.printStackTrace(); + Message.DELETE_ERROR.send(this.sender, track.getName()); + return true; + } + + Message.DELETE_SUCCESS.send(this.sender, trackName); + + LoggedAction.build().source(this.sender).targetName(trackName).targetType(Action.Target.Type.TRACK) + .description("webeditor", "delete") + .build().submit(this.plugin, this.sender); + + return true; + } + + private static String formatNode(LocaleManager localeManager, Node n) { + return n.getKey() + " &7(" + (n.getValue() ? "&a" : "&c") + n.getValue() + "&7)" + MessageUtils.getAppendableNodeContextString(localeManager, n) + + (n.hasExpiry() ? " &7(" + DurationFormatter.CONCISE.format(n.getExpiryDuration()) + ")" : ""); + } + + private static Set getAdded(Collection before, Collection after) { + Set added = new LinkedHashSet<>(after); + added.removeAll(before); + return added; + } + + private static Set getRemoved(Collection before, Collection after) { + Set removed = new LinkedHashSet<>(before); + removed.removeAll(after); + return removed; + } + + private static boolean hasBeenReordered(List before, List after, Collection diffAdded, Collection diffRemoved) { + after = new ArrayList<>(after); + before = new ArrayList<>(before); + + after.removeAll(diffAdded); + before.removeAll(diffRemoved); + + return !before.equals(after); + } + + } +}