Add /lp track <track> editor command (#2752)

This commit is contained in:
Luck 2021-01-13 14:33:01 +00:00
parent 505c073c8e
commit 489c09ddfc
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
10 changed files with 212 additions and 115 deletions

View File

@ -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),

View File

@ -325,6 +325,7 @@ public enum CommandSpec {
),
TRACK_INFO,
TRACK_EDITOR,
TRACK_APPEND(
arg("group", true)
),

View File

@ -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<T extends PermissionHolder> extends ChildCommand<T> {
public HolderEditor(HolderType type) {
@ -77,43 +68,21 @@ public class HolderEditor<T extends PermissionHolder> extends ChildCommand<T> {
if (target instanceof Group) {
Group group = (Group) target;
ConstraintNodeMatcher<Node> matcher = StandardNodeMatchers.key(Inheritance.key(group.getName()));
Map<UUID, User> 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
.<User>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)

View File

@ -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<PermissionHolder> holders = new ArrayList<>();
List<Track> tracks = new ArrayList<>();
if (type.includingGroups) {
plugin.getGroupManager().getAll().values().stream()
.sorted(Comparator
.<Group>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<UUID, User> users = new LinkedHashMap<>(plugin.getUserManager().getAll());
if (filter != null) {
ConstraintNodeMatcher<Node> 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)
.<User>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>) Boolean::compare).reversed())
// then sort according to their username
.thenComparing(User::getPlainDisplayName, String.CASE_INSENSITIVE_ORDER)
)
.forEach(holders::add);
ConstraintNodeMatcher<Node> matcher = filter != null ? StandardNodeMatchers.keyStartsWith(filter) : null;
WebEditorRequest.includeMatchingUsers(holders, matcher, type.includingOffline, plugin);
}
if (holders.isEmpty()) {

View File

@ -0,0 +1,100 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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<Track> {
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<Group> 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<PermissionHolder> users = new ArrayList<>();
if (!groups.isEmpty()) {
List<ConstraintNodeMatcher<Node>> 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<PermissionHolder> 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);
}
}

View File

@ -55,6 +55,7 @@ public class TrackParentCommand extends ParentCommand<Track, String> {
public TrackParentCommand() {
super(CommandSpec.TRACK, "Track", Type.TAKES_ARGUMENT_FOR_TARGET, ImmutableList.<Command<Track>>builder()
.add(new TrackInfo())
.add(new TrackEditor())
.add(new TrackAppend())
.add(new TrackInsert())
.add(new TrackRemove())

View File

@ -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;

View File

@ -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;

View File

@ -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<? super Group> holders, Predicate<? super Group> filter, LuckPermsPlugin plugin) {
plugin.getGroupManager().getAll().values().stream()
.filter(filter)
.sorted(Comparator
.<Group>comparingInt(g -> g.getWeight().orElse(0)).reversed()
.thenComparing(Group::getName, String.CASE_INSENSITIVE_ORDER)
)
.forEach(holders::add);
}
public static void includeMatchingUsers(List<? super User> holders, ConstraintNodeMatcher<Node> matcher, boolean includeOffline, LuckPermsPlugin plugin) {
includeMatchingUsers(holders, matcher == null ? Collections.emptyList() : Collections.singleton(matcher), includeOffline, plugin);
}
public static void includeMatchingUsers(List<? super User> holders, Collection<ConstraintNodeMatcher<Node>> matchers, boolean includeOffline, LuckPermsPlugin plugin) {
Map<UUID, User> users = new LinkedHashMap<>(plugin.getUserManager().getAll());
if (!matchers.isEmpty()) {
users.values().removeIf(user -> {
for (ConstraintNodeMatcher<Node> 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<Node> 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)
.<User>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>) Boolean::compare).reversed())
// then sort according to their username
.thenComparing(User::getPlainDisplayName, String.CASE_INSENSITIVE_ORDER)
)
.forEach(holders::add);
}
private static void findMatchingOfflineUsers(Map<UUID, User> users, ConstraintNodeMatcher<Node> matcher, LuckPermsPlugin plugin) {
Stream<UUID> 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);
});
}
}

View File

@ -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