From 489c09ddfc5976e71f1402fac15f1156031c0e56 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 13 Jan 2021 14:33:01 +0000 Subject: [PATCH] Add /lp track editor command (#2752) --- .../command/access/CommandPermission.java | 1 + .../common/command/spec/CommandSpec.java | 1 + .../commands/generic/other/HolderEditor.java | 51 ++------- .../common/commands/misc/EditorCommand.java | 75 +------------ .../common/commands/track/TrackEditor.java | 100 ++++++++++++++++++ .../commands/track/TrackParentCommand.java | 1 + .../common/event/EventDispatcher.java | 3 - .../common/model/PermissionHolder.java | 1 - .../common/webeditor/WebEditorRequest.java | 93 ++++++++++++++++ .../main/resources/luckperms_en.properties | 1 + 10 files changed, 212 insertions(+), 115 deletions(-) create mode 100644 common/src/main/java/me/lucko/luckperms/common/commands/track/TrackEditor.java diff --git a/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java b/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java index 05b2d0ccf..ea97c9732 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java @@ -145,6 +145,7 @@ public enum CommandPermission { GROUP_CLONE("clone", Type.GROUP), TRACK_INFO("info", Type.TRACK), + TRACK_EDITOR("editor", Type.TRACK), TRACK_APPEND("append", Type.TRACK), TRACK_INSERT("insert", Type.TRACK), TRACK_REMOVE("remove", Type.TRACK), diff --git a/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java b/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java index aeee95775..09b044ca7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java @@ -325,6 +325,7 @@ public enum CommandSpec { ), TRACK_INFO, + TRACK_EDITOR, TRACK_APPEND( arg("group", true) ), 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 3676ccf7d..5014ac2c5 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 @@ -31,33 +31,24 @@ import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.misc.EditorCommand; import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.HolderType; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher; import me.lucko.luckperms.common.node.matcher.StandardNodeMatchers; import me.lucko.luckperms.common.node.types.Inheritance; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; 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.webeditor.WebEditorRequest; import net.luckperms.api.node.Node; -import net.luckperms.api.query.QueryOptions; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.UUID; public class HolderEditor extends ChildCommand { public HolderEditor(HolderType type) { @@ -77,43 +68,21 @@ public class HolderEditor extends ChildCommand { if (target instanceof Group) { Group group = (Group) target; ConstraintNodeMatcher matcher = StandardNodeMatchers.key(Inheritance.key(group.getName())); - - Map users = new LinkedHashMap<>(plugin.getUserManager().getAll()); - - // only include online players who are in the group - users.values().removeIf(user -> user.normalData().asList().stream().noneMatch(matcher)); - - // fill up with other matching users - if (users.size() < EditorCommand.MAX_USERS) { - plugin.getStorage().searchUserNodes(matcher).join().stream() - .map(NodeEntry::getHolder) - .distinct() - .filter(uuid -> !users.containsKey(uuid)) - .sorted() - .limit(EditorCommand.MAX_USERS - users.size()) - .forEach(uuid -> { - User user = plugin.getStorage().loadUser(uuid, null).join(); - if (user != null) { - users.put(uuid, user); - } - plugin.getUserManager().getHouseKeeper().cleanup(uuid); - }); - } - - users.values().stream() - .sorted(Comparator - .comparingInt(u -> u.getCachedData().getMetaData(QueryOptions.nonContextual()).getWeight(MetaCheckEvent.Origin.INTERNAL)).reversed() - .thenComparing(User::getPlainDisplayName, String.CASE_INSENSITIVE_ORDER) - ) - .forEach(holders::add); - - // remove holders which the sender doesn't have perms to view - holders.removeIf(h -> ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), h) || ArgumentPermissions.checkGroup(plugin, sender, h, ImmutableContextSetImpl.EMPTY)); + WebEditorRequest.includeMatchingUsers(holders, matcher, true, plugin); } // include the original holder too holders.add(target); + // remove holders which the sender doesn't have perms to view + holders.removeIf(h -> ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), h) || ArgumentPermissions.checkGroup(plugin, sender, h, ImmutableContextSetImpl.EMPTY)); + + // they don't have perms to view any of them + if (holders.isEmpty()) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + Message.EDITOR_START.send(sender); return WebEditorRequest.generate(holders, Collections.emptyList(), sender, label, plugin) 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 982093d3d..c0fbbdcdd 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 @@ -33,32 +33,21 @@ import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; import me.lucko.luckperms.common.locale.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.User; import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher; import me.lucko.luckperms.common.node.matcher.StandardNodeMatchers; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; 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.webeditor.WebEditorRequest; import net.luckperms.api.node.Node; -import net.luckperms.api.query.QueryOptions; import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.UUID; public class EditorCommand extends SingleCommand { - public static final int MAX_USERS = 500; - public EditorCommand() { super(CommandSpec.EDITOR, "Editor", CommandPermission.EDITOR, Predicates.notInRange(0, 2)); } @@ -89,70 +78,16 @@ public class EditorCommand extends SingleCommand { // collect holders List holders = new ArrayList<>(); List tracks = new ArrayList<>(); - if (type.includingGroups) { - plugin.getGroupManager().getAll().values().stream() - .sorted(Comparator - .comparingInt(g -> g.getWeight().orElse(0)).reversed() - .thenComparing(Group::getName, String.CASE_INSENSITIVE_ORDER) - ) - .forEach(holders::add); + if (type.includingGroups) { + WebEditorRequest.includeMatchingGroups(holders, Predicates.alwaysTrue(), plugin); tracks.addAll(plugin.getTrackManager().getAll().values()); } + if (type.includingUsers) { // include all online players - Map users = new LinkedHashMap<>(plugin.getUserManager().getAll()); - - if (filter != null) { - ConstraintNodeMatcher matcher = StandardNodeMatchers.keyStartsWith(filter); - - // only include online players matching the permission - users.values().removeIf(user -> user.normalData().asList().stream().noneMatch(matcher)); - - // fill up with other matching users - if (type.includingOffline && users.size() < MAX_USERS) { - plugin.getStorage().searchUserNodes(matcher).join().stream() - .map(NodeEntry::getHolder) - .distinct() - .filter(uuid -> !users.containsKey(uuid)) - .sorted() - .limit(MAX_USERS - users.size()) - .forEach(uuid -> { - User user = plugin.getStorage().loadUser(uuid, null).join(); - if (user != null) { - users.put(uuid, user); - } - plugin.getUserManager().getHouseKeeper().cleanup(uuid); - }); - } - } else { - - // fill up with other users - if (type.includingOffline && users.size() < MAX_USERS) { - plugin.getStorage().getUniqueUsers().join().stream() - .filter(uuid -> !users.containsKey(uuid)) - .sorted() - .limit(MAX_USERS - users.size()) - .forEach(uuid -> { - User user = plugin.getStorage().loadUser(uuid, null).join(); - if (user != null) { - users.put(uuid, user); - } - plugin.getUserManager().getHouseKeeper().cleanup(uuid); - }); - } - } - - users.values().stream() - .sorted(Comparator - // sort firstly by the users relative weight (depends on the groups they inherit) - .comparingInt(u -> u.getCachedData().getMetaData(QueryOptions.nonContextual()).getWeight(MetaCheckEvent.Origin.INTERNAL)).reversed() - // then, prioritise users we actually have a username for - .thenComparing(u -> u.getUsername().isPresent(), ((Comparator) Boolean::compare).reversed()) - // then sort according to their username - .thenComparing(User::getPlainDisplayName, String.CASE_INSENSITIVE_ORDER) - ) - .forEach(holders::add); + ConstraintNodeMatcher matcher = filter != null ? StandardNodeMatchers.keyStartsWith(filter) : null; + WebEditorRequest.includeMatchingUsers(holders, matcher, type.includingOffline, plugin); } if (holders.isEmpty()) { diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackEditor.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackEditor.java new file mode 100644 index 000000000..16daaef41 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackEditor.java @@ -0,0 +1,100 @@ +/* + * 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.commands.track; + +import me.lucko.luckperms.common.command.CommandResult; +import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; +import me.lucko.luckperms.common.command.access.CommandPermission; +import me.lucko.luckperms.common.command.spec.CommandSpec; +import me.lucko.luckperms.common.command.utils.ArgumentList; +import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; +import me.lucko.luckperms.common.locale.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.node.matcher.ConstraintNodeMatcher; +import me.lucko.luckperms.common.node.matcher.StandardNodeMatchers; +import me.lucko.luckperms.common.node.types.Inheritance; +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.webeditor.WebEditorRequest; + +import net.luckperms.api.node.Node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class TrackEditor extends ChildCommand { + public TrackEditor() { + super(CommandSpec.TRACK_EDITOR, "editor", CommandPermission.TRACK_EDITOR, Predicates.alwaysFalse()); + } + + @Override + public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + + // run a sync task + plugin.getSyncTaskBuffer().requestDirectly(); + + // collect groups + List groups = new ArrayList<>(); + WebEditorRequest.includeMatchingGroups(groups, target::containsGroup, plugin); + + // remove groups which the sender doesn't have perms to view + groups.removeIf(holder -> ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), holder) || ArgumentPermissions.checkGroup(plugin, sender, holder, ImmutableContextSetImpl.EMPTY)); + + // then collect users which are a member of any of those groups + // (users which are on the track) + List users = new ArrayList<>(); + if (!groups.isEmpty()) { + List> matchers = groups.stream() + .map(group -> StandardNodeMatchers.key(Inheritance.key(group.getName()))) + .collect(Collectors.toList()); + + WebEditorRequest.includeMatchingUsers(users, matchers, true, plugin); + } + + // remove users which the sender doesn't have perms to view + users.removeIf(holder -> ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), holder)); + + List holders = new ArrayList<>(groups.size() + users.size()); + holders.addAll(groups); + holders.addAll(users); + + Message.EDITOR_START.send(sender); + + return WebEditorRequest.generate(holders, Collections.singletonList(target), sender, label, plugin) + .createSession(plugin, sender); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackParentCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackParentCommand.java index 61497bc24..770efc4d2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackParentCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackParentCommand.java @@ -55,6 +55,7 @@ public class TrackParentCommand extends ParentCommand { public TrackParentCommand() { super(CommandSpec.TRACK, "Track", Type.TAKES_ARGUMENT_FOR_TARGET, ImmutableList.>builder() .add(new TrackInfo()) + .add(new TrackEditor()) .add(new TrackAppend()) .add(new TrackInsert()) .add(new TrackRemove()) diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java index e5584e7b1..4be30ac17 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java @@ -97,11 +97,8 @@ import net.luckperms.api.node.Node; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java index 0fe25a6da..917417f87 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java @@ -25,7 +25,6 @@ package me.lucko.luckperms.common.model; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import me.lucko.luckperms.common.cacheddata.HolderCachedDataManager; diff --git a/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java index e7f2a6b26..bb0ef178c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java +++ b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java @@ -35,23 +35,38 @@ 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; +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.User; +import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher; 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.storage.misc.NodeEntry; 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.verbose.event.MetaCheckEvent; import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.node.Node; +import net.luckperms.api.query.QueryOptions; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Stream; import java.util.zip.GZIPOutputStream; /** @@ -59,6 +74,8 @@ import java.util.zip.GZIPOutputStream; */ public class WebEditorRequest { + public static final int MAX_USERS = 500; + /** * Generates a web editor request payload. * @@ -171,4 +188,80 @@ public class WebEditorRequest { return CommandResult.SUCCESS; } + public static void includeMatchingGroups(List holders, Predicate filter, LuckPermsPlugin plugin) { + plugin.getGroupManager().getAll().values().stream() + .filter(filter) + .sorted(Comparator + .comparingInt(g -> g.getWeight().orElse(0)).reversed() + .thenComparing(Group::getName, String.CASE_INSENSITIVE_ORDER) + ) + .forEach(holders::add); + } + + public static void includeMatchingUsers(List holders, ConstraintNodeMatcher matcher, boolean includeOffline, LuckPermsPlugin plugin) { + includeMatchingUsers(holders, matcher == null ? Collections.emptyList() : Collections.singleton(matcher), includeOffline, plugin); + } + + public static void includeMatchingUsers(List holders, Collection> matchers, boolean includeOffline, LuckPermsPlugin plugin) { + Map users = new LinkedHashMap<>(plugin.getUserManager().getAll()); + + if (!matchers.isEmpty()) { + users.values().removeIf(user -> { + for (ConstraintNodeMatcher matcher : matchers) { + if (user.normalData().asList().stream().anyMatch(matcher)) { + return false; + } + } + return true; + }); + } + + if (includeOffline && users.size() < MAX_USERS) { + if (matchers.isEmpty()) { + findMatchingOfflineUsers(users, null, plugin); + } else { + for (ConstraintNodeMatcher matcher : matchers) { + if (users.size() < MAX_USERS) { + findMatchingOfflineUsers(users, matcher, plugin); + } else { + break; + } + } + } + } + + users.values().stream() + .sorted(Comparator + // sort firstly by the users relative weight (depends on the groups they inherit) + .comparingInt(u -> u.getCachedData().getMetaData(QueryOptions.nonContextual()).getWeight(MetaCheckEvent.Origin.INTERNAL)).reversed() + // then, prioritise users we actually have a username for + .thenComparing(u -> u.getUsername().isPresent(), ((Comparator) Boolean::compare).reversed()) + // then sort according to their username + .thenComparing(User::getPlainDisplayName, String.CASE_INSENSITIVE_ORDER) + ) + .forEach(holders::add); + } + + private static void findMatchingOfflineUsers(Map users, ConstraintNodeMatcher matcher, LuckPermsPlugin plugin) { + Stream stream; + if (matcher == null) { + stream = plugin.getStorage().getUniqueUsers().join().stream(); + } else { + stream = plugin.getStorage().searchUserNodes(matcher).join().stream() + .map(NodeEntry::getHolder) + .distinct(); + } + + stream.filter(uuid -> !users.containsKey(uuid)) + .sorted() + .limit(MAX_USERS - users.size()) + .forEach(uuid -> { + User user = plugin.getStorage().loadUser(uuid, null).join(); + if (user != null) { + users.put(uuid, user); + } + plugin.getUserManager().getHouseKeeper().cleanup(uuid); + }); + } + } diff --git a/common/src/main/resources/luckperms_en.properties b/common/src/main/resources/luckperms_en.properties index 961506f0b..759418ea1 100644 --- a/common/src/main/resources/luckperms_en.properties +++ b/common/src/main/resources/luckperms_en.properties @@ -563,6 +563,7 @@ luckperms.usage.meta-clear.description=Clears all meta luckperms.usage.meta-clear.argument.type=the type of meta to remove luckperms.usage.meta-clear.argument.context=the contexts to filter by luckperms.usage.track-info.description=Gives info about the track +luckperms.usage.track-editor.description=Opens the web permission editor luckperms.usage.track-append.description=Appends a group onto the end of the track luckperms.usage.track-append.argument.group=the group to append luckperms.usage.track-insert.description=Inserts a group at a given position along the track