Compare commits

..
108 Commits
Author SHA1 Message Date
Luck 63878166db Release 5.2 2020-10-14 12:18:50 +01:00
Luck 2ea43d4b2e Some misc refactoring and cleanup 2020-09-10 21:13:12 +01:00
Luck 442c7d46f9 Ensure that player data is removed even if a corresponding user doesn't exist 2020-09-07 18:16:25 +01:00
Luck 971bb67b51 Add support for user deletion from the web editor 2020-09-07 18:13:33 +01:00
Luck 020aff66aa Refactor web editor session handling 2020-09-07 18:06:40 +01:00
Luck 198b86d7c3 Implement the basis for deleting player uuid data from storage 2020-09-07 17:22:42 +01:00
Luck 84e7fd4d09 Update usage string for /lp import 2020-09-07 16:55:08 +01:00
Luck 4027129052 Default to merge when using /lp import (#2601) 2020-09-07 16:54:27 +01:00
Luck 63f67ca72c Default use-vault-server to false (#2599) 2020-09-07 16:51:07 +01:00
Luck 2adaea2dac Avoid calling JsonObject#size for compat with older gson versions (#2598) 2020-09-05 22:00:56 +01:00
Luck 5e48224c78 Use a map factory when resolving permissions for cached data 2020-08-30 09:14:33 +01:00
Luck cc1d8984da Config option for registering brigadier command list data 2020-08-30 09:13:59 +01:00
Luck 8d9b4182f4 Modify issue templates slightly 2020-08-29 18:37:21 +01:00
Luck f972113d11 Add GitHub issue templates
Credit to @Andre601 and PR #1735 for the original proposal of these templates.

I've held off from adding this for a while, but I now feel that the GitHub UI is nice enough to justify the change.
2020-08-29 18:32:39 +01:00
Luck 4d5a24f2c4 ContextSetJsonSerializer output should be sorted 2020-08-14 16:25:55 +01:00
Luck e2b575dd24 Handle null or empty server/world field in SQL (#2553) 2020-08-13 11:40:33 +01:00
Luck 7fae141e33 Ignore cancelled events for OP command disabling (#2527) 2020-08-13 11:03:24 +01:00
Luck 6f90e545fc Don't attempt further poll operations after SQL messenger has closed (#2516) 2020-08-13 10:49:18 +01:00
Luck ee3bb22c2c Improve Bukkit identifyClassLoader (#2444) 2020-08-13 10:31:20 +01:00
Luck 361a4d66bc Refactor exporter into separate classes 2020-08-13 10:12:36 +01:00
Luck e1bac438aa Add /lp export --without-groups (#2498) 2020-08-13 10:00:49 +01:00
Luck ac9706b83f Ignore FileAlreadyExistsException in MoreFiles#createDirectories (#2525) 2020-08-10 22:29:12 +01:00
Luck fea757887c Use official configurate 3.7 branch 2020-08-01 11:08:46 +01:00
Luck 1eab127f50 Refactor op command detection 2020-08-01 10:20:49 +01:00
Luck 92e008ff0a Fix bug in PermissionHolder#hasNode (#2490)
Regression introduced in bd6d3ab7c0
2020-07-26 14:14:41 +01:00
Luck 9659333d0e Some misc cleanup for migration commands 2020-07-25 16:56:52 +01:00
Larry d8020e77fe Fix import with no users object (#2488, #2489) 2020-07-24 11:43:49 +01:00
Luck 232d17436c Rename config based static calculator 2020-07-21 11:26:16 +01:00
Luck 1461651e22 Include a wider range of potential contexts in web editor payload (#2470) 2020-07-21 11:25:50 +01:00
Luck ef556f7cf7 Pre-generate event classes (#2463) 2020-07-16 21:00:17 +01:00
Luck 47ca6b162b Catch throwable instead of exception when handling hikari errors (#2444) 2020-07-16 20:26:54 +01:00
Luck f72a5cb6b5 Ensure group/track names are always lowercase'd when created via the API (#2440) 2020-07-16 20:19:33 +01:00
Luck cd0fd80b33 Impose 15 second timeout on all http calls (#2436) 2020-07-16 20:15:53 +01:00
Sam Goodger 0fd7f643a7 Add option to use bytebin for exports/imports (#2432) 2020-07-04 20:41:34 +01:00
Mark 6c7c1b67b8 Try to reopen Redis pub-sub connection on failure (#2430) 2020-07-04 20:08:58 +01:00
Luck 4514a17eaf Remove MRUCache
Now we're using ConcurrentHashMap, it's usefulness in terms of performance is debatable
2020-07-03 18:23:34 +01:00
Luck adbd2fc81f Use ConcurrentHashMap instead of Caffeine in CachedDataManager
The behaviour of Caffeine cache invalidation calls is "undefined for an entry that is being loaded (or reloaded)" - this causes a nasty race condition in CachedDataManager, because we rely on the call to #invalidate to completely clear the cache and prevent old/outdated data from sticking around & being used for permission queries.

This is an unfortunate characteristic of Caffeine, because other than that, it is perfect for our use-case.
2020-07-03 17:57:53 +01:00
Luck 8b506b6a95 Don't re-use PermissionCache instances 2020-07-03 15:18:58 +01:00
Luck 426c8565fd Small optimization to processExportedPermissions 2020-07-03 14:57:46 +01:00
Luck bd6d3ab7c0 Optimize PermissionHolder#hasNode 2020-07-02 12:28:30 +01:00
Luck d12d13d6ea Fix NodeMap#size method and rename some things 2020-07-02 12:16:13 +01:00
Luck cc08390abe Remove NodeMap immutable cache 2020-07-02 12:06:37 +01:00
Luck b460e282b1 Add ConstraintNodeMatcher toString method for use in commands 2020-07-02 10:44:52 +01:00
Tobi 005d19f2a3 Fix JavaDoc typo (#2421) 2020-07-02 10:42:37 +01:00
Jonas Baars 8817e17d2c Fix typo in config file 2020-06-29 15:17:42 +01:00
Luck 0f23b7a460 Avoid calling #stream on the result of Bukkit getOnlinePlayers 2020-06-25 10:24:33 +01:00
Luck 21ae1dbac0 Fix potential race condition in MRUCache 2020-06-21 22:07:53 +01:00
Luck 6cb0f5e810 Use a non-async cache in CachedDataManager 2020-06-21 21:23:57 +01:00
Luck 2ac7d3dfe6 Refactor command argument parsing 2020-06-21 19:58:17 +01:00
Luck bfaa543ecd Fix NPE in ConfigurateConfigAdapter#getStringList (#2399) 2020-06-21 10:18:27 +01:00
Luck f893b1c632 Add optional time parameters to permission/parent removetemp commands (#1384) 2020-06-20 22:01:27 +01:00
Luck cb5a28f043 Use the only/default track if one is not specifed in the promote/demote command (#1581) 2020-06-20 21:03:01 +01:00
Luck bd9718c5af Add log notification filters (#1980) 2020-06-20 20:46:54 +01:00
Luck 97a0dbebcb Add DataTypeFilter#NONE 2020-06-13 21:07:59 +01:00
Luck df324de9ec Add DataTypeFilter 2020-06-12 23:14:33 +01:00
Luck 6fa690a49d Update API pom description 2020-06-12 20:36:41 +01:00
Luck 2bb1aa7511 Change behaviour of resolveInheritanceTree when 'resolve inheritance' flag is false 2020-06-12 20:34:57 +01:00
Luck 0bef0902c0 Update some dependencies 2020-06-10 20:41:39 +01:00
Luck f10f60e27f Define dependency on Vault during enable (#1959) 2020-06-10 20:24:11 +01:00
Luck 91337b49ea Just use the section char instead of some other random one
Idk wtf I was thinking
2020-06-08 23:47:10 +01:00
Luck f5e41f7e7e Cleanup chat meta commands 2020-06-08 23:44:25 +01:00
Luck fe0e82f285 Verbose command shouldn't be able to run as console 2020-06-04 14:46:11 +01:00
Luck 8996cf02af Add player argument to'/lp verbose command' to execute the test command as another player 2020-06-04 01:32:56 +01:00
Luck e37c77a1be Add '/lp verbose command <command>' for easy testing of command permissions 2020-06-03 22:32:24 +01:00
Luck 11998b2dd6 Some misc cleanup 2020-06-03 21:20:41 +01:00
Luck 8754123535 Fix parsing second optional argument in editor command 2020-05-30 02:39:37 +01:00
Luck 91d75424f4 Include users which inherit the group when running /lp group <group> editor 2020-05-30 02:13:18 +01:00
Luck c5c99abd16 Improve argument based perm checks for the editor 2020-05-30 01:54:18 +01:00
Luck 6c88f7629e Add optional filter argument to editor command 2020-05-30 01:47:31 +01:00
Luck 98c71739f8 Fix user ordering in the editor 2020-05-30 01:37:24 +01:00
Luck 25551641fc Add option to merge into instead of overriding existing data in imports (#2324) 2020-05-28 21:36:06 +01:00
Luck 00b701f129 Improve undo command generation (#2318) 2020-05-28 20:53:07 +01:00
Luck ab082aa311 Group names should always be lowercase in inheritance nodes 2020-05-28 00:49:46 +01:00
Joshua Schmitt dc32c84bb3 Increase world column max length (#2334) 2020-05-27 14:02:53 +01:00
Luck 0bb4161397 Use nicer toString for Durations in log messages 2020-05-27 10:46:24 +01:00
Luck 4a049ca732 Use contains instead of startsWith for context tab completions 2020-05-27 10:43:30 +01:00
Luck d5ac45d230 Formalize more of the contexts spec 2020-05-25 22:46:26 +01:00
Luck 6b3e4bdc71 Add ContextUpdateEvent and related functionality 2020-05-25 21:49:49 +01:00
Luck 5c0d82f306 Refactor config handler classes 2020-05-25 10:42:32 +01:00
Luck 5a6176def5 Enforce removal of server=global and world=global contexts at a higher level 2020-05-21 17:30:13 +01:00
Luck 13ebc87b29 Fix compatibility with modified SimplePluginManager in Mohist fork
https://github.com/Mohist-Community/Mohist/blob/eab0f722a48759152f5d562b39689317f647ce99/src/main/java/org/bukkit/plugin/SimplePluginManager.java#L674
2020-05-20 21:37:07 +01:00
Luck 1ca1bcf1d1 Make getUniqueUsers more resilient to invalid uuids 2020-05-20 21:23:40 +01:00
Luck ea5a05d770 Fix incorrectly reading node value from the value field when reading from the flatfile meta section (#2312) 2020-05-20 18:22:10 +01:00
Luck ed85ab1bfd Change ContextSet 'satisfy' behaviour (#2300) 2020-05-20 14:51:03 +01:00
Luck c09e4a1aa0 Update web app urls 2020-05-20 14:45:20 +01:00
Luck ee36fa9d81 Fix prefix being displayed twice in extensions info section 2020-05-19 17:47:07 +01:00
Luck bfa5fc43cd Rename getWithPermission method internally 2020-05-19 17:45:16 +01:00
Luck 5d6389249a Fix bug with searching for group permissions via the api 2020-05-19 13:32:06 +01:00
Luck 637cb0d836 Fix group info command 2020-05-19 09:55:00 +01:00
Luck bfebe6a5d0 Cleanup MetaStack, cache the computed prefix/suffix when meta accumulator completes 2020-05-19 00:36:39 +01:00
Luck 4cbbbd390f Don't schedule command list updates if the server is stopping (#2301) 2020-05-16 18:19:05 +01:00
Luck c120fa6a4d Fix startup on Nukkit (#2290) 2020-05-16 11:52:51 +01:00
Luck 918ea7503a Fix bug with duplicated users in editor (lucko/LuckPermsWeb#235) 2020-05-13 11:20:11 +01:00
Luck dbe07827bd Update readme 2020-05-12 23:12:53 +01:00
Luck 98eb756951 Add contextual data section to group info command too 2020-05-12 18:34:07 +01:00
Luck 86eeeb5b94 Slightly change plugin prefix 2020-05-12 17:54:06 +01:00
Luck a3e6628e11 Improve user info command 2020-05-12 16:54:02 +01:00
Luck 84bc4217de Fix command arg rewriting 2020-05-12 16:35:43 +01:00
Luck b88819b9bb Cleanup Bukkit/Nukkit dummy classes, some aren't necessary anymore 2020-05-11 21:58:24 +01:00
Luck c7a0e59919 Catch RejectedExecutionException in BufferedRequest (#2289) 2020-05-11 21:15:42 +01:00
Luck 9984d4be42 Optimize ActionLog and Paginated util 2020-05-11 21:00:42 +01:00
Luck a1c91b9007 Improve sync task performance 2020-05-11 13:46:02 +01:00
Luck 6a256d59f5 Fix null check in selectAllGroupPermissions 2020-05-11 13:12:30 +01:00
Luck fca2f1e6de Optimize loading lots of groups from SQL storage types (#2287) 2020-05-11 12:54:42 +01:00
Luck ec7994a561 Print more useful debug message when a LinkageError is thrown due to bad SLF4J classloading
This is a longstanding issue, mostly caused by plugins with bad Maven shade configurations.

https://github.com/lucko/LuckPerms/issues?q=slf4j
2020-05-11 01:41:01 +01:00
Luck 2839c36ea2 Fix NPE in Nukkit disconnect handling (#2269) 2020-05-10 22:24:47 +01:00
Luck 9f22bf2fbf Update the client commands list when permissions are changed (#2262) 2020-05-10 22:18:01 +01:00
Luck 5f0df1b167 Increase editor users limit to 1000 & sort users according to their max inherited group weight 2020-05-10 16:31:35 +01:00
314 changed files with 6238 additions and 4091 deletions

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
---
name: 🐛 ​ Bug report
about: Report an issue or problem with the way LuckPerms is functioning.
title: ''
labels: 'type: issue'
assignees: ''
---
<!-- Before going any further, please check to see if your problem is fixed by updating to a newer version. The latest development builds can be obtained from https://luckperms.net/download -->
### Description
<!-- Please provide a short description of the issue in the space below. -->
### Reproduction steps
<!-- Please provide a clear list of steps we should follow in order to reproduce your issue in the space below. -->
1. Open the config.yml file and set example to true.
2. Restart the server
3. Run /lp user example permission set x from the console
4. See error
### Expected behaviour
<!-- Please provide a description of what you expected to happen. -->
### Environment details
<!-- Please provide details of your server environment in the space below. -->
* Server type/version: `ExampleSpigot` running version `1.12.2` build `???`
* LuckPerms version: `v???`
<!-- If you think it would be helpful, please also include a "pastebin" of any relevant config files or log output. You should use https://gist.github.com/ to upload, then include the link below. -->
### Any other relevant details
<!-- Please include any other relevant details in the space below. -->
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 💬 ​ Ask a question
url: https://discord.gg/luckperms
about: We use Discord to provide support for the project. If you have a question, need help, or aren't sure if you've found a bug or not, please come and talk to us there!
+16
View File
@@ -0,0 +1,16 @@
---
name: 💡 ​ Suggestion
about: Suggest an idea for an improvement or change to LuckPerms.
title: ''
labels: 'type: suggestion'
assignees: ''
---
### Description
<!-- Please provide a short description of the suggestion in the space below. -->
### Proposed behaviour
<!-- Please provide a short explanation of how the feature should work / be changed, and how this will effect the project. -->
+11 -7
View File
@@ -5,17 +5,21 @@
[![Maven Central](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/net/luckperms/api/maven-metadata.xml.svg?label=maven%20central&colorB=brightgreen)](https://search.maven.org/artifact/net.luckperms/api)
[![Discord](https://img.shields.io/discord/241667244927483904.svg?logo=discord&label=)](https://discord.gg/luckperms)
LuckPerms is an advanced permissions implementation aiming to be a fast, reliable and flexible alternative to existing permission plugins. The project's main goals are centered around high performance and a wide feature set, filling the gaps of functionality and building upon existing features found in other plugins.
LuckPerms is a permissions plugin for Minecraft servers. It allows server admins to control what features players can use by creating groups and assigning permissions.
LuckPerms also includes an extensive API for developers, and support for a variety of Minecraft server software & data storage options.
It is:
Still not convinced? More information can be found on the wiki, under: [Why LuckPerms?](https://github.com/lucko/LuckPerms/wiki/Why-LuckPerms)
* **fast** - written with performance and scalability in mind.
* **reliable** - trusted by thousands of server admins, and the largest of server networks.
* **easy to use** - setup permissions using commands, directly in config files, or using the web editor.
* **flexible** - supports a variety of data storage options, and works on lots of different server types.
* **extensive** - a plethora of customization options and settings which can be changed to suit your server.
* **free** - available for download and usage at no cost, and permissively licensed so it can remain free forever.
For more information, see the wiki article on [Why LuckPerms?](https://github.com/lucko/LuckPerms/wiki/Why-LuckPerms)
## Useful Links
The latest **downloads** can be found on the [project's homepage](https://luckperms.net/).
More information about the project & how to use the plugin can be found in the [wiki](https://github.com/lucko/LuckPerms/wiki).
The latest downloads & other useful links can be found on the project homepage at [luckperms.net](https://luckperms.net/).
## Building
LuckPerms uses Gradle to handle dependencies & building.
+2 -2
View File
@@ -1,5 +1,5 @@
group = 'net.luckperms'
project.version = '5.1'
project.version = '5.2'
dependencies {
compileOnly 'org.checkerframework:checker-qual:2.5.5'
@@ -56,7 +56,7 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass
pom {
project {
name 'LuckPerms API'
description 'An advanced permissions plugin for Bukkit/Spigot, BungeeCord, Sponge, Nukkit and Velocity.'
description 'A permissions plugin for Minecraft servers.'
url 'https://luckperms.net'
licenses {
@@ -34,6 +34,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* <p>Context keys and values may not be null or empty. A key/value will be
* deemed empty if it's length is zero, or if it consists of only space
* characters.</p>
*
* @see ContextSet
*/
public interface Context {
@@ -138,6 +138,18 @@ public interface ContextManager {
*/
void unregisterCalculator(@NonNull ContextCalculator<?> calculator);
/**
* Signal to the {@link ContextManager} that a {@code subject}s
* current contexts have changed.
*
* <p>It is not strictly necessary to make a call to this method every time a context
* changes.</p>
*
* @param subject the subject
* @since 5.2
*/
void signalContextUpdate(@NonNull Object subject);
/**
* Gets the {@link ContextSetFactory}, responsible for creating
* {@link ContextSet} instances.
@@ -150,7 +162,11 @@ public interface ContextManager {
* Invalidates the lookup cache for a given subject
*
* @param subject the subject
* @deprecated use {@link #signalContextUpdate(Object)}
*/
void invalidateCache(@NonNull Object subject);
@Deprecated
default void invalidateCache(@NonNull Object subject) {
signalContextUpdate(subject);
}
}
@@ -0,0 +1,63 @@
/*
* 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 net.luckperms.api.context;
import net.luckperms.api.query.OptionKey;
/**
* Mode for determining whether a {@link ContextSet} satisfies another.
*
* @since 5.2
* @see ContextSet#isSatisfiedBy(ContextSet, ContextSatisfyMode)
*/
public enum ContextSatisfyMode {
/**
* Mode where a {@link ContextSet} A will be satisfied by another set B, if at least one of the
* key-value entries per key in A are also in B.
*
* <p>For example, given <code>A = {server=survival, world=overworld, world=nether}</code>,
* another set {@code X} will satisfy {@code A} if {@code X} contains
* <code>server=survival AND (world=overworld OR world=nether)</code>.</p>
*/
AT_LEAST_ONE_VALUE_PER_KEY,
/**
* Mode where a {@link ContextSet} A will be satisfied by another set B, if all key-value
* entries in A are also in B.
*
* <p>For example, given <code>A = {server=survival, world=overworld, world=nether}</code>,
* another set {@code X} will satisfy {@code A} if {@code X} contains
* <code>server=survival AND world=overworld AND world=nether</code>.</p>
*/
ALL_VALUES_PER_KEY;
/**
* The {@link OptionKey} for {@link ContextSatisfyMode}.
*/
public static final OptionKey<ContextSatisfyMode> KEY = OptionKey.of("contextsatisfymode", ContextSatisfyMode.class);
}
@@ -40,19 +40,33 @@ import java.util.Set;
* something will apply.</p>
*
* <p>A single "context" consists of a key and a value, both strings. The key
* represents the type of context, and the value represents the setting of the
* context key.</p>
* describes the type of the context, and the value represents what the key is
* set to.</p>
*
* <p>For example, a context with {@code key=world} and {@code value=world_nether}
* describes that a subject is in the "world_nether" world.</p>
*
* <p>Contexts are exposed to end users and manipulated when managing permissions.
* For this reason, context keys should strike a balance between being descriptive
* and succinct.</p>
*
* <p>Context keys and values are case-insensitive, and will be automatically
* converted to {@link String#toLowerCase() lowercase} when added to a
* context set. Keys and values cannot be null or empty (len=0 or consisting of
* only whitespace).</p>
*
* <p>If a context key is formed of more than one word, the parts should be
* separated by the '{@code -}' character. (e.g. "{@code server-type}")</p>
*
* <p>If a context key is likely to conflict with another plugin, it should be
* appropriately namespaced using the name of the plugin providing the context.
* The namespace should be at the start of the context key, and separated using
* the '{@code :}' character. (e.g. "{@code worldguard:region}" for WorldGuard
* regions)</p>
*
* <p>Contexts can be combined with each other to form so called
* "context sets" - simply a collection of context pairs.</p>
*
* <p>Context keys and values are case-insensitive, and will be converted to
* {@link String#toLowerCase() lowercase} by all implementations.</p>
*
* <p>Context keys and values may not be null or empty. A key/value will be
* deemed empty if it's length is zero, or if it consists of only space
* characters.</p>
*
* <p>Two default ContextSet implementations are provided.
* {@link MutableContextSet} allows the addition and removal of context keys
* after construction, and {@link ImmutableContextSet} does not.</p>
@@ -197,17 +211,47 @@ public interface ContextSet extends Iterable<Context> {
}
/**
* Returns if this {@link ContextSet} is fully "satisfied" by another set.
* Returns if the {@link ContextSet} contains any of the given context pairings.
*
* <p>For a context set to "satisfy" another, it must itself contain all of
* the context pairings in the other set.</p>
*
* <p>Mathematically, this method returns true if this set is a <b>subset</b> of the other.</p>
*
* @param other the other set to check
* @return true if all entries in this set are also in the other set
* @param key the key to look for
* @param values the values to look for
* @return true if the set contains any of the pairs
* @since 5.2
*/
boolean isSatisfiedBy(@NonNull ContextSet other);
default boolean containsAny(@NonNull String key, @NonNull Iterable<String> values) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(values, "values");
for (String value : values) {
if (contains(key, value)) {
return true;
}
}
return false;
}
/**
* Returns if this {@link ContextSet} is "satisfied" by another set.
*
* <p>{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is the mode used by this method.</p>
*
* @param other the other set
* @return true if this context set is satisfied by the other
*/
default boolean isSatisfiedBy(@NonNull ContextSet other) {
return isSatisfiedBy(other, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY);
}
/**
* Returns if this {@link ContextSet} is "satisfied" by another set, according to the given
* {@code mode}.
*
* @param other the other set
* @param mode the mode to use
* @return true if this context set is satisfied by the other
* @since 5.2
*/
boolean isSatisfiedBy(@NonNull ContextSet other, @NonNull ContextSatisfyMode mode);
/**
* Returns if the {@link ContextSet} is empty.
@@ -0,0 +1,76 @@
/*
* 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 net.luckperms.api.event.context;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
/**
* Called when a subject's current/active contexts are updated.
*
* <p>There are no guarantees that this event will be called for every update. It is merely to be
* used as a "hint" for plugins which depend on the current active contexts for a player.</p>
*
* <p>It will always be fired following a call to
* {@link ContextManager#signalContextUpdate(Object)}.</p>
*
* <p>The {@link #getSubject() subject} is always an instance of the platform's subject type. See
* {@link ContextManager} for details.</p>
*
* <p>Unlike most other LuckPerms events, this event is not fired asynchronously. Care should be
* taken to ensure listeners are lightweight. Additionally, listeners should ensure they do not
* cause further updates to player context, thus possibly causing a stack overflow.</p>
*
* @since 5.2
*/
public interface ContextUpdateEvent extends LuckPermsEvent {
/**
* Gets the subject whose contexts were updated.
*
* @return the subject
*/
@Param(0)
@NonNull Object getSubject();
/**
* Gets the subject whose contexts were updated, casted to a given type.
*
* @param subjectClass the type to cast to
* @param <T> the subject type
* @return the casted subject
*/
default <T> @NonNull Optional<T> getSubject(@NonNull Class<T> subjectClass) {
Object subject = getSubject();
return subjectClass.isInstance(subject) ? Optional.of(subjectClass.cast(subject)) : Optional.empty();
}
}
@@ -33,6 +33,7 @@ import net.luckperms.api.model.group.Group;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -277,10 +278,14 @@ public interface PermissionHolder {
@NonNull SortedSet<Node> resolveDistinctInheritedNodes(@NonNull QueryOptions queryOptions);
/**
* Gets a collection of the {@link Group}s this holder inherits nodes from, both directly
* and indirectly (through directly inherited groups).
* Gets a collection of the {@link Group}s this holder inherits nodes from.
*
* <p>It effectively resolves the whole "inheritance tree".</p>
* <p>If {@link Flag#RESOLVE_INHERITANCE} is set, this will include holders inherited from both
* directly and indirectly (through directly inherited groups). It will effectively resolve the
* whole "inheritance tree".</p>
*
* <p>If {@link Flag#RESOLVE_INHERITANCE} is not set, then the traversal will only go one
* level up the inheritance tree, and return only directly inherited groups.</p>
*
* <p>The collection will be ordered according to the platforms inheritance rules. The groups
* which are inherited from first will appear earlier in the list.</p>
@@ -147,6 +147,17 @@ public interface UserManager {
*/
@NonNull CompletableFuture<PlayerSaveResult> savePlayerData(@NonNull UUID uniqueId, @NonNull String username);
/**
* Deletes any data about a given player from the uuid caching system.
*
* <p>Note that this method does not affect any saved user/permissions data.</p>
*
* @param uniqueId the users mojang unique id
* @return a future encapsulating the result of the operation
* @since 5.2
*/
@NonNull CompletableFuture<Void> deletePlayerData(@NonNull UUID uniqueId);
/**
* Gets a set all "unique" user UUIDs.
*
@@ -100,6 +100,7 @@ public interface PlayerAdapter<T> {
default @NonNull CachedPermissionData getPermissionData(@NonNull T player) {
return getUser(player).getCachedData().getPermissionData(getQueryOptions(player));
}
/**
* Gets the current {@link CachedMetaData} for the {@code player},
* using their {@link #getQueryOptions(Object) active query options}.
@@ -26,6 +26,7 @@
package net.luckperms.api.query;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.ImmutableContextSet;
@@ -166,10 +167,29 @@ public interface QueryOptions {
* Gets whether this {@link QueryOptions} satisfies the given required
* {@link ContextSet context}.
*
* <p>{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is used if this {@link QueryOptions}
* instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.</p>
*
* @param contextSet the contexts
* @return the result
*/
boolean satisfies(@NonNull ContextSet contextSet);
default boolean satisfies(@NonNull ContextSet contextSet) {
return satisfies(contextSet, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY);
}
/**
* Gets whether this {@link QueryOptions} satisfies the given required
* {@link ContextSet context}.
*
* <p>The {@code defaultContextSatisfyMode} parameter is used if this {@link QueryOptions}
* instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.</p>
*
* @param contextSet the contexts
* @param defaultContextSatisfyMode the default context satisfy mode to use
* @return the result
* @since 5.2
*/
boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode);
/**
* Converts this {@link QueryOptions} to a mutable builder.
@@ -38,7 +38,7 @@ public interface QueryOptionsRegistry {
* Gets the default {@link QueryMode#CONTEXTUAL contextual}
* query options.
*
* <p>Prefer using the {@link QueryOptions#defaultContextualOptions()} accessor.</p>>
* <p>Prefer using the {@link QueryOptions#defaultContextualOptions()} accessor.</p>
*
* @return the default contextual query options
* @see QueryOptions#defaultContextualOptions()
@@ -49,7 +49,7 @@ public interface QueryOptionsRegistry {
* Gets the default {@link QueryMode#NON_CONTEXTUAL non contextual}
* query options.
*
* <p>Prefer using the {@link QueryOptions#nonContextual()} accessor.</p>>
* <p>Prefer using the {@link QueryOptions#nonContextual()} accessor.</p>
*
* @return the default non contextual query options
* @see QueryOptions#nonContextual()
@@ -48,6 +48,8 @@ import java.util.function.Consumer;
* it is possible that more {@link DataType}s may be added in the future.
* Ideally the {@link Comparator} implementations should be able to handle these
* smoothly.</p>
*
* @see DataQueryOrderFunction
*/
public enum DataQueryOrder implements Comparator<DataType> {
@@ -60,10 +62,7 @@ public enum DataQueryOrder implements Comparator<DataType> {
if (o1 == o2) {
return 0;
}
return Boolean.compare(
o1 == DataType.TRANSIENT,
o2 == DataType.TRANSIENT
);
return o1 == DataType.TRANSIENT ? 1 : -1;
}
},
@@ -76,10 +75,7 @@ public enum DataQueryOrder implements Comparator<DataType> {
if (o1 == o2) {
return 0;
}
return -Boolean.compare(
o1 == DataType.TRANSIENT,
o2 == DataType.TRANSIENT
);
return o1 == DataType.TRANSIENT ? -1 : 1;
}
};
@@ -44,6 +44,18 @@ public interface DataQueryOrderFunction {
*/
OptionKey<DataQueryOrderFunction> KEY = OptionKey.of("dataqueryorderfunction", DataQueryOrderFunction.class);
/**
* Creates a {@link DataQueryOrderFunction} that always returns the given
* {@code comparator}.
*
* @param comparator the comparator
* @return the data query order function
* @since 5.2
*/
static DataQueryOrderFunction always(Comparator<DataType> comparator) {
return id -> comparator;
}
/**
* Gets the {@link DataQueryOrder} comparator for the given
* {@link PermissionHolder.Identifier holder identifier}.
@@ -0,0 +1,120 @@
/*
* 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 net.luckperms.api.query.dataorder;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
* Represents which different {@link DataType}s are used for a query.
*
* <p>The {@link DataTypeFilter} enum simply represents some default
* implementations of the {@link Predicate} required by {@link QueryOptions}
* and the {@link #values(Predicate)} method.</p>
*
* <p>Users are free to implement their own predicate. However, be aware that
* it is possible that more {@link DataType}s may be added in the future.
* Ideally the {@link Predicate} implementations should be able to handle these
* smoothly.</p>
*
* @see DataTypeFilterFunction
* @since 5.2
*/
public enum DataTypeFilter implements Predicate<DataType> {
/**
* A data type filter indicating that all {@link DataType}s should be used.
*/
ALL {
@Override
public boolean test(DataType dataType) {
return true;
}
},
/**
* A data type filter indicating that no {@link DataType}s should be used.
*/
NONE {
@Override
public boolean test(DataType dataType) {
return false;
}
},
/**
* A data type filter indicating that only {@link DataType#NORMAL} should be used.
*/
NORMAL_ONLY {
@Override
public boolean test(DataType dataType) {
return dataType == DataType.NORMAL;
}
},
/**
* A data type filter indicating that only {@link DataType#TRANSIENT} should be used.
*/
TRANSIENT_ONLY {
@Override
public boolean test(DataType dataType) {
return dataType == DataType.TRANSIENT;
}
};
private static final List<DataType> ALL_LIST = Collections.unmodifiableList(Arrays.asList(DataType.NORMAL, DataType.TRANSIENT));
private static final List<DataType> NORMAL_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.NORMAL));
private static final List<DataType> TRANSIENT_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.TRANSIENT));
/**
* Gets a {@link List} of all {@link DataType}s, filtered by the {@code predicate}.
*
* @param predicate the predicate to filter with
* @return the list of data types
*/
public static @NonNull List<DataType> values(@NonNull Predicate<? super DataType> predicate) {
boolean normal = predicate.test(DataType.NORMAL);
boolean trans = predicate.test(DataType.TRANSIENT);
if (normal && trans) {
return ALL_LIST;
} else if (normal) {
return NORMAL_ONLY_LIST;
} else if (trans) {
return TRANSIENT_ONLY_LIST;
} else {
return Collections.emptyList();
}
}
}
@@ -0,0 +1,69 @@
/*
* 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 net.luckperms.api.query.dataorder;
import net.luckperms.api.model.PermissionHolder;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.query.OptionKey;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Predicate;
/**
* A function that generates a {@link DataTypeFilter} predicate for
* {@link PermissionHolder}s as required during inheritance.
*
* @since 5.2
*/
public interface DataTypeFilterFunction {
/**
* The {@link OptionKey} for {@link DataTypeFilterFunction}.
*/
OptionKey<DataTypeFilterFunction> KEY = OptionKey.of("datatypefilterfunction", DataTypeFilterFunction.class);
/**
* Creates a {@link DataTypeFilterFunction} that always returns the given
* {@code predicate}.
*
* @param predicate the predicate
* @return the data type filter function
*/
static DataTypeFilterFunction always(Predicate<DataType> predicate) {
return id -> predicate;
}
/**
* Gets the {@link DataTypeFilter} predicate for the given
* {@link PermissionHolder.Identifier holder identifier}.
*
* @param holderIdentifier the holder identifier
* @return the predicate to use
*/
@NonNull Predicate<DataType> getTypeFilter(PermissionHolder.@NonNull Identifier holderIdentifier);
}
+2 -2
View File
@@ -16,7 +16,7 @@ subprojects {
apply plugin: 'net.minecrell.licenser'
group = 'me.lucko.luckperms'
version = '5.1-SNAPSHOT'
version = '5.2-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
@@ -45,7 +45,7 @@ subprojects {
}
project.ext.majorVersion = '5'
project.ext.minorVersion = '1'
project.ext.minorVersion = '2'
project.ext.patchVersion = determinePatchVersion()
project.ext.apiVersion = project.ext.majorVersion + '.' + project.ext.minorVersion
project.ext.fullVersion = project.ext.apiVersion + '.' + project.ext.patchVersion
+2 -2
View File
@@ -10,8 +10,8 @@ repositories {
dependencies {
compile project(':common')
compileOnly 'com.destroystokyo.paper:paper-api:1.15.1-R0.1-SNAPSHOT'
compileOnly 'net.kyori:text-adapter-bukkit:3.0.3'
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
compileOnly 'net.kyori:text-adapter-bukkit:3.0.5'
compileOnly 'me.lucko:commodore:1.7'
compileOnly('net.milkbowl.vault:VaultAPI:1.6') {
exclude(module: 'bukkit')
@@ -25,7 +25,7 @@
package me.lucko.luckperms.bukkit;
import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.bukkit.configuration.ConfigurationSection;
@@ -25,7 +25,6 @@
package me.lucko.luckperms.bukkit;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
@@ -41,9 +40,8 @@ import org.bukkit.entity.Player;
import java.util.UUID;
public class BukkitSenderFactory extends SenderFactory<CommandSender> {
public BukkitSenderFactory(LuckPermsPlugin plugin) {
public class BukkitSenderFactory extends SenderFactory<LPBukkitPlugin, CommandSender> {
public BukkitSenderFactory(LPBukkitPlugin plugin) {
super(plugin);
}
@@ -102,6 +100,11 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> {
return sender.hasPermission(node);
}
@Override
protected void performCommand(CommandSender sender, String command) {
getPlugin().getBootstrap().getServer().dispatchCommand(sender, command);
}
private static final class SyncMessengerAgent implements Runnable {
private final CommandSender sender;
private final String message;
@@ -39,14 +39,18 @@ import org.bukkit.Server;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
/**
* Bootstrap plugin for LuckPerms running on Bukkit.
@@ -253,13 +257,23 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
}
@Override
public Stream<String> getPlayerList() {
return getServer().getOnlinePlayers().stream().map(Player::getName);
public Collection<String> getPlayerList() {
Collection<? extends Player> players = getServer().getOnlinePlayers();
List<String> list = new ArrayList<>(players.size());
for (Player player : players) {
list.add(player.getName());
}
return list;
}
@Override
public Stream<UUID> getOnlinePlayers() {
return getServer().getOnlinePlayers().stream().map(Player::getUniqueId);
public Collection<UUID> getOnlinePlayers() {
Collection<? extends Player> players = getServer().getOnlinePlayers();
List<UUID> list = new ArrayList<>(players.size());
for (Player player : players) {
list.add(player.getUniqueId());
}
return list;
}
@Override
@@ -268,6 +282,19 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
return player != null && player.isOnline();
}
@Override
public @Nullable String identifyClassLoader(ClassLoader classLoader) throws ReflectiveOperationException {
Class<?> pluginClassLoaderClass = Class.forName("org.bukkit.plugin.java.PluginClassLoader");
if (pluginClassLoaderClass.isInstance(classLoader)) {
Method getPluginMethod = pluginClassLoaderClass.getDeclaredMethod("getPlugin");
getPluginMethod.setAccessible(true);
JavaPlugin plugin = (JavaPlugin) getPluginMethod.invoke(classLoader);
return plugin.getName();
}
return null;
}
private static boolean checkIncompatibleVersion() {
try {
Class.forName("com.google.gson.JsonElement");
@@ -38,16 +38,18 @@ import me.lucko.luckperms.bukkit.inject.server.InjectorSubscriptionMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsDefaultsMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsPermissionMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsSubscriptionMap;
import me.lucko.luckperms.bukkit.listeners.BukkitAutoOpListener;
import me.lucko.luckperms.bukkit.listeners.BukkitCommandListUpdater;
import me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener;
import me.lucko.luckperms.bukkit.listeners.BukkitPlatformListener;
import me.lucko.luckperms.bukkit.messaging.BukkitMessagingFactory;
import me.lucko.luckperms.bukkit.util.PluginManagerUtil;
import me.lucko.luckperms.bukkit.vault.VaultHookManager;
import me.lucko.luckperms.common.api.LuckPermsApiProvider;
import me.lucko.luckperms.common.api.implementation.ApiUser;
import me.lucko.luckperms.common.calculator.CalculatorFactory;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.messaging.MessagingFactory;
@@ -62,7 +64,6 @@ import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.OfflinePlayer;
@@ -74,7 +75,8 @@ import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicePriority;
import java.io.File;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -156,7 +158,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
this.commandManager.register();
// setup brigadier
if (isBrigadierSupported()) {
if (isBrigadierSupported() && getConfiguration().get(ConfigKeys.REGISTER_COMMAND_LIST_DATA)) {
try {
LuckPermsBrigadier.register(this, command);
} catch (Exception e) {
@@ -182,7 +184,10 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
@Override
protected void setupContextManager() {
this.contextManager = new BukkitContextManager(this);
this.contextManager.registerCalculator(new WorldCalculator(this));
WorldCalculator worldCalculator = new WorldCalculator(this);
this.bootstrap.getServer().getPluginManager().registerEvents(worldCalculator, this.bootstrap);
this.contextManager.registerCalculator(worldCalculator);
}
@Override
@@ -203,6 +208,20 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
this.bootstrap.getServer().getScheduler().runTaskLaterAsynchronously(this.bootstrap, injector, 1);
}
/*
* This is an unfortunate solution to a problem which shouldn't even exist. As of Spigot 1.15,
* the way LP establishes it's load order relative to Vault triggers a dependency warning.
* This is a workaround to prevent that from showing, since at the moment, there is nothing I
* can reasonably do to improve this handling in LP without breaking plugins which use/obtain
* Vault in their onEnable without depending on us.
*
* Noteworthy discussion here:
* - https://github.com/lucko/LuckPerms/issues/1959
* - https://hub.spigotmc.org/jira/browse/SPIGOT-5546
* - https://github.com/PaperMC/Paper/pull/3509
*/
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getName(), "Vault");
// Provide vault support
tryVaultHook(false);
}
@@ -266,11 +285,12 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
// register autoop listener
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
getApiProvider().getEventBus().subscribe(UserDataRecalculateEvent.class, event -> {
User user = ApiUser.cast(event.getUser());
Optional<Player> player = getBootstrap().getPlayer(user.getUniqueId());
player.ifPresent(p -> refreshAutoOp(p, false));
});
getApiProvider().getEventBus().subscribe(new BukkitAutoOpListener(this));
}
// register bukkit command list updater
if (getConfiguration().get(ConfigKeys.UPDATE_CLIENT_COMMAND_LIST) && BukkitCommandListUpdater.isSupported()) {
getApiProvider().getEventBus().subscribe(new BukkitCommandListUpdater(this));
}
// Load any online users (in the case of a reload)
@@ -328,32 +348,6 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
}
}
public void refreshAutoOp(Player player, boolean callerIsSync) {
if (!getConfiguration().get(ConfigKeys.AUTO_OP)) {
return;
}
if (!callerIsSync && this.bootstrap.isServerStopping()) {
return;
}
User user = getUserManager().getIfLoaded(player.getUniqueId());
boolean value;
if (user != null) {
Map<String, Boolean> permData = user.getCachedData().getPermissionData(this.contextManager.getQueryOptions(player)).getPermissionMap();
value = permData.getOrDefault("luckperms.autoop", false);
} else {
value = false;
}
if (callerIsSync) {
player.setOp(value);
} else {
this.bootstrap.getScheduler().executeSync(() -> player.setOp(value));
}
}
private File resolveConfig() {
File configFile = new File(this.bootstrap.getDataFolder(), "config.yml");
if (!configFile.exists()) {
@@ -387,9 +381,10 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
@Override
public Stream<Sender> getOnlineSenders() {
List<Player> players = new ArrayList<>(this.bootstrap.getServer().getOnlinePlayers());
return Stream.concat(
Stream.of(getConsoleSender()),
this.bootstrap.getServer().getOnlinePlayers().stream().map(p -> getSenderFactory().wrap(p))
players.stream().map(p -> getSenderFactory().wrap(p))
);
}
@@ -88,11 +88,7 @@ public class BukkitContextManager extends ContextManager<Player, Player> {
}
@Override
public void invalidateCache(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
@@ -38,13 +38,17 @@ import net.luckperms.api.context.ImmutableContextSet;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class WorldCalculator implements ContextCalculator<Player> {
public class WorldCalculator implements ContextCalculator<Player>, Listener {
private final LPBukkitPlugin plugin;
public WorldCalculator(LPBukkitPlugin plugin) {
@@ -75,4 +79,9 @@ public class WorldCalculator implements ContextCalculator<Player> {
}
return builder.build();
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
this.plugin.getContextManager().signalContextUpdate(e.getPlayer());
}
}
@@ -1,79 +0,0 @@
/*
* 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.bukkit.inject.dummy;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Logger;
/**
* Dummy plugin instance
*/
public class DummyPlugin implements Plugin {
public static final DummyPlugin INSTANCE = new DummyPlugin();
private DummyPlugin() {
}
@Override
public boolean isEnabled() {
return true;
}
@Override public @NonNull File getDataFolder() { return null; }
@Override public @NonNull PluginDescriptionFile getDescription() { return null; }
@Override public @NonNull FileConfiguration getConfig() { return null; }
@Override public InputStream getResource(@NonNull String s) { return null; }
@Override public void saveConfig() {}
@Override public void saveDefaultConfig() {}
@Override public void saveResource(@NonNull String s, boolean b) {}
@Override public void reloadConfig() {}
@Override public @NonNull PluginLoader getPluginLoader() { return null; }
@Override public @NonNull Server getServer() { return null; }
@Override public void onDisable() {}
@Override public void onLoad() {}
@Override public void onEnable() {}
@Override public boolean isNaggable() { return false; }
@Override public void setNaggable(boolean b) {}
@Override public ChunkGenerator getDefaultWorldGenerator(@NonNull String s, String s1) { return null; }
@Override public @NonNull Logger getLogger() { return null; }
@Override public @NonNull String getName() { return null; }
@Override public boolean onCommand(@NonNull CommandSender commandSender, @NonNull Command command, @NonNull String s, @NonNull String[] strings) { return false; }
@Override public List<String> onTabComplete(@NonNull CommandSender commandSender, @NonNull Command command, @NonNull String s, @NonNull String[] strings) { return null; }
}
@@ -23,7 +23,9 @@
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.inject.dummy;
package me.lucko.luckperms.bukkit.inject.permissible;
import me.lucko.luckperms.common.util.EmptyCollections;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
@@ -33,14 +35,8 @@ import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
public class DummyPermissibleBase extends PermissibleBase {
private static final Field ATTACHMENTS_FIELD;
@@ -61,13 +57,9 @@ public class DummyPermissibleBase extends PermissibleBase {
public static void copyFields(PermissibleBase from, PermissibleBase to) {
try {
ATTACHMENTS_FIELD.set(to, ATTACHMENTS_FIELD.get(from));
} catch (Exception e) {
// ignore
}
try {
PERMISSIONS_FIELD.set(to, PERMISSIONS_FIELD.get(from));
} catch (Exception e) {
// ignore
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@@ -76,29 +68,11 @@ public class DummyPermissibleBase extends PermissibleBase {
private DummyPermissibleBase() {
super(null);
// we want the singleton dummy attachment to be stateless
// the behaviour of this class is to fail silently, so we can't use Collections.emptyX
try {
ATTACHMENTS_FIELD.set(this, new ArrayList<PermissionAttachment>(){
@Override public boolean add(PermissionAttachment permissionAttachment) { return true; }
@Override public void add(int index, PermissionAttachment element) { }
@Override public boolean addAll(Collection<? extends PermissionAttachment> c) { return true; }
@Override public boolean addAll(int index, Collection<? extends PermissionAttachment> c) { return true; }
});
ATTACHMENTS_FIELD.set(this, EmptyCollections.list());
PERMISSIONS_FIELD.set(this, EmptyCollections.map());
} catch (Exception e) {
// ignore
}
try {
PERMISSIONS_FIELD.set(this, new HashMap<String, PermissionAttachmentInfo>() {
@Override public PermissionAttachmentInfo put(String key, PermissionAttachmentInfo value) { return null; }
@Override public void putAll(Map<? extends String, ? extends PermissionAttachmentInfo> m) { }
@Override public PermissionAttachmentInfo putIfAbsent(String key, PermissionAttachmentInfo value) { return null; }
@Override public PermissionAttachmentInfo compute(String key, BiFunction<? super String, ? super PermissionAttachmentInfo, ? extends PermissionAttachmentInfo> remappingFunction) { return null; }
@Override public PermissionAttachmentInfo computeIfPresent(String key, BiFunction<? super String, ? super PermissionAttachmentInfo, ? extends PermissionAttachmentInfo> remappingFunction) { return null; }
@Override public PermissionAttachmentInfo computeIfAbsent(String key, Function<? super String, ? extends PermissionAttachmentInfo> mappingFunction) { return null; }
});
} catch (Exception e) {
// ignore
e.printStackTrace();
}
}
@@ -221,9 +221,7 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override
public @NonNull LuckPermsPermissionAttachment addAttachment(@NonNull Plugin plugin) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
Objects.requireNonNull(plugin, "plugin");
LuckPermsPermissionAttachment attachment = new LuckPermsPermissionAttachment(this, plugin);
attachment.hook();
@@ -232,12 +230,8 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override
public @NonNull PermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String permission, boolean value) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
if (permission == null) {
throw new NullPointerException("permission");
}
Objects.requireNonNull(plugin, "plugin");
Objects.requireNonNull(permission, "permission");
PermissionAttachment attachment = addAttachment(plugin);
attachment.setPermission(permission, value);
@@ -246,9 +240,7 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override
public LuckPermsPermissionAttachment addAttachment(@NonNull Plugin plugin, int ticks) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
Objects.requireNonNull(plugin, "plugin");
if (!plugin.isEnabled()) {
throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is not enabled");
@@ -264,12 +256,8 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override
public LuckPermsPermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String permission, boolean value, int ticks) {
if (plugin == null) {
throw new NullPointerException("plugin");
}
if (permission == null) {
throw new NullPointerException("permission");
}
Objects.requireNonNull(plugin, "plugin");
Objects.requireNonNull(permission, "permission");
LuckPermsPermissionAttachment attachment = addAttachment(plugin, ticks);
attachment.setPermission(permission, value);
@@ -278,9 +266,7 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override
public void removeAttachment(@NonNull PermissionAttachment attachment) {
if (attachment == null) {
throw new NullPointerException("attachment");
}
Objects.requireNonNull(attachment, "attachment");
LuckPermsPermissionAttachment luckPermsAttachment;
@@ -27,7 +27,6 @@ package me.lucko.luckperms.bukkit.inject.permissible;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
@@ -106,7 +105,7 @@ public class LuckPermsPermissionAttachment extends PermissionAttachment {
private PermissionAttachment source;
public LuckPermsPermissionAttachment(LuckPermsPermissible permissible, Plugin owner) {
super(DummyPlugin.INSTANCE, null);
super(owner, null);
this.permissible = permissible;
this.owner = owner;
@@ -114,7 +113,7 @@ public class LuckPermsPermissionAttachment extends PermissionAttachment {
}
public LuckPermsPermissionAttachment(LuckPermsPermissible permissible, PermissionAttachment source) {
super(DummyPlugin.INSTANCE, null);
super(source.getPlugin(), null);
this.permissible = permissible;
this.owner = source.getPlugin();
@@ -25,7 +25,6 @@
package me.lucko.luckperms.bukkit.inject.permissible;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase;
import me.lucko.luckperms.common.calculator.result.TristateResult;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
@@ -25,7 +25,6 @@
package me.lucko.luckperms.bukkit.inject.permissible;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase;
import me.lucko.luckperms.bukkit.util.CraftBukkitImplementation;
import org.bukkit.entity.Player;
@@ -25,9 +25,7 @@
package me.lucko.luckperms.bukkit.inject.server;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.util.ImmutableCollectors;
@@ -40,11 +38,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import java.util.function.Function;
/**
* A replacement map for the 'permSubs' instance in Bukkit's SimplePluginManager.
@@ -121,6 +120,21 @@ public final class LuckPermsSubscriptionMap extends HashMap<String, Map<Permissi
return super.put(key, value);
}
@Override
public void putAll(Map<? extends String, ? extends Map<Permissible, Boolean>> m) {
m.forEach(this::put);
}
@Override
public Map<Permissible, Boolean> putIfAbsent(String key, Map<Permissible, Boolean> value) {
return get(key);
}
@Override
public Map<Permissible, Boolean> computeIfAbsent(String key, Function<? super String, ? extends Map<Permissible, Boolean>> mappingFunction) {
return get(key);
}
// if the key isn't null and is a string, #get will always return a value for it
@Override
public boolean containsKey(Object key) {
@@ -212,18 +226,20 @@ public final class LuckPermsSubscriptionMap extends HashMap<String, Map<Permissi
@Override
public @NonNull Set<Permissible> keySet() {
// gather players (LPPermissibles)
Set<Permissible> players = LuckPermsSubscriptionMap.this.plugin.getBootstrap().getServer().getOnlinePlayers().stream()
.filter(player -> player.hasPermission(this.permission) || player.isPermissionSet(this.permission))
.collect(Collectors.toSet());
ImmutableSet<Permissible> backing;
// start with the backing set
Set<Permissible> set;
synchronized (this.backing) {
backing = ImmutableSet.copyOf(this.backing.keySet());
set = new HashSet<>(this.backing.keySet());
}
// then combine the players with the backing map
return Sets.union(players, backing);
// add any online players who meet requirements
for (Player player : LuckPermsSubscriptionMap.this.plugin.getBootstrap().getServer().getOnlinePlayers()) {
if (player.hasPermission(this.permission) || player.isPermissionSet(this.permission)) {
set.add(player);
}
}
return set;
}
@Override
@@ -0,0 +1,92 @@
/*
* 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.bukkit.listeners;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.api.implementation.ApiUser;
import me.lucko.luckperms.common.event.LuckPermsEventListener;
import me.lucko.luckperms.common.model.User;
import net.luckperms.api.event.EventBus;
import net.luckperms.api.event.context.ContextUpdateEvent;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import java.util.Map;
/**
* Implements the LuckPerms auto op feature.
*/
public class BukkitAutoOpListener implements LuckPermsEventListener {
private static final String NODE = "luckperms.autoop";
private final LPBukkitPlugin plugin;
public BukkitAutoOpListener(LPBukkitPlugin plugin) {
this.plugin = plugin;
}
@Override
public void bind(EventBus bus) {
bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate);
bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate);
}
private void onUserDataRecalculate(UserDataRecalculateEvent e) {
User user = ApiUser.cast(e.getUser());
this.plugin.getBootstrap().getPlayer(user.getUniqueId()).ifPresent(p -> refreshAutoOp(p, false));
}
private void onContextUpdate(ContextUpdateEvent e) {
e.getSubject(Player.class).ifPresent(p -> refreshAutoOp(p, true));
}
private void refreshAutoOp(Player player, boolean callerIsSync) {
if (!callerIsSync && this.plugin.getBootstrap().isServerStopping()) {
return;
}
User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId());
boolean value;
if (user != null) {
QueryOptions queryOptions = this.plugin.getContextManager().getQueryOptions(player);
Map<String, Boolean> permData = user.getCachedData().getPermissionData(queryOptions).getPermissionMap();
value = permData.getOrDefault(NODE, false);
} else {
value = false;
}
if (callerIsSync) {
player.setOp(value);
} else {
this.plugin.getBootstrap().getScheduler().executeSync(() -> player.setOp(value));
}
}
}
@@ -0,0 +1,118 @@
/*
* 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.bukkit.listeners;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.BufferedRequest;
import me.lucko.luckperms.common.event.LuckPermsEventListener;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.event.EventBus;
import net.luckperms.api.event.context.ContextUpdateEvent;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import org.bukkit.entity.Player;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Calls {@link Player#updateCommands()} when a players permissions change.
*/
public class BukkitCommandListUpdater implements LuckPermsEventListener {
public static boolean isSupported() {
try {
Player.class.getMethod("updateCommands");
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
private final LPBukkitPlugin plugin;
private final LoadingCache<UUID, SendBuffer> sendingBuffers = CaffeineFactory.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build(SendBuffer::new);
public BukkitCommandListUpdater(LPBukkitPlugin plugin) {
this.plugin = plugin;
}
@Override
public void bind(EventBus bus) {
bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate);
bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate);
}
private void onUserDataRecalculate(UserDataRecalculateEvent e) {
requestUpdate(e.getUser().getUniqueId());
}
private void onContextUpdate(ContextUpdateEvent e) {
e.getSubject(Player.class).ifPresent(p -> requestUpdate(p.getUniqueId()));
}
private void requestUpdate(UUID uniqueId) {
if (this.plugin.getBootstrap().isServerStopping()) {
return;
}
if (!this.plugin.getBootstrap().isPlayerOnline(uniqueId)) {
return;
}
// Buffer the request to send a commands update.
this.sendingBuffers.get(uniqueId).request();
}
// Called when the buffer times out.
private void sendUpdate(UUID uniqueId) {
if (this.plugin.getBootstrap().isServerStopping()) {
return;
}
this.plugin.getBootstrap().getScheduler().sync()
.execute(() -> this.plugin.getBootstrap().getPlayer(uniqueId).ifPresent(Player::updateCommands));
}
private final class SendBuffer extends BufferedRequest<Void> {
private final UUID uniqueId;
SendBuffer(UUID uniqueId) {
super(500, TimeUnit.MILLISECONDS, BukkitCommandListUpdater.this.plugin.getBootstrap().getScheduler());
this.uniqueId = uniqueId;
}
@Override
protected Void perform() {
sendUpdate(this.uniqueId);
return null;
}
}
}
@@ -201,7 +201,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
return;
}
this.plugin.refreshAutoOp(player, true);
this.plugin.getContextManager().signalContextUpdate(player);
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -32,38 +32,40 @@ import me.lucko.luckperms.common.locale.message.Message;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.server.RemoteServerCommandEvent;
import org.bukkit.event.server.ServerCommandEvent;
import java.util.regex.Pattern;
public class BukkitPlatformListener implements Listener {
private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(\\w+:)?(deop|op)( .*)?$");
private final LPBukkitPlugin plugin;
public BukkitPlatformListener(LPBukkitPlugin plugin) {
this.plugin = plugin;
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onPlayerCommand(PlayerCommandPreprocessEvent e) {
handleCommand(e.getPlayer(), e.getMessage().toLowerCase(), e);
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onServerCommand(ServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onRemoteServerCommand(RemoteServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(), e);
}
private void handleCommand(CommandSender sender, String s, Cancellable event) {
if (s.isEmpty()) {
private void handleCommand(CommandSender sender, String cmdLine, Cancellable event) {
if (cmdLine.isEmpty()) {
return;
}
@@ -71,15 +73,7 @@ public class BukkitPlatformListener implements Listener {
return;
}
if (s.charAt(0) == '/') {
s = s.substring(1);
}
if (s.contains(":")) {
s = s.substring(s.indexOf(':') + 1);
}
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
if (OP_COMMAND_PATTERN.matcher(cmdLine).matches()) {
event.setCancelled(true);
sender.sendMessage(Message.OP_DISABLED.asString(this.plugin.getLocaleManager()));
}
@@ -92,10 +86,4 @@ public class BukkitPlatformListener implements Listener {
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
this.plugin.getContextManager().invalidateCache(e.getPlayer());
this.plugin.refreshAutoOp(e.getPlayer(), true);
}
}
@@ -34,6 +34,7 @@ import de.bananaco.bpermissions.api.WorldManager;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -61,7 +62,6 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -83,7 +83,7 @@ public class MigrationBPermissions extends ChildCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "bPermissions");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -28,6 +28,7 @@ package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -74,7 +75,7 @@ public class MigrationGroupManager extends ChildCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "GroupManager");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -30,6 +30,7 @@ import com.platymuus.bukkit.permissions.PermissionsPlugin;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -62,7 +63,7 @@ public class MigrationPermissionsBukkit extends ChildCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PermissionsBukkit");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -30,6 +30,7 @@ import com.google.common.base.Strings;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -107,7 +108,7 @@ public class MigrationPermissionsEx extends ChildCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PermissionsEx");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -36,6 +36,7 @@ import com.zaxxer.hikari.HikariDataSource;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.LocaleManager;
@@ -84,7 +85,7 @@ public class MigrationPowerfulPerms extends ChildCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PowerfulPerms");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -28,6 +28,7 @@ package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -63,7 +64,6 @@ import org.tyrannyofheaven.bukkit.zPermissions.model.PermissionEntity;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -75,7 +75,7 @@ public class MigrationZPermissions extends ChildCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "zPermissions");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
@@ -0,0 +1,71 @@
/*
* 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.bukkit.util;
import com.google.common.graph.MutableGraph;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.SimplePluginManager;
import java.lang.reflect.Field;
public final class PluginManagerUtil {
private PluginManagerUtil() {}
private static final Field DEPENDENCY_GRAPH_FIELD;
static {
Field dependencyGraphField = null;
try {
dependencyGraphField = SimplePluginManager.class.getDeclaredField("dependencyGraph");
dependencyGraphField.setAccessible(true);
} catch (Exception e) {
// ignore
}
DEPENDENCY_GRAPH_FIELD = dependencyGraphField;
}
/**
* Injects a dependency relationship into the plugin manager.
*
* @param plugin the plugin
* @param depend the plugin being depended on
*/
@SuppressWarnings("unchecked")
public static void injectDependency(PluginManager pluginManager, String plugin, String depend) {
if (DEPENDENCY_GRAPH_FIELD == null || !(pluginManager instanceof SimplePluginManager)) {
return; // fail silently
}
try {
MutableGraph<String> graph = (MutableGraph<String>) DEPENDENCY_GRAPH_FIELD.get(pluginManager);
graph.putEdge(plugin, depend);
} catch (Exception e) {
// ignore
}
}
}
@@ -46,11 +46,11 @@ import me.lucko.luckperms.common.util.Uuids;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryOptions;
import net.milkbowl.vault.permission.Permission;
@@ -251,10 +251,9 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission {
Objects.requireNonNull(uuid, "uuid");
PermissionHolder user = lookupUser(uuid);
ContextSet contexts = getQueryOptions(uuid, world).context();
QueryOptions queryOptions = getQueryOptions(uuid, world);
return user.normalData().immutableInheritance().values().stream()
.filter(n -> n.getContexts().isSatisfiedBy(contexts))
return user.getOwnNodes(NodeType.INHERITANCE, queryOptions).stream()
.map(n -> {
Group group = this.plugin.getGroupManager().getIfLoaded(n.getGroupName());
if (group != null) {
+22 -1
View File
@@ -148,7 +148,7 @@ data:
#verifyServerCertificate: false
# The prefix for all LuckPerms SQL tables.
# - Change this is you want to use different tables for different servers.
# - Change this if you want to use different tables for different servers.
table-prefix: 'luckperms_'
# The prefix to use for all LuckPerms collections. Change this if you want to use different
@@ -304,6 +304,12 @@ require-sender-group-membership-to-modify: false
# '/lp log notify <on|off>'
log-notify: true
# Defines a list of log entries which should not be sent as notifications to users.
#
# - Each entry in the list is a RegEx expression which is matched against the log entry description.
log-notify-filtered-descriptions:
# - "parent add example"
# Defines the options for prefix and suffix stacking.
#
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
@@ -400,6 +406,15 @@ inheritance-traversal-algorithm: depth-first-pre-order
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort: false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode: at-least-one-value-per-key
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #
@@ -608,6 +623,12 @@ allow-invalid-usernames: false
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal: false
# If LuckPerms should update the list of commands sent to the client when permissions are changed.
update-client-command-list: true
# If LuckPerms should attempt to register "Brigadier" command list data for its commands.
register-command-list-data: true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors
resolve-command-selectors: false
@@ -16,6 +16,11 @@ luckperms {
record {
filter brigadier:string greedy_phrase;
}
command {
player brigadier:string single_word {
command brigadier:string greedy_phrase;
}
}
}
tree {
scope brigadier:string single_word {
@@ -100,6 +105,9 @@ luckperms {
unsettemp {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
check {
@@ -151,6 +159,9 @@ luckperms {
removetemp {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
clear {
@@ -342,6 +353,9 @@ luckperms {
unsettemp {
node brigadier:string quotable_phrase {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
check {
@@ -393,6 +407,9 @@ luckperms {
removetemp {
group brigadier:string single_word {
context brigadier:string greedy_phrase;
duration brigadier:string single_word {
context brigadier:string greedy_phrase;
}
}
}
clear {
+1 -1
View File
@@ -6,7 +6,7 @@ dependencies {
compile project(':common')
compileOnly 'net.md-5:bungeecord-api:1.15-SNAPSHOT'
compileOnly 'net.kyori:text-adapter-bungeecord:3.0.3'
compileOnly 'net.kyori:text-adapter-bungeecord:3.0.4'
compileOnly 'com.imaginarycode.minecraft:RedisBungee:0.4'
// migration plugins
@@ -25,7 +25,7 @@
package me.lucko.luckperms.bungee;
import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.md_5.bungee.config.Configuration;
@@ -26,7 +26,6 @@
package me.lucko.luckperms.bungee;
import me.lucko.luckperms.bungee.event.TristateCheckEvent;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
@@ -39,8 +38,8 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.UUID;
public class BungeeSenderFactory extends SenderFactory<CommandSender> {
public BungeeSenderFactory(LuckPermsPlugin plugin) {
public class BungeeSenderFactory extends SenderFactory<LPBungeePlugin, CommandSender> {
public BungeeSenderFactory(LPBungeePlugin plugin) {
super(plugin);
}
@@ -79,4 +78,9 @@ public class BungeeSenderFactory extends SenderFactory<CommandSender> {
protected boolean hasPermission(CommandSender sender, String node) {
return sender.hasPermission(node);
}
@Override
protected void performCommand(CommandSender sender, String command) {
getPlugin().getBootstrap().getProxy().getPluginManager().dispatchCommand(sender, command);
}
}
@@ -36,14 +36,19 @@ import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import net.luckperms.api.platform.Platform;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginDescription;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.InputStream;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
/**
* Bootstrap plugin for LuckPerms running on BungeeCord.
@@ -246,13 +251,23 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
}
@Override
public Stream<String> getPlayerList() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getName);
public Collection<String> getPlayerList() {
Collection<ProxiedPlayer> players = getProxy().getPlayers();
List<String> list = new ArrayList<>(players.size());
for (ProxiedPlayer player : players) {
list.add(player.getName());
}
return list;
}
@Override
public Stream<UUID> getOnlinePlayers() {
return getProxy().getPlayers().stream().map(ProxiedPlayer::getUniqueId);
public Collection<UUID> getOnlinePlayers() {
Collection<ProxiedPlayer> players = getProxy().getPlayers();
List<UUID> list = new ArrayList<>(players.size());
for (ProxiedPlayer player : players) {
list.add(player.getUniqueId());
}
return list;
}
@Override
@@ -260,9 +275,19 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
return getProxy().getPlayer(uniqueId) != null;
}
@Override
public @Nullable String identifyClassLoader(ClassLoader classLoader) throws Exception {
Class<?> pluginClassLoader = Class.forName("net.md_5.bungee.api.plugin.PluginClassloader");
if (pluginClassLoader.isInstance(classLoader)) {
PluginDescription desc = (PluginDescription) pluginClassLoader.getDeclaredField("desc").get(classLoader);
return desc.getName();
}
return null;
}
private static boolean checkIncompatibleVersion() {
try {
Class<?> aClass = Class.forName("com.google.gson.internal.bind.TreeTypeAdapter");
Class.forName("com.google.gson.internal.bind.TreeTypeAdapter");
return false;
} catch (ClassNotFoundException e) {
return true;
@@ -35,7 +35,7 @@ import me.lucko.luckperms.bungee.messaging.BungeeMessagingFactory;
import me.lucko.luckperms.common.api.LuckPermsApiProvider;
import me.lucko.luckperms.common.calculator.CalculatorFactory;
import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.messaging.MessagingFactory;
@@ -138,7 +138,10 @@ public class LPBungeePlugin extends AbstractLuckPermsPlugin {
@Override
protected void setupContextManager() {
this.contextManager = new BungeeContextManager(this);
this.contextManager.registerCalculator(new BackendServerCalculator(this));
BackendServerCalculator backendServerCalculator = new BackendServerCalculator(this);
this.bootstrap.getProxy().getPluginManager().registerListener(this.bootstrap, backendServerCalculator);
this.contextManager.registerCalculator(backendServerCalculator);
if (this.bootstrap.getProxy().getPluginManager().getPlugin("RedisBungee") != null) {
this.contextManager.registerCalculator(new RedisBungeeCalculator());
@@ -36,6 +36,9 @@ import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.ImmutableContextSet;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -43,7 +46,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer> {
public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer>, Listener {
private static String getServer(ProxiedPlayer player) {
return player.getServer() == null ? null : (player.getServer().getInfo() == null ? null : player.getServer().getInfo().getName().toLowerCase());
@@ -74,4 +77,9 @@ public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer>
}
return builder.build();
}
@EventHandler
public void onServerSwitch(ServerSwitchEvent e) {
this.plugin.getContextManager().signalContextUpdate(e.getPlayer());
}
}
@@ -74,7 +74,7 @@ public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedP
}
@Override
public void invalidateCache(ProxiedPlayer subject) {
protected void invalidateCache(ProxiedPlayer subject) {
this.contextsCache.invalidate(subject);
}
@@ -28,6 +28,7 @@ package me.lucko.luckperms.bungee.migration;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.migration.MigrationUtils;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@@ -61,7 +62,7 @@ public class MigrationBungeePerms extends ChildCommand<Object> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) {
ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "BungeePerms");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
+16 -1
View File
@@ -145,7 +145,7 @@ data:
#verifyServerCertificate: false
# The prefix for all LuckPerms SQL tables.
# - Change this is you want to use different tables for different servers.
# - Change this if you want to use different tables for different servers.
table-prefix: 'luckperms_'
# The prefix to use for all LuckPerms collections. Change this if you want to use different
@@ -312,6 +312,12 @@ require-sender-group-membership-to-modify: false
# '/lp log notify <on|off>'
log-notify: true
# Defines a list of log entries which should not be sent as notifications to users.
#
# - Each entry in the list is a RegEx expression which is matched against the log entry description.
log-notify-filtered-descriptions:
# - "parent add example"
# Defines the options for prefix and suffix stacking.
#
# - The feature allows you to display multiple prefixes or suffixes alongside a players username in
@@ -408,6 +414,15 @@ inheritance-traversal-algorithm: depth-first-pre-order
# and when this setting is 'false':, the rules are just applied during each step of the traversal.
post-traversal-inheritance-sort: false
# Defines the mode used to determine whether a set of contexts are satisfied.
#
# - Possible options:
# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the
# key-value entries per key in A are also in B.
# => all-values-per-key Set A will be satisfied by another set B, if all key-value
# entries in A are also in B.
context-satisfy-mode: at-least-one-value-per-key
# +----------------------------------------------------------------------------------------------+ #
# | Permission resolution settings | #
# +----------------------------------------------------------------------------------------------+ #
+15 -10
View File
@@ -2,13 +2,13 @@ dependencies {
compile project(':api')
compile 'org.checkerframework:checker-qual:2.5.5'
compile('net.kyori:text-api:3.0.3') {
compile('net.kyori:text-api:3.0.4') {
exclude(module: 'checker-qual')
}
compile('net.kyori:text-serializer-legacy:3.0.3') {
compile('net.kyori:text-serializer-legacy:3.0.4') {
exclude(module: 'text-api')
}
compile('net.kyori:text-serializer-gson:3.0.3') {
compile('net.kyori:text-serializer-gson:3.0.4') {
exclude(module: 'text-api')
exclude(module: 'gson')
}
@@ -18,24 +18,29 @@ dependencies {
}
compile 'com.google.code.gson:gson:2.7'
compile 'com.google.guava:guava:19.0'
compile 'com.github.ben-manes.caffeine:caffeine:2.8.1'
compile 'com.github.ben-manes.caffeine:caffeine:2.8.4'
compile 'com.squareup.okhttp3:okhttp:3.14.7'
compile 'com.squareup.okio:okio:1.17.5'
compile 'net.bytebuddy:byte-buddy:1.10.9'
compile('me.lucko.configurate:configurate-core:3.5') {
compile('org.spongepowered:configurate-core:3.7') {
exclude(module: 'checker-qual')
exclude(module: 'guava')
}
compile('me.lucko.configurate:configurate-yaml:3.5') {
compile('org.spongepowered:configurate-yaml:3.7') {
exclude(module: 'checker-qual')
exclude(module: 'snakeyaml')
}
compile('me.lucko.configurate:configurate-gson:3.5') {
compile('org.spongepowered:configurate-gson:3.7') {
exclude(module: 'checker-qual')
exclude(module: 'gson')
}
compile 'me.lucko.configurate:configurate-hocon:3.5'
compile('me.lucko.configurate:configurate-toml:3.5') {
compile ('org.spongepowered:configurate-hocon:3.7') {
exclude(module: 'checker-qual')
}
compile('me.lucko.configurate:configurate-toml:3.7') {
exclude(module: 'toml4j')
}
compile 'com.zaxxer:HikariCP:3.4.2'
compile 'com.zaxxer:HikariCP:3.4.5'
compile 'redis.clients:jedis:3.3.0'
compile 'org.mongodb:mongo-java-driver:3.12.2'
compile 'org.yaml:snakeyaml:1.23'
@@ -0,0 +1,96 @@
/*
* 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.actionlog;
import net.luckperms.api.actionlog.Action;
import java.util.Comparator;
import java.util.Optional;
final class ActionComparator implements Comparator<Action> {
public static final Comparator<Action> INSTANCE = new ActionComparator();
private ActionComparator() {
}
@Override
public int compare(Action o1, Action o2) {
int cmp = o1.getTimestamp().compareTo(o2.getTimestamp());
if (cmp != 0) {
return cmp;
}
Action.Source o1Source = o1.getSource();
Action.Source o2Source = o2.getSource();
cmp = o1Source.getUniqueId().compareTo(o2Source.getUniqueId());
if (cmp != 0) {
return cmp;
}
cmp = o1Source.getName().compareTo(o2Source.getName());
if (cmp != 0) {
return cmp;
}
Action.Target o1Target = o1.getTarget();
Action.Target o2Target = o2.getTarget();
cmp = o1Target.getType().compareTo(o2Target.getType());
if (cmp != 0) {
return cmp;
}
cmp = o1Target.getType().compareTo(o2Target.getType());
if (cmp != 0) {
return cmp;
}
cmp = compareOptionals(o1Target.getUniqueId(), o2Target.getUniqueId());
if (cmp != 0) {
return cmp;
}
cmp = o1Source.getName().compareTo(o2Source.getName());
if (cmp != 0) {
return cmp;
}
return o1.getDescription().compareTo(o2.getDescription());
}
@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "OptionalIsPresent"})
private static <T extends Comparable<T>> int compareOptionals(Optional<T> a, Optional<T> b) {
if (!a.isPresent()) {
return b.isPresent() ? -1 : 0;
} else if (!b.isPresent()) {
return 1;
} else {
return a.get().compareTo(b.get());
}
}
}
@@ -25,36 +25,33 @@
package me.lucko.luckperms.common.actionlog;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import net.luckperms.api.actionlog.Action;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
public class Log {
private static Log empty = null;
private static final Log EMPTY = new Log(ImmutableList.of());
public static Builder builder() {
return new Builder();
}
public static synchronized Log empty() {
if (empty == null) {
empty = builder().build();
}
return empty;
public static Log empty() {
return EMPTY;
}
private final SortedSet<LoggedAction> content;
public Log(SortedSet<LoggedAction> content) {
this.content = ImmutableSortedSet.copyOfSorted(content);
Log(List<LoggedAction> content) {
this.content = ImmutableSortedSet.copyOf(content);
}
public SortedSet<LoggedAction> getContent() {
@@ -64,38 +61,38 @@ public class Log {
public SortedSet<LoggedAction> getContent(UUID actor) {
return this.content.stream()
.filter(e -> e.getSource().getUniqueId().equals(actor))
.collect(Collectors.toCollection(TreeSet::new));
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getUserHistory(UUID uniqueId) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.USER)
.filter(e -> e.getTarget().getUniqueId().isPresent() && e.getTarget().getUniqueId().get().equals(uniqueId))
.collect(ImmutableCollectors.toSortedSet(Comparator.naturalOrder()));
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getGroupHistory(String name) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.GROUP)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet(Comparator.naturalOrder()));
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getTrackHistory(String name) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.TRACK)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet(Comparator.naturalOrder()));
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getSearch(String query) {
return this.content.stream()
.filter(e -> e.matchesSearch(query))
.collect(ImmutableCollectors.toSortedSet(Comparator.naturalOrder()));
.collect(ImmutableCollectors.toSortedSet());
}
public static class Builder {
private final SortedSet<LoggedAction> content = new TreeSet<>();
private final List<LoggedAction> content = new ArrayList<>();
public Builder add(LoggedAction e) {
this.content.add(e);
@@ -103,6 +100,9 @@ public class Log {
}
public Log build() {
if (this.content.isEmpty()) {
return EMPTY;
}
return new Log(this.content);
}
}
@@ -35,6 +35,9 @@ import me.lucko.luckperms.common.sender.Sender;
import net.luckperms.api.event.log.LogBroadcastEvent;
import net.luckperms.api.event.log.LogNotifyEvent;
import java.util.Collection;
import java.util.regex.Pattern;
public class LogDispatcher {
private final LuckPermsPlugin plugin;
@@ -42,6 +45,26 @@ public class LogDispatcher {
this.plugin = plugin;
}
private boolean shouldBroadcast(LoggedAction entry, LogBroadcastEvent.Origin origin) {
boolean cancelled = false;
if (!this.plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY)) {
cancelled = true;
} else if (origin == LogBroadcastEvent.Origin.REMOTE && !this.plugin.getConfiguration().get(ConfigKeys.BROADCAST_RECEIVED_LOG_ENTRIES)) {
cancelled = true;
} else {
Collection<Pattern> filters = this.plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY_FILTERED_DESCRIPTIONS);
for (Pattern filter : filters) {
if (filter.matcher(entry.getDescription()).matches()) {
cancelled = true;
break;
}
}
}
return !this.plugin.getEventDispatcher().dispatchLogBroadcast(cancelled, entry, origin);
}
private void broadcast(LoggedAction entry, LogNotifyEvent.Origin origin, Sender sender) {
this.plugin.getOnlineSenders()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
@@ -64,12 +87,19 @@ public class LogDispatcher {
this.plugin.getMessagingService().ifPresent(service -> service.pushLog(entry));
boolean shouldCancel = !this.plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY);
if (!this.plugin.getEventDispatcher().dispatchLogBroadcast(shouldCancel, entry, LogBroadcastEvent.Origin.LOCAL)) {
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL, sender);
}
}
public void broadcastFromApi(LoggedAction entry) {
this.plugin.getMessagingService().ifPresent(extendedMessagingService -> extendedMessagingService.pushLog(entry));
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL_API)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL_API, null);
}
}
public void dispatchFromApi(LoggedAction entry) {
if (!this.plugin.getEventDispatcher().dispatchLogPublish(false, entry)) {
try {
@@ -82,18 +112,8 @@ public class LogDispatcher {
broadcastFromApi(entry);
}
public void broadcastFromApi(LoggedAction entry) {
this.plugin.getMessagingService().ifPresent(extendedMessagingService -> extendedMessagingService.pushLog(entry));
boolean shouldCancel = !this.plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY);
if (!this.plugin.getEventDispatcher().dispatchLogBroadcast(shouldCancel, entry, LogBroadcastEvent.Origin.LOCAL_API)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL_API, null);
}
}
public void dispatchFromRemote(LoggedAction entry) {
boolean shouldCancel = !this.plugin.getConfiguration().get(ConfigKeys.BROADCAST_RECEIVED_LOG_ENTRIES) || !this.plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY);
if (!this.plugin.getEventDispatcher().dispatchLogBroadcast(shouldCancel, entry, LogBroadcastEvent.Origin.REMOTE)) {
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.REMOTE)) {
broadcast(entry, LogNotifyEvent.Origin.REMOTE, null);
}
}
@@ -34,6 +34,7 @@ import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.context.Context;
@@ -45,7 +46,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -58,15 +58,6 @@ import java.util.UUID;
*/
public class LoggedAction implements Action {
private static final Comparator<Action> COMPARATOR = Comparator
.<Action>comparingLong(a -> a.getTimestamp().getEpochSecond())
.thenComparing(a -> a.getSource().getUniqueId())
.thenComparing(a -> a.getSource().getName(), String.CASE_INSENSITIVE_ORDER)
.thenComparing(a -> a.getTarget().getType())
.thenComparing(e -> e.getTarget().getUniqueId().map(UUID::toString).orElse(""))
.thenComparing(a -> a.getTarget().getName(), String.CASE_INSENSITIVE_ORDER)
.thenComparing(Action::getDescription);
/**
* Creates a new log entry builder
*
@@ -131,7 +122,7 @@ public class LoggedAction implements Action {
@Override
public int compareTo(@NonNull Action other) {
Objects.requireNonNull(other, "other");
return COMPARATOR.compare(this, other);
return ActionComparator.INSTANCE.compare(this, other);
}
public boolean matchesSearch(String query) {
@@ -171,13 +162,7 @@ public class LoggedAction implements Action {
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + getTimestamp().hashCode();
result = result * PRIME + getSource().hashCode();
result = result * PRIME + getTarget().hashCode();
result = result * PRIME + getDescription().hashCode();
return result;
return Objects.hash(getTimestamp(), getSource(), getTarget(), getDescription());
}
private static final class SourceImpl implements Source {
@@ -265,36 +250,43 @@ public class LoggedAction implements Action {
private Target.Type targetType = null;
private String description = null;
@Override
public @NonNull Builder timestamp(@NonNull Instant timestamp) {
this.timestamp = timestamp.getEpochSecond();
return this;
}
@Override
public @NonNull Builder source(@NonNull UUID source) {
this.sourceUniqueId = Objects.requireNonNull(source, "source");
return this;
}
@Override
public @NonNull Builder sourceName(@NonNull String sourceName) {
this.sourceName = Objects.requireNonNull(sourceName, "sourceName");
return this;
}
@Override
public @NonNull Builder targetType(Action.Target.Type type) {
this.targetType = Objects.requireNonNull(type, "type");
return this;
}
@Override
public @NonNull Builder target(UUID target) {
this.targetUniqueId = target; // nullable
return this;
}
@Override
public @NonNull Builder targetName(@NonNull String targetName) {
this.targetName = Objects.requireNonNull(targetName, "targetName");
return this;
}
@Override
public @NonNull Builder description(@NonNull String description) {
this.description = Objects.requireNonNull(description, "description");
return this;
@@ -346,6 +338,8 @@ public class LoggedAction implements Action {
}
parts.add(context.getKey() + "=" + context.getValue());
}
} else if (o instanceof Duration) {
parts.add(DurationFormatter.CONCISE.format((Duration) o));
} else {
parts.add(String.valueOf(o));
}
@@ -38,13 +38,13 @@ import me.lucko.luckperms.common.api.implementation.ApiQueryOptionsRegistry;
import me.lucko.luckperms.common.api.implementation.ApiTrackManager;
import me.lucko.luckperms.common.api.implementation.ApiUserManager;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.messaging.LuckPermsMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.event.EventBus;
import net.luckperms.api.messaging.MessagingService;
import net.luckperms.api.messenger.MessengerProvider;
import net.luckperms.api.metastacking.MetaStackFactory;
@@ -140,7 +140,7 @@ public class LuckPermsApiProvider implements LuckPerms {
}
@Override
public @NonNull EventBus getEventBus() {
public @NonNull AbstractEventBus<?> getEventBus() {
return this.plugin.getEventDispatcher().getEventBus();
}
@@ -116,8 +116,8 @@ public class ApiContextManager implements net.luckperms.api.context.ContextManag
}
@Override
public void invalidateCache(@NonNull Object subject) {
public void signalContextUpdate(@NonNull Object subject) {
Objects.requireNonNull(subject, "subject");
this.handle.invalidateCache(checkType(subject));
this.handle.signalContextUpdate(checkType(subject));
}
}
@@ -115,7 +115,7 @@ public class ApiGroupManager extends ApiAbstractManager<Group, net.luckperms.api
@Deprecated
public @NonNull CompletableFuture<List<HeldNode<String>>> getWithPermission(@NonNull String permission) {
Objects.requireNonNull(permission, "permission");
return (CompletableFuture) this.plugin.getStorage().getUsersWithPermission(StandardNodeMatchers.key(permission));
return (CompletableFuture) this.plugin.getStorage().searchGroupNodes(StandardNodeMatchers.key(permission));
}
@SuppressWarnings("unchecked")
@@ -123,7 +123,7 @@ public class ApiGroupManager extends ApiAbstractManager<Group, net.luckperms.api
public @NonNull <T extends Node> CompletableFuture<Map<String, Collection<T>>> searchAll(@NonNull NodeMatcher<? extends T> matcher) {
Objects.requireNonNull(matcher, "matcher");
ConstraintNodeMatcher<? extends T> constraint = (ConstraintNodeMatcher<? extends T>) matcher;
return this.plugin.getStorage().getGroupsWithPermission(constraint).thenApply(list -> {
return this.plugin.getStorage().searchGroupNodes(constraint).thenApply(list -> {
ImmutableListMultimap.Builder<String, T> builder = ImmutableListMultimap.builder();
for (NodeEntry<String, ? extends T> row : list) {
builder.put(row.getHolder(), row.getNode());
@@ -174,7 +174,7 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo
@Override
public @NonNull Map<ImmutableContextSet, Collection<Node>> toMap() {
return ApiPermissionHolder.this.handle.getData(this.dataType).immutable().asMap();
return ApiPermissionHolder.this.handle.getData(this.dataType).asMap();
}
@Override
@@ -52,7 +52,7 @@ public class ApiPlatform implements Platform, PluginMetadata {
@Override
public @NonNull String getApiVersion() {
return "5.0";
return "5.2";
}
@Override
@@ -35,7 +35,6 @@ import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import net.luckperms.api.model.data.DataMutateResult;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.NodeEqualityPredicate;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -72,10 +71,7 @@ public class ApiUser extends ApiPermissionHolder implements net.luckperms.api.mo
@Override
public @NonNull String getPrimaryGroup() {
QueryOptions queryOptions = this.handle.getPlugin().getQueryOptionsForUser(this.handle)
.orElseGet(() -> this.handle.getPlugin().getContextManager().getStaticQueryOptions());
String value = this.handle.getCachedData().getMetaData(queryOptions).getPrimaryGroup(MetaCheckEvent.Origin.LUCKPERMS_API);
String value = this.handle.getCachedData().getMetaData(this.handle.getQueryOptions()).getPrimaryGroup(MetaCheckEvent.Origin.LUCKPERMS_API);
Objects.requireNonNull(value, "value"); // assert nonnull
return value;
}
@@ -114,6 +114,12 @@ public class ApiUserManager extends ApiAbstractManager<User, net.luckperms.api.m
return this.plugin.getStorage().savePlayerData(uniqueId, username);
}
@Override
public @NonNull CompletableFuture<Void> deletePlayerData(@NonNull UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uniqueId");
return this.plugin.getStorage().deletePlayerData(uniqueId);
}
@Override
public @NonNull CompletableFuture<Set<UUID>> getUniqueUsers() {
return this.plugin.getStorage().getUniqueUsers();
@@ -124,7 +130,7 @@ public class ApiUserManager extends ApiAbstractManager<User, net.luckperms.api.m
@Deprecated
public @NonNull CompletableFuture<List<HeldNode<UUID>>> getWithPermission(@NonNull String permission) {
Objects.requireNonNull(permission, "permission");
return (CompletableFuture) this.plugin.getStorage().getUsersWithPermission(StandardNodeMatchers.key(permission));
return (CompletableFuture) this.plugin.getStorage().searchUserNodes(StandardNodeMatchers.key(permission));
}
@SuppressWarnings("unchecked")
@@ -132,7 +138,7 @@ public class ApiUserManager extends ApiAbstractManager<User, net.luckperms.api.m
public <T extends Node> @NonNull CompletableFuture<Map<UUID, Collection<T>>> searchAll(@NonNull NodeMatcher<? extends T> matcher) {
Objects.requireNonNull(matcher, "matcher");
ConstraintNodeMatcher<? extends T> constraint = (ConstraintNodeMatcher<? extends T>) matcher;
return this.plugin.getStorage().getUsersWithPermission(constraint).thenApply(list -> {
return this.plugin.getStorage().searchUserNodes(constraint).thenApply(list -> {
ImmutableListMultimap.Builder<UUID, T> builder = ImmutableListMultimap.builder();
for (NodeEntry<UUID, ? extends T> row : list) {
builder.put(row.getHolder(), row.getNode());
@@ -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;
@@ -42,8 +44,10 @@ import me.lucko.luckperms.common.util.gson.JArray;
import me.lucko.luckperms.common.util.gson.JObject;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -71,20 +75,20 @@ import java.util.zip.GZIPOutputStream;
/**
* Handles export operations
*/
public class Exporter implements Runnable {
public abstract class Exporter implements Runnable {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
private final LuckPermsPlugin plugin;
protected final LuckPermsPlugin plugin;
private final Sender executor;
private final Path filePath;
private final boolean includeUsers;
private final ProgressLogger log;
private final boolean includeGroups;
protected final ProgressLogger log;
public Exporter(LuckPermsPlugin plugin, Sender executor, Path filePath, boolean includeUsers) {
protected Exporter(LuckPermsPlugin plugin, Sender executor, boolean includeUsers, boolean includeGroups) {
this.plugin = plugin;
this.executor = executor;
this.filePath = filePath;
this.includeUsers = includeUsers;
this.includeGroups = includeGroups;
this.log = new ProgressLogger(Message.EXPORT_LOG, Message.EXPORT_LOG_PROGRESS, null);
this.log.addListener(plugin.getConsoleSender());
@@ -93,34 +97,30 @@ public class Exporter implements Runnable {
@Override
public void run() {
JsonObject file = new JsonObject();
file.add("metadata", new JObject()
JsonObject json = new JsonObject();
json.add("metadata", new JObject()
.add("generatedBy", this.executor.getNameWithLocation())
.add("generatedAt", DATE_FORMAT.format(new Date(System.currentTimeMillis())))
.toJson());
this.log.log("Gathering group data...");
file.add("groups", exportGroups());
if (this.includeGroups) {
this.log.log("Gathering group data...");
json.add("groups", exportGroups());
this.log.log("Gathering track data...");
file.add("tracks", exportTracks());
this.log.log("Gathering track data...");
json.add("tracks", exportTracks());
}
if (this.includeUsers) {
this.log.log("Gathering user data...");
file.add("users", exportUsers());
json.add("users", exportUsers());
}
this.log.log("Finished gathering data, writing file...");
try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(this.filePath)), StandardCharsets.UTF_8))) {
GsonProvider.prettyPrinting().toJson(file, out);
} catch (IOException e) {
e.printStackTrace();
}
this.log.getListeners().forEach(l -> Message.LOG_EXPORT_SUCCESS.send(l, this.filePath.toFile().getAbsolutePath()));
processOutput(json);
}
protected abstract void processOutput(JsonObject json);
private JsonObject exportGroups() {
JsonObject out = new JsonObject();
List<Group> groups = this.plugin.getGroupManager().getAll().values().stream()
@@ -219,4 +219,57 @@ public class Exporter implements Runnable {
}
return outJson;
}
public static final class SaveFile extends Exporter {
private final Path filePath;
public SaveFile(LuckPermsPlugin plugin, Sender executor, Path filePath, boolean includeUsers, boolean includeGroups) {
super(plugin, executor, includeUsers, includeGroups);
this.filePath = filePath;
}
@Override
protected void processOutput(JsonObject json) {
this.log.log("Finished gathering data, writing file...");
try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(this.filePath)), StandardCharsets.UTF_8))) {
GsonProvider.prettyPrinting().toJson(json, out);
} catch (IOException e) {
e.printStackTrace();
}
this.log.getListeners().forEach(l -> Message.LOG_EXPORT_SUCCESS.send(l, this.filePath.toFile().getAbsolutePath()));
}
}
public static final class WebUpload extends Exporter {
private final String label;
public WebUpload(LuckPermsPlugin plugin, Sender executor, boolean includeUsers, boolean includeGroups, String label) {
super(plugin, executor, includeUsers, includeGroups);
this.label = label;
}
@Override
protected void processOutput(JsonObject json) {
this.log.log("Finished gathering data, uploading data...");
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
try (Writer writer = new OutputStreamWriter(new GZIPOutputStream(bytesOut), StandardCharsets.UTF_8)) {
GsonProvider.prettyPrinting().toJson(json, writer);
} catch (IOException e) {
e.printStackTrace();
}
try {
String pasteId = this.plugin.getBytebin().postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE, false).key();
this.log.getListeners().forEach(l -> Message.EXPORT_CODE.send(l, pasteId, this.label, pasteId));
} catch (UnsuccessfulRequestException e) {
this.log.getListeners().forEach(l -> Message.EXPORT_HTTP_REQUEST_FAILURE.send(l, e.getResponse().code(), e.getResponse().message()));
} catch (IOException e) {
new RuntimeException("Error uploading data to bytebin", e).printStackTrace();
this.log.getListeners().forEach(Message.EXPORT_HTTP_UNKNOWN_FAILURE::send);
}
}
}
}
@@ -44,6 +44,7 @@ import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -66,8 +67,9 @@ public class Importer implements Runnable {
private final LuckPermsPlugin plugin;
private final Set<Sender> notify;
private final JsonObject data;
private final boolean merge;
public Importer(LuckPermsPlugin plugin, Sender executor, JsonObject data) {
public Importer(LuckPermsPlugin plugin, Sender executor, JsonObject data, boolean merge) {
this.plugin = plugin;
if (executor.isConsole()) {
@@ -76,6 +78,7 @@ public class Importer implements Runnable {
this.notify = ImmutableSet.of(executor, plugin.getConsoleSender());
}
this.data = data;
this.merge = merge;
}
private static final class UserData {
@@ -92,7 +95,11 @@ public class Importer implements Runnable {
private void processGroup(String groupName, Set<Node> nodes) {
Group group = this.plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
group.setNodes(DataType.NORMAL, nodes);
if (this.merge) {
group.mergeNodes(DataType.NORMAL, nodes);
} else {
group.setNodes(DataType.NORMAL, nodes);
}
this.plugin.getStorage().saveGroup(group);
}
@@ -107,11 +114,23 @@ public class Importer implements Runnable {
if (userData.primaryGroup != null) {
user.getPrimaryGroup().setStoredValue(userData.primaryGroup);
}
user.setNodes(DataType.NORMAL, userData.nodes);
if (this.merge) {
user.mergeNodes(DataType.NORMAL, userData.nodes);
} else {
user.setNodes(DataType.NORMAL, userData.nodes);
}
this.plugin.getStorage().saveUser(user).join();
this.plugin.getUserManager().getHouseKeeper().cleanup(user.getUniqueId());
}
private Set<Map.Entry<String, JsonElement>> getDataSection(String id) {
if (this.data.has(id)) {
return this.data.get(id).getAsJsonObject().entrySet();
} else {
return Collections.emptySet();
}
}
@Override
public void run() {
long startTime = System.currentTimeMillis();
@@ -126,16 +145,16 @@ public class Importer implements Runnable {
Map<String, List<String>> tracks = new HashMap<>();
Map<UUID, UserData> users = new HashMap<>();
for (Map.Entry<String, JsonElement> group : this.data.get("groups").getAsJsonObject().entrySet()) {
for (Map.Entry<String, JsonElement> group : getDataSection("groups")) {
groups.put(group.getKey(), NodeJsonSerializer.deserializeNodes(group.getValue().getAsJsonObject().get("nodes").getAsJsonArray()));
}
for (Map.Entry<String, JsonElement> track : this.data.get("tracks").getAsJsonObject().entrySet()) {
for (Map.Entry<String, JsonElement> track : getDataSection("tracks")) {
JsonArray trackGroups = track.getValue().getAsJsonObject().get("groups").getAsJsonArray();
List<String> trackGroupsList = new ArrayList<>();
trackGroups.forEach(g -> trackGroupsList.add(g.getAsString()));
tracks.put(track.getKey(), trackGroupsList);
}
for (Map.Entry<String, JsonElement> user : this.data.get("users").getAsJsonObject().entrySet()) {
for (Map.Entry<String, JsonElement> user : getDataSection("users")) {
JsonObject jsonData = user.getValue().getAsJsonObject();
UUID uuid = UUID.fromString(user.getKey());
@@ -45,7 +45,13 @@ public class PreparedStatementBuilder {
return this;
}
public PreparedStatementBuilder append(char c) {
this.sb.append(c);
return this;
}
public PreparedStatementBuilder variable(String variable) {
this.sb.append('?');
this.variables.add(variable);
return this;
}
@@ -93,7 +93,7 @@ public class UpdateAction implements Action {
@Override
public void appendSql(PreparedStatementBuilder builder) {
builder.append("UPDATE {table} SET " + this.field.getSqlName() + "=?");
builder.append("UPDATE {table} SET " + this.field.getSqlName() + "=");
builder.variable(this.value);
}
}
@@ -55,9 +55,9 @@ public class Constraint {
public void appendSql(PreparedStatementBuilder builder, String field) {
// e.g. field LIKE ?
builder.append(field + " ");
builder.append(field).append(' ');
this.comparison.appendSql(builder);
builder.append(" ?");
builder.append(' ');
builder.variable(this.expressionValue);
}
@@ -29,6 +29,7 @@ import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@@ -75,7 +76,7 @@ public abstract class BufferedRequest<T> {
if (this.processor != null) {
try {
return this.processor.extendAndGetFuture();
} catch (IllegalStateException e) {
} catch (ProcessorAlreadyRanException e) {
// ignore
}
}
@@ -101,8 +102,8 @@ public abstract class BufferedRequest<T> {
*/
protected abstract T perform();
private static class Processor<R> {
private Supplier<R> supplier;
private static final class Processor<R> {
private final Supplier<R> supplier;
private final long delay;
private final TimeUnit unit;
@@ -110,11 +111,11 @@ public abstract class BufferedRequest<T> {
private final SchedulerAdapter schedulerAdapter;
private final Object[] mutex = new Object[0];
private CompletableFuture<R> future = new CompletableFuture<>();
private final CompletableFuture<R> future = new CompletableFuture<>();
private boolean usable = true;
private SchedulerTask scheduledTask;
private BoundTask boundTask = null;
private CompletionTask boundTask = null;
Processor(Supplier<R> supplier, long delay, TimeUnit unit, SchedulerAdapter schedulerAdapter) {
this.supplier = supplier;
@@ -122,19 +123,28 @@ public abstract class BufferedRequest<T> {
this.unit = unit;
this.schedulerAdapter = schedulerAdapter;
rescheduleTask();
scheduleTask();
}
private void rescheduleTask() {
private void rescheduleTask() throws ProcessorAlreadyRanException {
synchronized (this.mutex) {
if (!this.usable) {
throw new IllegalStateException("Processor not usable");
throw new ProcessorAlreadyRanException();
}
if (this.scheduledTask != null) {
this.scheduledTask.cancel();
}
this.boundTask = new BoundTask();
scheduleTask();
}
}
private void scheduleTask() {
this.boundTask = new CompletionTask();
try {
this.scheduledTask = this.schedulerAdapter.asyncLater(this.boundTask, this.delay, this.unit);
} catch (RejectedExecutionException e) {
// If we can't schedule the completion in the future, just do it now.
this.boundTask.run();
}
}
@@ -142,12 +152,12 @@ public abstract class BufferedRequest<T> {
return this.future;
}
CompletableFuture<R> extendAndGetFuture() {
CompletableFuture<R> extendAndGetFuture() throws ProcessorAlreadyRanException {
rescheduleTask();
return this.future;
}
private final class BoundTask implements Runnable {
private final class CompletionTask implements Runnable {
@Override
public void run() {
synchronized (Processor.this.mutex) {
@@ -172,14 +182,11 @@ public abstract class BufferedRequest<T> {
new RuntimeException("Processor " + Processor.this.supplier + " threw an exception whilst computing a result", e).printStackTrace();
Processor.this.future.completeExceptionally(e);
}
// allow supplier and future to be GCed
Processor.this.supplier = null;
Processor.this.future = null;
Processor.this.scheduledTask = null;
Processor.this.boundTask = null;
}
}
}
private static final class ProcessorAlreadyRanException extends Exception {
}
@@ -25,19 +25,16 @@
package me.lucko.luckperms.common.cacheddata;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import me.lucko.luckperms.common.cache.MRUCache;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.cacheddata.type.MetaAccumulator;
import me.lucko.luckperms.common.cacheddata.type.MetaCache;
import me.lucko.luckperms.common.cacheddata.type.PermissionCache;
import me.lucko.luckperms.common.calculator.CalculatorFactory;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.metastacking.SimpleMetaStack;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.cacheddata.CachedData;
import net.luckperms.api.cacheddata.CachedDataManager;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.cacheddata.CachedPermissionData;
@@ -46,31 +43,28 @@ import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.IntFunction;
/**
* Abstract implementation of {@link CachedDataManager}.
*/
public abstract class AbstractCachedDataManager implements CachedDataManager {
/**
* The plugin instance
*/
private final LuckPermsPlugin plugin;
private final Permission permissionDataManager;
private final Meta metaDataManager;
private final AbstractContainer<PermissionCache, CachedPermissionData> permission;
private final AbstractContainer<MetaCache, CachedMetaData> meta;
protected AbstractCachedDataManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.permissionDataManager = new Permission();
this.metaDataManager = new Meta();
this.permission = new AbstractContainer<>(this::calculatePermissions);
this.meta = new AbstractContainer<>(this::calculateMeta);
}
public LuckPermsPlugin getPlugin() {
@@ -79,22 +73,22 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
@Override
public @NonNull Container<CachedPermissionData> permissionData() {
return this.permissionDataManager;
return this.permission;
}
@Override
public @NonNull Container<CachedMetaData> metaData() {
return this.metaDataManager;
return this.meta;
}
@Override
public @NonNull PermissionCache getPermissionData(@NonNull QueryOptions queryOptions) {
return this.permissionDataManager.get(queryOptions);
return this.permission.get(queryOptions);
}
@Override
public @NonNull MetaCache getMetaData(@NonNull QueryOptions queryOptions) {
return this.metaDataManager.get(queryOptions);
return this.meta.get(queryOptions);
}
@Override
@@ -139,11 +133,13 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
/**
* Resolves the owners permissions data for the given {@link QueryOptions}.
*
*
* @param mapFactory a function to create a map instance to return the results in
* @param queryOptions the query options
* @return a map of permissions to back the {@link PermissionCache}
* @param <M> the map type
* @return the resolved permissions
*/
protected abstract Map<String, Boolean> resolvePermissions(QueryOptions queryOptions);
protected abstract <M extends Map<String, Boolean>> M resolvePermissions(IntFunction<M> mapFactory, QueryOptions queryOptions);
/**
* Resolves the owners meta data for the given {@link QueryOptions}.
@@ -153,245 +149,109 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
*/
protected abstract void resolveMeta(MetaAccumulator accumulator, QueryOptions queryOptions);
private PermissionCache calculatePermissions(QueryOptions queryOptions, PermissionCache data) {
private PermissionCache calculatePermissions(QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
CacheMetadata metadata = getMetadataForQueryOptions(queryOptions);
if (data == null) {
CacheMetadata metadata = getMetadataForQueryOptions(queryOptions);
data = new PermissionCache(queryOptions, metadata, getCalculatorFactory());
}
data.setPermissions(resolvePermissions(queryOptions));
return data;
ConcurrentHashMap<String, Boolean> sourcePermissions = resolvePermissions(ConcurrentHashMap::new, queryOptions);
return new PermissionCache(queryOptions, metadata, getCalculatorFactory(), sourcePermissions);
}
private MetaCache calculateMeta(QueryOptions queryOptions, MetaCache data) {
private MetaCache calculateMeta(QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
if (data == null) {
CacheMetadata metadata = getMetadataForQueryOptions(queryOptions);
data = new MetaCache(this.plugin, queryOptions, metadata);
}
CacheMetadata metadata = getMetadataForQueryOptions(queryOptions);
MetaAccumulator accumulator = newAccumulator(queryOptions);
resolveMeta(accumulator, queryOptions);
data.loadMeta(accumulator);
return data;
return new MetaCache(this.plugin, queryOptions, metadata, accumulator);
}
@Override
public final void invalidate() {
this.permissionDataManager.invalidate();
this.metaDataManager.invalidate();
this.permission.invalidate();
this.meta.invalidate();
}
@Override
public final void invalidatePermissionCalculators() {
this.permissionDataManager.cache.synchronous().asMap().values().forEach(PermissionCache::invalidateCache);
this.permission.cache.values().forEach(PermissionCache::invalidateCache);
}
public final void performCacheCleanup() {
this.permissionDataManager.cache.synchronous().cleanUp();
this.metaDataManager.cache.synchronous().cleanUp();
this.permission.cleanup();
this.meta.cleanup();
}
private final class Permission extends MRUCache<RecentPermissionData> implements Container<CachedPermissionData> {
private final AsyncLoadingCache<QueryOptions, PermissionCache> cache = CaffeineFactory.newBuilder()
.expireAfterAccess(2, TimeUnit.MINUTES)
.buildAsync(new PermissionCacheLoader());
private static final class AbstractContainer<C extends I, I extends CachedData> implements Container<I> {
private final Function<QueryOptions, C> cacheLoader;
private final LoadingMap<QueryOptions, C> cache;
public AbstractContainer(Function<QueryOptions, C> cacheLoader) {
this.cacheLoader = cacheLoader;
this.cache = LoadingMap.of(this.cacheLoader);
}
public void cleanup() {
this.cache.values().removeIf(value -> ((UsageTracked) value).usedSince(TimeUnit.MINUTES.toMillis(2)));
}
@Override
public @NonNull PermissionCache get(@NonNull QueryOptions queryOptions) {
public @NonNull C get(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
RecentPermissionData recent = getRecent();
if (recent != null && queryOptions.equals(recent.queryOptions)) {
return recent.permissionData;
}
int modCount = modCount();
PermissionCache data = this.cache.synchronous().get(queryOptions);
offerRecent(modCount, new RecentPermissionData(queryOptions, data));
//noinspection ConstantConditions
C data = this.cache.get(queryOptions);
((UsageTracked) data).recordUsage();
return data;
}
@Override
public @NonNull PermissionCache calculate(@NonNull QueryOptions queryOptions) {
public @NonNull C calculate(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
return calculatePermissions(queryOptions, null);
return this.cacheLoader.apply(queryOptions);
}
@Override
public void recalculate(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
this.cache.synchronous().refresh(queryOptions);
clearRecent();
CompletableFuture.runAsync(() -> {
final C value = this.cacheLoader.apply(queryOptions);
this.cache.put(queryOptions, value);
}, CaffeineFactory.executor());
}
@Override
public @NonNull CompletableFuture<? extends PermissionCache> reload(@NonNull QueryOptions queryOptions) {
public @NonNull CompletableFuture<? extends C> reload(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
// get the previous value - to use when recalculating
CompletableFuture<PermissionCache> previous = this.cache.getIfPresent(queryOptions);
// invalidate the previous value until we're done recalculating
this.cache.remove(queryOptions);
// invalidate any previous setting
this.cache.synchronous().invalidate(queryOptions);
clearRecent();
// if the previous value is already calculated, use it when recalculating.
PermissionCache value = getIfReady(previous);
if (value != null) {
return this.cache.get(queryOptions, c -> calculatePermissions(c, value));
}
// otherwise, just calculate a new value
return this.cache.get(queryOptions);
// request recalculation from the cache
return CompletableFuture.supplyAsync(() -> this.cache.get(queryOptions), CaffeineFactory.executor());
}
@Override
public void recalculate() {
Set<QueryOptions> keys = this.cache.synchronous().asMap().keySet();
Set<QueryOptions> keys = this.cache.keySet();
keys.forEach(this::recalculate);
}
@Override
public @NonNull CompletableFuture<Void> reload() {
Set<QueryOptions> keys = this.cache.synchronous().asMap().keySet();
Set<QueryOptions> keys = this.cache.keySet();
return CompletableFuture.allOf(keys.stream().map(this::reload).toArray(CompletableFuture[]::new));
}
@Override
public void invalidate(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
this.cache.synchronous().invalidate(queryOptions);
clearRecent();
this.cache.remove(queryOptions);
}
@Override
public void invalidate() {
this.cache.synchronous().invalidateAll();
clearRecent();
}
}
private final class Meta extends MRUCache<RecentMetaData> implements Container<CachedMetaData> {
private final AsyncLoadingCache<QueryOptions, MetaCache> cache = CaffeineFactory.newBuilder()
.expireAfterAccess(2, TimeUnit.MINUTES)
.buildAsync(new MetaCacheLoader());
@Override
public @NonNull MetaCache get(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
RecentMetaData recent = getRecent();
if (recent != null && queryOptions.equals(recent.queryOptions)) {
return recent.metaData;
}
int modCount = modCount();
MetaCache data = this.cache.synchronous().get(queryOptions);
offerRecent(modCount, new RecentMetaData(queryOptions, data));
//noinspection ConstantConditions
return data;
}
@Override
public @NonNull MetaCache calculate(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
return calculateMeta(queryOptions, null);
}
@Override
public void recalculate(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
this.cache.synchronous().refresh(queryOptions);
clearRecent();
}
@Override
public @NonNull CompletableFuture<? extends MetaCache> reload(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
// get the previous value - to use when recalculating
CompletableFuture<MetaCache> previous = this.cache.getIfPresent(queryOptions);
// invalidate any previous setting
this.cache.synchronous().invalidate(queryOptions);
clearRecent();
// if the previous value is already calculated, use it when recalculating.
MetaCache value = getIfReady(previous);
if (value != null) {
return this.cache.get(queryOptions, c -> calculateMeta(c, value));
}
// otherwise, just calculate a new value
return this.cache.get(queryOptions);
}
@Override
public void recalculate() {
Set<QueryOptions> keys = this.cache.synchronous().asMap().keySet();
keys.forEach(this::recalculate);
}
@Override
public @NonNull CompletableFuture<Void> reload() {
Set<QueryOptions> keys = this.cache.synchronous().asMap().keySet();
return CompletableFuture.allOf(keys.stream().map(this::reload).toArray(CompletableFuture[]::new));
}
@Override
public void invalidate(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
this.cache.synchronous().invalidate(queryOptions);
clearRecent();
}
@Override
public void invalidate() {
this.cache.synchronous().invalidateAll();
clearRecent();
}
}
private static boolean isReady(@Nullable CompletableFuture<?> future) {
return (future != null) && future.isDone()
&& !future.isCompletedExceptionally()
&& (future.join() != null);
}
/** Returns the current value or null if either not done or failed. */
private static <V> V getIfReady(@Nullable CompletableFuture<V> future) {
return isReady(future) ? future.join() : null;
}
private final class PermissionCacheLoader implements CacheLoader<QueryOptions, PermissionCache> {
@Override
public PermissionCache load(@NonNull QueryOptions queryOptions) {
return calculatePermissions(queryOptions, null);
}
@Override
public PermissionCache reload(@NonNull QueryOptions queryOptions, @NonNull PermissionCache oldData) {
return calculatePermissions(queryOptions, oldData);
}
}
private final class MetaCacheLoader implements CacheLoader<QueryOptions, MetaCache> {
@Override
public MetaCache load(@NonNull QueryOptions queryOptions) {
return calculateMeta(queryOptions, null);
}
@Override
public MetaCache reload(@NonNull QueryOptions queryOptions, @NonNull MetaCache oldData) {
return calculateMeta(queryOptions, oldData);
this.cache.clear();
}
}
@@ -400,37 +260,19 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
MetaStackDefinition.PREFIX_STACK_KEY :
MetaStackDefinition.SUFFIX_STACK_KEY
).orElse(null);
if (stack == null) {
stack = getDefaultMetaStackDefinition(type);
}
return stack;
}
private MetaAccumulator newAccumulator(QueryOptions queryOptions) {
return new MetaAccumulator(
new SimpleMetaStack(getMetaStackDefinition(queryOptions, ChatMetaType.PREFIX), ChatMetaType.PREFIX),
new SimpleMetaStack(getMetaStackDefinition(queryOptions, ChatMetaType.SUFFIX), ChatMetaType.SUFFIX)
getMetaStackDefinition(queryOptions, ChatMetaType.PREFIX),
getMetaStackDefinition(queryOptions, ChatMetaType.SUFFIX)
);
}
private static final class RecentPermissionData {
final QueryOptions queryOptions;
final PermissionCache permissionData;
RecentPermissionData(QueryOptions queryOptions, PermissionCache permissionData) {
this.queryOptions = queryOptions;
this.permissionData = permissionData;
}
}
private static final class RecentMetaData {
final QueryOptions queryOptions;
final MetaCache metaData;
RecentMetaData(QueryOptions queryOptions, MetaCache metaData) {
this.queryOptions = queryOptions;
this.metaData = metaData;
}
}
}
@@ -35,6 +35,7 @@ import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.query.QueryOptions;
import java.util.Map;
import java.util.function.IntFunction;
/**
* Holds an easily accessible cache of a holders data in a number of contexts
@@ -74,8 +75,8 @@ public abstract class HolderCachedDataManager<T extends PermissionHolder> extend
}
@Override
protected Map<String, Boolean> resolvePermissions(QueryOptions queryOptions) {
return this.holder.exportPermissions(queryOptions, true, getPlugin().getConfiguration().get(ConfigKeys.APPLYING_SHORTHAND));
protected <M extends Map<String, Boolean>> M resolvePermissions(IntFunction<M> mapFactory, QueryOptions queryOptions) {
return this.holder.exportPermissions(mapFactory, queryOptions, true, getPlugin().getConfiguration().get(ConfigKeys.APPLYING_SHORTHAND));
}
@Override
@@ -23,36 +23,16 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.cache;
package me.lucko.luckperms.common.cacheddata;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class UsageTracked {
private long lastUsed = System.currentTimeMillis();
/**
* Implementation of a most-recently-used cache with a mod counter, to prevent race conditions
* occurring when the cache is cleared whilst a calculation is taking place.
*
* @param <T> the cached type
*/
public abstract class MRUCache<T> {
private volatile T recent;
private final AtomicInteger modCount = new AtomicInteger();
protected int modCount() {
return this.modCount.get();
public void recordUsage() {
this.lastUsed = System.currentTimeMillis();
}
protected T getRecent() {
return this.recent;
}
protected void offerRecent(int validAt, T offer) {
if (validAt == this.modCount.get()) {
this.recent = offer;
}
}
protected void clearRecent() {
this.recent = null;
this.modCount.incrementAndGet();
public boolean usedSince(long duration) {
return this.lastUsed > (System.currentTimeMillis() - duration);
}
}
@@ -29,11 +29,10 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.metastacking.MetaStack;
import me.lucko.luckperms.common.metastacking.SimpleMetaStack;
import me.lucko.luckperms.common.node.types.Weight;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.metastacking.MetaStackDefinition;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.MetaNode;
@@ -55,8 +54,8 @@ public class MetaAccumulator {
public static MetaAccumulator makeFromConfig(LuckPermsPlugin plugin) {
return new MetaAccumulator(
new SimpleMetaStack(plugin.getConfiguration().get(ConfigKeys.PREFIX_FORMATTING_OPTIONS), ChatMetaType.PREFIX),
new SimpleMetaStack(plugin.getConfiguration().get(ConfigKeys.SUFFIX_FORMATTING_OPTIONS), ChatMetaType.SUFFIX)
plugin.getConfiguration().get(ConfigKeys.PREFIX_FORMATTING_OPTIONS),
plugin.getConfiguration().get(ConfigKeys.SUFFIX_FORMATTING_OPTIONS)
);
}
@@ -80,17 +79,21 @@ public class MetaAccumulator {
private int weight = 0;
private String primaryGroup;
private final MetaStack prefixStack;
private final MetaStack suffixStack;
private final MetaStackDefinition prefixDefinition;
private final MetaStackDefinition suffixDefinition;
private final MetaStackAccumulator prefixAccumulator;
private final MetaStackAccumulator suffixAccumulator;
public MetaAccumulator(MetaStack prefixStack, MetaStack suffixStack) {
Objects.requireNonNull(prefixStack, "prefixStack");
Objects.requireNonNull(suffixStack, "suffixStack");
public MetaAccumulator(MetaStackDefinition prefixDefinition, MetaStackDefinition suffixDefinition) {
Objects.requireNonNull(prefixDefinition, "prefixDefinition");
Objects.requireNonNull(suffixDefinition, "suffixDefinition");
this.meta = ArrayListMultimap.create();
this.prefixes = new TreeMap<>(Comparator.reverseOrder());
this.suffixes = new TreeMap<>(Comparator.reverseOrder());
this.prefixStack = prefixStack;
this.suffixStack = suffixStack;
this.prefixDefinition = prefixDefinition;
this.suffixDefinition = suffixDefinition;
this.prefixAccumulator = new MetaStackAccumulator(this.prefixDefinition, ChatMetaType.PREFIX);
this.suffixAccumulator = new MetaStackAccumulator(this.suffixDefinition, ChatMetaType.SUFFIX);
}
private void ensureState(State state) {
@@ -125,7 +128,7 @@ public class MetaAccumulator {
public void accumulateNode(Node n) {
ensureState(State.ACCUMULATING);
if (n instanceof MetaNode) {
MetaNode mn = (MetaNode) n;
this.meta.put(mn.getMetaKey(), mn.getMetaValue());
@@ -134,13 +137,13 @@ public class MetaAccumulator {
if (n instanceof PrefixNode) {
PrefixNode pn = (PrefixNode) n;
this.prefixes.putIfAbsent(pn.getPriority(), pn.getMetaValue());
this.prefixStack.accumulateToAll(pn);
this.prefixAccumulator.offer(pn);
}
if (n instanceof SuffixNode) {
SuffixNode pn = (SuffixNode) n;
this.suffixes.putIfAbsent(pn.getPriority(), pn.getMetaValue());
this.suffixStack.accumulateToAll(pn);
this.suffixAccumulator.offer(pn);
}
}
@@ -191,14 +194,24 @@ public class MetaAccumulator {
return this.primaryGroup;
}
public MetaStack getPrefixStack() {
public MetaStackDefinition getPrefixDefinition() {
ensureState(State.COMPLETE);
return this.prefixStack;
return this.prefixDefinition;
}
public MetaStack getSuffixStack() {
public MetaStackDefinition getSuffixDefinition() {
ensureState(State.COMPLETE);
return this.suffixStack;
return this.suffixDefinition;
}
public String getPrefix() {
ensureState(State.COMPLETE);
return this.prefixAccumulator.toFormattedString();
}
public String getSuffix() {
ensureState(State.COMPLETE);
return this.suffixAccumulator.toFormattedString();
}
@Override
@@ -209,7 +222,7 @@ public class MetaAccumulator {
"suffixes=" + this.suffixes + ", " +
"weight=" + this.weight + ", " +
"primaryGroup=" + this.primaryGroup + ", " +
"prefixStack=" + this.prefixStack + ", " +
"suffixStack=" + this.suffixStack + ")";
"prefixStack=" + this.prefixAccumulator + ", " +
"suffixStack=" + this.suffixAccumulator + ")";
}
}
@@ -56,8 +56,8 @@ public class MetaCache extends SimpleMetaCache implements CachedMetaData {
/** The object name passed to the verbose handler when checks are made */
private final String verboseCheckTarget;
public MetaCache(LuckPermsPlugin plugin, QueryOptions queryOptions, CacheMetadata metadata) {
super(plugin, queryOptions);
public MetaCache(LuckPermsPlugin plugin, QueryOptions queryOptions, CacheMetadata metadata, MetaAccumulator sourceMeta) {
super(plugin, queryOptions, sourceMeta);
this.plugin = plugin;
this.metadata = metadata;
@@ -68,28 +68,40 @@ public class MetaCache extends SimpleMetaCache implements CachedMetaData {
}
}
@Override
public String getMetaValue(String key, MetaCheckEvent.Origin origin) {
String value = super.getMetaValue(key, origin);
this.plugin.getVerboseHandler().offerMetaCheckEvent(origin, this.verboseCheckTarget, this.metadata.getQueryOptions(), key, String.valueOf(value));
return value;
}
@Override
public String getPrefix(MetaCheckEvent.Origin origin) {
String value = super.getPrefix(origin);
this.plugin.getVerboseHandler().offerMetaCheckEvent(origin, this.verboseCheckTarget, this.metadata.getQueryOptions(), Prefix.NODE_KEY, String.valueOf(value));
return value;
}
@Override
public String getSuffix(MetaCheckEvent.Origin origin) {
String value = super.getSuffix(origin);
this.plugin.getVerboseHandler().offerMetaCheckEvent(origin, this.verboseCheckTarget, this.metadata.getQueryOptions(), Suffix.NODE_KEY, String.valueOf(value));
return value;
}
@Override
public Map<String, List<String>> getMeta(MetaCheckEvent.Origin origin) {
return new MonitoredMetaMap(super.getMeta(origin), origin);
}
@Override
public int getWeight(MetaCheckEvent.Origin origin) {
int value = super.getWeight(origin);
this.plugin.getVerboseHandler().offerMetaCheckEvent(origin, this.verboseCheckTarget, this.metadata.getQueryOptions(), "weight", String.valueOf(value));
return value;
}
@Override
public @Nullable String getPrimaryGroup(MetaCheckEvent.Origin origin) {
String value = super.getPrimaryGroup(origin);
this.plugin.getVerboseHandler().offerMetaCheckEvent(origin, this.verboseCheckTarget, this.metadata.getQueryOptions(), "primarygroup", String.valueOf(value));
@@ -0,0 +1,120 @@
/*
* 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.cacheddata.type;
import net.luckperms.api.metastacking.MetaStackDefinition;
import net.luckperms.api.metastacking.MetaStackElement;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.types.ChatMetaNode;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class MetaStackAccumulator {
private final MetaStackDefinition definition;
private final List<Entry> entries;
public MetaStackAccumulator(MetaStackDefinition definition, ChatMetaType targetType) {
this.definition = definition;
List<MetaStackElement> elements = definition.getElements();
this.entries = new ArrayList<>(elements.size());
for (MetaStackElement element : elements) {
this.entries.add(new Entry(element, targetType));
}
}
public void offer(ChatMetaNode<?, ?> node) {
for (Entry entry : this.entries) {
entry.offer(node);
}
}
public String toFormattedString() {
List<String> elements = new LinkedList<>();
for (Entry entry : this.entries) {
ChatMetaNode<?, ?> node = entry.getNode();
if (node != null) {
elements.add(node.getMetaValue());
}
}
if (elements.isEmpty()) {
return null;
}
this.definition.getDuplicateRemovalFunction().processDuplicates(elements);
Iterator<String> it = elements.iterator();
if (!it.hasNext()) {
return null;
}
StringBuilder sb = new StringBuilder();
sb.append(this.definition.getStartSpacer());
// append first - we've checked hasNext already
sb.append(it.next());
// append rest
while (it.hasNext()){
sb.append(this.definition.getMiddleSpacer());
sb.append(it.next());
}
sb.append(this.definition.getEndSpacer());
return sb.toString();
}
private static final class Entry {
private final MetaStackElement element;
private final ChatMetaType type;
private @Nullable ChatMetaNode<?, ?> current = null;
Entry(MetaStackElement element, ChatMetaType type) {
this.element = element;
this.type = type;
}
public ChatMetaNode<?, ?> getNode() {
return this.current;
}
public boolean offer(ChatMetaNode<?, ?> node) {
if (this.element.shouldAccumulate(this.type, node, this.current)) {
this.current = node;
return true;
}
return false;
}
}
}
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.cacheddata.type;
import me.lucko.luckperms.common.cacheddata.CacheMetadata;
import me.lucko.luckperms.common.cacheddata.UsageTracked;
import me.lucko.luckperms.common.calculator.CalculatorFactory;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.calculator.result.TristateResult;
@@ -44,7 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Holds cached permissions data for a given context
*/
public class PermissionCache implements CachedPermissionData {
public class PermissionCache extends UsageTracked implements CachedPermissionData {
/**
* The query options this container is holding data for
@@ -68,13 +69,13 @@ public class PermissionCache implements CachedPermissionData {
*/
private final PermissionCalculator calculator;
public PermissionCache(QueryOptions queryOptions, CacheMetadata metadata, CalculatorFactory calculatorFactory) {
public PermissionCache(QueryOptions queryOptions, CacheMetadata metadata, CalculatorFactory calculatorFactory, ConcurrentHashMap<String, Boolean> sourcePermissions) {
this.queryOptions = queryOptions;
this.permissions = new ConcurrentHashMap<>();
this.permissions = sourcePermissions;
this.permissionsUnmodifiable = Collections.unmodifiableMap(this.permissions);
this.calculator = calculatorFactory.build(queryOptions, metadata);
this.calculator.setSourcePermissions(this.permissions); // Initial setup.
this.calculator.setSourcePermissions(this.permissions);
}
@Override
@@ -82,19 +83,6 @@ public class PermissionCache implements CachedPermissionData {
this.calculator.invalidateCache();
}
private void setPermissionsInternal(Map<String, Boolean> permissions) {
this.permissions.clear();
this.permissions.putAll(permissions);
this.calculator.setSourcePermissions(this.permissions);
invalidateCache();
}
public void setPermissions(Map<String, Boolean> toApply) {
if (!this.permissions.equals(toApply)) {
setPermissionsInternal(toApply);
}
}
public PermissionCalculator getCalculator() {
return this.calculator;
}
@@ -30,8 +30,8 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Multimaps;
import me.lucko.luckperms.common.cacheddata.UsageTracked;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.metastacking.MetaStack;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
@@ -51,7 +51,7 @@ import java.util.SortedMap;
/**
* Holds cached meta for a given context
*/
public class SimpleMetaCache implements CachedMetaData {
public class SimpleMetaCache extends UsageTracked implements CachedMetaData {
private final LuckPermsPlugin plugin;
@@ -59,21 +59,22 @@ public class SimpleMetaCache implements CachedMetaData {
private final QueryOptions queryOptions;
/* The data */
protected Map<String, List<String>> meta = ImmutableMap.of();
protected Map<String, String> flattenedMeta = ImmutableMap.of();
protected SortedMap<Integer, String> prefixes = ImmutableSortedMap.of();
protected SortedMap<Integer, String> suffixes = ImmutableSortedMap.of();
protected String primaryGroup = null;
protected MetaStack prefixStack = null;
protected MetaStack suffixStack = null;
protected final Map<String, List<String>> meta;
protected final Map<String, String> flattenedMeta;
protected final SortedMap<Integer, String> prefixes;
protected final SortedMap<Integer, String> suffixes;
protected final int weight;
protected final String primaryGroup;
private final MetaStackDefinition prefixDefinition;
private final MetaStackDefinition suffixDefinition;
private final String prefix;
private final String suffix;
public SimpleMetaCache(LuckPermsPlugin plugin, QueryOptions queryOptions) {
public SimpleMetaCache(LuckPermsPlugin plugin, QueryOptions queryOptions, MetaAccumulator sourceMeta) {
this.plugin = plugin;
this.queryOptions = queryOptions;
}
public void loadMeta(MetaAccumulator meta) {
this.meta = Multimaps.asMap(ImmutableListMultimap.copyOf(meta.getMeta()));
this.meta = Multimaps.asMap(ImmutableListMultimap.copyOf(sourceMeta.getMeta()));
MetaValueSelector metaValueSelector = this.queryOptions.option(MetaValueSelector.KEY)
.orElseGet(() -> this.plugin.getConfiguration().get(ConfigKeys.META_VALUE_SELECTOR));
@@ -93,11 +94,14 @@ public class SimpleMetaCache implements CachedMetaData {
}
this.flattenedMeta = builder.build();
this.prefixes = ImmutableSortedMap.copyOfSorted(meta.getPrefixes());
this.suffixes = ImmutableSortedMap.copyOfSorted(meta.getSuffixes());
this.primaryGroup = meta.getPrimaryGroup();
this.prefixStack = meta.getPrefixStack();
this.suffixStack = meta.getSuffixStack();
this.prefixes = ImmutableSortedMap.copyOfSorted(sourceMeta.getPrefixes());
this.suffixes = ImmutableSortedMap.copyOfSorted(sourceMeta.getSuffixes());
this.weight = sourceMeta.getWeight();
this.primaryGroup = sourceMeta.getPrimaryGroup();
this.prefixDefinition = sourceMeta.getPrefixDefinition();
this.suffixDefinition = sourceMeta.getSuffixDefinition();
this.prefix = sourceMeta.getPrefix();
this.suffix = sourceMeta.getSuffix();
}
public String getMetaValue(String key, MetaCheckEvent.Origin origin) {
@@ -111,8 +115,7 @@ public class SimpleMetaCache implements CachedMetaData {
}
public String getPrefix(MetaCheckEvent.Origin origin) {
MetaStack prefixStack = this.prefixStack;
return prefixStack == null ? null : prefixStack.toFormattedString();
return this.prefix;
}
@Override
@@ -121,8 +124,7 @@ public class SimpleMetaCache implements CachedMetaData {
}
public String getSuffix(MetaCheckEvent.Origin origin) {
MetaStack suffixStack = this.suffixStack;
return suffixStack == null ? null : suffixStack.toFormattedString();
return this.suffix;
}
@Override
@@ -149,23 +151,32 @@ public class SimpleMetaCache implements CachedMetaData {
return this.suffixes;
}
public int getWeight(MetaCheckEvent.Origin origin) {
return this.weight;
}
//@Override - not actually exposed in the API atm
public final int getWeight() {
return getWeight(MetaCheckEvent.Origin.LUCKPERMS_API);
}
public @Nullable String getPrimaryGroup(MetaCheckEvent.Origin origin) {
return this.primaryGroup;
}
@Override
public final @Nullable String getPrimaryGroup() {
return this.primaryGroup;
return getPrimaryGroup(MetaCheckEvent.Origin.LUCKPERMS_API);
}
@Override
public @NonNull MetaStackDefinition getPrefixStackDefinition() {
return this.prefixStack.getDefinition();
return this.prefixDefinition;
}
@Override
public @NonNull MetaStackDefinition getSuffixStackDefinition() {
return this.suffixStack.getDefinition();
return this.suffixDefinition;
}
@Override
@@ -33,6 +33,7 @@ import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.tabcomplete.CompletionSupplier;
import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.tabcomplete.TabCompletions;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.commands.group.CreateGroup;
import me.lucko.luckperms.common.commands.group.DeleteGroup;
import me.lucko.luckperms.common.commands.group.GroupParentCommand;
@@ -60,6 +61,7 @@ import me.lucko.luckperms.common.commands.user.UserParentCommand;
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.plugin.AbstractLuckPermsPlugin;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.ImmutableCollectors;
@@ -158,14 +160,14 @@ public class CommandManager {
// Handle no arguments
if (arguments.isEmpty() || (arguments.size() == 1 && arguments.get(0).trim().isEmpty())) {
Message.BLANK.send(sender, "&2Running &bLuckPerms v" + this.plugin.getBootstrap().getVersion() + "&2.");
Message.BLANK.send(sender, "&2Running &b" + AbstractLuckPermsPlugin.getPluginName() + " v" + this.plugin.getBootstrap().getVersion() + "&2.");
if (hasPermissionForAny(sender)) {
Message.VIEW_AVAILABLE_COMMANDS_PROMPT.send(sender, label);
return CommandResult.SUCCESS;
}
Collection<? extends Group> groups = this.plugin.getGroupManager().getAll().values();
if (groups.size() <= 1 && groups.stream().allMatch(g -> g.normalData().immutable().isEmpty())) {
if (groups.size() <= 1 && groups.stream().allMatch(g -> g.normalData().isEmpty())) {
Message.FIRST_TIME_SETUP.send(sender, label, sender.getName());
} else {
Message.NO_PERMISSION_FOR_SUBCOMMANDS.send(sender);
@@ -199,7 +201,7 @@ public class CommandManager {
// Try to execute the command.
CommandResult result;
try {
result = main.execute(this.plugin, sender, null, arguments, label);
result = main.execute(this.plugin, sender, null, new ArgumentList(arguments), label);
} catch (CommandException e) {
result = e.handle(sender, label, main);
} catch (Throwable e) {
@@ -223,14 +225,14 @@ public class CommandManager {
.from(1, partial -> mains.stream()
.filter(m -> m.getName().equalsIgnoreCase(arguments.get(0)))
.findFirst()
.map(cmd -> cmd.tabComplete(this.plugin, sender, arguments.subList(1, arguments.size())))
.map(cmd -> cmd.tabComplete(this.plugin, sender, new ArgumentList(arguments.subList(1, arguments.size()))))
.orElse(Collections.emptyList())
)
.complete(arguments);
}
private void sendCommandUsage(Sender sender, String label) {
Message.BLANK.send(sender, "&2Running &bLuckPerms v" + this.plugin.getBootstrap().getVersion() + "&2.");
Message.BLANK.send(sender, "&2Running &b" + AbstractLuckPermsPlugin.getPluginName() + " v" + this.plugin.getBootstrap().getVersion() + "&2.");
this.mainCommands.values().stream()
.filter(Command::shouldDisplay)
.filter(c -> c.isAuthorized(sender))
@@ -312,7 +314,7 @@ public class CommandManager {
String rewrite = rewrites.apply(arg);
if (rewrite != null) {
args.remove(i);
args.add(0, rewrite);
args.add(i, rewrite);
}
}
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.command.abstraction;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.locale.command.Argument;
import me.lucko.luckperms.common.locale.command.LocalizedCommandSpec;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -145,10 +146,10 @@ public abstract class Command<T> {
}
// Main execution method for the command.
public abstract CommandResult execute(LuckPermsPlugin plugin, Sender sender, T t, List<String> args, String label) throws CommandException;
public abstract CommandResult execute(LuckPermsPlugin plugin, Sender sender, T target, ArgumentList args, String label) throws CommandException;
// Tab completion method - default implementation is provided as some commands do not provide tab completions.
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, List<String> args) {
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) {
return Collections.emptyList();
}
@@ -33,7 +33,7 @@ import me.lucko.luckperms.common.sender.Sender;
*/
public abstract class CommandException extends Exception {
public abstract CommandResult handle(Sender sender);
protected abstract CommandResult handle(Sender sender);
public CommandResult handle(Sender sender, String label, Command<?> command) {
return handle(sender);
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.command.abstraction;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.locale.command.Argument;
import me.lucko.luckperms.common.locale.command.LocalizedCommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
@@ -71,9 +72,9 @@ public abstract class GenericChildCommand {
this.argumentCheck = argumentCheck;
}
public abstract CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHolder holder, List<String> args, String label, CommandPermission permission) throws CommandException;
public abstract CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHolder target, ArgumentList args, String label, CommandPermission permission) throws CommandException;
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, List<String> args) {
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) {
return Collections.emptyList();
}
@@ -28,6 +28,7 @@ package me.lucko.luckperms.common.command.abstraction;
import me.lucko.luckperms.common.command.CommandResult;
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.locale.command.LocalizedCommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.HolderType;
@@ -58,7 +59,7 @@ public class GenericParentCommand<T extends PermissionHolder> extends ChildComma
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, T holder, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, T holder, ArgumentList args, String label) {
if (args.isEmpty()) {
sendUsageDetailed(sender, label);
return CommandResult.INVALID_ARGS;
@@ -94,7 +95,7 @@ public class GenericParentCommand<T extends PermissionHolder> extends ChildComma
}
@Override
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, List<String> args) {
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) {
return TabCompleter.create()
.at(0, CompletionSupplier.startsWith(() -> this.children.stream()
.filter(s -> s.isAuthorized(sender, this.type))
@@ -28,6 +28,7 @@ package me.lucko.luckperms.common.command.abstraction;
import me.lucko.luckperms.common.command.CommandResult;
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.locale.command.LocalizedCommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -59,7 +60,7 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Void ignored, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
// check if required argument and/or subcommand is missing
if (args.size() < this.type.minArgs) {
sendUsage(sender, label);
@@ -118,7 +119,7 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
}
@Override
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, List<String> args) {
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) {
switch (this.type) {
case TAKES_ARGUMENT_FOR_TARGET:
return TabCompleter.create()
@@ -27,13 +27,13 @@ package me.lucko.luckperms.common.command.abstraction;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.locale.command.Argument;
import me.lucko.luckperms.common.locale.command.LocalizedCommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import java.util.List;
import java.util.function.Predicate;
/**
@@ -46,11 +46,11 @@ public abstract class SingleCommand extends Command<Void> {
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Void v, List<String> args, String label) throws CommandException {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) throws CommandException {
return execute(plugin, sender, args, label);
}
public abstract CommandResult execute(LuckPermsPlugin plugin, Sender sender, List<String> args, String label) throws CommandException;
public abstract CommandResult execute(LuckPermsPlugin plugin, Sender sender, ArgumentList args, String label) throws CommandException;
@Override
public void sendUsage(Sender sender, String label) {
@@ -37,6 +37,7 @@ public enum CommandPermission {
EDITOR("editor", Type.NONE),
DEBUG("debug", Type.NONE),
VERBOSE("verbose", Type.NONE),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE),
TREE("tree", Type.NONE),
SEARCH("search", Type.NONE),
CHECK("check", Type.NONE),
@@ -49,6 +49,18 @@ public interface CompletionSupplier {
return partial -> stringsSupplier.get().filter(TabCompleter.startsWithIgnoreCase(partial)).collect(Collectors.toList());
}
static CompletionSupplier contains(String... strings) {
return contains(() -> Arrays.stream(strings));
}
static CompletionSupplier contains(Collection<String> strings) {
return contains(strings::stream);
}
static CompletionSupplier contains(Supplier<Stream<String>> stringsSupplier) {
return partial -> stringsSupplier.get().filter(TabCompleter.containsIgnoreCase(partial)).collect(Collectors.toList());
}
List<String> supplyCompletions(String partial);
}
@@ -107,4 +107,13 @@ public class TabCompleter {
};
}
static Predicate<String> containsIgnoreCase(String substring) {
return string -> {
if (string.length() < substring.length()) {
return false;
}
return string.toLowerCase().contains(substring.toLowerCase());
};
}
}
@@ -99,7 +99,7 @@ public final class TabCompletions {
int index = partial.indexOf('=');
if (index == -1) {
// cursor is specifying the key
return CompletionSupplier.startsWith(potentialContexts.toMap().keySet()).supplyCompletions(partial);
return CompletionSupplier.contains(potentialContexts.toMap().keySet()).supplyCompletions(partial);
}
// cursor is specifying the value
@@ -0,0 +1,91 @@
/*
* 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.command.utils;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.Command;
import me.lucko.luckperms.common.command.abstraction.CommandException;
import me.lucko.luckperms.common.command.abstraction.GenericChildCommand;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.sender.Sender;
public abstract class ArgumentException extends CommandException {
public static class DetailedUsage extends ArgumentException {
@Override
protected CommandResult handle(Sender sender) {
throw new UnsupportedOperationException();
}
@Override
public CommandResult handle(Sender sender, String label, Command<?> command) {
command.sendDetailedUsage(sender, label);
return CommandResult.INVALID_ARGS;
}
@Override
public CommandResult handle(Sender sender, GenericChildCommand command) {
command.sendDetailedUsage(sender);
return CommandResult.INVALID_ARGS;
}
}
public static class PastDate extends ArgumentException {
@Override
protected CommandResult handle(Sender sender) {
Message.PAST_DATE_ERROR.send(sender);
return CommandResult.INVALID_ARGS;
}
}
public static class InvalidDate extends ArgumentException {
private final String invalidDate;
public InvalidDate(String invalidDate) {
this.invalidDate = invalidDate;
}
@Override
protected CommandResult handle(Sender sender) {
Message.ILLEGAL_DATE_ERROR.send(sender, this.invalidDate);
return CommandResult.INVALID_ARGS;
}
}
public static class InvalidPriority extends ArgumentException {
private final String invalidPriority;
public InvalidPriority(String invalidPriority) {
this.invalidPriority = invalidPriority;
}
@Override
public CommandResult handle(Sender sender) {
Message.META_INVALID_PRIORITY.send(sender, this.invalidPriority);
return CommandResult.INVALID_ARGS;
}
}
}
@@ -0,0 +1,246 @@
/*
* 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.command.utils;
import com.google.common.collect.ForwardingList;
import me.lucko.luckperms.common.command.abstraction.CommandException;
import me.lucko.luckperms.common.commands.user.UserParentCommand;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import me.lucko.luckperms.common.context.contextset.MutableContextSetImpl;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationParser;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.data.TemporaryNodeMergeStrategy;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
/**
* A list of {@link String} arguments, with extra methods to help
* with parsing.
*/
public class ArgumentList extends ForwardingList<String> {
private final List<String> backingList;
public ArgumentList(List<String> backingList) {
this.backingList = backingList;
}
@Override
protected List<String> delegate() {
return this.backingList;
}
public boolean indexOutOfBounds(int index) {
return index < 0 || index >= size();
}
@Override
public String get(int index) throws IndexOutOfBoundsException {
return super.get(index).replace("{SPACE}", " ");
}
public String getOrDefault(int index, String defaultValue) {
if (indexOutOfBounds(index)) {
return defaultValue;
}
return get(index);
}
@Override
public @NonNull ArgumentList subList(int fromIndex, int toIndex) {
return new ArgumentList(super.subList(fromIndex, toIndex));
}
public int getIntOrDefault(int index, int defaultValue) {
if (indexOutOfBounds(index)) {
return defaultValue;
}
try {
return Integer.parseInt(get(index));
} catch (NumberFormatException e) {
return defaultValue;
}
}
public String getLowercase(int index, Predicate<? super String> test) throws ArgumentException.DetailedUsage {
String arg = get(index).toLowerCase();
if (!test.test(arg)) {
throw new ArgumentException.DetailedUsage();
}
return arg;
}
public boolean getBooleanOrInsert(int index, boolean defaultValue) {
if (!indexOutOfBounds(index)) {
String arg = get(index);
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("false")) {
return Boolean.parseBoolean(arg);
}
}
add(index, Boolean.toString(defaultValue));
return defaultValue;
}
public int getPriority(int index) throws ArgumentException {
try {
return Integer.parseInt(get(index));
} catch (NumberFormatException e) {
throw new ArgumentException.InvalidPriority(get(index));
}
}
public UUID getUserTarget(int index, LuckPermsPlugin plugin, Sender sender) {
String arg = get(index);
return UserParentCommand.parseTargetUniqueId(arg, plugin, sender);
}
public Duration getDuration(int index) throws ArgumentException {
String arg = get(index);
return parseDuration(arg).orElseThrow(() -> new ArgumentException.InvalidDate(arg));
}
public Duration getDurationOrDefault(int index, Duration defaultValue) throws ArgumentException {
if (indexOutOfBounds(index)) {
return defaultValue;
}
return parseDuration(get(index)).orElse(defaultValue);
}
private static Optional<Duration> parseDuration(String input) throws ArgumentException.PastDate {
try {
long number = Long.parseLong(input);
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Duration duration = checkPastDate(Duration.between(now, Instant.ofEpochSecond(number)));
return Optional.of(duration);
} catch (NumberFormatException e) {
// ignore
}
try {
Duration duration = checkPastDate(DurationParser.parseDuration(input));
return Optional.of(duration);
} catch (IllegalArgumentException e) {
// ignore
}
return Optional.empty();
}
private static Duration checkPastDate(Duration duration) throws ArgumentException.PastDate {
if (duration.isNegative()) {
throw new ArgumentException.PastDate();
}
return duration;
}
public Optional<TemporaryNodeMergeStrategy> getTemporaryModifierAndRemove(int index) {
if (indexOutOfBounds(index)) {
return Optional.empty();
}
TemporaryNodeMergeStrategy strategy = parseTemporaryModifier(get(index));
if (strategy == null) {
return Optional.empty();
}
remove(index);
return Optional.of(strategy);
}
private static TemporaryNodeMergeStrategy parseTemporaryModifier(String s) {
switch (s.toLowerCase()) {
case "accumulate":
return TemporaryNodeMergeStrategy.ADD_NEW_DURATION_TO_EXISTING;
case "replace":
return TemporaryNodeMergeStrategy.REPLACE_EXISTING_IF_DURATION_LONGER;
case "deny":
case "none":
return TemporaryNodeMergeStrategy.NONE;
default:
return null;
}
}
public MutableContextSet getContextOrDefault(int fromIndex, LuckPermsPlugin plugin) throws CommandException {
if (size() <= fromIndex) {
return plugin.getConfiguration().getContextsFile().getDefaultContexts().mutableCopy();
}
return parseContext(fromIndex);
}
public ImmutableContextSet getContextOrEmpty(int fromIndex) {
if (size() <= fromIndex) {
return ImmutableContextSetImpl.EMPTY;
}
return parseContext(fromIndex).immutableCopy();
}
private MutableContextSet parseContext(int fromIndex) {
MutableContextSet contextSet = new MutableContextSetImpl();
List<String> entries = subList(fromIndex, size());
for (int i = 0; i < entries.size(); i++) {
String entry = entries.get(i);
int sep = entry.indexOf('=');
String key;
String value;
if (sep != -1) {
key = entry.substring(0, sep);
value = entry.substring(sep + 1);
} else {
key = i == 1 ? DefaultContextKeys.WORLD_KEY : DefaultContextKeys.SERVER_KEY;
value = entry;
}
if (!Context.isValidKey(key) || !Context.isValidValue(value)) {
continue;
}
contextSet.add(key, value);
}
return contextSet;
}
}
@@ -1,279 +0,0 @@
/*
* 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.command.utils;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.Command;
import me.lucko.luckperms.common.command.abstraction.CommandException;
import me.lucko.luckperms.common.command.abstraction.GenericChildCommand;
import me.lucko.luckperms.common.commands.user.UserParentCommand;
import me.lucko.luckperms.common.context.contextset.AbstractContextSet;
import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl;
import me.lucko.luckperms.common.context.contextset.MutableContextSetImpl;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.misc.DataConstraints;
import me.lucko.luckperms.common.util.DurationParser;
import net.luckperms.api.context.Context;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.data.TemporaryNodeMergeStrategy;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* Utility class to help process arguments, and throw checked exceptions if the arguments are invalid.
*/
public class ArgumentParser {
public static String parseString(int index, List<String> args) {
return args.get(index).replace("{SPACE}", " ");
}
public static String parseStringOrElse(int index, List<String> args, String other) {
if (index < 0 || index >= args.size()) {
return other;
}
return args.get(index).replace("{SPACE}", " ");
}
public static int parseIntOrElse(int index, List<String> args, int other) {
if (index < 0 || index >= args.size()) {
return other;
}
try {
return Integer.parseInt(args.get(index));
} catch (NumberFormatException e) {
return other;
}
}
public static String parseName(int index, List<String> args) throws ArgumentException {
String groupName = args.get(index).toLowerCase();
if (!DataConstraints.GROUP_NAME_TEST.test(groupName)) {
throw new DetailedUsageException();
}
return groupName;
}
public static String parseNameWithSpace(int index, List<String> args) throws ArgumentException {
String groupName = args.get(index).toLowerCase();
if (!DataConstraints.GROUP_NAME_TEST_ALLOW_SPACE.test(groupName)) {
throw new DetailedUsageException();
}
return groupName;
}
public static boolean parseBoolean(int index, List<String> args) {
if (index < args.size()) {
String bool = args.get(index);
if (bool.equalsIgnoreCase("true") || bool.equalsIgnoreCase("false")) {
return Boolean.parseBoolean(bool);
}
}
args.add(index, "true");
return true;
}
public static Duration parseDuration(int index, List<String> args) throws ArgumentException {
String input = args.get(index);
Duration duration;
try {
long number = Long.parseLong(input);
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
duration = Duration.between(now, Instant.ofEpochSecond(number));
} catch (NumberFormatException e) {
try {
duration = DurationParser.parseDuration(input);
} catch (IllegalArgumentException e1) {
throw new InvalidDateException(input);
}
}
if (duration.isNegative()) {
throw new PastDateException();
}
return duration;
}
public static TemporaryNodeMergeStrategy parseTemporaryModifier(String s) {
switch (s.toLowerCase()) {
case "accumulate":
return TemporaryNodeMergeStrategy.ADD_NEW_DURATION_TO_EXISTING;
case "replace":
return TemporaryNodeMergeStrategy.REPLACE_EXISTING_IF_DURATION_LONGER;
case "deny":
case "none":
return TemporaryNodeMergeStrategy.NONE;
default:
throw new IllegalArgumentException("Unknown value: " + s);
}
}
public static Optional<TemporaryNodeMergeStrategy> parseTemporaryModifier(int index, List<String> args) {
if (index < 0 || index >= args.size()) {
return Optional.empty();
}
String s = args.get(index);
try {
Optional<TemporaryNodeMergeStrategy> strategy = Optional.of(parseTemporaryModifier(s));
args.remove(index);
return strategy;
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
public static MutableContextSet parseContext(int fromIndex, List<String> args, LuckPermsPlugin plugin) throws CommandException {
if (args.size() <= fromIndex) {
return plugin.getConfiguration().getContextsFile().getDefaultContexts().mutableCopy();
}
return parseContext(fromIndex, args);
}
public static ImmutableContextSet parseContextSponge(int fromIndex, List<String> args) {
if (args.size() <= fromIndex) {
return ImmutableContextSetImpl.EMPTY;
}
return parseContext(fromIndex, args).immutableCopy();
}
private static MutableContextSet parseContext(int fromIndex, List<String> args) {
MutableContextSet contextSet = new MutableContextSetImpl();
List<String> entries = args.subList(fromIndex, args.size());
for (int i = 0; i < entries.size(); i++) {
String entry = entries.get(i);
int sep = entry.indexOf('=');
String key;
String value;
if (sep != -1) {
key = entry.substring(0, sep);
value = entry.substring(sep + 1);
} else {
key = i == 1 ? DefaultContextKeys.WORLD_KEY : DefaultContextKeys.SERVER_KEY;
value = entry;
}
// TODO reconsider a better place to insert / avoid this special case
if (!Context.isValidKey(key) || !Context.isValidValue(value) || AbstractContextSet.isGlobalServerWorldEntry(key, value)) {
continue;
}
contextSet.add(key, value);
}
return contextSet;
}
public static int parsePriority(int index, List<String> args) throws ArgumentException {
try {
return Integer.parseInt(args.get(index));
} catch (NumberFormatException e) {
throw new InvalidPriorityException(args.get(index));
}
}
public static UUID parseUserTarget(int index, List<String> args, LuckPermsPlugin plugin, Sender sender) {
final String target = args.get(index);
return UserParentCommand.parseTargetUniqueId(target, plugin, sender);
}
public abstract static class ArgumentException extends CommandException {
}
public static class DetailedUsageException extends ArgumentException {
@Override
public CommandResult handle(Sender sender) {
throw new UnsupportedOperationException();
}
@Override
public CommandResult handle(Sender sender, String label, Command<?> command) {
command.sendDetailedUsage(sender, label);
return CommandResult.INVALID_ARGS;
}
@Override
public CommandResult handle(Sender sender, GenericChildCommand command) {
command.sendDetailedUsage(sender);
return CommandResult.INVALID_ARGS;
}
}
public static class PastDateException extends ArgumentException {
@Override
public CommandResult handle(Sender sender) {
Message.PAST_DATE_ERROR.send(sender);
return CommandResult.INVALID_ARGS;
}
}
public static class InvalidDateException extends ArgumentException {
private final String invalidDate;
public InvalidDateException(String invalidDate) {
this.invalidDate = invalidDate;
}
@Override
public CommandResult handle(Sender sender) {
Message.ILLEGAL_DATE_ERROR.send(sender, this.invalidDate);
return CommandResult.INVALID_ARGS;
}
}
public static class InvalidPriorityException extends ArgumentException {
private final String invalidPriority;
public InvalidPriorityException(String invalidPriority) {
this.invalidPriority = invalidPriority;
}
@Override
public CommandResult handle(Sender sender) {
Message.META_INVALID_PRIORITY.send(sender, this.invalidPriority);
return CommandResult.INVALID_ARGS;
}
}
}
@@ -25,11 +25,9 @@
package me.lucko.luckperms.common.command.utils;
import java.util.List;
public class SortMode {
public static SortMode determine(List<String> args) {
public static SortMode determine(ArgumentList args) {
SortType type = SortType.PRIORITY;
boolean ascending = true;
for (String arg : args) {
@@ -34,8 +34,6 @@ import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.PermissionHolder;
import net.luckperms.api.node.ChatMetaType;
public class CommandMeta<T extends PermissionHolder> extends GenericParentCommand<T> {
public CommandMeta(LocaleManager locale, HolderType type) {
super(CommandSpec.META.localize(locale), "Meta", type, ImmutableList.<GenericChildCommand>builder()
@@ -44,18 +42,18 @@ public class CommandMeta<T extends PermissionHolder> extends GenericParentComman
.add(new MetaUnset(locale))
.add(new MetaSetTemp(locale))
.add(new MetaUnsetTemp(locale))
.add(new MetaAddChatMeta(locale, ChatMetaType.PREFIX))
.add(new MetaAddChatMeta(locale, ChatMetaType.SUFFIX))
.add(new MetaSetChatMeta(locale, ChatMetaType.PREFIX))
.add(new MetaSetChatMeta(locale, ChatMetaType.SUFFIX))
.add(new MetaRemoveChatMeta(locale, ChatMetaType.PREFIX))
.add(new MetaRemoveChatMeta(locale, ChatMetaType.SUFFIX))
.add(new MetaAddTempChatMeta(locale, ChatMetaType.PREFIX))
.add(new MetaAddTempChatMeta(locale, ChatMetaType.SUFFIX))
.add(new MetaSetTempChatMeta(locale, ChatMetaType.PREFIX))
.add(new MetaSetTempChatMeta(locale, ChatMetaType.SUFFIX))
.add(new MetaRemoveTempChatMeta(locale, ChatMetaType.PREFIX))
.add(new MetaRemoveTempChatMeta(locale, ChatMetaType.SUFFIX))
.add(MetaAddChatMeta.forPrefix(locale))
.add(MetaAddChatMeta.forSuffix(locale))
.add(MetaSetChatMeta.forPrefix(locale))
.add(MetaSetChatMeta.forSuffix(locale))
.add(MetaRemoveChatMeta.forPrefix(locale))
.add(MetaRemoveChatMeta.forSuffix(locale))
.add(MetaAddTempChatMeta.forPrefix(locale))
.add(MetaAddTempChatMeta.forSuffix(locale))
.add(MetaSetTempChatMeta.forPrefix(locale))
.add(MetaSetTempChatMeta.forSuffix(locale))
.add(MetaRemoveTempChatMeta.forPrefix(locale))
.add(MetaRemoveTempChatMeta.forSuffix(locale))
.add(new MetaClear(locale))
.build());
}
@@ -33,11 +33,12 @@ import me.lucko.luckperms.common.command.access.ArgumentPermissions;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.tabcomplete.TabCompletions;
import me.lucko.luckperms.common.command.utils.ArgumentParser;
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.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.command.LocalizedCommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -55,60 +56,75 @@ import net.luckperms.api.node.ChatMetaType;
import java.util.List;
public class MetaAddChatMeta extends GenericChildCommand {
public static MetaAddChatMeta forPrefix(LocaleManager locale) {
return new MetaAddChatMeta(
ChatMetaType.PREFIX,
CommandSpec.META_ADDPREFIX.localize(locale),
"addprefix",
CommandPermission.USER_META_ADD_PREFIX,
CommandPermission.GROUP_META_ADD_PREFIX
);
}
public static MetaAddChatMeta forSuffix(LocaleManager locale) {
return new MetaAddChatMeta(
ChatMetaType.SUFFIX,
CommandSpec.META_ADDSUFFIX.localize(locale),
"addsuffix",
CommandPermission.USER_META_ADD_SUFFIX,
CommandPermission.GROUP_META_ADD_SUFFIX
);
}
private final ChatMetaType type;
public MetaAddChatMeta(LocaleManager locale, ChatMetaType type) {
super(
type == ChatMetaType.PREFIX ? CommandSpec.META_ADDPREFIX.localize(locale) : CommandSpec.META_ADDSUFFIX.localize(locale),
"add" + type.name().toLowerCase(),
type == ChatMetaType.PREFIX ? CommandPermission.USER_META_ADD_PREFIX : CommandPermission.USER_META_ADD_SUFFIX,
type == ChatMetaType.PREFIX ? CommandPermission.GROUP_META_ADD_PREFIX : CommandPermission.GROUP_META_ADD_SUFFIX,
Predicates.inRange(0, 1)
);
private MetaAddChatMeta(ChatMetaType type, LocalizedCommandSpec spec, String name, CommandPermission userPermission, CommandPermission groupPermission) {
super(spec, name, userPermission, groupPermission, Predicates.inRange(0, 1));
this.type = type;
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHolder holder, List<String> args, String label, CommandPermission permission) throws CommandException {
if (ArgumentPermissions.checkModifyPerms(plugin, sender, permission, holder)) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHolder target, ArgumentList args, String label, CommandPermission permission) throws CommandException {
if (ArgumentPermissions.checkModifyPerms(plugin, sender, permission, target)) {
Message.COMMAND_NO_PERMISSION.send(sender);
return CommandResult.NO_PERMISSION;
}
int priority = ArgumentParser.parsePriority(0, args);
String meta = ArgumentParser.parseString(1, args);
MutableContextSet context = ArgumentParser.parseContext(2, args, plugin);
int priority = args.getPriority(0);
String meta = args.get(1);
MutableContextSet context = args.getContextOrDefault(2, plugin);
if (ArgumentPermissions.checkContext(plugin, sender, permission, context) ||
ArgumentPermissions.checkGroup(plugin, sender, holder, context)) {
ArgumentPermissions.checkGroup(plugin, sender, target, context)) {
Message.COMMAND_NO_PERMISSION.send(sender);
return CommandResult.NO_PERMISSION;
}
DataMutateResult result = holder.setNode(DataType.NORMAL, this.type.builder(meta, priority).withContext(context).build(), true);
DataMutateResult result = target.setNode(DataType.NORMAL, this.type.builder(meta, priority).withContext(context).build(), true);
if (result.wasSuccessful()) {
TextComponent.Builder builder = Message.ADD_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
TextComponent.Builder builder = Message.ADD_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), target.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
"¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta,
'¥'
"§3Raw " + this.type.name().toLowerCase() + ": §r" + meta,
'§'
));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());
LoggedAction.build().source(sender).target(holder)
LoggedAction.build().source(sender).target(target)
.description("meta" , "add" + this.type.name().toLowerCase(), priority, meta, context)
.build().submit(plugin, sender);
StorageAssistant.save(holder, sender, plugin);
StorageAssistant.save(target, sender, plugin);
return CommandResult.SUCCESS;
} else {
Message.ALREADY_HAS_CHAT_META.send(sender, holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context));
Message.ALREADY_HAS_CHAT_META.send(sender, target.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context));
return CommandResult.STATE_ERROR;
}
}
@Override
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, List<String> args) {
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) {
return TabCompleter.create()
.from(2, TabCompletions.contexts(plugin))
.complete(args);
Loaded 100 of 314 files, more files were not shown because too many files have changed in this diff. Show more