mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2024-11-30 22:53:27 +01:00
Cleanup verbose & treeview packages. Return known permissions from the PermissionVault as Sponge PermissionDescriptions
This commit is contained in:
parent
1d5e3205ac
commit
d98b464ce9
@ -64,7 +64,7 @@ public class PermissionCalculator {
|
||||
Tristate result = lookupCache.get(permission);
|
||||
|
||||
// log this permission lookup to the verbose handler
|
||||
plugin.getVerboseHandler().offer(objectName, permission, result);
|
||||
plugin.getVerboseHandler().offerCheckData(objectName, permission, result);
|
||||
|
||||
// return the result
|
||||
return result;
|
||||
|
@ -36,6 +36,7 @@ import me.lucko.luckperms.common.locale.LocaleManager;
|
||||
import me.lucko.luckperms.common.locale.Message;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.utils.Predicates;
|
||||
import me.lucko.luckperms.common.verbose.VerboseFilter;
|
||||
import me.lucko.luckperms.common.verbose.VerboseListener;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
@ -71,14 +72,14 @@ public class VerboseCommand extends SingleCommand {
|
||||
|
||||
String filter = filters.isEmpty() ? "" : filters.stream().collect(Collectors.joining(" "));
|
||||
|
||||
if (!VerboseListener.isValidFilter(filter)) {
|
||||
if (!VerboseFilter.isValidFilter(filter)) {
|
||||
Message.VERBOSE_INVALID_FILTER.send(sender, filter);
|
||||
return CommandResult.FAILURE;
|
||||
}
|
||||
|
||||
boolean notify = !mode.equals("record");
|
||||
|
||||
plugin.getVerboseHandler().register(sender, filter, notify);
|
||||
plugin.getVerboseHandler().registerListener(sender, filter, notify);
|
||||
|
||||
if (notify) {
|
||||
if (!filter.equals("")) {
|
||||
@ -98,7 +99,7 @@ public class VerboseCommand extends SingleCommand {
|
||||
}
|
||||
|
||||
if (mode.equals("off") || mode.equals("false") || mode.equals("paste")) {
|
||||
VerboseListener listener = plugin.getVerboseHandler().unregister(sender.getUuid());
|
||||
VerboseListener listener = plugin.getVerboseHandler().unregisterListener(sender.getUuid());
|
||||
|
||||
if (mode.equals("paste")) {
|
||||
if (listener == null) {
|
||||
|
@ -136,4 +136,9 @@ public final class AbstractSender<T> implements Sender {
|
||||
return this.uuid.equals(Constants.IMPORT_UUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return ref.get() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -112,4 +112,11 @@ public interface Sender {
|
||||
*/
|
||||
boolean isImport();
|
||||
|
||||
/**
|
||||
* Gets whether this sender is still valid & receiving messages.
|
||||
*
|
||||
* @return if this sender is valid
|
||||
*/
|
||||
boolean isValid();
|
||||
|
||||
}
|
||||
|
@ -38,7 +38,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
|
||||
import me.lucko.luckperms.common.commands.utils.Util;
|
||||
import me.lucko.luckperms.common.locale.Message;
|
||||
import me.lucko.luckperms.common.utils.DateUtil;
|
||||
import me.lucko.luckperms.common.utils.FakeSender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -58,7 +57,7 @@ public class Importer implements Runnable {
|
||||
private final Set<Sender> notify;
|
||||
private final List<String> commands;
|
||||
private final Map<Integer, Result> cmdResult;
|
||||
private final FakeSender fake;
|
||||
private final ImporterSender fake;
|
||||
|
||||
private long lastMsg = 0;
|
||||
private int executing = -1;
|
||||
@ -82,7 +81,7 @@ public class Importer implements Runnable {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
this.cmdResult = new HashMap<>();
|
||||
this.fake = new FakeSender(commandManager.getPlugin(), this::logMessage);
|
||||
this.fake = new ImporterSender(commandManager.getPlugin(), this::logMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,7 +23,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package me.lucko.luckperms.common.utils;
|
||||
package me.lucko.luckperms.common.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@ -40,7 +40,7 @@ import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class FakeSender implements Sender {
|
||||
public class ImporterSender implements Sender {
|
||||
private final LuckPermsPlugin plugin;
|
||||
private final Consumer<String> messageConsumer;
|
||||
|
||||
@ -93,4 +93,9 @@ public class FakeSender implements Sender {
|
||||
public boolean isImport() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -35,24 +35,28 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* An immutable and sorted version of TreeNode
|
||||
*
|
||||
* Entries in the children map are sorted first by whether they have any children, and then alphabetically
|
||||
* Entries in the children map are sorted first by whether they have
|
||||
* any children, and then alphabetically
|
||||
*/
|
||||
public class ImmutableTreeNode implements Comparable<ImmutableTreeNode> {
|
||||
private Map<String, ImmutableTreeNode> children = null;
|
||||
|
||||
public ImmutableTreeNode(Map<String, ImmutableTreeNode> children) {
|
||||
public ImmutableTreeNode(Stream<Map.Entry<String, ImmutableTreeNode>> children) {
|
||||
if (children != null) {
|
||||
LinkedHashMap<String, ImmutableTreeNode> sortedMap = children.entrySet().stream()
|
||||
LinkedHashMap<String, ImmutableTreeNode> sortedMap = children
|
||||
.sorted((o1, o2) -> {
|
||||
// sort first by if the node has any children
|
||||
int childStatus = o1.getValue().compareTo(o2.getValue());
|
||||
if (childStatus != 0) {
|
||||
return childStatus;
|
||||
}
|
||||
|
||||
// then alphabetically
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(o1.getKey(), o2.getKey());
|
||||
})
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
@ -65,6 +69,13 @@ public class ImmutableTreeNode implements Comparable<ImmutableTreeNode> {
|
||||
return Optional.ofNullable(children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node endings of each branch of the tree at this stage
|
||||
*
|
||||
* The key represents the depth of the node.
|
||||
*
|
||||
* @return the node endings
|
||||
*/
|
||||
public List<Map.Entry<Integer, String>> getNodeEndings() {
|
||||
if (children == null) {
|
||||
return Collections.emptyList();
|
||||
@ -80,6 +91,7 @@ public class ImmutableTreeNode implements Comparable<ImmutableTreeNode> {
|
||||
results.addAll(node.getValue().getNodeEndings().stream()
|
||||
.map(e -> Maps.immutableEntry(
|
||||
e.getKey() + 1, // increment level
|
||||
// add this node's key infront of the child value
|
||||
node.getKey() + "." + e.getValue())
|
||||
)
|
||||
.collect(Collectors.toList()));
|
||||
|
@ -30,9 +30,12 @@ import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@ -42,8 +45,14 @@ import java.util.concurrent.Executor;
|
||||
public class PermissionVault implements Runnable {
|
||||
private static final Splitter DOT_SPLIT = Splitter.on('.').omitEmptyStrings();
|
||||
|
||||
// the root node in the tree
|
||||
@Getter
|
||||
private final TreeNode rootNode;
|
||||
|
||||
// the known permissions already in the vault
|
||||
private final Set<String> knownPermissions;
|
||||
|
||||
// a queue of permission strings to be processed by the tree
|
||||
private final Queue<String> queue;
|
||||
|
||||
@Setter
|
||||
@ -51,6 +60,7 @@ public class PermissionVault implements Runnable {
|
||||
|
||||
public PermissionVault(Executor executor) {
|
||||
rootNode = new TreeNode();
|
||||
knownPermissions = ConcurrentHashMap.newKeySet(3000);
|
||||
queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
executor.execute(this);
|
||||
@ -61,7 +71,10 @@ public class PermissionVault implements Runnable {
|
||||
while (true) {
|
||||
for (String e; (e = queue.poll()) != null; ) {
|
||||
try {
|
||||
insert(e.toLowerCase());
|
||||
String s = e.toLowerCase();
|
||||
if (knownPermissions.add(s)) {
|
||||
insert(s);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
@ -81,13 +94,19 @@ public class PermissionVault implements Runnable {
|
||||
queue.offer(permission);
|
||||
}
|
||||
|
||||
public Set<String> getKnownPermissions() {
|
||||
return ImmutableSet.copyOf(knownPermissions);
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return rootNode.getDeepSize();
|
||||
}
|
||||
|
||||
private void insert(String permission) {
|
||||
// split the permission up into parts
|
||||
List<String> parts = DOT_SPLIT.splitToList(permission);
|
||||
|
||||
// insert the permission into the node structure
|
||||
TreeNode current = rootNode;
|
||||
for (String part : parts) {
|
||||
current = current.getChildMap().computeIfAbsent(part, s -> new TreeNode());
|
||||
|
@ -25,13 +25,14 @@
|
||||
|
||||
package me.lucko.luckperms.common.treeview;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Represents one "branch" of the node tree
|
||||
* Represents one "branch" or "level" of the node tree
|
||||
*/
|
||||
public class TreeNode {
|
||||
private Map<String, TreeNode> children = null;
|
||||
@ -60,7 +61,12 @@ public class TreeNode {
|
||||
if (children == null) {
|
||||
return new ImmutableTreeNode(null);
|
||||
} else {
|
||||
return new ImmutableTreeNode(children.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().makeImmutableCopy())));
|
||||
return new ImmutableTreeNode(children.entrySet().stream()
|
||||
.map(e -> Maps.immutableEntry(
|
||||
e.getKey(),
|
||||
e.getValue().makeImmutableCopy()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,62 +42,188 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A readable view of a branch of {@link TreeNode}s.
|
||||
*/
|
||||
public class TreeView {
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
|
||||
|
||||
// the root of the tree
|
||||
private final String rootPosition;
|
||||
private final int maxLevels;
|
||||
|
||||
// how many levels / branches to display
|
||||
private final int maxLevel;
|
||||
|
||||
// the actual tree object
|
||||
private final ImmutableTreeNode view;
|
||||
|
||||
public TreeView(PermissionVault source, String rootPosition, int maxLevels) {
|
||||
public TreeView(PermissionVault source, String rootPosition, int maxLevel) {
|
||||
this.rootPosition = rootPosition;
|
||||
this.maxLevels = maxLevels;
|
||||
this.maxLevel = maxLevel;
|
||||
|
||||
Optional<TreeNode> root = findRoot(source);
|
||||
Optional<TreeNode> root = findRoot(rootPosition, source);
|
||||
this.view = root.map(TreeNode::makeImmutableCopy).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this TreeView has any content.
|
||||
*
|
||||
* @return true if the treeview has data
|
||||
*/
|
||||
public boolean hasData() {
|
||||
return view != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the root of the tree node at the given position
|
||||
*
|
||||
* @param source the node source
|
||||
* @return the root, if it exists
|
||||
*/
|
||||
private static Optional<TreeNode> findRoot(String rootPosition, PermissionVault source) {
|
||||
// get the root of the permission vault
|
||||
TreeNode root = source.getRootNode();
|
||||
|
||||
// just return the root
|
||||
if (rootPosition.equals(".")) {
|
||||
return Optional.of(root);
|
||||
}
|
||||
|
||||
// get the parts of the node
|
||||
List<String> parts = Splitter.on('.').omitEmptyStrings().splitToList(rootPosition);
|
||||
|
||||
// for each part
|
||||
for (String part : parts) {
|
||||
|
||||
// check the current root has some children
|
||||
if (!root.getChildren().isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// get the current roots children
|
||||
Map<String, TreeNode> branch = root.getChildren().get();
|
||||
|
||||
// get the new root
|
||||
root = branch.get(part);
|
||||
if (root == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.of(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the view to a readable list
|
||||
*
|
||||
* <p>The list contains KV pairs, where the key is the tree padding/structure,
|
||||
* and the value is the actual permission.</p>
|
||||
*
|
||||
* @return a list of the nodes in this view
|
||||
*/
|
||||
private List<Map.Entry<String, String>> asTreeList() {
|
||||
// work out the prefix to apply
|
||||
// since the view is relative, we need to prepend this to all permissions
|
||||
String prefix = rootPosition.equals(".") ? "" : (rootPosition + ".");
|
||||
|
||||
|
||||
List<Map.Entry<String, String>> ret = new ArrayList<>();
|
||||
|
||||
// iterate the node endings in the view
|
||||
for (Map.Entry<Integer, String> s : view.getNodeEndings()) {
|
||||
// don't include the node if it exceeds the max level
|
||||
if (s.getKey() >= maxLevel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// generate the tree padding characters from the node level
|
||||
String treeStructure = Strings.repeat("│ ", s.getKey()) + "├── ";
|
||||
// generate the permission, using the prefix and the node
|
||||
String permission = prefix + s.getValue();
|
||||
|
||||
ret.add(Maps.immutableEntry(treeStructure, permission));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the data contained in this TreeView to a paste, and returns the URL.
|
||||
*
|
||||
* @param version the plugin version string
|
||||
* @return the url, or null
|
||||
* @see PasteUtils#paste(String, List)
|
||||
*/
|
||||
public String uploadPasteData(String version) {
|
||||
// only paste if there is actually data here
|
||||
if (!hasData()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// get the data contained in the view in a list form
|
||||
// for each entry, the key is the padding tree characters
|
||||
// and the value is the actual permission string
|
||||
List<Map.Entry<String, String>> ret = asTreeList();
|
||||
ImmutableList.Builder<String> builder = getPasteHeader(version, "none", ret.size());
|
||||
builder.add("```");
|
||||
|
||||
// build the header of the paste
|
||||
ImmutableList.Builder<String> builder = getPasteHeader(version, "none", ret.size());
|
||||
|
||||
// add the tree data
|
||||
builder.add("```");
|
||||
for (Map.Entry<String, String> e : ret) {
|
||||
builder.add(e.getKey() + e.getValue());
|
||||
}
|
||||
|
||||
builder.add("```");
|
||||
|
||||
// clear the initial data map
|
||||
ret.clear();
|
||||
|
||||
// upload the return the data
|
||||
return PasteUtils.paste("LuckPerms Permission Tree", ImmutableList.of(Maps.immutableEntry("luckperms-tree.md", builder.build().stream().collect(Collectors.joining("\n")))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the data contained in this TreeView to a paste, and returns the URL.
|
||||
*
|
||||
* <p>Unlike {@link #uploadPasteData(String)}, this method will check each permission
|
||||
* against a corresponding user, and colorize the output depending on the check results.</p>
|
||||
*
|
||||
* @param version the plugin version string
|
||||
* @param username the username of the reference user
|
||||
* @param checker the permission data instance to check against
|
||||
* @return the url, or null
|
||||
* @see PasteUtils#paste(String, List)
|
||||
*/
|
||||
public String uploadPasteData(String version, String username, PermissionData checker) {
|
||||
// only paste if there is actually data here
|
||||
if (!hasData()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// get the data contained in the view in a list form
|
||||
// for each entry, the key is the padding tree characters
|
||||
// and the value is the actual permission string
|
||||
List<Map.Entry<String, String>> ret = asTreeList();
|
||||
ImmutableList.Builder<String> builder = getPasteHeader(version, username, ret.size());
|
||||
builder.add("```diff");
|
||||
|
||||
// build the header of the paste
|
||||
ImmutableList.Builder<String> builder = getPasteHeader(version, username, ret.size());
|
||||
|
||||
// add the tree data
|
||||
builder.add("```diff");
|
||||
for (Map.Entry<String, String> e : ret) {
|
||||
|
||||
// lookup a permission value for the node
|
||||
Tristate tristate = checker.getPermissionValue(e.getValue());
|
||||
|
||||
// append the data to the paste
|
||||
builder.add(getTristateDiffPrefix(tristate) + e.getKey() + e.getValue());
|
||||
}
|
||||
|
||||
builder.add("```");
|
||||
|
||||
// clear the initial data map
|
||||
ret.clear();
|
||||
|
||||
// upload the return the data
|
||||
return PasteUtils.paste("LuckPerms Permission Tree", ImmutableList.of(Maps.immutableEntry("luckperms-tree.md", builder.build().stream().collect(Collectors.joining("\n")))));
|
||||
}
|
||||
|
||||
@ -123,52 +249,9 @@ public class TreeView {
|
||||
.add("### Metadata")
|
||||
.add("| Selection | Max Recursion | Reference User | Size | Produced at |")
|
||||
.add("|-----------|---------------|----------------|------|-------------|")
|
||||
.add("| " + selection + " | " + maxLevels + " | " + referenceUser + " | **" + size + "** | " + date + " |")
|
||||
.add("| " + selection + " | " + maxLevel + " | " + referenceUser + " | **" + size + "** | " + date + " |")
|
||||
.add("")
|
||||
.add("### Output");
|
||||
}
|
||||
|
||||
private Optional<TreeNode> findRoot(PermissionVault source) {
|
||||
TreeNode root = source.getRootNode();
|
||||
|
||||
if (rootPosition.equals(".")) {
|
||||
return Optional.of(root);
|
||||
}
|
||||
|
||||
List<String> parts = Splitter.on('.').omitEmptyStrings().splitToList(rootPosition);
|
||||
for (String part : parts) {
|
||||
|
||||
if (!root.getChildren().isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Map<String, TreeNode> branch = root.getChildren().get();
|
||||
|
||||
root = branch.get(part);
|
||||
if (root == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.of(root);
|
||||
}
|
||||
|
||||
private List<Map.Entry<String, String>> asTreeList() {
|
||||
String prefix = rootPosition.equals(".") ? "" : (rootPosition + ".");
|
||||
List<Map.Entry<String, String>> ret = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<Integer, String> s : view.getNodeEndings()) {
|
||||
if (s.getKey() >= maxLevels) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String treeStructure = Strings.repeat("│ ", s.getKey()) + "├── ";
|
||||
String node = prefix + s.getValue();
|
||||
|
||||
ret.add(Maps.immutableEntry(treeStructure, node));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ package me.lucko.luckperms.common.treeview;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* Builds a {@link TreeView}.
|
||||
*/
|
||||
@Accessors(fluent = true)
|
||||
public class TreeViewBuilder {
|
||||
public static TreeViewBuilder newBuilder() {
|
||||
|
@ -30,12 +30,26 @@ import lombok.Getter;
|
||||
|
||||
import me.lucko.luckperms.api.Tristate;
|
||||
|
||||
/**
|
||||
* Holds the data from a permission check
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class CheckData {
|
||||
|
||||
private final String checked;
|
||||
private final String node;
|
||||
private final Tristate value;
|
||||
/**
|
||||
* The name of the entity which was checked
|
||||
*/
|
||||
private final String checkTarget;
|
||||
|
||||
/**
|
||||
* The permission which was checked for
|
||||
*/
|
||||
private final String permission;
|
||||
|
||||
/**
|
||||
* The result of the permission check
|
||||
*/
|
||||
private final Tristate result;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,141 @@
|
||||
package me.lucko.luckperms.common.verbose;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import me.lucko.luckperms.common.utils.Scripting;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
/**
|
||||
* Tests verbose filters
|
||||
*/
|
||||
@UtilityClass
|
||||
public class VerboseFilter {
|
||||
|
||||
/**
|
||||
* Evaluates whether the passed check data passes the filter
|
||||
*
|
||||
* @param data the check data
|
||||
* @param filter the filter
|
||||
* @return if the check data passes the filter
|
||||
*/
|
||||
public static boolean passesFilter(CheckData data, String filter) {
|
||||
if (filter.equals("")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the script engine
|
||||
ScriptEngine engine = Scripting.getScriptEngine();
|
||||
if (engine == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// tokenize the filter
|
||||
StringTokenizer tokenizer = new StringTokenizer(filter, " |&()!", true);
|
||||
|
||||
// build an expression which can be evaluated by the javascript engine
|
||||
StringBuilder expressionBuilder = new StringBuilder();
|
||||
|
||||
// read the tokens
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
|
||||
// if the token is a delimiter, just append it to the expression
|
||||
if (isDelim(token)) {
|
||||
expressionBuilder.append(token);
|
||||
|
||||
} else {
|
||||
|
||||
// if the token is not a delimiter, it must be a string.
|
||||
// we replace non-delimiters with a boolean depending on if the string matches the check data.
|
||||
boolean value = data.getCheckTarget().equalsIgnoreCase(token) ||
|
||||
data.getPermission().toLowerCase().startsWith(token.toLowerCase()) ||
|
||||
data.getResult().name().equalsIgnoreCase(token);
|
||||
|
||||
expressionBuilder.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
// build the expression
|
||||
String expression = expressionBuilder.toString().replace("&", "&&").replace("|", "||");
|
||||
|
||||
// evaluate the expression using the script engine
|
||||
try {
|
||||
String result = engine.eval(expression).toString();
|
||||
if (!result.equals("true") && !result.equals("false")) {
|
||||
throw new IllegalArgumentException(expression + " - " + result);
|
||||
}
|
||||
|
||||
return Boolean.parseBoolean(result);
|
||||
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether a filter is valid
|
||||
*
|
||||
* @param filter the filter to test
|
||||
* @return true if the filter is valid
|
||||
*/
|
||||
public static boolean isValidFilter(String filter) {
|
||||
if (filter.equals("")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the script engine
|
||||
ScriptEngine engine = Scripting.getScriptEngine();
|
||||
if (engine == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// tokenize the filter
|
||||
StringTokenizer tokenizer = new StringTokenizer(filter, " |&()!", true);
|
||||
|
||||
// build an expression which can be evaluated by the javascript engine
|
||||
StringBuilder expressionBuilder = new StringBuilder();
|
||||
|
||||
// read the tokens
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
|
||||
// if the token is a delimiter, just append it to the expression
|
||||
if (isDelim(token)) {
|
||||
expressionBuilder.append(token);
|
||||
} else {
|
||||
expressionBuilder.append("true"); // dummy result
|
||||
}
|
||||
}
|
||||
|
||||
// build the expression
|
||||
String expression = expressionBuilder.toString().replace("&", "&&").replace("|", "||");
|
||||
|
||||
// evaluate the expression using the script engine
|
||||
try {
|
||||
String result = engine.eval(expression).toString();
|
||||
if (!result.equals("true") && !result.equals("false")) {
|
||||
throw new IllegalArgumentException(expression + " - " + result);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDelim(String token) {
|
||||
return token.equals(" ") ||
|
||||
token.equals("|") ||
|
||||
token.equals("&") ||
|
||||
token.equals("(") ||
|
||||
token.equals(")") ||
|
||||
token.equals("!");
|
||||
}
|
||||
|
||||
}
|
@ -37,13 +37,22 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Accepts {@link CheckData} and passes it onto registered {@link VerboseListener}s.
|
||||
*/
|
||||
public class VerboseHandler implements Runnable {
|
||||
private final String pluginVersion;
|
||||
|
||||
// the listeners currently registered
|
||||
private final Map<UUID, VerboseListener> listeners;
|
||||
|
||||
// a queue of check data
|
||||
private final Queue<CheckData> queue;
|
||||
|
||||
// if there are any listeners currently registered
|
||||
private boolean listening = false;
|
||||
|
||||
// if the handler should shutdown
|
||||
@Setter
|
||||
private boolean shutdown = false;
|
||||
|
||||
@ -55,31 +64,67 @@ public class VerboseHandler implements Runnable {
|
||||
executor.execute(this);
|
||||
}
|
||||
|
||||
public void offer(String checked, String node, Tristate value) {
|
||||
/**
|
||||
* Offers check data to the handler, to be eventually passed onto listeners.
|
||||
*
|
||||
* <p>The check data is added to a queue to be processed later, to avoid blocking
|
||||
* the main thread each time a permission check is made.</p>
|
||||
*
|
||||
* @param checkTarget the target of the permission check
|
||||
* @param permission the permission which was checked for
|
||||
* @param result the result of the permission check
|
||||
*/
|
||||
public void offerCheckData(String checkTarget, String permission, Tristate result) {
|
||||
// don't bother even processing the check if there are no listeners registered
|
||||
if (!listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
queue.offer(new CheckData(checked, node, value));
|
||||
// add the check data to a queue to be processed later.
|
||||
queue.offer(new CheckData(checkTarget, permission, result));
|
||||
}
|
||||
|
||||
public void register(Sender sender, String filter, boolean notify) {
|
||||
/**
|
||||
* Registers a new listener for the given player.
|
||||
*
|
||||
* @param sender the sender to notify, if notify is true
|
||||
* @param filter the filter string
|
||||
* @param notify if the sender should be notified in chat on each check
|
||||
*/
|
||||
public void registerListener(Sender sender, String filter, boolean notify) {
|
||||
listening = true;
|
||||
listeners.put(sender.getUuid(), new VerboseListener(pluginVersion, sender, filter, notify));
|
||||
}
|
||||
|
||||
public VerboseListener unregister(UUID uuid) {
|
||||
/**
|
||||
* Removes a listener for a given player
|
||||
*
|
||||
* @param uuid the players uuid
|
||||
* @return the existing listener, if one was actually registered
|
||||
*/
|
||||
public VerboseListener unregisterListener(UUID uuid) {
|
||||
// immediately flush, so the listener gets all current data
|
||||
flush();
|
||||
|
||||
VerboseListener ret = listeners.remove(uuid);
|
||||
|
||||
// stop listening if there are no listeners left
|
||||
if (listeners.isEmpty()) {
|
||||
listening = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
// remove listeners where the sender is no longer valid
|
||||
listeners.values().removeIf(l -> !l.getNotifiedSender().isValid());
|
||||
if (listeners.isEmpty()) {
|
||||
listening = false;
|
||||
}
|
||||
|
||||
flush();
|
||||
|
||||
if (shutdown) {
|
||||
@ -92,6 +137,9 @@ public class VerboseHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the current check data to the listeners.
|
||||
*/
|
||||
public synchronized void flush() {
|
||||
for (CheckData e; (e = queue.poll()) != null; ) {
|
||||
for (VerboseListener listener : listeners.values()) {
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package me.lucko.luckperms.common.verbose;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -35,49 +36,59 @@ import me.lucko.luckperms.common.commands.sender.Sender;
|
||||
import me.lucko.luckperms.common.locale.Message;
|
||||
import me.lucko.luckperms.common.utils.DateUtil;
|
||||
import me.lucko.luckperms.common.utils.PasteUtils;
|
||||
import me.lucko.luckperms.common.utils.Scripting;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
/**
|
||||
* Accepts and processes {@link CheckData}, passed from the {@link VerboseHandler}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class VerboseListener {
|
||||
private static final int DATA_TRUNCATION = 10000;
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
|
||||
private static final Function<Tristate, String> TRISTATE_COLOR = tristate -> {
|
||||
switch (tristate) {
|
||||
case TRUE:
|
||||
return "&2";
|
||||
case FALSE:
|
||||
return "&c";
|
||||
default:
|
||||
return "&7";
|
||||
}
|
||||
};
|
||||
|
||||
// how much data should we store before stopping.
|
||||
private static final int DATA_TRUNCATION = 10000;
|
||||
|
||||
// the time when the listener was first registered
|
||||
private final long startTime = System.currentTimeMillis();
|
||||
|
||||
// the version of the plugin. (used when we paste data to gist)
|
||||
private final String pluginVersion;
|
||||
private final Sender holder;
|
||||
|
||||
// the sender to notify each time the listener processes a check which passes the filter
|
||||
@Getter
|
||||
private final Sender notifiedSender;
|
||||
|
||||
// the filter string
|
||||
private final String filter;
|
||||
|
||||
// if we should notify the sender
|
||||
private final boolean notify;
|
||||
|
||||
// the number of checks we have processed
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
private final AtomicInteger matchedCounter = new AtomicInteger(0);
|
||||
private final List<CheckData> results = new ArrayList<>();
|
||||
|
||||
// the number of checks we have processed and accepted, based on the filter rules for this
|
||||
// listener
|
||||
private final AtomicInteger matchedCounter = new AtomicInteger(0);
|
||||
|
||||
// the checks which passed the filter, up to a max size of #DATA_TRUNCATION
|
||||
private final List<CheckData> results = new ArrayList<>(DATA_TRUNCATION / 10);
|
||||
|
||||
/**
|
||||
* Accepts and processes check data.
|
||||
*
|
||||
* @param data the data to process
|
||||
*/
|
||||
public void acceptData(CheckData data) {
|
||||
counter.incrementAndGet();
|
||||
if (!matches(data, filter)) {
|
||||
if (!VerboseFilter.passesFilter(data, filter)) {
|
||||
return;
|
||||
}
|
||||
matchedCounter.incrementAndGet();
|
||||
@ -87,98 +98,23 @@ public class VerboseListener {
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
Message.VERBOSE_LOG.send(holder, "&a" + data.getChecked() + "&7 -- &a" + data.getNode() + "&7 -- " + TRISTATE_COLOR.apply(data.getValue()) + data.getValue().name().toLowerCase() + "");
|
||||
Message.VERBOSE_LOG.send(notifiedSender, "&a" + data.getCheckTarget() + "&7 -- &a" + data.getPermission() + "&7 -- " + getTristateColor(data.getResult()) + data.getResult().name().toLowerCase() + "");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean matches(CheckData data, String filter) {
|
||||
if (filter.equals("")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ScriptEngine engine = Scripting.getScriptEngine();
|
||||
if (engine == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringTokenizer tokenizer = new StringTokenizer(filter, " |&()!", true);
|
||||
StringBuilder expression = new StringBuilder();
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
if (!isDelim(token)) {
|
||||
boolean b = data.getChecked().equalsIgnoreCase(token) ||
|
||||
data.getNode().toLowerCase().startsWith(token.toLowerCase()) ||
|
||||
data.getValue().name().equalsIgnoreCase(token);
|
||||
|
||||
token = "" + b;
|
||||
}
|
||||
|
||||
expression.append(token);
|
||||
}
|
||||
|
||||
try {
|
||||
String exp = expression.toString().replace("&", "&&").replace("|", "||");
|
||||
String result = engine.eval(exp).toString();
|
||||
if (!result.equals("true") && !result.equals("false")) {
|
||||
throw new IllegalArgumentException(exp + " - " + result);
|
||||
}
|
||||
|
||||
return Boolean.parseBoolean(result);
|
||||
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isValidFilter(String filter) {
|
||||
if (filter.equals("")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ScriptEngine engine = Scripting.getScriptEngine();
|
||||
if (engine == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringTokenizer tokenizer = new StringTokenizer(filter, " |&()!", true);
|
||||
StringBuilder expression = new StringBuilder();
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
if (!isDelim(token)) {
|
||||
token = "true"; // dummy result
|
||||
}
|
||||
|
||||
expression.append(token);
|
||||
}
|
||||
|
||||
try {
|
||||
String exp = expression.toString().replace("&", "&&").replace("|", "||");
|
||||
String result = engine.eval(exp).toString();
|
||||
if (!result.equals("true") && !result.equals("false")) {
|
||||
throw new IllegalArgumentException(exp + " - " + result);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDelim(String token) {
|
||||
return token.equals(" ") || token.equals("|") || token.equals("&") || token.equals("(") || token.equals(")") || token.equals("!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the captured data in this listener to a paste and returns the url
|
||||
*
|
||||
* @return the url
|
||||
* @see PasteUtils#paste(String, List)
|
||||
*/
|
||||
public String uploadPasteData() {
|
||||
long now = System.currentTimeMillis();
|
||||
String startDate = DATE_FORMAT.format(new Date(startTime));
|
||||
String endDate = DATE_FORMAT.format(new Date(now));
|
||||
long secondsTaken = (now - startTime) / 1000L;
|
||||
String duration = DateUtil.formatTime(secondsTaken);
|
||||
|
||||
String filter = this.filter;
|
||||
if (filter == null || filter.equals("")){
|
||||
filter = "any";
|
||||
@ -186,7 +122,7 @@ public class VerboseListener {
|
||||
filter = "`" + filter + "`";
|
||||
}
|
||||
|
||||
ImmutableList.Builder<String> output = ImmutableList.<String>builder()
|
||||
ImmutableList.Builder<String> prettyOutput = ImmutableList.<String>builder()
|
||||
.add("## Verbose Checking Output")
|
||||
.add("#### This file was automatically generated by [LuckPerms](https://github.com/lucko/LuckPerms) " + pluginVersion)
|
||||
.add("")
|
||||
@ -197,33 +133,33 @@ public class VerboseListener {
|
||||
.add("| End Time | " + endDate + " |")
|
||||
.add("| Duration | " + duration +" |")
|
||||
.add("| Count | **" + matchedCounter.get() + "** / " + counter + " |")
|
||||
.add("| User | " + holder.getName() + " |")
|
||||
.add("| User | " + notifiedSender.getName() + " |")
|
||||
.add("| Filter | " + filter + " |")
|
||||
.add("");
|
||||
|
||||
if (matchedCounter.get() > results.size()) {
|
||||
output.add("**WARN:** Result set exceeded max size of " + DATA_TRUNCATION + ". The output below was truncated to " + DATA_TRUNCATION + " entries.");
|
||||
output.add("");
|
||||
prettyOutput.add("**WARN:** Result set exceeded max size of " + DATA_TRUNCATION + ". The output below was truncated to " + DATA_TRUNCATION + " entries.");
|
||||
prettyOutput.add("");
|
||||
}
|
||||
|
||||
output.add("### Output")
|
||||
prettyOutput.add("### Output")
|
||||
.add("Format: `<checked>` `<permission>` `<value>`")
|
||||
.add("")
|
||||
.add("___")
|
||||
.add("");
|
||||
|
||||
ImmutableList.Builder<String> data = ImmutableList.<String>builder()
|
||||
ImmutableList.Builder<String> csvOutput = ImmutableList.<String>builder()
|
||||
.add("User,Permission,Result");
|
||||
|
||||
results.stream()
|
||||
.peek(c -> output.add("`" + c.getChecked() + "` - " + c.getNode() + " - **" + c.getValue().toString() + "** "))
|
||||
.forEach(c -> data.add(escapeCommas(c.getChecked()) + "," + escapeCommas(c.getNode()) + "," + c.getValue().name().toLowerCase()));
|
||||
|
||||
results.forEach(c -> {
|
||||
prettyOutput.add("`" + c.getCheckTarget() + "` - " + c.getPermission() + " - **" + c.getResult().toString() + "** ");
|
||||
csvOutput.add(escapeCommas(c.getCheckTarget()) + "," + escapeCommas(c.getPermission()) + "," + c.getResult().name().toLowerCase());
|
||||
});
|
||||
results.clear();
|
||||
|
||||
List<Map.Entry<String, String>> content = ImmutableList.of(
|
||||
Maps.immutableEntry("luckperms-verbose.md", output.build().stream().collect(Collectors.joining("\n"))),
|
||||
Maps.immutableEntry("raw-data.csv", data.build().stream().collect(Collectors.joining("\n")))
|
||||
Maps.immutableEntry("luckperms-verbose.md", prettyOutput.build().stream().collect(Collectors.joining("\n"))),
|
||||
Maps.immutableEntry("raw-data.csv", csvOutput.build().stream().collect(Collectors.joining("\n")))
|
||||
);
|
||||
|
||||
return PasteUtils.paste("LuckPerms Verbose Checking Output", content);
|
||||
@ -233,4 +169,15 @@ public class VerboseListener {
|
||||
return s.contains(",") ? "\"" + s + "\"" : s;
|
||||
}
|
||||
|
||||
private static String getTristateColor(Tristate tristate) {
|
||||
switch (tristate) {
|
||||
case TRUE:
|
||||
return "&2";
|
||||
case FALSE:
|
||||
return "&c";
|
||||
default:
|
||||
return "&7";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ import me.lucko.luckperms.common.config.LuckPermsConfiguration;
|
||||
import me.lucko.luckperms.common.constants.CommandPermission;
|
||||
import me.lucko.luckperms.common.contexts.ContextManager;
|
||||
import me.lucko.luckperms.common.contexts.LuckPermsCalculator;
|
||||
import me.lucko.luckperms.common.data.ImporterSender;
|
||||
import me.lucko.luckperms.common.dependencies.DependencyManager;
|
||||
import me.lucko.luckperms.common.locale.LocaleManager;
|
||||
import me.lucko.luckperms.common.locale.NoopLocaleManager;
|
||||
@ -66,7 +67,6 @@ import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
|
||||
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
|
||||
import me.lucko.luckperms.common.tasks.UpdateTask;
|
||||
import me.lucko.luckperms.common.treeview.PermissionVault;
|
||||
import me.lucko.luckperms.common.utils.FakeSender;
|
||||
import me.lucko.luckperms.common.utils.UuidCache;
|
||||
import me.lucko.luckperms.common.verbose.VerboseHandler;
|
||||
import me.lucko.luckperms.sponge.calculators.SpongeCalculatorFactory;
|
||||
@ -497,7 +497,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
|
||||
@Override
|
||||
public Sender getConsoleSender() {
|
||||
if (!game.isServerAvailable()) {
|
||||
return new FakeSender(this, s -> logger.info(s));
|
||||
return new ImporterSender(this, s -> logger.info(s));
|
||||
}
|
||||
return getSenderFactory().wrap(game.getServer().getConsole());
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@EqualsAndHashCode(of = {"id", "description", "owner"})
|
||||
@EqualsAndHashCode(of = "id")
|
||||
@ToString(of = {"id", "description", "owner"})
|
||||
public final class LuckPermsPermissionDescription implements LPPermissionDescription {
|
||||
|
||||
|
@ -73,6 +73,7 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -202,7 +203,19 @@ public class LuckPermsService implements LPPermissionService {
|
||||
|
||||
@Override
|
||||
public ImmutableSet<LPPermissionDescription> getDescriptions() {
|
||||
return ImmutableSet.copyOf(descriptionSet);
|
||||
Set<LPPermissionDescription> descriptions = new HashSet<>(descriptionSet);
|
||||
|
||||
// collect known values from the permission vault
|
||||
for (String knownPermission : plugin.getPermissionVault().getKnownPermissions()) {
|
||||
LPPermissionDescription desc = new LuckPermsPermissionDescription(this, knownPermission, null, null);
|
||||
|
||||
// don't override plugin defined values
|
||||
if (!descriptions.contains(desc)) {
|
||||
descriptions.add(desc);
|
||||
}
|
||||
}
|
||||
|
||||
return ImmutableSet.copyOf(descriptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -247,7 +247,7 @@ public class PersistedSubject implements LPSubject {
|
||||
public Tristate getPermissionValue(@NonNull ImmutableContextSet contexts, @NonNull String node) {
|
||||
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_PERMISSION_VALUE)) {
|
||||
Tristate t = permissionLookupCache.get(PermissionLookup.of(node, contexts));
|
||||
service.getPlugin().getVerboseHandler().offer("local:" + getParentCollection().getIdentifier() + "/" + identifier, node, t);
|
||||
service.getPlugin().getVerboseHandler().offerCheckData("local:" + getParentCollection().getIdentifier() + "/" + identifier, node, t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user