Compare commits

..
90 Commits
Author SHA1 Message Date
Luck 4fb07ff181 API changes for 3.1 2017-04-10 22:26:20 +01:00
Luck 4631dcf857 Ensure expired permissions are removed when users/groups are loaded for commands 2017-04-10 20:57:59 +01:00
Luck f6f9840eb7 Re-implement bulk updates 2017-04-10 19:33:23 +01:00
Luck 4de8165c95 fix issue with /reloading and permission registration 2017-04-10 18:17:09 +01:00
Luck 242672f49d Remove old bulk edit commands 2017-04-10 14:56:46 +01:00
creator3 c32c975a71 Fix accumulation of temporary perms with a world or server context (#235) 2017-04-08 20:49:10 +01:00
Luck 82e759d708 Make sure world names are lowercase'd 2017-04-08 17:00:36 +01:00
Luck 9de44d2605 Don't attempt to migrate empty permissions 2017-04-07 21:03:27 +01:00
Luck 132d0cf578 Fix export command switchprimarygroup output, and log player uuid if their username isn't included 2017-04-07 19:59:53 +01:00
Luck 029dc9f8d9 Make the standard MySQL driver the default again 2017-04-06 11:30:17 +01:00
Luck 5567b1dad8 Use the mariadb driver by default 2017-04-05 21:23:18 +01:00
Luck ea00ec64af Refactor NodeModel into a more useful class, remove duplicate context serialization methods 2017-04-04 19:19:31 +01:00
Luck 7259e6be0a Fixes for the verbose uploader 2017-04-04 17:17:57 +01:00
Luck 2e40557b39 Add '/lp group <group> listmembers' command - closes #203 2017-04-04 16:07:41 +01:00
Luck e68fc7c558 Improve performance of resolve methods in PermissionHolder, other cleanup 2017-04-04 15:22:25 +01:00
Luck 055dfb000d Only allow one server and one world in context sets parsed from command args 2017-04-04 13:47:50 +01:00
Luck b997ab6e3f Properly sort storage files - closes #227 2017-04-03 10:57:29 +01:00
Luck 25f31d0bb8 Cleanup sponge delegate classes 2017-04-03 10:27:31 +01:00
Luck 4cfd8a7e2c Fix broken command parameter & update default locale file 2017-04-03 09:53:26 +01:00
Luck 0ec19a8fee Implement accumulation of static contexts from a file 2017-04-03 01:42:49 +01:00
Luck 2749563f5d Replace guava caches with caffeine 2017-04-03 00:53:04 +01:00
Luck e7e2e3f7e0 Allow context pairs to be specified in commands, fix misuse of exceptions in the permission holder class 2017-04-02 18:44:05 +01:00
Luck 0a33c0aac5 Fix compatibility with older gson versions - closes #222 2017-04-01 07:26:05 +01:00
Articuno 24e09fd2d9 Add ru_RU.yml (#224) 2017-03-30 18:45:50 +01:00
Luck 4d0499a61e Avoid parsing UUID in sponge world calculator 2017-03-30 18:40:36 +01:00
Luck ae82807139 Add "use-vault-server" config option, depreciate vault primary group override feature 2017-03-27 17:35:28 +01:00
Luck 65147e1935 Remove missing permission nodes before adding new ones when saving users/groups with SQL storage 2017-03-27 16:54:59 +01:00
Luck 8e557d122b Fix some concurrency issues with login handling 2017-03-26 18:40:09 +01:00
Luck 486b19aa90 Cleanup some misc stuff 2017-03-26 16:10:49 +01:00
Luck 14cb34ac95 Force some config options to be read as lower case 2017-03-25 23:23:20 +00:00
Luck abfc23b04d Provide tab completion for rewritten arguments & add some more rules 2017-03-25 20:27:33 +00:00
Luck ebb24aefa1 Fix parent set command also adding default 2017-03-25 20:06:30 +00:00
Luck 2573f46f40 Fix removing temporary permissions/parents/meta in global context 2017-03-25 19:58:56 +00:00
Luck 5dcb8cd73a Output group/track lists in alphabetical order 2017-03-25 19:48:17 +00:00
Luck e4bfe09882 Don't bother loading test classes (#217) 2017-03-25 11:38:02 +00:00
Luck eecbfe04db Add es_ES locale 2017-03-25 11:13:45 +00:00
Luck 671e2adc32 Cleanup remaining (un)setPermission methods 2017-03-25 11:04:25 +00:00
Luck 1137e476dd Rewrite inheritance resolution implementation 2017-03-24 22:18:18 +00:00
Luck cb63b321d0 correct zPermissions migration logging tag 2017-03-23 23:18:03 +00:00
Luck c36b0d2975 Automatically push updates to other servers via the messaging service when commands are ran 2017-03-21 21:58:45 +00:00
Luck f8ad562b95 Rename "debug" classes to "verbose" 2017-03-20 17:22:18 +00:00
Luck c35ab38f36 Slight speed improvement for import processes 2017-03-19 20:52:35 +00:00
Luck 073b775566 Implement nicer json format for Sponge local data 2017-03-19 17:54:12 +00:00
Luck 85c7a7db8d Implement new file layout for YAML / JSON storage files (#211) 2017-03-19 15:37:30 +00:00
Luck 1e134df27d Clarify the file names of H2 and SQLite databases 2017-03-18 23:00:12 +00:00
Luck bdff84885a Enforce that ContextSets use the HashMultimap variant - closes #208 2017-03-18 20:18:18 +00:00
Luck f1a9c3722c Fix PM migration command not being registered 2017-03-18 17:26:44 +00:00
Luck 8b402f6423 Fix NoSuchMethodError being thrown for older Bukkit config classes 2017-03-18 15:34:29 +00:00
Luck a0aec52661 Correct the case of group names when retrieved from Node instances 2017-03-18 14:43:34 +00:00
Luck 407ab43dd1 Update PM migration to support latest version 2017-03-17 23:21:57 +00:00
Luck c2165e0196 Fix primary groups being selected in reverse order 2017-03-17 23:00:37 +00:00
Luck 2e75bc3f3b Add some alternate options for primary group calculation 2017-03-17 22:54:18 +00:00
Luck e91c2ca427 Add method to export Message enum and update language file 2017-03-17 21:17:59 +00:00
Luck 965ff04bcc Fix ClassCastException with MongoDB user search 2017-03-17 20:54:40 +00:00
Luck ac4bd418f3 Rename online-mode config option to use-server-uuids to better reflect/clarify its purpose 2017-03-17 20:51:06 +00:00
Luck e9e844c1f7 Include internal weight value in meta output if not already present 2017-03-13 20:17:54 +00:00
Luck aa2941fcdb Refactor and cleanup commands 2017-03-13 20:03:03 +00:00
Luck dbc909a317 Cleanup migration commands 2017-03-13 19:10:36 +00:00
Luck 11465a4ce9 Fix BungeePerms migration to account for group weights and negated permissions 2017-03-12 20:00:02 +00:00
Luck f5007f94dc Add server version info to the startup banner 2017-03-12 14:59:27 +00:00
Luck d18ac2e6eb Cleanup pom files, maybe improve build speed a bit 2017-03-12 14:28:35 +00:00
Luck b9fc5c39ae Listen for changes in data files and automatically update 2017-03-11 23:05:03 +00:00
Luck 9dc2278083 Cleanup a bit 2017-03-11 21:10:27 +00:00
Luck 281060a972 Make the clone commands work for existing tracks/groups too 2017-03-11 18:30:27 +00:00
Luck 91626f079d Fix mistake in removeprefix/removesuffix command descriptions 2017-03-11 18:26:29 +00:00
Luck 7305c6c54b Migrate group weightings onto the group itself as well as to the chat meta held by it 2017-03-11 18:24:25 +00:00
Luck bce7fa871d Fix getAllNodes cache not working properly, remove pre-process contexts on Bungee & cleanup some unused methods 2017-03-11 17:55:50 +00:00
Luck a35fd80763 remove placeholder expansion from main project 2017-03-11 14:02:17 +00:00
Luck b383f9dfbc Add some more placeholders to the expansion 2017-03-11 13:30:08 +00:00
Luck 680ad3c145 Update hikari version 2017-03-11 10:32:10 +00:00
Luck 7b712e7f90 Update readme 2017-03-10 23:30:08 +00:00
Luck 2462eac497 Run exporter in own thread and process user exports concurrently 2017-03-10 20:44:19 +00:00
Luck bb818c8942 Cleanup powerfulperms migration & remove support for older PP versions 2017-03-09 19:10:48 +00:00
Luck ddc881af11 Add new argument util method for reading context pairs 2017-03-09 17:56:15 +00:00
Luck 838fba9173 Switch all usages of file reader/writers to use java nio methods - closes #204 2017-03-09 17:55:33 +00:00
Luck 0b6f326c18 Fallback to uuid in the search command if the username is unknown 2017-03-07 22:17:49 +00:00
Luck 00895fc7ab Don't send json text on MC 1.7 2017-03-06 18:22:11 +00:00
Luck 07f38aa283 Fix possible issue with context conversion 2017-03-05 22:56:00 +00:00
Luck ecf1d3790c Fix issue where group nodes could be unset using the permission commands, and where users with per-server groups wouldn't be assigned to the default group globally 2017-03-05 19:05:05 +00:00
Luck 53c9e5bb4a Fix race condition with Bukkit join handling where players are denied entry but subsequently login slightly later 2017-03-05 00:59:44 +00:00
Luck b20b03113a Fix groupmanager global group migration on windows systems 2017-03-01 21:26:20 +00:00
Luck ec8273aa9f Change bungeecord leave event priority to wait longer before unloading data 2017-02-26 21:53:03 +00:00
Luck 2862f0dfae Fix creating delimiter patterns 2017-02-26 21:28:40 +00:00
Luck fd4963c74b Fix getUuid and getName API methods returning null for User, Group and Track instances 2017-02-26 20:46:37 +00:00
Luck df99ef4481 misc cleanup 2017-02-26 12:43:35 +00:00
Luck 35d1057673 Refactor sponge commands to use the proxied interfaces (closes #197) 2017-02-26 11:55:59 +00:00
Luck e6a90a18c4 Sort subjects properly 2017-02-26 01:16:54 +00:00
Luck 26afb69a14 Cleanup LuckPermsSubjectData (fixes #194) 2017-02-26 01:00:02 +00:00
Luck e8c0c43e9d misc cleanup 2017-02-24 19:31:45 +00:00
Luck 1707f333bf update javadocs link in readme 2017-02-23 21:59:43 +00:00
328 changed files with 11415 additions and 8560 deletions

No files matched your search

+62 -134
View File
@@ -1,5 +1,5 @@
# LuckPerms Language File
# Locale: en_US
# Locale: en_US (English)
# Author: Luck
prefix: "&7&l[&b&lL&3&lP&7&l] &c"
@@ -9,84 +9,66 @@ log-error: "&7&l[&bLuck&3Perms&7&l] &4[ERROR] {0}"
empty: "{0}"
player-online: "&aOnline"
player-offline: "&cOffline"
loading-error: "Permissions data could not be loaded. Please contact an administrator."
loading-error: "Permissions data could not be loaded. Please try again later."
op-disabled: "&bThe vanilla OP system is disabled on this server."
op-disabled-sponge: "&2Server Operator status has no effect when a permission plugin is installed. Please edit user data directly."
log: "&3LOG &3&l> {0}"
export-log: "&3EXPORT &3&l> &f{0}"
export-log-progress: "&3EXPORT &3&l> &7{0}"
migration-log: "&3MIGRATION &7[&3{0}&7] &3&l> &f{1}"
migration-log-progress: "&3MIGRATION &7[&3{0}&7] &3&l> &7{1}"
command-not-recognised: "Command not recognised."
command-no-permission: "You do not have permission to use this command!"
already-haspermission: "{0} already has this permission!"
does-not-havepermission: "{0} does not have this permission set."
already-has-temp-permission: "{0} already has this permission set temporarily!"
does-not-have-temp-permission: "{0} does not have this permission set temporarily."
user-not-found: "&bUser could not be found."
user-not-online: "&bUser &a{0}&b is not online."
user-no-data: "&bUser &a{0}&b does not have any data loaded."
user-save-success: "&7(User data was saved to storage)"
user-save-error: "There was an error whilst saving the user."
user-create-fail: "There was an error whilst creating a new user."
group-not-found: "&bGroup could not be found."
group-save-success: "&7(Group data was saved to storage)"
group-save-error: "There was an error whilst saving the group."
track-not-found: "&bTrack could not be found."
track-save-success: "&7(Track data was saved to storage)"
track-save-error: "There was an error whilst saving the track."
user-invalid-entry: "&d{0}&c is not a valid username/uuid."
group-invalid-entry: "Group names can only contain alphanumeric characters."
track-invalid-entry: "Track names can only contain alphanumeric characters."
server-invalid-entry: "Server names can only contain alphanumeric characters."
use-inherit-command: "Use the 'parent add' command instead of specifying the node."
use-inherit-command: "Use the 'parent add' and 'parent remove' commands instead of specifying the node."
verbose-invalid-filter: "&cInvalid verbose filter: &f{0}"
verbose-on: "&bVerbose checking output set to &aTRUE &bfor all permissions."
verbose-on_query: "&bVerbose checking output set to &aTRUE &bfor permissions matching the following filters: &f{0}"
verbose-on-query: "&bVerbose checking output set to &aTRUE &bfor permissions matching the following filters: &f{0}"
verbose-off: "&bVerbose checking output set to &cFALSE&b."
verbose-recording-on: "&bVerbose recording set to &aTRUE &bfor all permissions."
verbose-recording-on-query: "&bVerbose recording set to &aTRUE &bfor permissions matching the following filters: &f{0}"
verbose-recording-upload-start: "&bVerbose recording was disabled. Uploading results..."
verbose-recording-url: "&aVerbose results URL:"
tree-upload-start: "&bGenerating permission tree..."
tree-empty: "&aUnable to generate tree. No results were found."
tree-url: "&aPermission Tree URL:"
search-searching: "&aSearching for users and groups with &b{0}&a..."
search-result: "&aFound &b{0}&a entries from &b{1}&a users and &b{2}&a groups."
search-showing-users: "&bShowing user entries:"
search-showing-groups: "&bShowing group entries:"
search-showing-users-with-page: "&bShowing user entries: {0}"
search-showing-groups-with-page: "&bShowing group entries: {0}"
check-result: "&aPermission check result on user &b{0}&a for permission &b{1}&a: &f{2}"
create-success: "&b{0}&a was successfully created."
delete-success: "&b{0}&a was successfully deleted."
rename-success: "&b{0}&a was successfully renamed to &b{1}&a."
clone-success: "&b{0}&a was successfully cloned to &b{1}&a."
clone-success: "&b{0}&a was successfully cloned onto &b{1}&a."
already-inherits: "{0} already inherits '{1}'."
does-not-inherit: "{0} does not inherit '{1}'."
already-temp-inherits: "{0} already temporarily inherits '{1}'."
does-not-temp-inherit: "{0} does not temporarily inherit '{1}'."
track-already-contains: "Track {0} already contains the group '{1}'."
track-does-not-contain: "Track {0} does not contain the group '{1}'."
track-ambiguous-call: "The user specified is a member of multiple groups on this track. Unable to determine their location."
group-already-exists: "That group already exists!"
group-does-not-exist: "That group does not exist!"
group-load-error: "An unexpected error occurred. Group not loaded."
@@ -95,9 +77,7 @@ track-already-exists: "That track already exists!"
track-does-not-exist: "That track does not exist!"
track-load-error: "An unexpected error occurred. Track not loaded."
tracks-load-error: "An unexpected error occurred. Unable to load all tracks."
track-empty: "The track cannot be used as it is empty or contains only one group."
update-task-request: "&bUpdate task scheduled."
update-task-complete: "&aUpdate task finished."
update-task-complete-network: "&aUpdate task finished. Now attempting to push to other servers."
@@ -124,7 +104,7 @@ info: >
{PREFIX}&f- &3Context Calculators: &a{14}\n
{PREFIX}&f- &3Unique permissions: &a{15}\n
{PREFIX}&f- &bConfiguration:\n
{PREFIX}&f- &3Online Mode: {16}\n
{PREFIX}&f- &3Use Server UUIDs: {16}\n
{PREFIX}&f- &bPermission Calculation:\n
{PREFIX}&f- &3Including Global: {17}\n
{PREFIX}&f- &3Including Global World: {18}\n
@@ -137,48 +117,46 @@ create-group-error: "There was an error whilst creating the group."
delete-group-error: "There was an error whilst deleting the group."
delete-group-error-default: "You cannot delete the default group."
groups-list: "&aGroups: {0}"
create-track-error: "There was an error whilst creating the track."
delete-track-error: "There was an error whilst deleting the track."
tracks-list: "&aTracks: {0}"
listnodes: "&b{0}'s Nodes:"
listnodes-with-page: "&b{0}'s Nodes: {1}"
listnodes-temp: "&b{0}'s Temporary Nodes:\n{1}"
listparents: "&b{0}'s Parent Groups:\n{1}"
listparents-temp: "&b{0}'s Temporary Parent Groups:\n{1}"
listgroups: "&b{0}'s Groups:\n{1}"
listgroups-temp: "&b{0}'s Temporary Groups:\n{1}"
list-tracks: "&b{0}'s Tracks:\n{1}"
listnodes-temp: >
&b{0}'s Temporary Nodes:\n
{1}
listparents: >
&b{0}'s Parent Groups:\n
{1}
listparents-temp: >
&b{0}'s Temporary Parent Groups:\n
{1}
listgroups: >
&b{0}'s Groups:\n
{1}
listgroups-temp: >
&b{0}'s Temporary Groups:\n
{1}
list-tracks: >
&b{0}'s Tracks:\n
{1}
list-tracks-empty: "{0} is not on any tracks."
setpermission-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a."
setpermission-server-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a."
setpermission-server-world-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a, world &b{4}&a."
setpermission-temp-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a for a duration of &b{3}&a."
setpermission-temp-server-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a for a duration of &b{4}&a."
setpermission-temp-server-world-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
unsetpermission-success: "&aUnset &b{0}&a for &b{1}&a."
unsetpermission-server-success: "&aUnset &b{0}&a for &b{1}&a on server &b{2}&a."
unsetpermission-server-world-success: "&aUnset &b{0}&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
unset-temp-permission-success: "&aUnset temporary permission &b{0}&a for &b{1}&a."
unset-temp-permission-server-success: "&aUnset temporary permission &b{0}&a for &b{1}&a on server &b{2}&a."
unset-temp-permission-server-world-success: "&aUnset temporary permission &b{0}&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
set-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a."
set-inherit-server-success: "&b{0}&a now inherits permissions from &b{2}&a on server &b{3}&a."
set-inherit-server-world-success: "&b{0}&a now inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a."
set-parent-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a."
set-parent-server-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a on server &b{2}&a."
set-parent-server-world-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a on server &b{2}&a, world &b{3}&a."
set-temp-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a for a duration of &b{2}&a."
set-temp-inherit-server-success: "&b{0}&a now inherits permissions from &b{1}&a on server &b{2}&a for a duration of &b{3}&a."
set-temp-inherit-server-world-success: "&b{0}&a now inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a, for a duration of &b{4}&a."
unset-inherit-success: "&b{0}&a no longer inherits permissions from &b{1}&a."
unset-inherit-server-success: "&b{0}&a no longer inherits permissions from &b{1}&a on server &b{2}&a."
unset-inherit-server-world-success: "&b{0}&a no longer inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a."
unset-temp-inherit-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a."
unset-temp-inherit-server-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a on server &b{2}&a."
unset-temp-inherit-server-world-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a on server &b{2}&a, world &b{3}&a."
context-pair-inline: "&3{0}=&b{1}"
context-pair--global-inline: "&eglobal"
context-pair-end: "&a."
context-pair-sep: "&a, "
context-pair: "&8(&7{0}=&f{1}&8)"
check-permission: "&b{0}&a has permission &b{1}&a set to {2}&a in context {3}&a."
check-permission-inherited: "&b{0}&a has permission &b{1}&a set to {2}&a in context {3}&a, inherited from &b{4}&a."
setpermission-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a in context {3}&a."
setpermission-temp-success: "&aSet &b{0}&a to &b{1}&a for &b{2}&a for a duration of &b{3}&a in context {4}&a."
unsetpermission-success: "&aUnset &b{0}&a for &b{1}&a in context {2}&a."
unset-temp-permission-success: "&aUnset temporary permission &b{0}&a for &b{1}&a in context {2}&a."
set-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a in context {2}&a."
set-temp-inherit-success: "&b{0}&a now inherits permissions from &b{1}&a for a duration of &b{2}&a in context {3}&a."
set-parent-success: "&b{0}&a had their existing parent groups cleared, and now only inherits &b{1}&a in context {2}&a."
unset-inherit-success: "&b{0}&a no longer inherits permissions from &b{1}&a in context {2}&a."
unset-temp-inherit-success: "&b{0}&a no longer temporarily inherits permissions from &b{1}&a in context {2}&a."
clear-success: "&b{0}&a's permissions were cleared. (&b{1}&a nodes were removed.)"
clear-success-singular: "&b{0}&a's permissions were cleared. (&b{1}&a node was removed.)"
parent-clear-success: "&b{0}&a's parents were cleared. (&b{1}&a nodes were removed.)"
@@ -187,7 +165,6 @@ meta-clear-success: "&b{0}&a's meta was cleared. (&b{1}&a nodes were removed.)"
meta-clear-success-singular: "&b{0}&a's meta was cleared. (&b{1}&a node was removed.)"
illegal-date-error: "Could not parse date '{0}'."
past-date-error: "You cannot set a date in the past!"
chat-meta-prefix-header: "&b{0}'s Prefixes"
chat-meta-suffix-header: "&b{0}'s Suffixes"
meta-header: "&b{0}'s Meta"
@@ -198,59 +175,22 @@ meta-entry-with-context: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7inherited from &a{2}&8
chat-meta-prefix-none: "&b{0} has no prefixes."
chat-meta-suffix-none: "&b{0} has no suffixes."
meta-none: "&b{0} has no meta."
meta-invalid-priority: "Invalid priority '{0}'. Expected a number."
already-has-prefix: "{0} already has that prefix set."
already-has-suffix: "{0} already has that suffix set."
does-not-have-prefix: "{0} doesn't have that prefix set."
does-not-have-suffix: "{0} doesn't have that suffix set."
addprefix-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a."
addprefix-server-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a."
addprefix-server-world-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a."
removeprefix-success: "&b{0}&a had prefix &f\"{1}&f\"&a at priority &b{2}&a removed."
removeprefix-server-success: "&b{0}&a had prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
removeprefix-server-world-success: "&b{0}&a had prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
addsuffix-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a."
addsuffix-server-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a."
addsuffix-server-world-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a."
removesuffix-success: "&b{0}&a had suffix &f\"{1}&f\"&a at priority &b{2}&a removed."
removesuffix-server-success: "&b{0}&a had suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
removesuffix-server-world-success: "&b{0}&a had suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
add-temp-prefix-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a for a duration of &b{3}&a."
add-temp-prefix-server-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, for a duration of &b{4}&a."
add-temp-prefix-server-world-success: "&b{0}&a had prefix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
remove-temp-prefix-success: "&b{0}&a had temporary prefix &f\"{1}&f\"&a at priority &b{2}&a removed."
remove-temp-prefix-server-success: "&b{0}&a had temporary prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
remove-temp-prefix-server-world-success: "&b{0}&a had temporary prefix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
add-temp-suffix-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a for a duration of &b{3}&a."
add-temp-suffix-server-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, for a duration of &b{4}&a."
add-temp-suffix-server-world-success: "&b{0}&a had suffix &f\"{1}&f\"&a set at a priority of &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
remove-temp-suffix-success: "&b{0}&a had temporary suffix &f\"{1}&f\"&a at priority &b{1}&a removed."
remove-temp-suffix-server-success: "&b{0}&a had temporary suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a."
remove-temp-suffix-server-world-success: "&b{0}&a had temporary suffix &f\"{1}&f\"&a at priority &b{2}&a removed on server &b{3}&a, world &b{4}&a."
already-has-chat-meta: "{0} already has that {1} set."
does-not-have-chat-meta: "{0} doesn't have that {1} set."
add-chatmeta-success: "&b{0}&a had {1} &f\"{2}&f\"&a set at a priority of &b{3}&a in context {4}&a."
add-temp-chatmeta-success: "&b{0}&a had {1} &f\"{2}&f\"&a set at a priority of &b{3}&a for a duration of &b{4}&a in context {5}&a."
remove-chatmeta-success: "&b{0}&a had {1} &f\"{2}&f\"&a at priority &b{3}&a removed in context {4}&a."
bulk-remove-chatmeta-success: "&b{0}&a had all {1}es at priority &b{3}&a removed in context {4}&a."
remove-temp-chatmeta-success: "&b{0}&a had temporary {1} &f\"{2}&f\"&a at priority &b{3}&a removed in context {4}&a."
bulk-remove-temp-chatmeta-success: "&b{0}&a had all temporary {1}es at priority &b{3}&a removed in context {4}&a."
already-has-meta: "{0} already has that meta key value pair set."
set-meta-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a."
set-meta-server-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a."
set-meta-server-world-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a, world &b{4}&a."
set-meta-temp-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a for a duration of &b{3}&a."
set-meta-temp-server-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a for a duration of &b{4}&a."
set-meta-temp-server-world-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a on server &b{3}&a, world &b{4}&a, for a duration of &b{5}&a."
unset-meta-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a."
unset-meta-server-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a."
unset-meta-server-world-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
unset-meta-temp-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a."
unset-meta-temp-server-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a."
unset-meta-temp-server-world-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a on server &b{2}&a, world &b{3}&a."
set-meta-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a in context {3}&a."
set-meta-temp-success: "&aSet meta value for key &f\"{0}&f\"&a to &f\"{1}&f\"&a for &b{2}&a for a duration of &b{3}&a in context {4}&a."
unset-meta-success: "&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a in context {2}&a."
unset-meta-temp-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a in context {2}&a."
bulk-change-type-error: "Invalid type. Was expecting 'server' or 'world'."
bulk-change-success: "&aApplied bulk change successfully. {0} records were changed."
user-info-general: >
{PREFIX}&b&l> &bUser Info: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
@@ -273,6 +213,7 @@ info-temp-parent-header: "&f- &aTemporary Parent Groups:"
user-getuuid: "&bThe UUID of &b{0}&b is &b{1}&b."
user-removegroup-error-primary: "You cannot remove a user from their primary group."
user-primarygroup-success: "&b{0}&a's primary group was set to &b{1}&a."
user-primarygroup-warn-option: "&cWarning: The primary group calculation method being used by this server &7({0}) &cmay not reflect this change."
user-primarygroup-error-alreadyhas: "The user already has this group set as their primary group."
user-primarygroup-error-notmember: "&b{0}&a was not already a member of &b{1}&a, adding them now."
user-track-error-not-contain-group: "The user specified isn't already in any groups on this track."
@@ -284,7 +225,7 @@ user-promote-success-server: "&aPromoting user along track &b{0}&a from &b{1}&a
user-promote-success-server-world: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a, world &b{4}&a."
user-promote-error-endoftrack: "The end of track &4{0}&c was reached. Unable to promote user."
user-promote-error-malformed: >
{PREFIX}The next group on the track, {0}, no longer exists. Unable to promote user.
{PREFIX}The next group on the track, {0}, no longer exists. Unable to promote user.\n
{PREFIX}Either create the group, or remove it from the track and try again.
user-demote-success: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a."
user-demote-success-server: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a."
@@ -293,9 +234,9 @@ user-demote-endoftrack: "The end of track &4{0}&c was reached, so &4{1}&c was re
user-demote-error-malformed: >
{PREFIX}The previous group on the track, {0}, no longer exists. Unable to demote user.\n
{PREFIX}Either create the group, or remove it from the track and try again.
user-demote-error-not-contain-group: "Demotions are done based on primary groups. The users primary group is not on the track specified."
user-showpos: "&aShowing &b{0}&a's position on track &b{1}&a.{2}"
user-showpos: >
&aShowing &b{0}&a's position on track &b{1}&a.\n
{2}
group-info-general: >
{PREFIX}&b&l> &bGroup Info: &f{0}\n
{PREFIX}&f- &3Display Name: &f{1}\n
@@ -307,7 +248,6 @@ group-info-general: >
{PREFIX}&f- &3Suffixes: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
group-set-weight: "&aSet weight to &b{0}&a for group &b{1}&a."
track-info: >
{PREFIX}&b&l> &bShowing Track: &f{0}\n
{PREFIX}&f- &7Path: &f{1}
@@ -317,55 +257,43 @@ track-insert-success: "&aGroup &b{0}&a was successfully inserted into track &b{1
track-insert-error-number: "Expected number but instead received: {0}"
track-insert-error-invalid-pos: "Unable to insert at position {0}. Index out of bounds."
track-remove-success: "&aGroup &b{0}&a was successfully removed from track &b{1}&a."
log-load-error: "The log could not be loaded."
log-invalid-page: "Invalid page number."
log-invalid-page-range: "Invalid page number. Please enter a value between 1 and {0}."
log-no-entries: "&bNo log entries to show."
log-entry: "&b#{0} -> &8(&7{1} ago&8) {2}"
log-notify-toggle-on: "&aEnabled&b logging output."
log-notify-toggle-off: "&cDisabled&b logging output."
log-notify-already-on: "You are already receiving notifications."
log-notify-already-off: "You aren't currently receiving notifications."
log-notify-unknown: "State unknown. Expecting \"on\" or \"off\"."
log-search-header: "&aShowing recent actions for query &b{0} &a(page &f{1}&a of &f{2}&a)"
log-recent-header: "&aShowing recent actions (page &f{0}&a of &f{1}&a)"
log-recent-by-header: "&aShowing recent actions by &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-user-header: "&aShowing history for user &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-group-header: "&aShowing history for group &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-track-header: "&aShowing history for track &b{0} &a(page &f{1}&a of &f{2}&a)"
log-export-already-exists: "Error: File {0} already exists."
log-export-not-writable: "Error: File {0} is not writable."
log-export-empty: "The log is empty and therefore cannot be exported."
log-export-failure: "An unexpected error occured whilst writing to the file."
log-export-success: "&aSuccessfully exported the log to &b{0}&a."
import-already-running: "Another import process is already running. Please wait for it to finish and try again."
export-already-running: "Another export process is already running. Please wait for it to finish and try again."
import-log-doesnt-exist: "Error: File {0} does not exist."
import-log-not-readable: "Error: File {0} is not readable."
import-log-failure: "An unexpected error occured whilst reading from the log file."
import-progress: "&b(Import) &b-> &f{0} &fpercent complete &7- &b{1}&f/&b{2} &foperations complete with &c{3} &ferrors."
import-progress-sin: "&b(Import) &b-> &f{0} &fpercent complete &7- &b{1}&f/&b{2} &foperations complete with &c{3} &ferror."
import-start: "&b(Import) &b-> &fStarting import process."
import-end-complete: "&b(Import) &a&lCOMPLETED &7- took &b{0} &7seconds - &7No errors."
import-end-complete-err: "&b(Import) &a&lCOMPLETED &7- took &b{0} &7seconds - &c{1} errors."
import-end-complete-err-sin: "&b(Import) &a&lCOMPLETED &7- took &b{0} &7seconds - &c{1} error."
import-end-error-header: >
{PREFIX}&b(Import) &7------------> &fShowing Error #&b{0} &7<------------\n
{PREFIX}&b(Import) &fWhilst executing: &3Command #{1}\n
{PREFIX}&b(Import) &fCommand: &7{2}\n
{PREFIX}&b(Import) &fType: &3{3}\n
{PREFIX}&b(Import) &fOutput:
import-end-error-content: "&b(Import) &b-> &c{0}"
import-end-error-footer: "&b(Import) &7<------------------------------------------>"
migration-not-console: "Migration must be performed from the Console."
+338
View File
@@ -0,0 +1,338 @@
# LuckPerms Language File
# Locale: es_ES (Spanish)
# Author: Jose Chavez (Twitter: @JoCHav3z)
prefix: "&7&l[&b&lL&3&lP&7&l] &c"
log-info: "&7&l[&bL&3P&7&l] &3{0}"
log-warn: "&7&l[&bLuck&3Perms&7&l] &c[ADVERTENCIA] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[ERROR] {0}"
empty: "{0}"
player-online: "&aConectado"
player-offline: "&cDesconectado"
loading-error: "La Informacion de los permisos no pudo ser cargada. Por favor contacte al administrador."
op-disabled: "&bEl sistema OP de Vanilla esta deshabilitado en este servidor."
op-disabled-sponge: "&2El estado de operador del servidor no tiene efecto cuando un plugin de permisos esta instalado. Por favor edite la informacion de usuario directamente."
log: "&3LOG &3&l> {0}"
export-log: "&3EXPORTAR &3&l> &f{0}"
export-log-progress: "&3EXPORTADO &3&l> &7{0}"
migration-log: "&3MIGRACION &7[&3{0}&7] &3&l> &f{1}"
migration-log-progress: "&3MIGRADO &7[&3{0}&7] &3&l> &7{1}"
command-not-recognised: "Comando no reconocido."
command-no-permission: "¡No tienes permiso de usar este comando!"
already-haspermission: "{0} ¡Ya tienes este permiso!"
does-not-havepermission: "{0} No tienes este permiso establecido."
already-has-temp-permission: "{0} ¡Ya tienes este permiso establecido temporalmente!"
does-not-have-temp-permission: "{0} No tienes este permiso establecido temporalmente."
user-not-found: "&bNo se pudo encontrar al usuario."
user-not-online: "&bUsuario &a{0}&b no esta conectado."
user-no-data: "&bUsuario &a{0}&b no tiene ningún dato cargado."
user-save-success: "&7(Los datos del usuario se guardaron en el almacenamiento)"
user-save-error: "Se ha producido un error al guardar el usuario."
user-create-fail: "Se produjo un error al crear un nuevo usuario."
group-not-found: "&bEl Grupo no se pudo encontrar."
group-save-success: "&7(Los datos del grupo se guardaron en el almacenamiento)"
group-save-error: "Se ha producido un error al guardar el grupo."
track-not-found: "&bNo se pudo encontrar la pista."
track-save-success: "&7(Los datos de la pista se guardaron en el almacenamiento)"
track-save-error: "Se ha producido un error al guardar la pista."
user-invalid-entry: "&d{0}&c El username/uuid no es valido."
group-invalid-entry: "Los nombres de grupo sólo pueden contener caracteres alfanuméricos."
track-invalid-entry: "Los nombres de pista sólo pueden contener caracteres alfanuméricos."
server-invalid-entry: "Los nombres de servidor sólo pueden contener caracteres alfanuméricos."
use-inherit-command: "Usa el comando 'parent add' y 'parent remove' en vez de especificar el nodo."
verbose-invalid-filter: "&cFiltro Verbose invalido: &f{0}"
verbose-on: "&bEl chequeo Verbose de salida se ha establecido en &aTRUE &bpara todos los permisos."
verbose-on-query: "&bEl chequeo Verbose de salida que se establecio en &aTRUE &bpara los permisos se ha pareado con los siguientes filtros: &f{0}"
verbose-off: "&bEl chequeo Verbose de salida que se establecio en &cFALSE&b."
verbose-recording-on: "&bGrabacion de Verbose establecida en &aTRUE &bpara todos los permisos."
verbose-recording-on-query: "&bGrabacion de Verbose establecida en &aTRUE &bpara los permisos se ha pareado con los siguientes filtros: &f{0}"
verbose-recording-upload-start: "&bGrabacion de Verbose se ha deshabilitado. Cargando resultados..."
verbose-recording-url: "&aURL de resultados de Verbose:"
tree-upload-start: "&bGenerando arbol de permisos..."
tree-empty: "&aNo se puede generar el árbol. No se encontraron resultados."
tree-url: "&aURL de arbol de permisos:"
search-searching: "&aBúsqueda de usuarios y grupos con &b{0}&a..."
search-result: "&aEncontradas &b{0}&a entradas por parte de &b{1}&a usuarios y &b{2}&a grupos."
search-showing-users: "&bMostrando entradas del usuario:"
search-showing-groups: "&bMostrando entradas del grupo:"
search-showing-users-with-page: "&bMostrando entradas del usuario: {0}"
search-showing-groups-with-page: "&bMostrando entradas del grupo: {0}"
check-result: "&aResultado de la comprobación del permiso en el usuario &b{0}&a para permiso &b{1}&a: &f{2}"
create-success: "&b{0}&a se creó correctamente."
delete-success: "&b{0}&a fue eliminado correctamente."
rename-success: "&b{0}&a fue exitosamente renombrado a &b{1}&a."
clone-success: "&b{0}&a fue clonado con éxito en &b{1}&a."
already-inherits: "{0} ya hereda '{1}'."
does-not-inherit: "{0} no hereda '{1}'."
already-temp-inherits: "{0} ya lo heredo temporalmente a '{1}'."
does-not-temp-inherit: "{0} no lo heredo temporalmente a '{1}'."
track-already-contains: "La pista {0} ya contiene el grupo '{1}'."
track-does-not-contain: "La pista {0} no contiene el grupo '{1}'."
track-ambiguous-call: "El usuario especificado es miembro de varios grupos en esta pista. No se puede determinar su ubicación."
group-already-exists: "¡Ese grupo ya existe!"
group-does-not-exist: "¡Ese grupo no existe!"
group-load-error: "Ocurrió un error inesperado. No se pudo cargar el grupo."
groups-load-error: "Ocurrió un error inesperado. No se pueden cargar todos los grupos."
track-already-exists: "¡La pista ya existe!"
track-does-not-exist: "¡La pista no existe!"
track-load-error: "Ocurrió un error inesperado. No se pudo cargar la pista."
tracks-load-error: "Ocurrió un error inesperado. No se pueden cargar todas las pistas."
track-empty: "La pista no se puede utilizar porque está vacía o contiene sólo un grupo."
update-task-request: "&bActualizar tarea programada."
update-task-complete: "&aLa actualizacion de la tarea a finalizado."
update-task-complete-network: "&aLa actualizacion de la tarea a finalizado. Ahora intentando enviar a otros servidores."
update-task-push-success: "&aOtros servidores fueron notificados vía &b{0} Mensajería &aexitoxamente."
update-task-push-failure: "&cError al empujar cambios a otros servidores."
update-task-push-failure-not-setup: "&cError al presionar los cambios a otros servidores. No se ha configurado un servicio de mensajería."
reload-config-success: "&aSe ha vuelto a cargar el archivo de configuración. &7(Algunas opciones sólo se aplicarán después de que el servidor se haya reiniciado.)"
info: >
{PREFIX}&2Running &bLuckPerms v{0}&2 by &bLuck&2.\n
{PREFIX}&f- &3Platform: &f{1}\n
{PREFIX}&f- &3Storage Method: &f{2}\n
{PREFIX}&f- &3Server Name: &f{3}\n
{PREFIX}&f- &3Sync Interval: &a{4} &fminutes\n
{PREFIX}&f- &3Messaging Service: &f{5}\n
{PREFIX}&f- &bCounts:\n
{PREFIX}&f- &3Online Players: &a{6}\n
{PREFIX}&f- &3Loaded Users: &a{7}\n
{PREFIX}&f- &3Loaded Groups: &a{8}\n
{PREFIX}&f- &3Loaded Tracks: &a{9}\n
{PREFIX}&f- &3Log size: &a{10}\n
{PREFIX}&f- &3UUID Cache size: &a{11}\n
{PREFIX}&f- &3Translations loaded: &a{12}\n
{PREFIX}&f- &3Pre-process contexts: &a{13}\n
{PREFIX}&f- &3Context Calculators: &a{14}\n
{PREFIX}&f- &3Unique permissions: &a{15}\n
{PREFIX}&f- &bConfiguration:\n
{PREFIX}&f- &3Use Server UUIDs: {16}\n
{PREFIX}&f- &bPermission Calculation:\n
{PREFIX}&f- &3Including Global: {17}\n
{PREFIX}&f- &3Including Global World: {18}\n
{PREFIX}&f- &3Applying Global Groups: {19}\n
{PREFIX}&f- &3Applying Global World Groups: {20}\n
{PREFIX}&f- &3Applying Wildcards: {21}\n
{PREFIX}&f- &3Applying Regex: {22}\n
{PREFIX}&f- &3Applying Shorthand: {23}
create-group-error: "Se produjo un error al crear el grupo."
delete-group-error: "Se produjo un error al eliminar el grupo."
delete-group-error-default: "No puede eliminar el grupo predeterminado."
groups-list: "&aGrupos: {0}"
create-track-error: "Se produjo un error al crear la pista."
delete-track-error: "Se produjo un error al eliminar la pista."
tracks-list: "&aPistas: {0}"
listnodes: "&b{0}'s Nodos:"
listnodes-with-page: "&b{0}'s Nodos: {1}"
listnodes-temp: >
&b{0}'s Temporary Nodes:\n
{1}
listparents: >
&b{0}'s Parent Groups:\n
{1}
listparents-temp: >
&b{0}'s Temporary Parent Groups:\n
{1}
listgroups: >
&b{0}'s Groups:\n
{1}
listgroups-temp: >
&b{0}'s Temporary Groups:\n
{1}
list-tracks: >
&b{0}'s Tracks:\n
{1}
list-tracks-empty: "{0} no está en ninguna pista."
setpermission-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a."
setpermission-server-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a."
setpermission-server-world-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
setpermission-temp-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a por una duracion de &b{3}&a."
setpermission-temp-server-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a por una duracion de &b{4}&a."
setpermission-temp-server-world-success: "&aEstablecer &b{0}&a a &b{1}&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
unsetpermission-success: "&aDesestablecer &b{0}&a para &b{1}&a."
unsetpermission-server-success: "&aDesestablecer &b{0}&a para &b{1}&a en el servidor &b{2}&a."
unsetpermission-server-world-success: "&aDesestablecer &b{0}&a para &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
unset-temp-permission-success: "&aDesestablecer temporalmente el permiso &b{0}&a para &b{1}&a."
unset-temp-permission-server-success: "&aDesestablecer temporalmente el permiso &b{0}&a para &b{1}&a en el servidor &b{2}&a."
unset-temp-permission-server-world-success: "&aDesestablecer temporalmente el permiso &b{0}&a para &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
set-inherit-success: "&b{0}&a ahora hereda los permisos de &b{1}&a."
set-inherit-server-success: "&b{0}&a ahora hereda los permisos de &b{1}&a en el servidor &b{2}&a."
set-inherit-server-world-success: "&b{0}&a ahora hereda los permisos de &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
set-parent-success: "&b{0}&a sus grupos parentales existentes han sido borrados, y ahora solo heredan &b{1}&a."
set-parent-server-success: "&b{0}&a sus grupos parentales existentes han sido borrados, y ahora solo heredan &b{1}&a en el servidor &b{2}&a."
set-parent-server-world-success: "&b{0}&a sus grupos parentales existentes han sido borrados, y ahora solo heredan &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
set-temp-inherit-success: "&b{0}&a ahora heredan permisos de &b{1}&a por una duracion de &b{2}&a."
set-temp-inherit-server-success: "&b{0}&a ahora heredan permisos de &b{1}&a en el servidor &b{2}&a por una duracion de &b{3}&a."
set-temp-inherit-server-world-success: "&b{0}&a ahora heredan permisos de &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a, por una duracion de &b{4}&a."
unset-inherit-success: "&b{0}&a ya no hereda los permisos de &b{1}&a."
unset-inherit-server-success: "&b{0}&a ya no hereda los permisos de &b{1}&a en el servidor &b{2}&a."
unset-inherit-server-world-success: "&b{0}&a ya no hereda los permisos de &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
unset-temp-inherit-success: "&b{0}&a ya no hereda temporalmente los permisos de &b{1}&a."
unset-temp-inherit-server-success: "&b{0}&a ya no hereda temporalmente los permisos &b{1}&a en el servidor &b{2}&a."
unset-temp-inherit-server-world-success: "&b{0}&a ya no hereda temporalmente los permisos &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
clear-success: "&b{0}&a's permisos fueron borrados. (&b{1}&a nodos fueron removidos.)"
clear-success-singular: "&b{0}&a's permisos fueron borrados. (&b{1}&a nodo fue removido.)"
parent-clear-success: "&b{0}&a's los padres fueron eliminados. (&b{1}&a nodos fueron removidos.)"
parent-clear-success-singular: "&b{0}&a's los padres fueron eliminados. (&b{1}&a nodo fue removido.)"
meta-clear-success: "&b{0}&a's meta fue eliminada. (&b{1}&a nodos fueron removidos.)"
meta-clear-success-singular: "&b{0}&a's meta fue eliminada. (&b{1}&a nodo fue removido.)"
illegal-date-error: "No se pudo analizar la fecha '{0}'."
past-date-error: "¡No puedes fijar una fecha en el pasado!"
chat-meta-prefix-header: "&b{0}'s Prefijos"
chat-meta-suffix-header: "&b{0}'s Sufijos"
meta-header: "&b{0}'s Meta"
chat-meta-entry: "&b-> {0} &f- &f\"{1}&f\" &8(&7heredado de &a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f\"{1}&f\" &8(&7heredado de &a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7heredado de &a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7heredado de &a{2}&8){3}"
chat-meta-prefix-none: "&b{0} no tiene prefijos."
chat-meta-suffix-none: "&b{0} no tiene sufijos."
meta-none: "&b{0} no tiene meta."
meta-invalid-priority: "Prioridad no válida '{0}'. Se esperaba un número."
already-has-prefix: "{0} ya tiene ese prefijo establecido."
already-has-suffix: "{0} ya tiene ese sufijo establecido."
does-not-have-prefix: "{0} no tiene ese prefijo establecido."
does-not-have-suffix: "{0} no tiene ese sufijo establecido."
addprefix-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido como prioridad de &b{2}&a."
addprefix-server-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido como prioridad de &b{2}&a en el servidor &b{3}&a."
addprefix-server-world-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido como prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
removeprefix-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido."
removeprefix-server-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
removeprefix-server-world-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
addsuffix-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a."
addsuffix-server-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a."
addsuffix-server-world-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
removesuffix-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido."
removesuffix-server-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
removesuffix-server-world-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
add-temp-prefix-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a por una duracion de &b{3}&a."
add-temp-prefix-server-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, por una duracion de &b{4}&a."
add-temp-prefix-server-world-success: "&b{0}&a tiene el prefijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
remove-temp-prefix-success: "&b{0}&a tiene temporalmente el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido."
remove-temp-prefix-server-success: "&b{0}&a tiene temporalmente el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
remove-temp-prefix-server-world-success: "&b{0}&a tiene temporalmente el prefijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
add-temp-suffix-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a por una duracion de &b{3}&a."
add-temp-suffix-server-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, por una duracion de &b{4}&a."
add-temp-suffix-server-world-success: "&b{0}&a tiene el sufijo &f\"{1}&f\"&a establecido en prioridad de &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
remove-temp-suffix-success: "&b{0}&a tiene temporalmente el sufijo &f\"{1}&f\"&a en prioridad de &b{1}&a removido."
remove-temp-suffix-server-success: "&b{0}&a tiene temporalmente el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a."
remove-temp-suffix-server-world-success: "&b{0}&a tiene temporalmente el sufijo &f\"{1}&f\"&a en prioridad de &b{2}&a removido en el servidor &b{3}&a, mundo &b{4}&a."
already-has-meta: "{0} ya tiene ese par de valores de llave meta establecido."
set-meta-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a."
set-meta-server-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a."
set-meta-server-world-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
set-meta-temp-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a por una duracion de &b{3}&a."
set-meta-temp-server-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a por una duracion de &b{4}&a."
set-meta-temp-server-world-success: "&aEstablecer el valor meta para la llave &f\"{0}&f\"&a a &f\"{1}&f\"&a para &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a, por una duracion de &b{5}&a."
unset-meta-success: "&aDesestablecer el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a."
unset-meta-server-success: "&aDesestablecer el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a en el servidor &b{2}&a."
unset-meta-server-world-success: "&aDesestablecer el valor meta con la llave &f\"{0}&f\"&a for &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
unset-meta-temp-success: "&aDesestablecer temporalmente el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a."
unset-meta-temp-server-success: "&aDesestablecer temporalmente el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a en el servidor &b{2}&a."
unset-meta-temp-server-world-success: "&aDesestablecer temporalmente el valor meta con la llave &f\"{0}&f\"&a para &b{1}&a en el servidor &b{2}&a, mundo &b{3}&a."
bulk-change-type-error: "Tipo no válido. Se estaba esperando 'server' o 'world'."
bulk-change-success: "&aEl cambio masivo fue aplicado correctamente. {0} los registros fueron cambiados."
user-info-general: >
{PREFIX}&b&l> &bUser Info: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
{PREFIX}&f- &3Status: {2}\n
{PREFIX}&f- &3Primary Group: &f{3}\n
{PREFIX}&f- &aCounts:\n
{PREFIX}&f- &3Permissions: &a{4}\n
{PREFIX}&f- &3Temporary Permissions: &a{5}\n
{PREFIX}&f- &3Prefixes: &a{6}\n
{PREFIX}&f- &3Suffixes: &a{7}\n
{PREFIX}&f- &3Meta: &a{8}
user-info-data: >
{PREFIX}&f- &aCached Data:\n
{PREFIX}&f- &3Is Loaded: {0}\n
{PREFIX}&f- &3Current Contexts: {1}\n
{PREFIX}&f- &3Current Prefix: {2}\n
{PREFIX}&f- &3Current Suffix: {3}
info-parent-header: "&f- &aGrupos Parentales:"
info-temp-parent-header: "&f- &aGrupos Temporalmente Parentales:"
user-getuuid: "&bEl UUID de &b{0}&b es &b{1}&b."
user-removegroup-error-primary: "No puede eliminar un usuario de su grupo principal."
user-primarygroup-success: "&b{0}&a's grupo primario se estableció &b{1}&a."
user-primarygroup-warn-option: "&cAdvertencia: El método de cálculo de grupo primario utilizado por este servidor &7({0}) &cpuede no reflejar este cambio."
user-primarygroup-error-alreadyhas: "El usuario ya tiene este grupo configurado como su grupo principal."
user-primarygroup-error-notmember: "&b{0}&a ya no era miembro de &b{1}&a, agregándolos ahora."
user-track-error-not-contain-group: "El usuario especificado ya no está en ningún grupo de esta pista."
user-track-added-to-first: "&b{0}&a no está en ningún grupo en esta pista, por lo que se agregaron al primer grupo, &b{1}&a."
user-track-added-to-first-server: "&b{0}&a no está en ningún grupo en esta pista, por lo que se agregaron al primer grupo, &b{1}&a, en el servidor &b{2}&a."
user-track-added-to-first-server-world: "&b{0}&a no está en ningún grupo en esta pista, por lo que se agregaron al primer grupo, &b{1}&a, en el servidor &b{2}&a, mundo &b{3}&a."
user-promote-success: "&aPromover al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a."
user-promote-success-server: "&aPromover al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a."
user-promote-success-server-world: "&aPromover al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
user-promote-error-endoftrack: "El final de la pista &4{0}&c fue alcanzado. No se puede promover al usuario."
user-promote-error-malformed: >
{PREFIX}The next group on the track, {0}, no longer exists. Unable to promote user.\n
{PREFIX}Either create the group, or remove it from the track and try again.
user-demote-success: "&aDegradando al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a."
user-demote-success-server: "&aDegradando al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a."
user-demote-success-server-world: "&aDegradando al usuario a lo largo de la pista &b{0}&a desde &b{1}&a a &b{2}&a en el servidor &b{3}&a, mundo &b{4}&a."
user-demote-endoftrack: "El final de la pista &4{0}&c fue alcanzado, asi que &4{1}&c fue removido de &4{2}&c."
user-demote-error-malformed: >
{PREFIX}The previous group on the track, {0}, no longer exists. Unable to demote user.\n
{PREFIX}Either create the group, or remove it from the track and try again.
user-showpos: >
&aShowing &b{0}&a's position on track &b{1}&a.\n
{2}
group-info-general: >
{PREFIX}&b&l> &bGroup Info: &f{0}\n
{PREFIX}&f- &3Display Name: &f{1}\n
{PREFIX}&f- &3Weight: &f{2}\n
{PREFIX}&f- &aCounts:\n
{PREFIX}&f- &3Permissions: &a{3}\n
{PREFIX}&f- &3Temporary Permissions: &a{4}\n
{PREFIX}&f- &3Prefixes: &a{5}\n
{PREFIX}&f- &3Suffixes: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
group-set-weight: "&aEstablesca el peso a &b{0}&a para el grupo &b{1}&a."
track-info: >
{PREFIX}&b&l> &bShowing Track: &f{0}\n
{PREFIX}&f- &7Path: &f{1}
track-clear: "&b{0}&a's pista de grupos fue borrada."
track-append-success: "&aGrupo &b{0}&a fue añadido con éxito a la pista &b{1}&a."
track-insert-success: "&aGrupo &b{0}&a fue insertado con éxito en la pista &b{1}&a en la posicion &b{2}&a."
track-insert-error-number: "Numero esperado en lugar de recibido: {0}"
track-insert-error-invalid-pos: "No se puede insertar en la posición {0}. Indice fuera de los limites."
track-remove-success: "&aGrupo &b{0}&a se eliminó correctamente de la pista &b{1}&a."
log-load-error: "No se pudo cargar el registro."
log-invalid-page: "Número de página no válido."
log-invalid-page-range: "Número de página no válido. Por favor introduzca un valor entre 1 y {0}."
log-no-entries: "&bNo hay entradas de registro que mostrar."
log-entry: "&b#{0} -> &8(&7{1} hace&8) {2}"
log-notify-toggle-on: "&aHabilitada&b salida de registro."
log-notify-toggle-off: "&cDeshabilitada&b salida de registro."
log-notify-already-on: "Ya está recibiendo notificaciones."
log-notify-already-off: "Actualmente no estás recibiendo notificaciones."
log-notify-unknown: "Estado desconocido. Esperando \"on\" o \"off\"."
log-search-header: "&aMostrar acciones recientes para la consulta &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-recent-header: "&aMostrando acciones recientes (pagina &f{0}&a de &f{1}&a)"
log-recent-by-header: "&aMostrando las acciones recientes por &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-history-user-header: "&aMostrando el historial del usuario &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-history-group-header: "&aMostrando la historia del grupo &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-history-track-header: "&aMostrando la historia de la pista &b{0} &a(pagina &f{1}&a de &f{2}&a)"
log-export-already-exists: "Error: Archivo {0} ya existe."
log-export-not-writable: "Error: Archivo {0} no se puede escribir."
log-export-empty: "El registro está vacío y por lo tanto no puede ser exportado."
log-export-failure: "Se ha producido un error inesperado al escribir en el archivo."
log-export-success: "&aExitosamente exportó el registro a &b{0}&a."
import-already-running: "Ya se está ejecutando otro proceso de importación. Por favor espere a que finalice e inténtelo de nuevo."
export-already-running: "Ya se está ejecutando otro proceso de exportación. Por favor espere a que finalice e inténtelo de nuevo."
import-log-doesnt-exist: "Error: Archivo {0} no existe."
import-log-not-readable: "Error: Archivo {0} no es legible."
import-log-failure: "Se ha producido un error inesperado al leer del archivo de registro."
import-progress: "&b(Import) &b-> &f{0} &fporcentaje completado &7- &b{1}&f/&b{2} &fOperaciones completas con &c{3} &ferrores."
import-progress-sin: "&b(Import) &b-> &f{0} &fporcentaje completado &7- &b{1}&f/&b{2} &fOperaciones completas con &c{3} &ferrores."
import-start: "&b(Import) &b-> &fInicio del proceso de importación."
import-end-complete: "&b(Import) &a&lCOMPLETADO &7- tomo &b{0} &7segundos - &7No errores."
import-end-complete-err: "&b(Import) &a&lCOMPLETADO &7- tomo &b{0} &7segundos - &c{1} errores."
import-end-complete-err-sin: "&b(Import) &a&lCOMPLETADO &7- tomo &b{0} &7segundos - &c{1} errores."
import-end-error-header: >
{PREFIX}&b(Import) &7------------> &fShowing Error #&b{0} &7<------------\n
{PREFIX}&b(Import) &fWhilst executing: &3Command #{1}\n
{PREFIX}&b(Import) &fCommand: &7{2}\n
{PREFIX}&b(Import) &fType: &3{3}\n
{PREFIX}&b(Import) &fOutput:
import-end-error-content: "&b(Import) &b-> &c{0}"
import-end-error-footer: "&b(Import) &7<------------------------------------------>"
+338
View File
@@ -0,0 +1,338 @@
# LuckPerms Language File
# Locale: ru_RU
# Author: Pixelmon.PRO
prefix: "&7&l[&b&lL&3&lP&7&l] &c"
log-info: "&7&l[&bL&3P&7&l] &3{0}"
log-warn: "&7&l[&bLuck&3Perms&7&l] &c[WARN] {0}"
log-error: "&7&l[&bLuck&3Perms&7&l] &4[ERROR] {0}"
empty: "{0}"
player-online: "&aОнлайн"
player-offline: "&cОффлайн"
loading-error: "Не удалось загрузить данные разрешений (пермов). Пожалуйста обратитесь к администратору."
op-disabled: "&bНа этом сервере отключена ванильная система OP."
op-disabled-sponge: "&2Статус оператора сервера не влияет на установленный плагин разрешений (пермов). Пожалуйста отредактируйте данные пользователя напрямую."
log: "&3LOG &3&l> {0}"
export-log: "&3ЭКСПОРТ &3&l> &f{0}"
export-log-progress: "&3ЭКСПОРТ &3&l> &7{0}"
migration-log: "&3ПЕРЕМЕЩЕНИЕ &7[&3{0}&7] &3&l> &f{1}"
migration-log-progress: "&3ПЕРЕМЕЩЕНИЕ &7[&3{0}&7] &3&l> &7{1}"
command-not-recognised: "Команда не распознана."
command-no-permission: "У вас нет разрешения на использование этой команды!"
already-haspermission: "{0} уже имеет такое разрешение (перм)!"
does-not-havepermission: "{0} не имеет этого набора разрешений (пермов)."
already-has-temp-permission: "{0} уже имеет этот набор временных разрешений (пермов)!"
does-not-have-temp-permission: "{0} не имеет этого набора временных разрешений (пермов)."
user-not-found: "&bНе удалось найти пользователя."
user-not-online: "&bПользователь &a{0}&b не онлайн."
user-no-data: "&bПользователь &a{0}&b не имеет загруженных данных."
user-save-success: "&7(Пользовательские данные были сохранены на хранение)"
user-save-error: "При сохранении пользователя произошла ошибка."
user-create-fail: "При создании нового пользователя возникла ошибка."
group-not-found: "&bНе удалось найти группу."
group-save-success: "&7(Групповые данные были сохранены на хранение)"
group-save-error: "При сохранении группы произошла ошибка."
track-not-found: "&bНе удалось найти путь (track)."
track-save-success: "&7(Данные о путе (track) сохранены на хранение)"
track-save-error: "При сохранении пути (track) произошла ошибка."
user-invalid-entry: "&d{0}&c не является допустимым именем пользователя/uuid."
group-invalid-entry: "Имена групп могут содержать только буквенно-цифровые символы."
track-invalid-entry: "Имена путей (track) могут содержать только буквенно-цифровые символы."
server-invalid-entry: "Имена серверов могут содержать только буквенно-цифровые символы."
use-inherit-command: "Используйте команды «parent add» и «parent remove» вместо указания узла (node)."
verbose-invalid-filter: "&cНедопустимый подробный (verbose) фильтр: &f{0}"
verbose-on: "&Запись подробной проверки установлена на &aВЕРНЫЙ &bдля всех разрешений (пермов)."
verbose-on-query: "&Запись подробной проверки установлена на &aВЕРНЫЙ &bДля разрешений, соответствующих следующим фильтрам: &f{0}"
verbose-off: "&Запись подробной проверки установлена на &cНЕВЕРНЫЙ&b."
verbose-recording-on: "&Запись подробной записи установлена ​​на &aВЕРНЫЙ &bдля всех разрешений."
verbose-recording-on-query: "&Запись подробной записи установлена ​​на &aВЕРНЫЙ &bДля разрешений, соответствующих следующим фильтрам: &f{0}"
verbose-recording-upload-start: "&bЗапись подробной информации была отключена. Загрузка результатов..."
verbose-recording-url: "&aПодробный URL результатов:"
tree-upload-start: "&bСоздание дерева разрешений..."
tree-empty: "&aНе удалось создать дерево. Ничего не найдено."
tree-url: "&aURL дерева разрешений:"
search-searching: "&aПоиск пользователей и групп с помощью &b{0}&a..."
search-result: "&aНайдено &b{0}&a записи из &b{1}&a пользователей и &b{2}&a групп."
search-showing-users: "&bПросмотр записей пользователя:"
search-showing-groups: "&bПросмотр записей в группах:"
search-showing-users-with-page: "&bПросмотр записей пользователя: {0}"
search-showing-groups-with-page: "&bПросмотр записей в группах: {0}"
check-result: "&aРезультат проверки разрешения (перма) на пользователя &b{0}&a на разрешение (перма) &b{1}&a: &f{2}"
create-success: "&b{0}&a было успешно создано."
delete-success: "&b{0}&a успешно удален."
rename-success: "&b{0}&a был успешно переименован в &b{1}&a."
clone-success: "&b{0}&a был успешно клонирован на &b{1}&a."
already-inherits: "{0} уже наследует '{1}'."
does-not-inherit: "{0} не наследует '{1}'."
already-temp-inherits: "{0} уже временно наследует '{1}'."
does-not-temp-inherit: "{0} временно не наследует '{1}'."
track-already-contains: "Путь (Track) {0} уже содержит группу '{1}'."
track-does-not-contain: "Путь (Track) {0} не содержит группу '{1}'."
track-ambiguous-call: "Указанный пользователь является членом нескольких групп на этом пути (track). Невозможно определить их местоположение."
group-already-exists: "Эта группа уже существует!"
group-does-not-exist: "Эта группа не существует!"
group-load-error: "Произошла непредвиденная ошибка. Группа не загружена."
groups-load-error: "Произошла непредвиденная ошибка. Не удалось загрузить все группы."
track-already-exists: "Этот путь (track) уже существует!"
track-does-not-exist: "Этот путь (track) не существует!"
track-load-error: "Произошла непредвиденная ошибка. Путь (track) не загружен."
tracks-load-error: "Произошла непредвиденная ошибка. Не удалось загрузить все пути (tracks)."
track-empty: "Путь (track) нельзя использовать, так как он пуст или содержит только одну группу."
update-task-request: "&bОбновление задачи запланировано."
update-task-complete: "&aОбновление задачи завершено."
update-task-complete-network: "&aОбновление задачи завершено. Теперь пытаемся подключиться к другим серверам."
update-task-push-success: "&aДругие серверы были уведомлены через &b{0} Сообщения и удаленный доступ."
update-task-push-failure: "&cОшибка при нажатии изменений на других серверах."
update-task-push-failure-not-setup: "&cОшибка при нажатии изменений на других серверах. Служба обмена сообщениями не настроена."
reload-config-success: "&aФайл конфигурации был перезагружен. &7(Некоторые параметры будут применяться только после перезапуска сервера.)"
info: >
{PREFIX}&2Выполнение &bLuckPerms v{0}&2 by &bLuck&2.\n
{PREFIX}&f- &3Платформа: &f{1}\n
{PREFIX}&f- &3Способ Хранения: &f{2}\n
{PREFIX}&f- &3Имя Сервера: &f{3}\n
{PREFIX}&f- &3Интервал Синхронизации: &a{4} &fминут\n
{PREFIX}&f- &3Служба обмена сообщениями: &f{5}\n
{PREFIX}&f- &bГрафы:\n
{PREFIX}&f- &3Игроки Онлайн: &a{6}\n
{PREFIX}&f- &3Загруженные пользователи: &a{7}\n
{PREFIX}&f- &3Загруженные группы: &a{8}\n
{PREFIX}&f- &3Загруженные пути (tracks): &a{9}\n
{PREFIX}&f- &3Размер лога: &a{10}\n
{PREFIX}&f- &3UUID Размер кеша: &a{11}\n
{PREFIX}&f- &3Переводы загружены: &a{12}\n
{PREFIX}&f- &3Контексты перед обработкой: &a{13}\n
{PREFIX}&f- &3Счетчик контекста: &a{14}\n
{PREFIX}&f- &3Уникальные разрешения (пермы): &a{15}\n
{PREFIX}&f- &bКонфигурация:\n
{PREFIX}&f- &3Использует сервер UUIDs: {16}\n
{PREFIX}&f- &bСчетчик разрешений (Пермов):\n
{PREFIX}&f- &3Включая глобальные: {17}\n
{PREFIX}&f- &3Включая глобальный мир: {18}\n
{PREFIX}&f- &3Применения глобальных групп: {19}\n
{PREFIX}&f- &3Применения глобальных групп мира: {20}\n
{PREFIX}&f- &3Применения подстановочных знаков (Wildcards): {21}\n
{PREFIX}&f- &3Применение регулярных выражений: {22}\n
{PREFIX}&f- &3Применение стенографии (Shorthand): {23}
create-group-error: "При создании группы произошла ошибка."
delete-group-error: "При удалении группы произошла ошибка."
delete-group-error-default: "Вы не можете удалить группу по умолчанию."
groups-list: "&aГруппы: {0}"
create-track-error: "При создании пути (track) произошла ошибка."
delete-track-error: "При удалении пути (Track) произошла ошибка."
tracks-list: "&aGenb (Tracks): {0}"
listnodes: "&b{0}'s Узлы:"
listnodes-with-page: "&b{0}'s Узлы: {1}"
listnodes-temp: >
&b{0}'s Временные узлы:\n
{1}
listparents: >
&b{0}'s Родительские группы:\n
{1}
listparents-temp: >
&b{0}'s Временные родительские группы:\n
{1}
listgroups: >
&b{0}'s Группы:\n
{1}
listgroups-temp: >
&b{0}'s Временные группы:\n
{1}
list-tracks: >
&b{0}'s Пути (Tracks):\n
{1}
list-tracks-empty: "{0} Не на каких-либо путях (tracks)."
setpermission-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a."
setpermission-server-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a на сервер &b{3}&a."
setpermission-server-world-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a на сервер &b{3}&a, мир &b{4}&a."
setpermission-temp-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a На срок &b{3}&a."
setpermission-temp-server-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a на сервер &b{3}&a на срок &b{4}&a."
setpermission-temp-server-world-success: "&aУстановить &b{0}&a к &b{1}&a для &b{2}&a на сервер &b{3}&a, мир &b{4}&a, на срок &b{5}&a."
unsetpermission-success: "&aСбросить &b{0}&a для &b{1}&a."
unsetpermission-server-success: "&aСбросить &b{0}&a для &b{1}&a on server &b{2}&a."
unsetpermission-server-world-success: "&aСбросить &b{0}&a для &b{1}&a on server &b{2}&a, world &b{3}&a."
unset-temp-permission-success: "&aСбросить временное разрешение &b{0}&a для &b{1}&a."
unset-temp-permission-server-success: "&aСбросить временное разрешение &b{0}&a для &b{1}&a на сервере &b{2}&a."
unset-temp-permission-server-world-success: "&aСбросить временное разрешение &b{0}&a для &b{1}&a на сервере &b{2}&a, мире &b{3}&a."
set-inherit-success: "&b{0}&a теперь наследует разрешения от &b{1}&a."
set-inherit-server-success: "&b{0}&a теперь наследует разрешения от &b{1}&a на сервере &b{2}&a."
set-inherit-server-world-success: "&b{0}&a теперь наследует разрешения от &b{1}&a на сервере &b{2}&a, мире &b{3}&a."
set-parent-success: "&b{0}&a их существующие родительские группы очищены, и теперь только наследуют &b{1}&a."
set-parent-server-success: "&b{0}&a их существующие родительские группы очищены, и теперь только наследуют &b{1}&a на сервере &b{2}&a."
set-parent-server-world-success: "&b{0}&a их существующие родительские группы очищены, и теперь только наследуют &b{1}&a на сервере &b{2}&a, мире &b{3}&a."
set-temp-inherit-success: "&b{0}&a теперь наследует разрешения (пермы) от &b{1}&a на срок &b{2}&a."
set-temp-inherit-server-success: "&b{0}&a теперь наследует разрешения (пермы) от &b{1}&a на сервере &b{2}&a for a duration of &b{3}&a."
set-temp-inherit-server-world-success: "&b{0}&a теперь наследует разрешения (пермы) от &b{1}&a на сервере &b{2}&a, мире &b{3}&a, на срок &b{4}&a."
unset-inherit-success: "&b{0}&a больше не наследует разрешения (пермы) от &b{1}&a."
unset-inherit-server-success: "&b{0}&a больше не наследует разрешения (пермы) &b{1}&a на сервере &b{2}&a."
unset-inherit-server-world-success: "&b{0}&a больше не наследует разрешения (пермы) &b{1}&a на сервере &b{2}&a, мире &b{3}&a."
unset-temp-inherit-success: "&b{0}&a больше не наследует временные разрешения (пермы) &b{1}&a."
unset-temp-inherit-server-success: "&b{0}&a больше не наследует временные разрешения (пермы) &b{1}&a на сервере &b{2}&a."
unset-temp-inherit-server-world-success: "&b{0}&a больше не наследует временные разрешения (пермы) &b{1}&a на сервере &b{2}&a, мире &b{3}&a."
clear-success: "&b{0}&a's разрешения (пермы) были очищены. (&b{1}&a узлы были удалены.)"
clear-success-singular: "&b{0}&a's разрешения (пермы) были очищены. (&b{1}&a узел удален.)"
parent-clear-success: "&b{0}&a's родители очищены. (&b{1}&a узлы были удалены.)"
parent-clear-success-singular: "&b{0}&a's родители очищены. (&b{1}&a узел удален.)"
meta-clear-success: "&b{0}&a's Meta очищена. (&b{1}&a узлы были удалены.)"
meta-clear-success-singular: "&b{0}&a's Meta очищена. (&b{1}&a узел удален.)"
illegal-date-error: "Не удалось выполнить синтаксический анализ даты '{0}'."
past-date-error: "Вы не можете установить дату в прошлом!"
chat-meta-prefix-header: "&b{0}'s Префиксы"
chat-meta-suffix-header: "&b{0}'s Суффиксы"
meta-header: "&b{0}'s Meta"
chat-meta-entry: "&b-> {0} &f- &f\"{1}&f\" &8(&7унаследованный от &a{2}&8)"
chat-meta-entry-with-context: "&b-> {0} &f- &f\"{1}&f\" &8(&7унаследованный от &a{2}&8){3}"
meta-entry: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7унаследованный от &a{2}&8)"
meta-entry-with-context: "&b-> &a{0} &f= &f\"{1}&f\" &8(&7унаследованный от &a{2}&8){3}"
chat-meta-prefix-none: "&b{0} не имеет префиксов."
chat-meta-suffix-none: "&b{0} не имеет суффиксов."
meta-none: "&b{0} не имеет meta."
meta-invalid-priority: "Недопустимый приоритет '{0}'. Ожидается число."
already-has-prefix: "{0} Уже имеет этот набор префиксов."
already-has-suffix: "{0} Уже имеет этот набор суффиксов."
does-not-have-prefix: "{0} Не имеет этого набора префиксов."
does-not-have-suffix: "{0} Не имеет этого набора суффиксов."
addprefix-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a."
addprefix-server-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a на сервере &b{3}&a."
addprefix-server-world-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a на сервере &b{3}&a, мире &b{4}&a."
removeprefix-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено."
removeprefix-server-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a."
removeprefix-server-world-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a, мире &b{4}&a."
addsuffix-success: "&b{0}&a имеет суффикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a."
addsuffix-server-success: "&b{0}&a имеет суффикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a на сервере &b{3}&a."
addsuffix-server-world-success: "&b{0}&a имеет суффикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a on server &b{3}&a, мире &b{4}&a."
removesuffix-success: "&b{0}&a имеет суффикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено."
removesuffix-server-success: "&b{0}&a имеет суффикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a."
removesuffix-server-world-success: "&b{0}&a имеет суффикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a, мире &b{4}&a."
add-temp-prefix-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a на срок &b{3}&a."
add-temp-prefix-server-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a установленный в приоритете of &b{2}&a на сервере &b{3}&a, на срок &b{4}&a."
add-temp-prefix-server-world-success: "&b{0}&a имеет префикс &f\"{1}&f\"&a установленный в приоритете of &b{2}&a на сервере &b{3}&a, мире &b{4}&a, на срок &b{5}&a."
remove-temp-prefix-success: "&b{0}&a Имел временный префикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено."
remove-temp-prefix-server-success: "&b{0}&a Имел временный префикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a."
remove-temp-prefix-server-world-success: "&b{0}&a Имел временный префикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a, мире &b{4}&a."
add-temp-suffix-success: "&b{0}&a имел суффикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a на срок &b{3}&a."
add-temp-suffix-server-success: "&b{0}&a имел суффикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a на сервере &b{3}&a, на срок &b{4}&a."
add-temp-suffix-server-world-success: "&b{0}&a имел суффикс &f\"{1}&f\"&a установленный в приоритете &b{2}&a на сервере &b{3}&a, мире &b{4}&a, на срок &b{5}&a."
remove-temp-suffix-success: "&b{0}&a Имел временный суффикс &f\"{1}&f\"&a в приоритете &b{1}&a удалено."
remove-temp-suffix-server-success: "&b{0}&a Имел временный суффикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a."
remove-temp-suffix-server-world-success: "&b{0}&a Имел временный суффикс &f\"{1}&f\"&a в приоритете &b{2}&a удалено на сервере &b{3}&a, мире &b{4}&a."
already-has-meta: "{0} Уже установлено, что установлена ​​пара мета-значения ключа."
set-meta-success: "&aУстановить метазначение для ключа &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a."
set-meta-server-success: "&aУстановить метазначение для ключа &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a на сервере &b{3}&a."
set-meta-server-world-success: "&aУстановить метазначение для ключа &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a на сервере &b{3}&a, мире &b{4}&a."
set-meta-temp-success: "&aУстановить метазначение для ключа &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a на срок &b{3}&a."
set-meta-temp-server-success: "&aУстановить метазначение для ключа &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a на сервере &b{3}&a на срок &b{4}&a."
set-meta-temp-server-world-success: "&aУстановить метазначение для ключа &f\"{0}&f\"&a к &f\"{1}&f\"&a для &b{2}&a на сервере &b{3}&a, мире &b{4}&a, на срок &b{5}&a."
unset-meta-success: "&aСбросить метазначение с помощью ключа &f\"{0}&f\"&a для &b{1}&a."
unset-meta-server-success: "&aСбросить метазначение с помощью ключа &f\"{0}&f\"&a для &b{1}&a на сервере &b{2}&a."
unset-meta-server-world-success: "&aСбросить метазначение с помощью ключа &f\"{0}&f\"&a для &b{1}&a на сервере &b{2}&a, мире &b{3}&a."
unset-meta-temp-success: "&aUnset temporary meta value with key &f\"{0}&f\"&a для &b{1}&a."
unset-meta-temp-server-success: "&aСбросить временное мета-значение с помощью ключа &f\"{0}&f\"&a для &b{1}&a на сервере &b{2}&a."
unset-meta-temp-server-world-success: "&aСбросить временное мета-значение с помощью ключа &f\"{0}&f\"&a для &b{1}&a на сервере &b{2}&a, мире &b{3}&a."
bulk-change-type-error: "Недопустимый тип. ожидание 'server' или 'world'."
bulk-change-success: "&aПрименение изменения успешно. {0} записей изменено."
user-info-general: >
{PREFIX}&b&l> &bИнформация о пользователе: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
{PREFIX}&f- &3Статус: {2}\n
{PREFIX}&f- &3Первичная группа: &f{3}\n
{PREFIX}&f- &aГрафы:\n
{PREFIX}&f- &3Разрешения (пермы): &a{4}\n
{PREFIX}&f- &3Временные разрешения (пермы): &a{5}\n
{PREFIX}&f- &3Префиксы: &a{6}\n
{PREFIX}&f- &3Суффиксы: &a{7}\n
{PREFIX}&f- &3Meta: &a{8}
user-info-data: >
{PREFIX}&f- &aКэшированные данные:\n
{PREFIX}&f- &3Загружается: {0}\n
{PREFIX}&f- &3Текущие контексты: {1}\n
{PREFIX}&f- &3Текущие префиксы: {2}\n
{PREFIX}&f- &3Текущие суффиксы: {3}
info-parent-header: "&f- &aРодительские группы:"
info-temp-parent-header: "&f- &aВременные родительские группы:"
user-getuuid: "&bUUID для &b{0}&b это &b{1}&b."
user-removegroup-error-primary: "Вы не можете удалить пользователя из своей основной группы."
user-primarygroup-success: "&b{0}&a's для основной группы установил значение &b{1}&a."
user-primarygroup-warn-option: "&cВнимание: метод расчета основной группы, используемый этим сервером &7({0}) &cМожет не отражать это изменение."
user-primarygroup-error-alreadyhas: "Пользователь уже установил эту группу в качестве основной группы."
user-primarygroup-error-notmember: "&b{0}&a еще не был членом &b{1}&a, Добавить его сейчас."
user-track-error-not-contain-group: "Указанный пользователь не находится ни в каких группах на этом пути (track)."
user-track-added-to-first: "&b{0}&a Не находится ни в каких группах на этом пути (track), поэтому они были добавлены в первую группу, &b{1}&a."
user-track-added-to-first-server: "&b{0}&a Не находится ни в каких группах на этом пути (track), поэтому они были добавлены в первую группу, &b{1}&a, на сервере &b{2}&a."
user-track-added-to-first-server-world: "&b{0}&a Не находится ни в каких группах на этом пути (track), поэтому они были добавлены в первую группу, &b{1}&a, на сервере &b{2}&a, мире &b{3}&a."
user-promote-success: "&aПродвижение пользователя по пути (track) &b{0}&a от &b{1}&a к &b{2}&a."
user-promote-success-server: "&aПродвижение пользователя по пути (track) &b{0}&a от &b{1}&a к &b{2}&a на сервере &b{3}&a."
user-promote-success-server-world: "&aПродвижение пользователя по пути (track) &b{0}&a от &b{1}&a к &b{2}&a на сервере &b{3}&a, мире &b{4}&a."
user-promote-error-endoftrack: "Конец пути (track) &4{0}&c ,был достигнут. Не удалось продвинуть пользователя."
user-promote-error-malformed: >
{PREFIX}Следующая группа на пути (track), {0}, более не существует. Не удалось продвинуть пользователя.\n
{PREFIX}Создайте группу или удалите ее с пути (track) и повторите попытку.
user-demote-success: "&aПонижение пользователя по пути (track) &b{0}&a от &b{1}&a к &b{2}&a."
user-demote-success-server: "&aПонижение пользователя по пути (track) &b{0}&a от &b{1}&a к &b{2}&a на сервере &b{3}&a."
user-demote-success-server-world: "&aПонижение пользователя по пути (track) &b{0}&a от &b{1}&a к &b{2}&a на сервере &b{3}&a, мире &b{4}&a."
user-demote-endoftrack: "Конец пути (track) &4{0}&c был достингут, поэтому &4{1}&c был удален из &4{2}&c."
user-demote-error-malformed: >
{PREFIX}Предыдущая группа на пути (track), {0}, более не существуют. Невозможно понизить рейтинг пользователя.\n
{PREFIX}Создайте группу или удалите ее с пути (track) и повторите попытку.
user-showpos: >
&aпоказывает &b{0}&a's позицию на пути (track) &b{1}&a.\n
{2}
group-info-general: >
{PREFIX}&b&l> &bИнфо о группе: &f{0}\n
{PREFIX}&f- &3Отображаемое имя: &f{1}\n
{PREFIX}&f- &3Значение: &f{2}\n
{PREFIX}&f- &aГрафы:\n
{PREFIX}&f- &3Разрешения (пермы): &a{3}\n
{PREFIX}&f- &3Временные разрешения (пермы): &a{4}\n
{PREFIX}&f- &3Префиксы: &a{5}\n
{PREFIX}&f- &3Суффиксы: &a{6}\n
{PREFIX}&f- &3Meta: &a{7}
group-set-weight: "&aУстановить значение &b{0}&a для группы &b{1}&a."
track-info: >
{PREFIX}&b&l> &bпоказать путь (track): &f{0}\n
{PREFIX}&f- &7Путь: &f{1}
track-clear: "&b{0}&a's путь (track) групп был очищен."
track-append-success: "&aГруппа &b{0}&a успешно добавлен в путь (track) &b{1}&a."
track-insert-success: "&aГруппа &b{0}&a был успешно добавлен в путь (track) &b{1}&a на позиции &b{2}&a."
track-insert-error-number: "Ожидаемое число, но вместо этого получено: {0}"
track-insert-error-invalid-pos: "Невозможно вставить в позицию {0}. Индекс вне пределов."
track-remove-success: "&aгруппа &b{0}&a Успешно удален с пути (track) &b{1}&a."
log-load-error: "Не удалось загрузить лог."
log-invalid-page: "Недействительный номер страницы."
log-invalid-page-range: "Недопустимый номер страницы. Введите значение от 1 до {0}."
log-no-entries: "&bНет логов для отображения."
log-entry: "&b#{0} -> &8(&7{1} ago&8) {2}"
log-notify-toggle-on: "&aВключен&b вывод лога."
log-notify-toggle-off: "&cОтключен&b вывод лога."
log-notify-already-on: "Вы уже получаете оповещения."
log-notify-already-off: "Вы не получаете оповещений."
log-notify-unknown: "Состояние неизвестно. Ожидание \"on\" или \"off\"."
log-search-header: "&aПросмотр недавних действий для запроса &b{0} &a(page &f{1}&a of &f{2}&a)"
log-recent-header: "&aПоследние действия (page &f{0}&a of &f{1}&a)"
log-recent-by-header: "&aПоказывать последние действия &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-user-header: "&aОтображение истории для пользователя &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-group-header: "&aОтображение истории для группы &b{0} &a(page &f{1}&a of &f{2}&a)"
log-history-track-header: "&aОтображение истории для пути (track) &b{0} &a(page &f{1}&a of &f{2}&a)"
log-export-already-exists: "Ошибка: файл {0} уже существует."
log-export-not-writable: "Ошибка: файл {0} недоступен для записи."
log-export-empty: "Журнал пуст и поэтому не может быть экспортирован."
log-export-failure: "Произошла непредвиденная ошибка во время записи в файл."
log-export-success: "&aФайл журнала успешно экспортирован в &b{0}&a."
import-already-running: "Другой процесс импорта уже запущен. Дождитесь завершения и повторите попытку."
export-already-running: "Еще один процесс экспорта уже запущен. Дождитесь завершения и повторите попытку."
import-log-doesnt-exist: "Ошибка: файл {0} не существует."
import-log-not-readable: "Ошибка: файл {0} не читается."
import-log-failure: "Произошла непредвиденная ошибка при чтении лога."
import-progress: "&b(Импортировать) &b-> &f{0} &fПроцент завершен &7- &b{1}&f/&b{2} &fОперации в комплекте с &c{3} &fошибками."
import-progress-sin: "&b(Импортировать) &b-> &f{0} &fПроцент завершен &7- &b{1}&f/&b{2} &fОперации в комплекте с &c{3} &fошибками."
import-start: "&b(Импортировать) &b-> &fЗапуск процесса импорта."
import-end-complete: "&b(Импортировать) &a&lЗАВЕРШЕНО &7- заняло &b{0} &7секунд - &7Нет ошибок."
import-end-complete-err: "&b(Импортировать) &a&lЗАВЕРШЕНО &7- заняло &b{0} &7секунд - &c{1} ошибок."
import-end-complete-err-sin: "&b(Импортировать) &a&lЗАВЕРШЕНО &7- заняло &b{0} &7секунд - &c{1} ошибок."
import-end-error-header: >
{PREFIX}&b(Import) &7------------> &fОтображение ошибки #&b{0} &7<------------\n
{PREFIX}&b(Import) &fВыполняя: &3Команду #{1}\n
{PREFIX}&b(Import) &fКоманда: &7{2}\n
{PREFIX}&b(Import) &fТип: &3{3}\n
{PREFIX}&b(Import) &fВывод:
import-end-error-content: "&b(Импортировать) &b-> &c{0}"
import-end-error-footer: "&b(Импортировать) &7<------------------------------------------>"
+6 -6
View File
@@ -11,7 +11,7 @@ LuckPerms also includes an extensive API for developers, and support for a varie
## Useful Links
* **Development Builds** - <https://ci.lucko.me/job/LuckPerms>
* **Javadocs** - <https://jd.lucko.me/LuckPerms>
* **Javadocs** - <https://luckperms.lucko.me/javadocs/>
* **Wiki** - <https://github.com/lucko/LuckPerms/wiki>
## Building
@@ -32,13 +32,13 @@ LuckPerms is licensed under the permissive MIT license. Please see [`LICENSE.txt
## LuckPerms vs Other Plugins
### Bukkit
![alt text](https://static.lucko.me/luckperms-compare-copy.png "Feature comparison")
###### _Features checked on 23rd Aug 2016. If you find any inaccuracies, please let me know._
![alt text](https://luckperms.lucko.me/assets/bukkit-compare.png "Feature comparison")
### Sponge
![alt text](https://static.lucko.me/luckperms-compare-sponge.png "Feature comparison")
###### _Features checked on 8th Oct 2016. If you find any inaccuracies, please let me know._
![alt text](https://luckperms.lucko.me/assets/sponge-compare.png "Feature comparison")
## Thanks
[![JProfiler](https://www.ej-technologies.com/images/product_banners/jprofiler_large.png)](http://www.ej-technologies.com/products/jprofiler/overview.html)
Thanks to ej-technologies for granting LuckPerms an open source licence to their [Java Profiling Software](http://www.ej-technologies.com/products/jprofiler/overview.html "Java Profiler").
Thanks to ej-technologies for granting LuckPerms an open source licence to their [Java Profiling Software](http://www.ej-technologies.com/products/jprofiler/overview.html "Java Profiler").
+3 -2
View File
@@ -5,17 +5,18 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>luckperms-api</artifactId>
<build>
<finalName>LuckPerms-API-${release.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -29,8 +29,8 @@ import java.util.Optional;
/**
* Singleton for the {@link LuckPermsApi}.
*
* <p> Ideally, the ServiceManager for the platform should be used to obtain and cache an instance, however, this can be
* used if you need static access.
* <p>Ideally, the ServiceManager for the platform should be used to obtain and cache an instance, however, this can be
* used if you need static access.</p>
*/
public final class LuckPerms {
private static LuckPermsApi api = null;
@@ -27,7 +27,7 @@ import me.lucko.luckperms.api.context.ContextSet;
/**
* Context and options for a permission lookup.
*
* <p> All values are immutable.
* <p>All values are immutable.</p>
*
* @since 2.11
*/
@@ -192,16 +192,13 @@ public class Contexts {
if (o == this) return true;
if (!(o instanceof Contexts)) return false;
final Contexts other = (Contexts) o;
final Object this$context = this.getContexts();
final Object other$context = other.getContexts();
if (this$context == null ? other$context != null : !this$context.equals(other$context)) return false;
if (this.isOp() != other.isOp()) return false;
if (this.isIncludeGlobal() != other.isIncludeGlobal()) return false;
if (this.isIncludeGlobalWorld() != other.isIncludeGlobalWorld()) return false;
if (this.isApplyGroups() != other.isApplyGroups()) return false;
if (this.isApplyGlobalGroups() != other.isApplyGlobalGroups()) return false;
if (this.isApplyGlobalWorldGroups() != other.isApplyGlobalWorldGroups()) return false;
return true;
return (this.getContexts() == null ? other.getContexts() == null : this.getContexts().equals(other.getContexts())) &&
this.isOp() == other.isOp() &&
this.isIncludeGlobal() == other.isIncludeGlobal() &&
this.isIncludeGlobalWorld() == other.isIncludeGlobalWorld() &&
this.isApplyGroups() == other.isApplyGroups() &&
this.isApplyGlobalGroups() == other.isApplyGlobalGroups() &&
this.isApplyGlobalWorldGroups() == other.isApplyGlobalWorldGroups();
}
/**
@@ -213,8 +210,8 @@ public class Contexts {
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $context = this.getContexts();
result = result * PRIME + ($context == null ? 43 : $context.hashCode());
final Object contexts = this.getContexts();
result = result * PRIME + (contexts == null ? 43 : contexts.hashCode());
result = result * PRIME + (this.isOp() ? 79 : 97);
result = result * PRIME + (this.isIncludeGlobal() ? 79 : 97);
result = result * PRIME + (this.isIncludeGlobalWorld() ? 79 : 97);
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.api;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.function.Supplier;
public enum DataMutateResult {
SUCCESS(true, null),
ALREADY_HAS(false, ObjectAlreadyHasException::new),
LACKS(false, ObjectLacksException::new),
FAIL(false, RuntimeException::new);
private boolean value;
private final Supplier<? extends Exception> exceptionSupplier;
DataMutateResult(boolean value, Supplier<? extends Exception> exceptionSupplier) {
this.value = value;
this.exceptionSupplier = exceptionSupplier;
}
public void throwException() {
if (exceptionSupplier != null) {
sneakyThrow(exceptionSupplier.get());
}
}
public boolean asBoolean() {
return value;
}
// allows us to throw checked exceptions without declaring it, as #throwException throws a number of
// exception types.
private static void sneakyThrow(Throwable t) {
sneakyThrow0(t);
}
private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T {
throw (T) t;
}
}
@@ -82,7 +82,9 @@ public interface Group extends PermissionHolder {
* @throws ObjectAlreadyHasException if the group already inherits the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(Group group) throws ObjectAlreadyHasException;
/**
@@ -94,7 +96,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(Group group, String server) throws ObjectAlreadyHasException;
/**
@@ -107,7 +111,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(Group group, String server, String world) throws ObjectAlreadyHasException;
/**
@@ -119,7 +125,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(Group group, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -132,7 +140,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(Group group, String server, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -146,7 +156,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void setInheritGroup(Group group, String server, String world, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -156,7 +168,9 @@ public interface Group extends PermissionHolder {
* @throws ObjectLacksException if the group does not already inherit the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(Group group) throws ObjectLacksException;
/**
@@ -167,7 +181,9 @@ public interface Group extends PermissionHolder {
* @throws ObjectLacksException if the group does not already inherit the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(Group group, boolean temporary) throws ObjectLacksException;
/**
@@ -179,7 +195,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(Group group, String server) throws ObjectLacksException;
/**
@@ -192,7 +210,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(Group group, String server, String world) throws ObjectLacksException;
/**
@@ -205,7 +225,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(Group group, String server, boolean temporary) throws ObjectLacksException;
/**
@@ -219,7 +241,9 @@ public interface Group extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void unsetInheritGroup(Group group, String server, String world, boolean temporary) throws ObjectLacksException;
/**
@@ -29,8 +29,8 @@ import java.util.UUID;
/**
* Represents the internal LuckPerms log.
*
* <p> The returned instance provides a copy of the data at the time of retrieval. Any changes made to log entries will
* only apply to this instance of the log. You can add to the log using the {@link Storage}, and then request an updated copy.
* <p>The returned instance provides a copy of the data at the time of retrieval. Any changes made to log entries will
* only apply to this instance of the log. You can add to the log using the {@link Storage}, and then request an updated copy.</p>
*/
public interface Log {
@@ -27,7 +27,7 @@ import java.util.UUID;
/**
* A single entry in the log
*
* <p> Implements {@link Comparable} ordering based upon the timestamp of the entry.
* <p>Implements {@link Comparable} ordering based upon the timestamp of the entry.</p>
*/
public class LogEntry implements Comparable<LogEntry> {
private static final String FORMAT = "&8(&e%s&8) [&a%s&8] (&b%s&8) &7--> &f%s";
@@ -90,7 +90,7 @@ public class LogEntry implements Comparable<LogEntry> {
public String getFormatted() {
return String.format(FORMAT,
actorName,
String.valueOf(actorName).equals("null") ? actor.toString() : actorName,
Character.toString(type),
actedName,
action
@@ -171,13 +171,13 @@ public class LogEntry implements Comparable<LogEntry> {
if (o == this) return true;
if (!(o instanceof LogEntry)) return false;
final LogEntry other = (LogEntry) o;
if (this.getTimestamp() != other.getTimestamp()) return false;
if (this.getActor() == null ? other.getActor() != null : !this.getActor().equals(other.getActor())) return false;
if (this.getActorName() == null ? other.getActorName() != null : !this.getActorName().equals(other.getActorName())) return false;
if (this.getType() != other.getType()) return false;
if (this.getActed() == null ? other.getActed() != null : !this.getActed().equals(other.getActed())) return false;
if (this.getActedName() == null ? other.getActedName() != null : !this.getActedName().equals(other.getActedName())) return false;
return this.getAction() == null ? other.getAction() == null : this.getAction().equals(other.getAction());
return this.getTimestamp() == other.getTimestamp() &&
(this.getActor() == null ? other.getActor() == null : this.getActor().equals(other.getActor())) &&
(this.getActorName() == null ? other.getActorName() == null : this.getActorName().equals(other.getActorName())) &&
this.getType() == other.getType() &&
(this.getActed() == null ? other.getActed() == null : this.getActed().equals(other.getActed())) &&
(this.getActedName() == null ? other.getActedName() == null : this.getActedName().equals(other.getActedName())) &&
(this.getAction() == null ? other.getAction() == null : this.getAction().equals(other.getAction()));
}
@Override
@@ -25,8 +25,8 @@ package me.lucko.luckperms.api;
/**
* A wrapper interface for platform logger instances
*
* <p> Bukkit/Bungee both use java.util.logging, and Sponge uses org.slf4j. This class wraps those classes so the
* commons module can access a logger.
* <p>Bukkit/Bungee both use java.util.logging, and Sponge uses org.slf4j. This class wraps those classes so the
* commons module can access a logger.</p>
*/
public interface Logger {
@@ -113,7 +113,9 @@ public interface LuckPermsApi {
User getUser(UUID uuid);
/**
* Gets a wrapped user object from the user storage. This method does not return null, unlike {@link #getUser(UUID)}
* Gets a wrapped user object from the user storage.
*
* <p>This method does not return null, unlike {@link #getUser(UUID)}</p>
*
* @param uuid the uuid of the user to get
* @return an optional {@link User} object
@@ -131,8 +133,9 @@ public interface LuckPermsApi {
User getUser(String name);
/**
* Gets a wrapped user object from the user storage. This method does not return null, unlike {@link
* #getUser(String)}
* Gets a wrapped user object from the user storage.
*
* <p>This method does not return null, unlike {@link #getUser(String)}</p>
*
* @param name the username of the user to get
* @return an optional {@link User} object
@@ -175,7 +178,9 @@ public interface LuckPermsApi {
Group getGroup(String name);
/**
* Gets a wrapped group object from the group storage. This method does not return null, unlike {@link #getGroup}
* Gets a wrapped group object from the group storage.
*
* <p>This method does not return null, unlike {@link #getGroup}</p>
*
* @param name the name of the group to get
* @return an optional {@link Group} object
@@ -209,7 +214,9 @@ public interface LuckPermsApi {
Track getTrack(String name);
/**
* Gets a wrapped track object from the track storage. This method does not return null, unlike {@link #getTrack}
* Gets a wrapped track object from the track storage.
*
* <p>This method does not return null, unlike {@link #getTrack}</p>
*
* @param name the name of the track to get
* @return an optional {@link Track} object
@@ -32,8 +32,8 @@ public interface MessagingService {
/**
* Uses the messaging service to inform other servers about changes.
*
* <p> This will push the update asynchronously, and this method will return immediately. Calling this method is
* equivalent to running "/lp networksync", except will not sync this server.
* <p>This will push the update asynchronously, and this method will return immediately. Calling this method is
* equivalent to running "/lp networksync", except will not sync this server.</p>
*/
void pushUpdate();
@@ -34,7 +34,9 @@ import java.util.Set;
* A collection of utilities to help retrieve meta values for {@link PermissionHolder}s
*
* @since 2.7
* @deprecated in favour of using {@link NodeFactory#makeMetaNode(String, String)} or {@link me.lucko.luckperms.api.caching.MetaData}.
*/
@Deprecated
public class MetaUtils {
private static String escapeDelimiters(String s, String... delims) {
@@ -92,42 +94,24 @@ public class MetaUtils {
* @param holder the holder to apply the meta node to
* @param server the server to apply the meta on, can be null
* @param world the world to apply the meta on, can be null
* @param node the meta node
* @param key the meta key
* @param value the meta value
* @throws NullPointerException if the holder, node or value is null
* @throws IllegalArgumentException if the node or value is empty
*/
public static void setMeta(PermissionHolder holder, String server, String world, String node, String value) {
if (holder == null) {
throw new NullPointerException("holder");
}
public static void setMeta(PermissionHolder holder, String server, String world, String key, String value) {
if (holder == null) throw new NullPointerException("holder");
if (key == null) throw new NullPointerException("node");
if (value == null) throw new NullPointerException("value");
if (key.equals("")) throw new IllegalArgumentException("node is empty");
if (value.equals("")) throw new IllegalArgumentException("value is empty");
if (node == null) {
throw new NullPointerException("node");
}
if (value == null) {
throw new NullPointerException("value");
}
if (node.equals("")) {
throw new IllegalArgumentException("node is empty");
}
if (value.equals("")) {
throw new IllegalArgumentException("value is empty");
}
if (server == null || server.equals("")) {
server = "global";
}
node = escapeCharacters(node);
key = escapeCharacters(key);
value = escapeCharacters(value);
Set<Node> toRemove = new HashSet<>();
for (Node n : holder.getEnduringPermissions()) {
if (n.isMeta() && n.getMeta().getKey().equals(node)) {
if (n.isMeta() && n.getMeta().getKey().equals(key)) {
toRemove.add(n);
}
}
@@ -135,22 +119,20 @@ public class MetaUtils {
for (Node n : toRemove) {
try {
holder.unsetPermission(n);
} catch (ObjectLacksException ignored) {
}
} catch (ObjectLacksException ignored) {}
}
Node.Builder metaNode = LuckPerms.getApi().buildNode("meta." + node + "." + value).setValue(true);
if (!server.equalsIgnoreCase("global")) {
Node.Builder metaNode = LuckPerms.getApi().buildNode("meta." + key + "." + value).setValue(true);
if (server != null) {
metaNode.setServer(server);
}
if (world != null && !world.equals("")) {
metaNode.setServer(server).setWorld(world);
if (world != null) {
metaNode.setWorld(world);
}
try {
holder.setPermission(metaNode.build());
} catch (ObjectAlreadyHasException ignored) {
}
} catch (ObjectAlreadyHasException ignored) {}
}
/**
@@ -167,18 +149,8 @@ public class MetaUtils {
* @throws IllegalArgumentException if the node is empty
*/
public static String getMeta(PermissionHolder holder, String server, String world, String node, String defaultValue, boolean includeGlobal) {
if (holder == null) {
throw new NullPointerException("holder");
}
if (server == null || server.equals("")) {
server = "global";
}
if (node == null) {
throw new NullPointerException("node");
}
if (holder == null) throw new NullPointerException("holder");
if (node == null) throw new NullPointerException("node");
if (node.equals("")) {
throw new IllegalArgumentException("node is empty");
}
@@ -186,23 +158,10 @@ public class MetaUtils {
node = escapeCharacters(node);
for (Node n : holder.getPermissions()) {
if (!n.getValue()) {
continue;
}
if (!n.getValue() || !n.isMeta()) continue;
if (!n.isMeta()) {
continue;
}
if (!server.equalsIgnoreCase("global")) {
if (!n.shouldApplyOnServer(server, includeGlobal, false)) {
continue;
}
}
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) {
continue;
}
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
Map.Entry<String, String> meta = n.getMeta();
if (meta.getKey().equalsIgnoreCase(node)) {
@@ -214,27 +173,23 @@ public class MetaUtils {
}
private static void setChatMeta(boolean prefix, PermissionHolder holder, String value, int priority, String server, String world) {
if (holder == null) {
throw new NullPointerException("holder");
}
if (holder == null) throw new NullPointerException("holder");
if (value == null || value.equals("")) {
throw new IllegalArgumentException("value is null/empty");
}
Node.Builder node = LuckPerms.getApi().buildNode(prefix ? "prefix" : "suffix" + "." + priority + "." + escapeCharacters(value));
node.setValue(true);
if (!server.equalsIgnoreCase("global")) {
if (server != null) {
node.setServer(server);
}
if (world != null && !world.equals("")) {
node.setServer(server).setWorld(world);
if (world != null) {
node.setWorld(world);
}
try {
holder.setPermission(node.build());
} catch (ObjectAlreadyHasException ignored) {
}
} catch (ObjectAlreadyHasException ignored) {}
}
/**
@@ -271,26 +226,14 @@ public class MetaUtils {
if (holder == null) {
throw new NullPointerException("holder");
}
if (server == null) {
server = "global";
}
int priority = Integer.MIN_VALUE;
String meta = null;
for (Node n : holder.getAllNodes(Contexts.allowAll())) {
if (!n.getValue()) {
continue;
}
if (!n.getValue()) continue;
if (!server.equalsIgnoreCase("global")) {
if (!n.shouldApplyOnServer(server, includeGlobal, false)) {
continue;
}
}
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) {
continue;
}
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
if (prefix ? !n.isPrefix() : !n.isSuffix()) {
continue;
@@ -33,7 +33,7 @@ import java.util.Set;
/**
* An immutable permission node
*
* <p> Use {@link LuckPermsApi#buildNode(String)} to get an instance.
* <p>Use {@link LuckPermsApi#buildNode(String)} to get an instance.</p>
*
* @since 2.6
*/
@@ -85,15 +85,49 @@ public interface Node extends Map.Entry<String, Boolean> {
Optional<String> getWorld();
/**
* Returns if this node is server specific
*
* @return true if this node is server specific
*/
boolean isServerSpecific();
/**
* Returns if this node is server specific
*
* @return true if this node is server specific
*/
boolean isWorldSpecific();
/**
* Returns if this node applies globally, and has no specific context
*
* @return true if this node applies globally, and has no specific context
* @since 3.1
*/
boolean appliesGlobally();
/**
* Returns if this node has any specific context in order to apply.
*
* @return true if this node has specific context
* @since 3.1
*/
boolean hasSpecificContext();
/**
* Returns if this node is able to apply in the given context
*
* @param includeGlobal if global server values should apply
* @param includeGlobalWorld if global world values should apply
* @param server the server being checked against, or null
* @param world the world being checked against, or null
* @param context the context being checked against, or null
* @param applyRegex if regex should be applied
* @return true if the node should apply, otherwise false
* @since 3.1
*/
boolean shouldApply(boolean includeGlobal, boolean includeGlobalWorld, String server, String world, ContextSet context, boolean applyRegex);
/**
* If this node should apply on a specific server
*
@@ -203,31 +237,49 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean hasExpired();
/**
* Gets the extra contexts required for this node to apply
*
* @return the extra contexts required for this node to apply
* @since 2.13
*/
ContextSet getContexts();
/**
* The same as {@link #getContexts()}, but also includes values for "server" and "world" keys if present.
*
* @return the full contexts required for this node to apply
* @since 3.1
*/
ContextSet getFullContexts();
/**
* Converts this node into a serialized form
*
* @return a serialized node string
* @deprecated because this serialized form is no longer used by the implementation.
*/
@Deprecated
String toSerializedNode();
/**
* Returns if this is a group node
*
* @return true if this is a group node
*/
boolean isGroupNode();
/**
* Returns the name of the group
*
* @return the name of the group
* @throws IllegalStateException if this is not a group node. See {@link #isGroupNode()}
*/
String getGroupName();
/**
* @return true is this node is a wildcard node
* Returns if this node is a wildcard node
*
* @return true if this node is a wildcard node
*/
boolean isWildcard();
@@ -240,6 +292,8 @@ public interface Node extends Map.Entry<String, Boolean> {
int getWildcardLevel();
/**
* Returns if this node is a meta node
*
* @return true if this node is a meta node
*/
boolean isMeta();
@@ -33,7 +33,7 @@ public interface NodeFactory {
* Creates a node from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @param value the value of the node
* @return a node instance
* @throws NullPointerException if the permission is null
*/
@@ -60,18 +60,30 @@ public interface NodeFactory {
/**
* Creates a node builder from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @param value the value of the node
* @return a node builder instance
* @throws NullPointerException if the permission is null
*/
Node.Builder newBuilderFromSerialisedNode(String serialisedPermission, boolean value);
/**
* Creates a node builder from a group
*
* @param group the group
* @return a node builder instance
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @since 3.1
*/
Node.Builder makeGroupNode(Group group);
/**
* Creates a node builder from a key value pair
*
* @param key the key
* @param key the key
* @param value the value
* @return a node builder instance
* @throws NullPointerException if the key or value is null
@@ -82,7 +94,7 @@ public interface NodeFactory {
* Creates a node builder from a prefix string and priority
*
* @param priority the priority
* @param prefix the prefix string
* @param prefix the prefix string
* @return a node builder instance
* @throws NullPointerException if the prefix is null
*/
@@ -92,7 +104,7 @@ public interface NodeFactory {
* Creates a node builder from a prefix string and priority
*
* @param priority the priority
* @param suffix the suffix string
* @param suffix the suffix string
* @return a node builder instance
* @throws NullPointerException if the suffix is null
*/
@@ -32,15 +32,15 @@ import java.util.SortedSet;
/**
* An object capable of holding permissions
*
* <p> Any changes made will be lost unless the instance is saved back to the {@link Storage}.
* <p>Any changes made will be lost unless the instance is saved back to the {@link Storage}.</p>
*/
public interface PermissionHolder {
/**
* Gets the objects name
*
* <p> {@link User#getUuid()}, {@link User#getName()} or {@link Group#getName()} should normally be used instead of
* this method.
* <p>{@link User#getUuid()}, {@link User#getName()} or {@link Group#getName()} should normally be used instead of
* this method.</p>
*
* @return the identifier for this object. Either a uuid string or name.
*/
@@ -74,9 +74,11 @@ public interface PermissionHolder {
/**
* Gets a mutable sorted set of the nodes that this object has and inherits, filtered by context
* Unlike {@link #getAllNodesFiltered(Contexts)}, this method will not filter individual nodes. The context is only
* used to determine which groups should apply.
* Nodes are sorted into priority order.
*
* <p>Unlike {@link #getAllNodesFiltered(Contexts)}, this method will not filter individual nodes. The context is only
* used to determine which groups should apply.</p>
*
* <p>Nodes are sorted into priority order.</p>
*
* @param contexts the context for the lookup,
* @return a mutable sorted set of permissions
@@ -87,8 +89,9 @@ public interface PermissionHolder {
/**
* Gets a mutable set of the nodes that this object has and inherits, filtered by context.
* Unlike {@link #getAllNodes(Contexts)}, this method WILL filter individual nodes, and only return ones that fully
* meet the context provided.
*
* <p>Unlike {@link #getAllNodes(Contexts)}, this method WILL filter individual nodes, and only return ones that fully
* meet the context provided.</p>
*
* @param contexts the context for the lookup
* @return a mutable set of permissions
@@ -99,6 +102,7 @@ public interface PermissionHolder {
/**
* Converts the output of {@link #getAllNodesFiltered(Contexts)}, and expands shorthand permissions.
*
* @param contexts the context for the lookup
* @param lowerCase if the keys should be made lowercase whilst being exported
* @return a mutable map of permissions
@@ -206,7 +210,7 @@ public interface PermissionHolder {
boolean hasPermission(String node, boolean b, String server, String world, boolean temporary);
/**
* Cheks to see if the object inherits a certain permission
* Checks to see if the object inherits a certain permission
*
* @param node the node to check for
* @return a Tristate for the holders inheritance status for the node
@@ -300,17 +304,27 @@ public interface PermissionHolder {
*/
void setPermission(Node node) throws ObjectAlreadyHasException;
/**
* Sets a permission for the object
*
* @param node The node to be set
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
*/
DataMutateResult setPermissionUnchecked(Node node);
/**
* Sets a transient permission for the object
*
* <p> A transient node is a permission that does not persist.
* <p>A transient node is a permission that does not persist.
* Whenever a user logs out of the server, or the server restarts, this permission will disappear.
* It is never saved to the datastore, and therefore will not apply on other servers.
* It is never saved to the datastore, and therefore will not apply on other servers.</p>
*
* <p> This is useful if you want to temporarily set a permission for a user while they're online, but don't
* want it to persist, and have to worry about removing it when they log out.
* <p>This is useful if you want to temporarily set a permission for a user while they're online, but don't
* want it to persist, and have to worry about removing it when they log out.</p>
*
* <p> For unsetting a transient permission, see {@link #unsetTransientPermission(Node)}
* <p>For unsetting a transient permission, see {@link #unsetTransientPermission(Node)}</p>
*
* @param node The node to be set
* @throws ObjectAlreadyHasException if the object already has the permission
@@ -319,6 +333,25 @@ public interface PermissionHolder {
*/
void setTransientPermission(Node node) throws ObjectAlreadyHasException;
/**
* Sets a transient permission for the object
*
* <p>A transient node is a permission that does not persist.
* Whenever a user logs out of the server, or the server restarts, this permission will disappear.
* It is never saved to the datastore, and therefore will not apply on other servers.</p>
*
* <p>This is useful if you want to temporarily set a permission for a user while they're online, but don't
* want it to persist, and have to worry about removing it when they log out.</p>
*
* <p>For unsetting a transient permission, see {@link #unsetTransientPermission(Node)}</p>
*
* @param node The node to be set
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
*/
DataMutateResult setTransientPermissionUnchecked(Node node);
/**
* Sets a permission for the object
*
@@ -416,6 +449,16 @@ public interface PermissionHolder {
*/
void unsetPermission(Node node) throws ObjectLacksException;
/**
* Unsets a permission for the object
*
* @param node The node to be unset
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
*/
DataMutateResult unsetPermissionUnchecked(Node node);
/**
* Unsets a transient permission for the object
*
@@ -426,6 +469,16 @@ public interface PermissionHolder {
*/
void unsetTransientPermission(Node node) throws ObjectLacksException;
/**
* Unsets a transient permission for the object
*
* @param node The node to be unset
* @throws NullPointerException if the node is null
* @return the result of the operation
* @since 3.1
*/
DataMutateResult unsetTransientPermissionUnchecked(Node node);
/**
* Unsets a permission for the object
*
@@ -34,11 +34,11 @@ import java.util.function.Consumer;
*
* <p>All methods return {@link CompletableFuture}s, which will be populated with the result once the data has been
* loaded asynchronously. Care should be taken when using the methods to ensure that the main server thread is not
* blocked.
* blocked.</p>
*
* <p>Methods such as {@link CompletableFuture#get()} and equivalent should <strong>not</strong> be called on the main
* server thread. If you need to use the result of these operations on the main server thread, please register a
* callback using {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} and {@link #getSyncExecutor()}.
* callback using {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} and {@link #getSyncExecutor()}.</p>
*
* @since 2.14
*/
@@ -41,7 +41,7 @@ public interface Track {
/**
* Gets an ordered list of the groups on this track
*
* <p> Index 0 is the first/lowest group in (or start of) the track
* <p>Index 0 is the first/lowest group in (or start of) the track</p>
*
* @return an ordered {@link List} of the groups on this track
*/
@@ -25,8 +25,8 @@ package me.lucko.luckperms.api;
/**
* Represents a permission setting.
*
* <p> Consider a value of {@link #FALSE} to be a "negated" setting, and a value of {@link #UNDEFINED} to be a
* non-existent value.
* <p>Consider a value of {@link #FALSE} to be a "negated" setting, and a value of {@link #UNDEFINED} to be a
* non-existent value.</p>
*/
public enum Tristate {
@@ -63,7 +63,8 @@ public enum Tristate {
/**
* Returns the value of the Tristate as a boolean.
* <p> A value of {@link #UNDEFINED} converts to false.
*
* <p>A value of {@link #UNDEFINED} converts to false.</p>
*
* @return a boolean representation of the Tristate.
*/
@@ -69,8 +69,8 @@ public interface User extends PermissionHolder {
/**
* Refresh and re-assign the users permissions.
*
* <p> This request is not buffered, and the refresh call will be ran directly. This should ideally be called on
* an asynchronous thread.
* <p>This request is not buffered, and the refresh call will be ran directly. This should ideally be called on
* an asynchronous thread.</p>
*/
void refreshPermissions();
@@ -128,7 +128,9 @@ public interface User extends PermissionHolder {
* @throws ObjectAlreadyHasException if the user is already a member of the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(Group group) throws ObjectAlreadyHasException;
/**
@@ -140,7 +142,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(Group group, String server) throws ObjectAlreadyHasException;
/**
@@ -153,7 +157,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(Group group, String server, String world) throws ObjectAlreadyHasException;
/**
@@ -165,7 +171,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(Group group, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -178,7 +186,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(Group group, String server, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -192,7 +202,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void addGroup(Group group, String server, String world, long expireAt) throws ObjectAlreadyHasException;
/**
@@ -202,7 +214,9 @@ public interface User extends PermissionHolder {
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(Group group) throws ObjectLacksException;
/**
@@ -213,7 +227,9 @@ public interface User extends PermissionHolder {
* @throws ObjectLacksException if the user isn't a member of the group
* @throws NullPointerException if the group is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(Group group, boolean temporary) throws ObjectLacksException;
/**
@@ -225,7 +241,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(Group group, String server) throws ObjectLacksException;
/**
@@ -238,7 +256,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the server or world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(Group group, String server, String world) throws ObjectLacksException;
/**
@@ -251,7 +271,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group or server is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(Group group, String server, boolean temporary) throws ObjectLacksException;
/**
@@ -265,7 +287,9 @@ public interface User extends PermissionHolder {
* @throws NullPointerException if the group, server or world is null
* @throws IllegalStateException if the group instance was not obtained from LuckPerms.
* @throws IllegalArgumentException if the expiry time is in the past or the server/world is invalid
* @deprecated in favour of {@link NodeFactory#makeGroupNode(Group)}
*/
@Deprecated
void removeGroup(Group group, String server, String world, boolean temporary) throws ObjectLacksException;
/**
@@ -27,15 +27,15 @@ import java.util.UUID;
/**
* A UUID cache for online users, between external Mojang UUIDs, and internal LuckPerms UUIDs.
*
* <p> This UuidCache is a means of allowing users to have the same internal UUID across a network of offline mode
* <p>This UuidCache is a means of allowing users to have the same internal UUID across a network of offline mode
* servers or mixed offline mode and online mode servers. Platforms running in offline mode generate a UUID for a
* user when they first join the server, but this UUID will then not be consistent across the network. LuckPerms will
* instead check the datastore cache, to get a UUID for a user that is consistent across an entire network.
*
* <p> If you want to get a user object from the Storage using the api on a server in offline mode, you will need to use
* <p>If you want to get a user object from the Storage using the api on a server in offline mode, you will need to use
* this cache, OR use Storage#getUUID, for users that are not online.
*
* <p> THIS IS ONLY EFFECTIVE FOR ONLINE PLAYERS. USE THE DATASTORE METHODS FOR OFFLINE PLAYERS.
* <p><strong>This is only effective for online players. Use {@link Storage#getUUID(String)} for offline players.</strong></p>
*/
public interface UuidCache {
@@ -45,7 +45,7 @@ public interface PermissionData {
/**
* Invalidates the underlying permission calculator cache.
*
* <p> Can be called to allow for an update in defaults.
* <p>Can be called to allow for an update in defaults.</p>
*/
void invalidateCache();
@@ -30,7 +30,7 @@ import java.util.Set;
* Holds cached permission and meta lookup data for a {@link me.lucko.luckperms.api.User}.
*
* <p>Data is only likely to be available for online users. All calls will account for inheritance, as well as any
* default data provided by the platform. This calls are heavily cached and are therefore fast.
* default data provided by the platform. This calls are heavily cached and are therefore fast.</p>
*
* @since 2.13
*/
@@ -39,7 +39,7 @@ public interface UserData {
/**
* Gets PermissionData from the cache, given a specified context.
*
* <p> If the data is not cached, it is calculated. Therefore, this call could be costly.
* <p>If the data is not cached, it is calculated. Therefore, this call could be costly.</p>
*
* @param contexts the contexts to get the permission data in
* @return a permission data instance
@@ -50,7 +50,7 @@ public interface UserData {
/**
* Gets MetaData from the cache, given a specified context.
*
* <p> If the data is not cached, it is calculated. Therefore, this call could be costly.
* <p>If the data is not cached, it is calculated. Therefore, this call could be costly.</p>
*
* @param contexts the contexts to get the permission data in
* @return a meta data instance
@@ -79,8 +79,8 @@ public interface UserData {
/**
* Calculates permission data and stores it in the cache.
*
* <p> If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.
* <p>If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.</p>
*
* @param contexts the contexts to recalculate in.
* @throws NullPointerException if contexts is null
@@ -90,8 +90,8 @@ public interface UserData {
/**
* Calculates meta data and stores it in the cache.
*
* <p> If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.
* <p>If there is already data cached for the given contexts, and if the resultant output is different,
* the cached value is updated.</p>
*
* @param contexts the contexts to recalculate in.
* @throws NullPointerException if contexts is null
@@ -119,7 +119,7 @@ public interface UserData {
/**
* Ensures that PermissionData and MetaData is cached for a context.
*
* <p> If the cache does not contain any data for the context, it will be calculated and saved.
* <p>If the cache does not contain any data for the context, it will be calculated and saved.</p>
*
* @param contexts the contexts to pre-calculate for
* @throws NullPointerException if contexts is null
@@ -128,7 +128,8 @@ public interface UserData {
/**
* Invalidates all of the underlying Permission calculators.
* Can be called to allow for an update in defaults.
*
* <p>Can be called to allow for an update in defaults.</p>
*/
void invalidatePermissionCalculators();
@@ -22,14 +22,11 @@
package me.lucko.luckperms.api.context;
import java.util.Map;
/**
* Calculates whether contexts are applicable to {@link T}
*
* <p>Somewhat inspired by the system used on Sponge.
*
* @param <T> the subject type. Is ALWAYS the player class of the platform.
* @since 2.13
*/
public interface ContextCalculator<T> {
@@ -43,13 +40,4 @@ public interface ContextCalculator<T> {
*/
MutableContextSet giveApplicableContext(T subject, MutableContextSet accumulator);
/**
* Checks to see if a context is applicable to a subject
*
* @param subject the subject to check against
* @param context the context to check for
* @return true if met, or false if not. If this calculator does not calculate the given context, return false.
*/
boolean isContextApplicable(T subject, Map.Entry<String, String> context);
}
@@ -25,18 +25,20 @@ package me.lucko.luckperms.api.context;
import com.google.common.collect.Multimap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Holds contexts.
* Implementations may be either mutable or immutable.
* Holder of contexts.
*
* <p>Implementations may be either mutable or immutable.</p>
*
* @since 2.13
*/
public interface ContextSet {
/**
* Make a singleton ImmutableContextSet from a context pair
* Creates an ImmutableContextSet from a context pair
*
* @param key the key
* @param value the value
@@ -48,7 +50,22 @@ public interface ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing map
* Creates an ImmutableContextSet from two context pairs
*
* @param key1 the first key
* @param value1 the first value
* @param key2 the second key
* @param value2 the second value
* @return a new ImmutableContextSet containing the two pairs
* @throws NullPointerException if any of the keys or values are null
* @since 3.1
*/
static ImmutableContextSet of(String key1, String value1, String key2, String value2) {
return ImmutableContextSet.of(key1, value1, key2, value2);
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
@@ -59,7 +76,7 @@ public interface ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing iterable of Map Entries
* Creates an ImmutableContextSet from an existing iterable of Map Entries
*
* @param iterable the iterable to copy from
* @return a new ImmutableContextSet representing the pairs in the iterable
@@ -70,7 +87,7 @@ public interface ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing multimap
* Creates an ImmutableContextSet from an existing multimap
*
* @param multimap the multimap to copy from
* @return a new ImmutableContextSet representing the pairs in the multimap
@@ -82,7 +99,7 @@ public interface ContextSet {
}
/**
* Creates a new ImmutableContextSet from an existing set.
* Creates an new ImmutableContextSet from an existing set.
* Only really useful for converting between mutable and immutable types.
*
* @param contextSet the context set to copy from
@@ -167,6 +184,17 @@ public interface ContextSet {
*/
Set<String> getValues(String key);
/**
* Returns any value from this set matching the key, if present.
*
* @param key the key to find values for
* @return an optional containing any match
* @since 3.1
*/
default Optional<String> getAnyValue(String key) {
return getValues(key).stream().findAny();
}
/**
* Check if thr set contains a given key mapped to a given value
*
@@ -187,6 +215,35 @@ public interface ContextSet {
*/
boolean hasIgnoreCase(String key, String value);
/**
* Checks to see if all entries in this context set are also included in another set.
*
* @param other the other set to check
* @return true if all entries in this set are also in the other set
* @since 3.1
*/
default boolean isSatisfiedBy(ContextSet other) {
if (this.isEmpty()) {
// this is empty, so is therefore always satisfied.
return true;
} else if (other.isEmpty()) {
// this set isn't empty, but the other one is
return false;
} else if (this.size() > other.size()) {
// this set has more unique entries than the other set, so there's no way this can be satisfied.
return false;
} else {
// neither are empty, we need to compare the individual entries
for (Map.Entry<String, String> pair : toSet()) {
if (!other.has(pair.getKey(), pair.getValue())) {
return false;
}
}
return true;
}
}
/**
* Check if the set is empty
*
@@ -23,9 +23,10 @@
package me.lucko.luckperms.api.context;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import java.util.Collection;
import java.util.Map;
@@ -37,10 +38,10 @@ import java.util.Set;
* @since 2.16
*/
public final class ImmutableContextSet implements ContextSet {
private static final ImmutableContextSet EMPTY = new ImmutableContextSet(ImmutableMultimap.of());
private static final ImmutableContextSet EMPTY = new ImmutableContextSet(ImmutableSetMultimap.of());
/**
* Make a singleton ImmutableContextSet from a context pair
* Creates an ImmutableContextSet from a context pair
*
* @param key the key
* @param value the value
@@ -55,11 +56,39 @@ public final class ImmutableContextSet implements ContextSet {
throw new NullPointerException("value");
}
return new ImmutableContextSet(ImmutableMultimap.of(key.toLowerCase(), value));
return new ImmutableContextSet(ImmutableSetMultimap.of(key.toLowerCase(), value));
}
/**
* Creates a ImmutableContextSet from an existing map
* Creates an ImmutableContextSet from two context pairs
*
* @param key1 the first key
* @param value1 the first value
* @param key2 the second key
* @param value2 the second value
* @return a new ImmutableContextSet containing the two pairs
* @throws NullPointerException if any of the keys or values are null
* @since 3.1
*/
public static ImmutableContextSet of(String key1, String value1, String key2, String value2) {
if (key1 == null) {
throw new NullPointerException("key1");
}
if (value1 == null) {
throw new NullPointerException("value1");
}
if (key2 == null) {
throw new NullPointerException("key2");
}
if (value2 == null) {
throw new NullPointerException("value2");
}
return new ImmutableContextSet(ImmutableSetMultimap.of(key1.toLowerCase(), value1, key2.toLowerCase(), value2));
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
@@ -70,16 +99,16 @@ public final class ImmutableContextSet implements ContextSet {
throw new NullPointerException("map");
}
ImmutableMultimap.Builder<String, String> b = ImmutableMultimap.builder();
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : map.entrySet()) {
b.put(e.getKey(), e.getValue());
b.put(e.getKey().toLowerCase(), e.getValue());
}
return new ImmutableContextSet(b.build());
}
/**
* Creates a ImmutableContextSet from an existing iterable of Map Entries
* Creates an ImmutableContextSet from an existing iterable of Map Entries
*
* @param iterable the iterable to copy from
* @return a new ImmutableContextSet representing the pairs in the iterable
@@ -94,7 +123,7 @@ public final class ImmutableContextSet implements ContextSet {
}
/**
* Creates a ImmutableContextSet from an existing multimap
* Creates an ImmutableContextSet from an existing multimap
*
* @param multimap the multimap to copy from
* @return a new ImmutableContextSet representing the pairs in the multimap
@@ -121,7 +150,7 @@ public final class ImmutableContextSet implements ContextSet {
}
/**
* Creates a new empty ContextSet.
* Creates an new empty ContextSet.
*
* @return a new ContextSet
*/
@@ -129,10 +158,10 @@ public final class ImmutableContextSet implements ContextSet {
return EMPTY;
}
private final Multimap<String, String> map;
private final SetMultimap<String, String> map;
ImmutableContextSet(Multimap<String, String> contexts) {
this.map = ImmutableMultimap.copyOf(contexts);
this.map = ImmutableSetMultimap.copyOf(contexts);
}
@Override
@@ -25,8 +25,10 @@ package me.lucko.luckperms.api.context;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import java.util.Collection;
import java.util.Map;
@@ -60,6 +62,36 @@ public final class MutableContextSet implements ContextSet {
return set;
}
/**
* Makes a MutableContextSet from two context pairs
*
* @param key1 the first key
* @param value1 the first value
* @param key2 the second key
* @param value2 the second value
* @return a new MutableContextSet containing the two pairs
* @throws NullPointerException if any of the keys or values are null
* @since 3.1
*/
public static MutableContextSet of(String key1, String value1, String key2, String value2) {
if (key1 == null) {
throw new NullPointerException("key1");
}
if (value1 == null) {
throw new NullPointerException("value1");
}
if (key2 == null) {
throw new NullPointerException("key2");
}
if (value2 == null) {
throw new NullPointerException("value2");
}
MutableContextSet ret = singleton(key1, value1);
ret.add(key2, value2);
return ret;
}
/**
* Creates a MutableContextSet from an existing map
*
@@ -136,14 +168,14 @@ public final class MutableContextSet implements ContextSet {
return new MutableContextSet();
}
private final Multimap<String, String> map;
private final SetMultimap<String, String> map;
public MutableContextSet() {
this.map = Multimaps.synchronizedMultimap(HashMultimap.create());
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create());
}
private MutableContextSet(Multimap<String, String> contexts) {
this.map = Multimaps.synchronizedMultimap(HashMultimap.create(contexts));
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(contexts));
}
@Override
@@ -178,7 +210,7 @@ public final class MutableContextSet implements ContextSet {
@Override
public Multimap<String, String> toMultimap() {
return map;
return ImmutableSetMultimap.copyOf(map);
}
@Override
@@ -284,7 +316,7 @@ public final class MutableContextSet implements ContextSet {
*/
public void addAll(Iterable<? extends Map.Entry<String, String>> iterable) {
if (iterable == null) {
throw new NullPointerException("contexts");
throw new NullPointerException("iterable");
}
for (Map.Entry<String, String> e : iterable) {
@@ -300,7 +332,7 @@ public final class MutableContextSet implements ContextSet {
*/
public void addAll(Map<String, String> map) {
if (map == null) {
throw new NullPointerException("contexts");
throw new NullPointerException("map");
}
addAll(map.entrySet());
}
@@ -334,7 +366,7 @@ public final class MutableContextSet implements ContextSet {
throw new NullPointerException("value");
}
map.entries().removeIf(entry -> entry.getKey().equalsIgnoreCase(key) && entry.getValue().equals(value));
map.entries().removeIf(entry -> entry.getKey().equals(key) && entry.getValue().equals(value));
}
/**
@@ -40,6 +40,6 @@ public enum CreationCause {
/**
* The creation was caused by a LuckPerms internal
*/
INTERNAL;
INTERNAL
}
@@ -40,6 +40,6 @@ public enum DeletionCause {
/**
* The deletion was caused by a LuckPerms internal
*/
INTERNAL;
INTERNAL
}
@@ -35,6 +35,6 @@ public enum TrackAction {
/**
* The user was demoted down a track
*/
DEMOTION;
DEMOTION
}
+13 -7
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,10 +14,6 @@
<defaultGoal>clean package</defaultGoal>
<finalName>LuckPerms-Bukkit-Legacy-${release.version}.${patch.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>../</directory>
<filtering>false</filtering>
@@ -30,7 +26,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -39,7 +35,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<version>${shade.version}</version>
<executions>
<execution>
<phase>package</phase>
@@ -77,6 +73,16 @@
<artifactId>luckperms-bukkit</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>me.lucko.luckperms</groupId>
<artifactId>luckperms-common</artifactId>
</exclusion>
<exclusion>
<groupId>me.lucko.luckperms</groupId>
<artifactId>luckperms-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Shade gson and guava, as they are not included in earlier Bukkit releases -->
-61
View File
@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>luckperms-bukkit-placeholders</artifactId>
<build>
<finalName>LuckPermsPlaceholderExpansion</finalName>
<resources>
<resource>
<directory>../</directory>
<filtering>false</filtering>
<includes>
<include>LICENSE.txt</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- LuckPerms API -->
<dependency>
<groupId>me.lucko.luckperms</groupId>
<artifactId>luckperms-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- BukkitAPI -->
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- PlaceholderAPI -->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,220 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.api.placeholders;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.util.TimeUtil;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.LuckPermsApi;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.Track;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.caching.UserData;
import me.lucko.luckperms.api.context.MutableContextSet;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Optional;
import java.util.stream.Collectors;
/*
* PlaceholderAPI Expansion for LuckPerms, implemented using the LuckPerms API.
*
* Placeholders:
* - group_name
* - groups
* - has_permission_<node>
* - inherits_permission_<node>
* - in_group_<name>
* - inherits_group_<name>
* - on_track_<track>
* - expiry_time_<node>
* - group_expiry_time_<group>
* - prefix
* - suffix
* - meta_<node>
*/
public class LuckPermsPlaceholderExpansion extends PlaceholderExpansion {
private static final String IDENTIFIER = "luckperms";
private static final String PLUGIN_NAME = "LuckPerms";
private static final String AUTHOR = "Luck";
private static String formatBoolean(boolean b) {
return b ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
}
private LuckPermsApi api = null;
@Override
public boolean canRegister() {
return Bukkit.getServicesManager().isProvidedFor(LuckPermsApi.class);
}
@Override
public boolean register() {
if (!canRegister()) {
return false;
}
api = Bukkit.getServicesManager().getRegistration(LuckPermsApi.class).getProvider();
return super.register();
}
@Override
public String getVersion() {
return "2.13";
}
private Contexts makeContexts(Player player) {
MutableContextSet contextSet = new MutableContextSet();
contextSet.add("server", api.getConfiguration().getVaultServer());
contextSet.add("world", player.getWorld().getName());// ignore world?
return Contexts.of(contextSet.makeImmutable(), api.getConfiguration().getVaultIncludeGlobal(), true, true, true, true, player.isOp());
}
@Override
public String onPlaceholderRequest(Player player, String identifier) {
if (player == null || api == null) {
return "";
}
Optional<User> u = api.getUserSafe(api.getUuidCache().getUUID(player.getUniqueId()));
if (!u.isPresent()) {
return "";
}
final User user = u.get();
Optional<UserData> d = user.getUserDataCache();
if (!d.isPresent()) {
return "";
}
final UserData data = d.get();
identifier = identifier.toLowerCase();
if (identifier.equalsIgnoreCase("group_name")) {
return user.getPrimaryGroup();
}
if (identifier.equalsIgnoreCase("groups")) {
return user.getGroupNames().stream().collect(Collectors.joining(", "));
}
if (identifier.startsWith("has_permission_") && identifier.length() > "has_permission_".length()) {
String node = identifier.substring("has_permission_".length());
return formatBoolean(user.hasPermission(node, true));
}
if (identifier.startsWith("inherits_permission_") && identifier.length() > "inherits_permission_".length()) {
String node = identifier.substring("inherits_permission_".length());
return formatBoolean(data.getPermissionData(makeContexts(player)).getPermissionValue(node).asBoolean());
}
if (identifier.startsWith("in_group_") && identifier.length() > "in_group_".length()) {
String groupName = identifier.substring("in_group_".length());
return formatBoolean(user.getGroupNames().contains(groupName));
}
if (identifier.startsWith("inherits_group_") && identifier.length() > "inherits_group_".length()) {
String groupName = identifier.substring("inherits_group_".length());
return formatBoolean(data.getPermissionData(makeContexts(player)).getPermissionValue("group." + groupName).asBoolean());
}
if (identifier.startsWith("on_track_") && identifier.length() > "on_track_".length()) {
String trackName = identifier.substring("on_track_".length());
Optional<Track> track = api.getTrackSafe(trackName);
if (!track.isPresent()) return "";
return formatBoolean(track.get().containsGroup(user.getPrimaryGroup()));
}
if (identifier.startsWith("expiry_time_") && identifier.length() > "expiry_time_".length()) {
String node = identifier.substring("expiry_time_".length());
long currentTime = System.currentTimeMillis() / 1000L;
for (Node n : user.getTemporaryPermissionNodes()) {
if (n.toSerializedNode().equalsIgnoreCase(node)) {
return TimeUtil.getTime((int) (n.getExpiryUnixTime() - currentTime));
}
}
return "";
}
if (identifier.startsWith("group_expiry_time_") && identifier.length() > "group_expiry_time_".length()) {
String node = "group." + identifier.substring("group_expiry_time_".length());
long currentTime = System.currentTimeMillis() / 1000L;
for (Node n : user.getTemporaryPermissionNodes()) {
if (n.getPermission().equalsIgnoreCase(node)) {
return TimeUtil.getTime((int) (n.getExpiryUnixTime() - currentTime));
}
}
return "";
}
if (identifier.equalsIgnoreCase("prefix")) {
String prefix = data.calculateMeta(makeContexts(player)).getPrefix();
if (prefix == null) {
prefix = "";
}
return prefix;
}
if (identifier.equalsIgnoreCase("suffix")) {
String suffix = data.calculateMeta(makeContexts(player)).getSuffix();
if (suffix == null) {
suffix = "";
}
return suffix;
}
if (identifier.startsWith("meta_") && identifier.length() > "meta_".length()) {
String node = identifier.substring("meta_".length());
String meta = data.getMetaData(makeContexts(player)).getMeta().get(node);
if (meta == null) {
meta = "";
}
return meta;
}
return null;
}
@Override
public String getIdentifier() {
return IDENTIFIER;
}
@Override
public String getPlugin() {
return PLUGIN_NAME;
}
@Override
public String getAuthor() {
return AUTHOR;
}
}
+13 -20
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -33,7 +33,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -42,7 +42,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<version>${shade.version}</version>
<executions>
<execution>
<phase>package</phase>
@@ -66,11 +66,11 @@
</build>
<dependencies>
<!-- BukkitAPI -->
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<version>1.11.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Vault -->
@@ -94,6 +94,13 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- LilyPad - used as a messaging service -->
<dependency>
<groupId>lilypad.client.connect</groupId>
@@ -101,20 +108,6 @@
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- HikariCP - needed as source -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.5.1</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>
<!-- **************Migration Plugins************** -->
<dependency>
<groupId>org.tyrannyofheaven.bukkit</groupId>
@@ -141,7 +134,7 @@
<dependency>
<groupId>com.github.cheesesoftware</groupId>
<artifactId>PowerfulPermsAPI</artifactId>
<version>1.0.1</version>
<version>1.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -57,6 +57,11 @@ public class BukkitConfig extends AbstractConfiguration {
configuration = YamlConfiguration.loadConfiguration(configFile);
}
@Override
public boolean contains(String path) {
return configuration.contains(path);
}
@Override
public String getString(String path, String def) {
return configuration.getString(path, def);
@@ -22,12 +22,15 @@
package me.lucko.luckperms.bukkit;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.bukkit.model.Injector;
import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.common.caching.UserCache;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.Message;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.utils.AbstractListener;
import me.lucko.luckperms.common.utils.LoginHelper;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -45,52 +48,95 @@ import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
class BukkitListener extends AbstractListener implements Listener {
@RequiredArgsConstructor
public class BukkitListener implements Listener {
private final LPBukkitPlugin plugin;
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
private final Set<UUID> deniedLogin = new HashSet<>();
BukkitListener(LPBukkitPlugin plugin) {
super(plugin);
this.plugin = plugin;
}
private final Set<UUID> deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>());
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
@EventHandler(priority = EventPriority.LOW)
public void onPlayerPreLogin(AsyncPlayerPreLoginEvent e) {
/* Called when the player first attempts a connection with the server.
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins) */
/* the player was denied entry to the server before this priority.
log this, so we can handle appropriately later. */
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
plugin.getLog().warn("Connection from " + e.getUniqueId() + " was already denied. No permissions data will be loaded.");
deniedAsyncLogin.add(e.getUniqueId());
return;
}
/* either the plugin hasn't finished starting yet, or there was an issue connecting to the DB, performing file i/o, etc.
we don't let players join in this case, because it means they can connect to the server without their permissions data.
some server admins rely on negating perms to stop users from causing damage etc, so it's really important that
this data is loaded. */
if (!plugin.isStarted() || !plugin.getStorage().isAcceptingLogins()) {
// log that the user tried to login, but was denied at this stage.
deniedAsyncLogin.add(e.getUniqueId());
// The datastore is disabled, prevent players from joining the server
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.toString());
// actually deny the connection.
plugin.getLog().warn("Permissions storage is not loaded yet. Denying connection from: " + e.getUniqueId() + " - " + e.getName());
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
return;
}
// Process login
onAsyncLogin(e.getUniqueId(), e.getName());
/* Actually process the login for the connection.
We do this here to delay the login until the data is ready.
If the login gets cancelled later on, then this will be cleaned up.
This includes:
- loading uuid data
- loading permissions
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
try {
LoginHelper.loadUser(plugin, e.getUniqueId(), e.getName());
} catch (Exception ex) {
ex.printStackTrace();
// there was some error loading data. deny the connection
deniedAsyncLogin.add(e.getUniqueId());
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerPreLoginMonitor(AsyncPlayerPreLoginEvent e) {
// If they were denied before/at LOW, then don't bother handling here.
/* Listen to see if the event was cancelled after we initially handled the connection
If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
// Check to see if this connection was denied at LOW.
if (deniedAsyncLogin.remove(e.getUniqueId())) {
// This is a problem, as they were denied at low priority, but are now being allowed.
if (e.getLoginResult() == AsyncPlayerPreLoginEvent.Result.ALLOWED) {
plugin.getLog().severe("Player connection was re-allowed for " + e.getUniqueId());
e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "");
}
return;
}
if (plugin.isStarted() && plugin.getStorage().isAcceptingLogins() && e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
// Login event was cancelled by another plugin
onLeave(e.getUniqueId());
// Login event was cancelled by another plugin
if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
// Schedule cleanup of this user.
plugin.getUserManager().scheduleUnload(e.getUniqueId());
}
}
@EventHandler(priority = EventPriority.LOW)
public void onPlayerLogin(PlayerLoginEvent e) {
/* Called when the player starts logging into the server.
At this point, the users data should be present and loaded.
Listening on LOW priority to allow plugins to further modify data here. (auth plugins, etc.) */
/* the player was denied entry to the server before this priority.
log this, so we can handle appropriately later. */
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
plugin.getLog().warn("Login from " + e.getPlayer().getUniqueId() + " was denied before an attachment could be injected.");
deniedLogin.add(e.getPlayer().getUniqueId());
return;
}
@@ -98,14 +144,17 @@ class BukkitListener extends AbstractListener implements Listener {
final Player player = e.getPlayer();
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
if (user == null) {
deniedLogin.add(e.getPlayer().getUniqueId());
// User wasn't loaded for whatever reason.
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.toString());
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_ERROR.asString(plugin.getLocaleManager()));
return;
}
// User instance is there, now we can inject our custom Permissible into the player.
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
try {
// Make a new permissible for the user
LPPermissible lpPermissible = new LPPermissible(player, user, plugin);
@@ -119,30 +168,49 @@ class BukkitListener extends AbstractListener implements Listener {
plugin.refreshAutoOp(player);
// We assume all users are not op, but those who are need extra calculation.
if (player.isOp()) {
plugin.doAsync(() -> {
UserCache userData = user.getUserData();
if (userData == null) {
return;
}
// We assume all users are not op, but those who are need extra calculation.
plugin.doAsync(() -> user.getUserData().preCalculate(plugin.getPreProcessContexts(true)));
userData.preCalculate(plugin.getPreProcessContexts(true));
});
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLoginMonitor(PlayerLoginEvent e) {
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
/* Listen to see if the event was cancelled after we initially handled the login
If the connection was cancelled here, we need to do something to clean up the data that was loaded. */
// If they were denied before/at LOW, then don't bother handling here.
if (deniedLogin.remove(e.getPlayer().getUniqueId())) {
return;
// Check to see if this connection was denied at LOW.
if (deniedLogin.remove(e.getPlayer().getUniqueId())) {
// This is a problem, as they were denied at low priority, but are now being allowed.
if (e.getResult() == PlayerLoginEvent.Result.ALLOWED) {
plugin.getLog().severe("Player connection was re-allowed for " + e.getPlayer().getUniqueId());
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, "");
}
// The player got denied on sync login.
plugin.getServer().getScheduler().runTaskLater(plugin, () -> onLeave(e.getPlayer().getUniqueId()), 20L);
} else {
plugin.refreshAutoOp(e.getPlayer());
return;
}
// Login event was cancelled by another plugin
if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) {
// Schedule cleanup of this user.
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
return;
}
// everything is going well. login was processed ok, this is just to refresh auto-op status.
plugin.refreshAutoOp(e.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR) // Allow other plugins to see data when this event gets called.
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent e) {
final Player player = e.getPlayer();
@@ -154,8 +222,8 @@ class BukkitListener extends AbstractListener implements Listener {
player.setOp(false);
}
// Call internal leave handling
onLeave(player.getUniqueId());
// Request that the users data is unloaded.
plugin.getUserManager().scheduleUnload(player.getUniqueId());
}
@EventHandler
@@ -164,7 +232,7 @@ class BukkitListener extends AbstractListener implements Listener {
return;
}
String s = e.getMessage()
String s = e.getMessage().toLowerCase()
.replace("/", "")
.replace("bukkit:", "")
.replace("spigot:", "")
@@ -172,7 +240,7 @@ class BukkitListener extends AbstractListener implements Listener {
if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) {
e.setCancelled(true);
e.getPlayer().sendMessage(Message.OP_DISABLED.toString());
e.getPlayer().sendMessage(Message.OP_DISABLED.asString(plugin.getLocaleManager()));
}
}
@@ -39,6 +39,7 @@ import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.bukkit.vault.VaultHook;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.api.ApiProvider;
import me.lucko.luckperms.common.caching.UserCache;
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
import me.lucko.luckperms.common.calculators.CalculatorFactory;
import me.lucko.luckperms.common.commands.sender.Sender;
@@ -46,11 +47,10 @@ import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.contexts.ServerCalculator;
import me.lucko.luckperms.common.contexts.StaticCalculator;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.debug.DebugHandler;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
@@ -62,6 +62,7 @@ import me.lucko.luckperms.common.managers.impl.GenericGroupManager;
import me.lucko.luckperms.common.managers.impl.GenericTrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.Storage;
@@ -72,7 +73,10 @@ import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
import me.lucko.luckperms.common.treeview.PermissionVault;
import me.lucko.luckperms.common.utils.BufferedRequest;
import me.lucko.luckperms.common.utils.FileWatcher;
import me.lucko.luckperms.common.utils.LoggerImpl;
import me.lucko.luckperms.common.utils.LoginHelper;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import org.bukkit.World;
import org.bukkit.command.PluginCommand;
@@ -86,6 +90,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -107,12 +112,12 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private GroupManager groupManager;
private TrackManager trackManager;
private Storage storage;
private FileWatcher fileWatcher = null;
private InternalMessagingService messagingService = null;
private UuidCache uuidCache;
private BukkitListener listener;
private ApiProvider apiProvider;
private Logger log;
private Importer importer;
private DefaultsProvider defaultsProvider;
private ChildPermissionProvider childPermissionProvider;
private LocaleManager localeManager;
@@ -122,21 +127,27 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private CalculatorFactory calculatorFactory;
private BufferedRequest<Void> updateTaskBuffer;
private boolean started = false;
private DebugHandler debugHandler;
private VerboseHandler verboseHandler;
private BukkitSenderFactory senderFactory;
private PermissionVault permissionVault;
@Override
public void onEnable() {
public void onLoad() {
// setup minimal functionality in order to load initial dependencies
scheduler = new LPBukkitScheduler(this);
localeManager = new NoopLocaleManager();
senderFactory = new BukkitSenderFactory(this);
log = new LoggerImpl(getConsoleSender());
DependencyManager.loadDependencies(this, Collections.singletonList(Dependency.CAFFEINE));
}
@Override
public void onEnable() {
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
ignoringLogs = ConcurrentHashMap.newKeySet();
debugHandler = new DebugHandler(scheduler.getAsyncBukkitExecutor(), getVersion());
verboseHandler = new VerboseHandler(scheduler.getAsyncBukkitExecutor(), getVersion());
permissionVault = new PermissionVault(scheduler.getAsyncBukkitExecutor());
getLog().info("Loading configuration...");
@@ -170,6 +181,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
listener = new BukkitListener(this);
pm.registerEvents(listener, this);
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
fileWatcher = new FileWatcher(this);
getScheduler().doAsyncRepeating(fileWatcher, 30L);
}
// initialise datastore
storage = StorageFactory.getInstance(this, StorageType.H2);
@@ -212,6 +228,10 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
@@ -247,14 +267,13 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
userManager = new GenericUserManager(this);
groupManager = new GenericGroupManager(this);
trackManager = new GenericTrackManager(this);
importer = new Importer(commandManager);
calculatorFactory = new BukkitCalculatorFactory(this);
cachedStateManager = new CachedStateManager(this);
contextManager = new ContextManager<>();
worldCalculator = new WorldCalculator(this);
contextManager.registerCalculator(worldCalculator);
contextManager.registerCalculator(new ServerCalculator<>(getConfiguration()));
contextManager.registerCalculator(new StaticCalculator<>(getConfiguration()));
// Provide vault support
tryVaultHook(false);
@@ -282,20 +301,23 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
scheduler.doAsyncRepeating(new CacheHousekeepingTask(this), 2400L);
// register permissions
registerPermissions(getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE);
try {
registerPermissions(getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE);
} catch (Exception e) {
// this throws an exception if the plugin is /reloaded, grr
}
if (!getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
scheduler.doSync(() -> getServer().getOperators().forEach(o -> o.setOp(false)));
}
// replace the temporary executor when the Bukkit one starts
getServer().getScheduler().runTaskAsynchronously(this, () -> {
scheduler.setUseBukkitAsync(true);
});
getServer().getScheduler().runTaskAsynchronously(this, () -> scheduler.setUseBukkitAsync(true));
// Load any online users (in the case of a reload)
for (Player player : getServer().getOnlinePlayers()) {
scheduler.doAsync(() -> {
listener.onAsyncLogin(player.getUniqueId(), player.getName());
LoginHelper.loadUser(this, player.getUniqueId(), player.getName());
User user = getUserManager().get(getUuidCache().getUUID(player.getUniqueId()));
if (user != null) {
scheduler.doSync(() -> {
@@ -323,7 +345,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
defaultsProvider.close();
permissionVault.setShutdown(true);
debugHandler.setShutdown(true);
verboseHandler.setShutdown(true);
for (Player player : getServer().getOnlinePlayers()) {
Injector.unInject(player, false, false);
@@ -341,6 +363,10 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
getLog().info("Closing datastore...");
storage.shutdown();
if (fileWatcher != null) {
fileWatcher.close();
}
if (messagingService != null) {
getLog().info("Closing messaging service...");
messagingService.close();
@@ -368,12 +394,12 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
groupManager = null;
trackManager = null;
storage = null;
fileWatcher = null;
messagingService = null;
uuidCache = null;
listener = null;
apiProvider = null;
log = null;
importer = null;
defaultsProvider = null;
childPermissionProvider = null;
localeManager = null;
@@ -382,7 +408,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
worldCalculator = null;
calculatorFactory = null;
updateTaskBuffer = null;
debugHandler = null;
verboseHandler = null;
senderFactory = null;
permissionVault = null;
}
@@ -420,11 +446,21 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
try {
LPPermissible permissible = Injector.getPermissible(player.getUniqueId());
if (permissible == null) {
if (permissible == null || !permissible.getActive().get()) {
return;
}
Map<String, Boolean> backing = permissible.getUser().getUserData().getPermissionData(permissible.calculateContexts()).getImmutableBacking();
User user = permissible.getUser();
if (user == null) {
return;
}
UserCache userData = user.getUserData();
if (userData == null) {
return;
}
Map<String, Boolean> backing = userData.getPermissionData(permissible.calculateContexts()).getImmutableBacking();
boolean op = Optional.ofNullable(backing.get("luckperms.autoop")).orElse(false);
player.setOp(op);
} catch (Exception ignored) {}
@@ -448,7 +484,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public String getServerVersion() {
return getServer().getVersion();
return getServer().getVersion() + " - " + getServer().getBukkitVersion();
}
@Override
@@ -500,7 +536,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public boolean isPlayerOnline(UUID external) {
return getServer().getPlayer(external) != null;
Player player = getServer().getPlayer(external);
return player != null && player.isOnline();
}
@Override
@@ -528,6 +565,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
MutableContextSet set = MutableContextSet.create();
set.add("server", getConfiguration().get(ConfigKeys.SERVER));
set.add("world", s);
set.addAll(configuration.getStaticContexts().getContextSet());
return set.makeImmutable();
})
.collect(Collectors.toList())
@@ -542,6 +580,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
MutableContextSet set = MutableContextSet.create();
set.add("server", getConfiguration().get(ConfigKeys.VAULT_SERVER));
set.add("world", s);
set.addAll(configuration.getStaticContexts().getContextSet());
return set.makeImmutable();
})
.collect(Collectors.toList())
@@ -597,16 +636,6 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
return map;
}
@SuppressWarnings("deprecation")
@Override
public UUID getUuidFromUsername(String playerName) {
try {
return getServer().getOfflinePlayer(playerName).getUniqueId();
} catch (Exception e) {
return null;
}
}
private void registerPermissions(PermissionDefault def) {
PluginManager pm = getServer().getPluginManager();
@@ -33,8 +33,6 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.bukkit.entity.Player;
import java.util.Map;
@RequiredArgsConstructor
public class WorldCalculator implements ContextCalculator<Player> {
private static final String WORLD_KEY = "world";
@@ -43,27 +41,13 @@ public class WorldCalculator implements ContextCalculator<Player> {
@Override
public MutableContextSet giveApplicableContext(Player subject, MutableContextSet accumulator) {
String world = getWorld(subject);
String world = subject.getWorld().getName();
world = plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(world, world);
if (world != null) {
accumulator.add(Maps.immutableEntry(WORLD_KEY, world));
accumulator.add(Maps.immutableEntry(WORLD_KEY, world.toLowerCase()));
}
return accumulator;
}
@Override
public boolean isContextApplicable(Player subject, Map.Entry<String, String> context) {
if (!context.getKey().equals(WORLD_KEY)) {
return false;
}
String world = getWorld(subject);
return world != null && world.equals(context.getValue());
}
private String getWorld(Player player) {
String world = player.getWorld().getName();
return plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(world, world);
}
}
@@ -22,7 +22,6 @@
package me.lucko.luckperms.bukkit.compat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.Constructor;
@@ -58,7 +57,7 @@ public class BukkitJsonMessageHandler {
}
}
Class<?> packetChatClass = Class.forName(getVersionedClassName("PacketPlayOutChat"));
Class<?> packetChatClass = ReflectionUtil.nmsClass("PacketPlayOutChat");
Constructor[] packetConstructors = packetChatClass.getDeclaredConstructors();
for (Constructor c : packetConstructors) {
Class<?>[] parameters = c.getParameterTypes();
@@ -68,30 +67,19 @@ public class BukkitJsonMessageHandler {
}
}
Class<?> baseComponentClass = Class.forName(getVersionedClassName("IChatBaseComponent"));
Class<?> chatSerializerClass = baseComponentClass.getClasses()[0];
Class<?> baseComponentClass = ReflectionUtil.nmsClass("IChatBaseComponent");
Class<?> chatSerializerClass;
if (baseComponentClass.getClasses().length > 0) {
chatSerializerClass = baseComponentClass.getClasses()[0];
} else {
// 1.7 class is here instead.
chatSerializerClass = ReflectionUtil.nmsClass("ChatSerializer");
}
SERIALIZE_METHOD = chatSerializerClass.getDeclaredMethod("a", String.class);
}
private static String getVersionedClassName(String className) {
Class server = Bukkit.getServer().getClass();
if (!server.getSimpleName().equals("CraftServer")) {
throw new RuntimeException("Couldn't reflect into server " + server);
}
String version;
if (server.getName().equals("org.bukkit.craftbukkit.CraftServer")) {
// Non versioned class
version = ".";
} else {
version = server.getName().substring("org.bukkit.craftbukkit".length());
version = version.substring(0, version.length() - "CraftServer".length());
}
return "net.minecraft.server" + version + className;
}
private static synchronized boolean trySetup(Object player) {
if (setup) return true;
if (triedAndFailed) return false;
@@ -37,9 +37,9 @@ public class MessageHandler {
}
public void sendJsonMessage(CommandSender sender, FancyMessage message) {
if (sender instanceof Player) {
if (ReflectionUtil.isChatCompatible() && sender instanceof Player) {
Player player = (Player) sender;
String json = message.toJSONString();
String json = message.exportToJson();
// Try Bukkit.
if (bukkitHandler.sendJsonMessage(player, json)) {
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.compat;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
@UtilityClass
public class ReflectionUtil {
private static final String SERVER_VERSION = _getServerVersion();
private static final boolean CHAT_COMPATIBLE = !SERVER_VERSION.startsWith(".v1_7_");
private static String _getServerVersion() {
Class<?> server = Bukkit.getServer().getClass();
if (!server.getSimpleName().equals("CraftServer")) {
return ".";
}
if (server.getName().equals("org.bukkit.craftbukkit.CraftServer")) {
// Non versioned class
return ".";
} else {
String version = server.getName().substring("org.bukkit.craftbukkit".length());
return version.substring(0, version.length() - "CraftServer".length());
}
}
public static String getServerVersion() {
return SERVER_VERSION;
}
public static boolean isChatCompatible() {
return CHAT_COMPATIBLE;
}
public static String nms(String className) {
return "net.minecraft.server" + getServerVersion() + className;
}
public static Class<?> nmsClass(String className) throws ClassNotFoundException {
return Class.forName(nms(className));
}
public static String obc(String className) {
return "org.bukkit.craftbukkit" + getServerVersion() + className;
}
public static Class<?> obcClass(String className) throws ClassNotFoundException {
return Class.forName(obc(className));
}
}
@@ -64,12 +64,11 @@ public class BungeeMessagingService extends AbstractMessagingService implements
@Override
public void run() {
Collection<? extends Player> players = plugin.getServer().getOnlinePlayers();
if (players.isEmpty()) {
Player p = Iterables.getFirst(players, null);
if (p == null) {
return;
}
Player p = Iterables.getFirst(players, null);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF(message);
@@ -29,21 +29,24 @@ import de.bananaco.bpermissions.api.Permission;
import de.bananaco.bpermissions.api.World;
import de.bananaco.bpermissions.api.WorldManager;
import me.lucko.luckperms.api.MetaUtils;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -54,9 +57,6 @@ import static me.lucko.luckperms.common.constants.Permission.MIGRATION;
public class MigrationBPermissions extends SubCommand<Object> {
private static Field uConfigField;
private static Method getConfigurationSectionMethod = null;
private static Method getKeysMethod = null;
static {
try {
uConfigField = Class.forName("de.bananaco.bpermissions.imp.YamlWorld").getDeclaredField("uconfig");
@@ -87,7 +87,24 @@ public class MigrationBPermissions extends SubCommand<Object> {
log.log("Forcing the plugin to load all data. This could take a while.");
for (World world : worldManager.getAllWorlds()) {
log.log("Loading users in world " + world.getName());
Set<String> users = getUsers(world);
YamlConfiguration yamlWorldUsers = null;
try {
yamlWorldUsers = (YamlConfiguration) uConfigField.get(world);
} catch (Throwable t) {
t.printStackTrace();
}
if (yamlWorldUsers == null) {
continue;
}
ConfigurationSection configSection = yamlWorldUsers.getConfigurationSection("users");
if (configSection == null) {
continue;
}
Set<String> users = configSection.getKeys(false);
if (users == null) {
log.logErr("Couldn't get a list of users.");
return CommandResult.FAILURE;
@@ -109,7 +126,7 @@ public class MigrationBPermissions extends SubCommand<Object> {
log.log("Starting group migration in world " + world.getName() + ".");
AtomicInteger groupCount = new AtomicInteger(0);
for (Calculable group : world.getAll(CalculableType.GROUP)) {
String groupName = group.getName().toLowerCase();
String groupName = MigrationUtils.standardizeName(group.getName());
if (group.getName().equalsIgnoreCase(world.getDefaultGroup())) {
groupName = "default";
}
@@ -118,23 +135,31 @@ public class MigrationBPermissions extends SubCommand<Object> {
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group lpGroup = plugin.getGroupManager().getIfLoaded(groupName);
migrateHolder(log, world, group, lpGroup);
MigrationUtils.setGroupWeight(lpGroup, group.getPriority());
migrateHolder(world, group, lpGroup);
plugin.getStorage().saveGroup(lpGroup);
log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet());
}
log.log("Migrated " + groupCount.get() + " groups in world " + world.getName() + ".");
// Migrate all users
log.log("Starting user migration in world " + world.getName() + ".");
AtomicInteger userCount = new AtomicInteger(0);
for (Calculable user : world.getAll(CalculableType.USER)) {
// There is no mention of UUIDs in the API. I assume that name = uuid. idk?
UUID uuid;
UUID uuid = null;
try {
uuid = UUID.fromString(user.getName());
} catch (IllegalArgumentException e) {
uuid = plugin.getUuidFromUsername(user.getName());
try {
//noinspection deprecation
uuid = Bukkit.getOfflinePlayer(user.getName()).getUniqueId();
} catch (Exception ex) {
e.printStackTrace();
}
}
if (uuid == null) {
@@ -146,7 +171,7 @@ public class MigrationBPermissions extends SubCommand<Object> {
plugin.getStorage().loadUser(uuid, "null").join();
User lpUser = plugin.getUserManager().get(uuid);
migrateHolder(log, world, user, lpUser);
migrateHolder(world, user, lpUser);
plugin.getStorage().saveUser(lpUser);
plugin.getUserManager().cleanup(lpUser);
@@ -161,78 +186,46 @@ public class MigrationBPermissions extends SubCommand<Object> {
return CommandResult.SUCCESS;
}
@SuppressWarnings("unchecked")
private static Set<String> getUsers(World world) {
try {
Object yamlWorldUsers = uConfigField.get(world);
if (getConfigurationSectionMethod == null) {
getConfigurationSectionMethod = yamlWorldUsers.getClass().getMethod("getConfigurationSection", String.class);
getConfigurationSectionMethod.setAccessible(true);
}
Object configSection = getConfigurationSectionMethod.invoke(yamlWorldUsers, "users");
if (configSection == null) {
return Collections.emptySet();
}
if (getKeysMethod == null) {
getKeysMethod = configSection.getClass().getMethod("getKeys", boolean.class);
getKeysMethod.setAccessible(true);
}
return (Set<String>) getKeysMethod.invoke(configSection, false);
} catch (Throwable t) {
t.printStackTrace();
return null;
}
}
private static void migrateHolder(ProgressLogger log, World world, Calculable c, PermissionHolder holder) {
private static void migrateHolder(World world, Calculable c, PermissionHolder holder) {
// Migrate the groups permissions in this world
for (Permission p : c.getPermissions()) {
try {
holder.setPermission(p.name(), p.isTrue(), "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
if (p.name().isEmpty()) {
continue;
}
holder.setPermission(NodeFactory.make(p.name(), p.isTrue(), "global", world.getName()));
// Include any child permissions
for (Map.Entry<String, Boolean> child : p.getChildren().entrySet()) {
try {
holder.setPermission(child.getKey(), child.getValue(), "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
if (child.getKey().isEmpty()) {
continue;
}
holder.setPermission(NodeFactory.make(child.getKey(), child.getValue(), "global", world.getName()));
}
}
// Migrate any inherited groups
for (Group parent : c.getGroups()) {
try {
holder.setPermission("group." + parent.getName(), true, "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
String parentName = MigrationUtils.standardizeName(parent.getName());
if (parent.getName().equalsIgnoreCase(world.getDefaultGroup())) {
parentName = "default";
}
holder.setPermission(NodeFactory.make("group." + parentName, true, "global", world.getName()));
}
// Migrate existing meta
for (Map.Entry<String, String> meta : c.getMeta().entrySet()) {
if (meta.getKey().equalsIgnoreCase("prefix") || meta.getKey().equalsIgnoreCase("suffix")) {
String chatMeta = MetaUtils.escapeCharacters(meta.getValue());
try {
holder.setPermission(meta.getKey().toLowerCase() + "." + c.getPriority() + "." + chatMeta, true);
} catch (Exception ex) {
log.handleException(ex);
}
if (meta.getKey().isEmpty() || meta.getValue().isEmpty()) {
continue;
}
try {
holder.setPermission("meta." + meta.getKey() + "." + meta.getValue(), true, "global", world.getName());
} catch (Exception ex) {
log.handleException(ex);
if (meta.getKey().equalsIgnoreCase("prefix") || meta.getKey().equalsIgnoreCase("suffix")) {
holder.setPermission(NodeFactory.makeChatMetaNode(meta.getKey().equalsIgnoreCase("prefix"), c.getPriority(), meta.getValue()).setWorld(world.getName()).build());
continue;
}
holder.setPermission(NodeFactory.makeMetaNode(meta.getKey(), meta.getValue()).setWorld(world.getName()).build());
}
}
}
@@ -22,20 +22,19 @@
package me.lucko.luckperms.bukkit.migration;
import com.google.common.collect.Maps;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.Arg;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import org.anjocaido.groupmanager.GlobalGroups;
import org.anjocaido.groupmanager.GroupManager;
@@ -47,8 +46,10 @@ import org.bukkit.Bukkit;
import org.bukkit.World;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@@ -73,8 +74,8 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.logErr("Was expecting true/false, but got " + args.get(0) + " instead.");
return CommandResult.STATE_ERROR;
}
boolean migrateAsGlobal = Boolean.parseBoolean(args.get(0));
final Function<String, String> worldMappingFunc = s -> migrateAsGlobal ? "" : s;
final boolean migrateAsGlobal = Boolean.parseBoolean(args.get(0));
final Function<String, String> worldMappingFunc = s -> migrateAsGlobal ? null : s;
if (!Bukkit.getPluginManager().isPluginEnabled("GroupManager")) {
log.logErr("Plugin not loaded.");
@@ -82,7 +83,6 @@ public class MigrationGroupManager extends SubCommand<Object> {
}
List<String> worlds = Bukkit.getWorlds().stream().map(World::getName).map(String::toLowerCase).collect(Collectors.toList());
GroupManager gm = (GroupManager) Bukkit.getPluginManager().getPlugin("GroupManager");
// Migrate Global Groups
@@ -91,32 +91,25 @@ public class MigrationGroupManager extends SubCommand<Object> {
AtomicInteger globalGroupCount = new AtomicInteger(0);
for (Group g : gg.getGroupList()) {
plugin.getStorage().createAndLoadGroup(g.getName().toLowerCase(), CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(g.getName().toLowerCase());
String groupName = MigrationUtils.standardizeName(g.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(groupName);
for (String node : g.getPermissionList()) {
boolean value = true;
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
} else if (node.startsWith("+")) {
node = node.substring(1);
value = true;
if (node.isEmpty()) {
continue;
}
try {
group.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
group.setPermission(MigrationUtils.parseNode(node, true).build());
}
for (String s : g.getInherits()) {
try {
group.setPermission("group." + s.toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
if (s.isEmpty()) {
continue;
}
group.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(s)));
}
plugin.getStorage().saveGroup(group);
@@ -125,14 +118,9 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Migrated " + globalGroupCount.get() + " global groups");
// Collect data
// UUID --> Map<Entry<String, String>, Boolean> where k=world, v = node
Map<UUID, Map<Map.Entry<String, String>, Boolean>> users = new HashMap<>();
// UUID --> primary group name
Map<UUID, Set<Node>> users = new HashMap<>();
Map<UUID, String> primaryGroups = new HashMap<>();
// String --> Map<Entry<String, String>, Boolean> where k=world, v = node
Map<String, Map<Map.Entry<String, String>, Boolean>> groups = new HashMap<>();
Map<String, Set<Node>> groups = new HashMap<>();
WorldsHolder wh = gm.getWorldsHolder();
@@ -145,25 +133,48 @@ public class MigrationGroupManager extends SubCommand<Object> {
WorldDataHolder wdh = wh.getWorldData(world);
AtomicInteger groupWorldCount = new AtomicInteger(0);
for (Group g : wdh.getGroupList()) {
groups.putIfAbsent(g.getName().toLowerCase(), new HashMap<>());
for (Group group : wdh.getGroupList()) {
String groupName = MigrationUtils.standardizeName(group.getName());
for (String node : g.getPermissionList()) {
boolean value = true;
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
} else if (node.startsWith("+")) {
node = node.substring(1);
value = true;
groups.putIfAbsent(groupName, new HashSet<>());
for (String node : group.getPermissionList()) {
if (node.isEmpty()) {
continue;
}
groups.get(g.getName().toLowerCase()).put(Maps.immutableEntry(worldMappingFunc.apply(world), node), value);
groups.get(groupName).add(MigrationUtils.parseNode(node, true).setWorld(worldMappingFunc.apply(world)).build());
}
for (String s : g.getInherits()) {
groups.get(g.getName().toLowerCase()).put(Maps.immutableEntry(worldMappingFunc.apply(world), "group." + s.toLowerCase()), true);
for (String s : group.getInherits()) {
if (s.isEmpty()) {
continue;
}
groups.get(groupName).add(NodeFactory.make("group." + MigrationUtils.standardizeName(s), true, null, worldMappingFunc.apply(world)));
}
String[] metaKeys = group.getVariables().getVarKeyList();
for (String key : metaKeys) {
String value = group.getVariables().getVarString(key);
key = key.toLowerCase();
if (key.isEmpty() || value.isEmpty()) {
continue;
}
if (key.equals("build")) {
continue;
}
if (key.equals("prefix") || key.equals("suffix")) {
groups.get(groupName).add(NodeFactory.makeChatMetaNode(key.equals("prefix"), 50, value).setWorld(worldMappingFunc.apply(world)).build());
} else {
groups.get(groupName).add(NodeFactory.makeMetaNode(key, value).setWorld(worldMappingFunc.apply(world)).build());
}
}
log.logAllProgress("Migrated {} groups so far in world " + world, groupWorldCount.incrementAndGet());
}
log.log("Migrated " + groupWorldCount.get() + " groups in world " + world);
@@ -178,35 +189,51 @@ public class MigrationGroupManager extends SubCommand<Object> {
continue;
}
users.putIfAbsent(uuid, new HashMap<>());
users.putIfAbsent(uuid, new HashSet<>());
for (String node : user.getPermissionList()) {
boolean value = true;
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
} else if (node.startsWith("+")) {
node = node.substring(1);
value = true;
if (node.isEmpty()) {
continue;
}
users.get(uuid).put(Maps.immutableEntry(worldMappingFunc.apply(world), node), value);
users.get(uuid).add(MigrationUtils.parseNode(node, true).setWorld(worldMappingFunc.apply(world)).build());
}
String finalWorld = worldMappingFunc.apply(world);
// Collect sub groups
users.get(uuid).putAll(user.subGroupListStringCopy().stream()
.map(n -> "group." + n)
.map(n -> Maps.immutableEntry(finalWorld, n))
.collect(Collectors.toMap(n -> n, n -> true))
String finalWorld = worldMappingFunc.apply(world);
users.get(uuid).addAll(user.subGroupListStringCopy().stream()
.filter(n -> !n.isEmpty())
.map(n -> "group." + MigrationUtils.standardizeName(n))
.map(n -> NodeFactory.make(n, true, null, finalWorld))
.collect(Collectors.toSet())
);
primaryGroups.put(uuid, user.getGroupName());
// Get primary group
primaryGroups.put(uuid, MigrationUtils.standardizeName(user.getGroupName()));
String[] metaKeys = user.getVariables().getVarKeyList();
for (String key : metaKeys) {
String value = user.getVariables().getVarString(key);
key = key.toLowerCase();
if (key.isEmpty() || value.isEmpty()) {
continue;
}
if (key.equals("build")) {
continue;
}
if (key.equals("prefix") || key.equals("suffix")) {
users.get(uuid).add(NodeFactory.makeChatMetaNode(key.equals("prefix"), 100, value).setWorld(worldMappingFunc.apply(world)).build());
} else {
users.get(uuid).add(NodeFactory.makeMetaNode(key, value).setWorld(worldMappingFunc.apply(world)).build());
}
}
log.logProgress("Migrated {} users so far in world " + world, userWorldCount.incrementAndGet());
}
log.log("Migrated " + userWorldCount.get() + " users in world " + world);
}
log.log("All data has now been processed, now starting the import process.");
@@ -214,23 +241,12 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
for (Map.Entry<String, Map<Map.Entry<String, String>, Boolean>> e : groups.entrySet()) {
for (Map.Entry<String, Set<Node>> e : groups.entrySet()) {
plugin.getStorage().createAndLoadGroup(e.getKey(), CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(e.getKey());
for (Map.Entry<Map.Entry<String, String>, Boolean> n : e.getValue().entrySet()) {
// n.key.key = world
// n.key.value = node
// n.value = true/false
try {
if (n.getKey().getKey().equals("")) {
group.setPermission(n.getKey().getValue(), n.getValue());
} else {
group.setPermission(n.getKey().getValue(), n.getValue(), "global", n.getKey().getKey());
}
} catch (Exception ex) {
log.handleException(ex);
}
for (Node node : e.getValue()) {
group.setPermission(node);
}
plugin.getStorage().saveGroup(group);
@@ -240,34 +256,19 @@ public class MigrationGroupManager extends SubCommand<Object> {
log.log("Starting user migration.");
AtomicInteger userCount = new AtomicInteger(0);
for (Map.Entry<UUID, Map<Map.Entry<String, String>, Boolean>> e : users.entrySet()) {
for (Map.Entry<UUID, Set<Node>> e : users.entrySet()) {
plugin.getStorage().loadUser(e.getKey(), "null").join();
me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().get(e.getKey());
for (Map.Entry<Map.Entry<String, String>, Boolean> n : e.getValue().entrySet()) {
// n.key.key = world
// n.key.value = node
// n.value = true/false
try {
if (n.getKey().getKey().equals("")) {
user.setPermission(n.getKey().getValue(), n.getValue());
} else {
user.setPermission(n.getKey().getValue(), n.getValue(), "global", n.getKey().getKey());
}
} catch (Exception ex) {
log.handleException(ex);
}
for (Node node : e.getValue()) {
user.setPermission(node);
}
String primaryGroup = primaryGroups.get(e.getKey());
if (primaryGroup != null) {
try {
user.setPermission("group." + primaryGroup, true);
} catch (ObjectAlreadyHasException ignored) {}
user.setPrimaryGroup(primaryGroup);
try {
user.unsetPermission("group.default");
} catch (ObjectLacksException ignored) {}
user.setPermission(NodeFactory.make("group." + primaryGroup));
user.getPrimaryGroup().setStoredValue(primaryGroup);
user.unsetPermission(NodeFactory.make("group.default"));
}
plugin.getStorage().saveUser(user);
@@ -22,20 +22,19 @@
package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.api.MetaUtils;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import org.bukkit.Bukkit;
import org.bukkit.World;
@@ -99,59 +98,49 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
for (PermissionGroup group : manager.getGroupList()) {
int groupWeight = maxWeight - group.getRank();
final String name = group.getName().toLowerCase();
plugin.getStorage().createAndLoadGroup(name, CreationCause.INTERNAL).join();
Group lpGroup = plugin.getGroupManager().getIfLoaded(name);
final String groupName = MigrationUtils.standardizeName(group.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
Group lpGroup = plugin.getGroupManager().getIfLoaded(groupName);
MigrationUtils.setGroupWeight(lpGroup, groupWeight);
try {
for (String node : group.getOwnPermissions(null)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpGroup.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(MigrationUtils.parseNode(node, true).build());
}
} catch (NullPointerException ignored) {
// No docs on if #getOwnPermissions(null) is ok. Should be fine though.
}
for (String world : worlds) {
if (world.isEmpty()) {
continue;
}
for (String node : group.getOwnPermissions(world)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpGroup.setPermission(node, value, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(MigrationUtils.parseNode(node, true).setWorld(world.toLowerCase()).build());
}
}
for (PermissionGroup g : group.getParents()) {
try {
lpGroup.setPermission("group." + g.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g.getName())));
}
for (String world : worlds) {
if (world.isEmpty()) {
continue;
}
for (PermissionGroup g : group.getParents(world)) {
try {
lpGroup.setPermission("group." + g.getName().toLowerCase(), true, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g.getName()), true, "global", world.toLowerCase()));
}
}
@@ -159,21 +148,11 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
String suffix = group.getOwnSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
lpGroup.setPermission("prefix." + groupWeight + "." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.makePrefixNode(groupWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
lpGroup.setPermission("suffix." + groupWeight + "." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpGroup.setPermission(NodeFactory.makeSuffixNode(groupWeight, suffix).build());
}
plugin.getStorage().saveGroup(lpGroup);
@@ -195,7 +174,11 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
} catch (IllegalArgumentException e) {
u = ni.nameToUUID(user.getIdentifier());
if (u == null) {
u = plugin.getUuidFromUsername(user.getIdentifier());
try {
u = Bukkit.getOfflinePlayer(user.getIdentifier()).getUniqueId();
} catch (Exception ex) {
e.printStackTrace();
}
}
}
@@ -209,53 +192,49 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
try {
for (String node : user.getOwnPermissions(null)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpUser.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(MigrationUtils.parseNode(node, true).build());
}
} catch (NullPointerException ignored) {
// No docs on if #getOwnPermissions(null) is ok. Should be fine though.
}
for (String world : worlds) {
if (world.isEmpty()) {
continue;
}
for (String node : user.getOwnPermissions(world)) {
boolean value = true;
if (node.startsWith("-")) {
node = node.substring(1);
value = false;
if (node.isEmpty()) {
continue;
}
try {
lpUser.setPermission(node, value, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(MigrationUtils.parseNode(node, true).setWorld(world.toLowerCase()).build());
}
}
for (String s : user.getGroupNames()) {
try {
lpUser.setPermission("group." + s.toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
for (String g : user.getGroupNames()) {
if (g.isEmpty()) {
continue;
}
lpUser.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g)));
}
for (String world : worlds) {
for (String s : user.getGroupNames(world)) {
try {
lpUser.setPermission("group." + s.toLowerCase(), true, "global", world.toLowerCase());
} catch (Exception ex) {
log.handleException(ex);
if (world.isEmpty()) {
continue;
}
for (String g : user.getGroupNames(world)) {
if (g.isEmpty()) {
continue;
}
lpUser.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(g), true, "global", world.toLowerCase()));
}
}
@@ -263,21 +242,11 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
String suffix = user.getOwnSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
lpUser.setPermission("prefix." + maxWeight + "." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(NodeFactory.makePrefixNode(maxWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
lpUser.setPermission("suffix." + maxWeight + "." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
lpUser.setPermission(NodeFactory.makeSuffixNode(maxWeight, suffix).build());
}
// Lowest rank is the highest group #logic
@@ -291,13 +260,9 @@ public class MigrationPermissionsEx extends SubCommand<Object> {
}
if (primary != null && !primary.equalsIgnoreCase("default")) {
try {
lpUser.setPermission("group." + primary.toLowerCase(), true);
} catch (ObjectAlreadyHasException ignored) {}
lpUser.setPrimaryGroup(primary);
try {
lpUser.unsetPermission("group.default");
} catch (ObjectLacksException ignored) {}
lpUser.setPermission(NodeFactory.make("group." + primary.toLowerCase()));
lpUser.getPrimaryGroup().setStoredValue(primary);
lpUser.unsetPermission(NodeFactory.make("group.default"));
}
plugin.getUserManager().cleanup(lpUser);
@@ -22,106 +22,52 @@
package me.lucko.luckperms.bukkit.migration;
import lombok.SneakyThrows;
import com.github.cheesesoftware.PowerfulPermsAPI.CachedGroup;
import com.github.cheesesoftware.PowerfulPermsAPI.Group;
import com.github.cheesesoftware.PowerfulPermsAPI.Permission;
import com.github.cheesesoftware.PowerfulPermsAPI.PermissionManager;
import com.github.cheesesoftware.PowerfulPermsAPI.PowerfulPermsPlugin;
import com.github.cheesesoftware.PowerfulPermsAPI.ResultRunnable;
import com.google.common.util.concurrent.ListenableFuture;
import com.zaxxer.hikari.HikariDataSource;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.bukkit.migration.utils.LPResultRunnable;
import me.lucko.luckperms.common.commands.Arg;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageType;
import me.lucko.luckperms.common.utils.HikariSupplier;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import org.bukkit.Bukkit;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import static me.lucko.luckperms.common.constants.Permission.MIGRATION;
// Only supports the latest versions of the PP API. (it seems to change randomly almost every release)
public class MigrationPowerfulPerms extends SubCommand<Object> {
private static Method getPlayerPermissionsMethod = null;
private static Method getPlayerGroupsMethod = null;
private static Method getGroupMethod = null;
// lol, nice "api"
private static boolean superLegacy = false;
private static boolean legacy = false;
static {
try {
Class.forName("com.github.cheesesoftware.PowerfulPermsAPI.ResponseRunnable");
legacy = true;
} catch (ClassNotFoundException ignored) {}
if (legacy) {
try {
getPlayerPermissionsMethod = PermissionManager.class.getMethod("getPlayerOwnPermissions", UUID.class, ResultRunnable.class);
getPlayerPermissionsMethod.setAccessible(true);
} catch (NoSuchMethodException ignored) {}
} else {
try {
getPlayerPermissionsMethod = PermissionManager.class.getMethod("getPlayerOwnPermissions", UUID.class);
getPlayerPermissionsMethod.setAccessible(true);
} catch (NoSuchMethodException ignored) {}
}
try {
getGroupMethod = CachedGroup.class.getMethod("getGroup");
getGroupMethod.setAccessible(true);
superLegacy = true;
} catch (NoSuchMethodException ignored) {}
if (!legacy) {
try {
getPlayerGroupsMethod = PermissionManager.class.getMethod("getPlayerOwnGroups", UUID.class);
getPlayerGroupsMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} else {
try {
getPlayerGroupsMethod = PermissionManager.class.getMethod("getPlayerOwnGroups", UUID.class, ResultRunnable.class);
getPlayerGroupsMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
try {
getPlayerGroupsMethod = PermissionManager.class.getMethod("getPlayerGroups", UUID.class, ResultRunnable.class);
getPlayerGroupsMethod.setAccessible(true);
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
}
}
}
}
public MigrationPowerfulPerms() {
super("powerfulperms", "Migration from PowerfulPerms", MIGRATION, Predicates.not(5),
Arg.list(
@@ -136,44 +82,37 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) throws CommandException {
try {
return run(plugin, sender, args);
} catch (Throwable t) {
t.printStackTrace();
return CommandResult.FAILURE;
}
}
private CommandResult run(LuckPermsPlugin plugin, Sender sender, List<String> args) {
ProgressLogger log = new ProgressLogger("PowerfulPerms");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
log.log("Starting.");
if (!Bukkit.getPluginManager().isPluginEnabled("PowerfulPerms")) {
log.logErr("PowerfulPerms is not loaded.");
log.logErr("Plugin not loaded.");
return CommandResult.STATE_ERROR;
}
final String address = args.get(0);
final String database = args.get(1);
final String username = args.get(2);
final String password = args.get(3);
final String dbTable = args.get(4);
String method = plugin.getConfiguration().get(ConfigKeys.STORAGE_METHOD);
StorageType type = StorageType.parse(method);
if (type == null || type != StorageType.MYSQL) {
// We need to load the Hikari/MySQL stuff.
DependencyManager.loadDependencies(plugin, DependencyManager.STORAGE_DEPENDENCIES.get(StorageType.MYSQL));
}
String address = args.get(0);
String database = args.get(1);
String username = args.get(2);
String password = args.get(3);
String dbTable = args.get(4);
// Find a list of UUIDs
log.log("Getting a list of UUIDs to migrate.");
Set<UUID> uuids = new HashSet<>();
try (HikariDataSource hikari = new HikariDataSource()) {
hikari.setMaximumPoolSize(2);
hikari.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
hikari.addDataSourceProperty("serverName", address.split(":")[0]);
hikari.addDataSourceProperty("port", address.split(":")[1]);
hikari.addDataSourceProperty("databaseName", database);
hikari.addDataSourceProperty("user", username);
hikari.addDataSourceProperty("password", password);
try (HikariSupplier hikari = new HikariSupplier(address, database, username, password)) {
hikari.setup("powerfulperms-migrator-pool");
try (Connection c = hikari.getConnection()) {
DatabaseMetaData meta = c.getMetaData();
@@ -220,23 +159,54 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
PowerfulPermsPlugin ppPlugin = (PowerfulPermsPlugin) Bukkit.getPluginManager().getPlugin("PowerfulPerms");
PermissionManager pm = ppPlugin.getPermissionManager();
Collection<Group> groups = pm.getGroups().values();
int maxWeight = 0;
// Groups first.
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
Map<Integer, Group> groups = pm.getGroups(); // All versions
for (Group g : groups.values()) {
plugin.getStorage().createAndLoadGroup(g.getName().toLowerCase(), CreationCause.INTERNAL).join();
final me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(g.getName().toLowerCase());
for (Group g : groups) {
maxWeight = Math.max(maxWeight, g.getRank());
for (Permission p : g.getOwnPermissions()) { // All versions
applyPerm(group, p, log);
final String groupName = MigrationUtils.standardizeName(g.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
final me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(groupName);
MigrationUtils.setGroupWeight(group, g.getRank());
for (Permission p : g.getOwnPermissions()) {
applyPerm(group, p);
}
for (Group parent : g.getParents()) { // All versions
try {
group.setPermission("group." + parent.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
for (Group parent : g.getParents()) {
group.setPermission(NodeFactory.make("group." + parent.getName().toLowerCase(), true));
}
// server --> prefix afaik
for (Map.Entry<String, String> prefix : g.getPrefixes().entrySet()) {
String server = prefix.getKey().toLowerCase();
if (prefix.getKey().equals("*") || prefix.getKey().equals("all")) {
server = null;
}
if (server != null) {
group.setPermission(NodeFactory.makePrefixNode(g.getRank(), prefix.getValue()).setServer(server).build());
} else {
group.setPermission(NodeFactory.makePrefixNode(g.getRank(), prefix.getValue()).build());
}
}
for (Map.Entry<String, String> suffix : g.getSuffixes().entrySet()) {
String server = suffix.getKey().toLowerCase();
if (suffix.getKey().equals("*") || suffix.getKey().equals("all")) {
server = null;
}
if (server != null) {
group.setPermission(NodeFactory.makeSuffixNode(g.getRank(), suffix.getValue()).setServer(server).build());
} else {
group.setPermission(NodeFactory.makeSuffixNode(g.getRank(), suffix.getValue()).build());
}
}
@@ -245,291 +215,134 @@ public class MigrationPowerfulPerms extends SubCommand<Object> {
}
log.log("Migrated " + groupCount.get() + " groups");
// Now users.
// Migrate all users
log.log("Starting user migration.");
final Map<UUID, CountDownLatch> progress = new HashMap<>();
AtomicInteger userCount = new AtomicInteger(0);
// Increment the max weight from the group migrations. All user meta should override.
maxWeight += 5;
// Migrate all users and their groups
for (UUID uuid : uuids) {
progress.put(uuid, new CountDownLatch(2));
// Create a LuckPerms user for the UUID
plugin.getStorage().loadUser(uuid, "null").join();
User user = plugin.getUserManager().get(uuid);
// Get a list of Permissions held by the user from the PP API.
getPlayerPermissions(pm, uuid, perms -> { // Changes each version
perms.forEach(p -> applyPerm(user, p, log));
List<Permission> permissions = joinFuture(pm.getPlayerOwnPermissions(uuid));
// Update the progress so the user can be saved and unloaded.
synchronized (progress) {
progress.get(uuid).countDown();
if (progress.get(uuid).getCount() == 0) {
plugin.getStorage().saveUser(user);
plugin.getUserManager().cleanup(user);
}
}
});
for (Permission p : permissions) {
applyPerm(user, p);
}
// Migrate the user's groups to LuckPerms from PP.
Consumer<Map<String, List<CachedGroup>>> callback = groups1 -> {
for (Map.Entry<String, List<CachedGroup>> e : groups1.entrySet()) {
final String server;
if (e.getKey() != null && (e.getKey().equals("") || e.getKey().equalsIgnoreCase("all"))) {
server = null;
} else {
server = e.getKey();
}
if (superLegacy) {
e.getValue().stream()
.filter(cg -> !cg.isNegated())
.map(cg -> {
try {
return (Group) getGroupMethod.invoke(cg);
} catch (IllegalAccessException | InvocationTargetException e1) {
e1.printStackTrace();
return null;
}
})
.forEach(g -> {
if (g != null) {
if (server == null) {
try {
user.setPermission("group." + g.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
user.setPermission("group." + g.getName().toLowerCase(), true, server);
} catch (Exception ex) {
log.handleException(ex);
}
}
}
});
} else {
e.getValue().stream()
.filter(g -> !g.hasExpired() && !g.isNegated())
.forEach(g -> {
final Group group = pm.getGroup(g.getGroupId());
if (g.willExpire()) {
if (server == null) {
try {
user.setPermission("group." + group.getName().toLowerCase(), true, g.getExpirationDate().getTime() / 1000L);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
user.setPermission("group." + group.getName().toLowerCase(), true, server, g.getExpirationDate().getTime() / 1000L);
} catch (Exception ex) {
log.handleException(ex);
}
}
} else {
if (server == null) {
try {
user.setPermission("group." + group.getName().toLowerCase(), true);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
user.setPermission("group." + group.getName().toLowerCase(), true, server);
} catch (Exception ex) {
log.handleException(ex);
}
}
}
});
}
// server --> list of groups
Map<String, List<CachedGroup>> parents = joinFuture(pm.getPlayerOwnGroups(uuid));
for (Map.Entry<String, List<CachedGroup>> parent : parents.entrySet()) {
String server = parent.getKey().toLowerCase();
if (parent.getKey().equals("*") || parent.getKey().equals("all")) {
server = null;
}
// Update the progress so the user can be saved and unloaded.
synchronized (progress) {
progress.get(uuid).countDown();
if (progress.get(uuid).getCount() == 0) {
plugin.getStorage().saveUser(user);
plugin.getUserManager().cleanup(user);
}
}
};
if (!legacy) {
try {
ListenableFuture<LinkedHashMap<String, List<CachedGroup>>> future = (ListenableFuture<LinkedHashMap<String, List<CachedGroup>>>) getPlayerGroupsMethod.invoke(pm, uuid);
try {
if (future.isDone()) {
callback.accept(future.get());
} else {
future.addListener(() -> {
try {
callback.accept(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}, Runnable::run);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} else {
try {
getPlayerGroupsMethod.invoke(pm, uuid, new LPResultRunnable<LinkedHashMap<String, List<CachedGroup>>>() {
@Override
public void run() {
callback.accept(getResult());
}
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
for (CachedGroup group : parent.getValue()) {
applyGroup(pm, user, group, server);
}
}
String prefix = joinFuture(pm.getPlayerOwnPrefix(uuid));
String suffix = joinFuture(pm.getPlayerOwnSuffix(uuid));
if (prefix != null && !prefix.equals("")) {
user.setPermission(NodeFactory.makePrefixNode(maxWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
user.setPermission(NodeFactory.makeSuffixNode(maxWeight, suffix).build());
}
String primary = joinFuture(pm.getPlayerPrimaryGroup(uuid)).getName().toLowerCase();
if (!primary.equals("default")) {
user.setPermission(NodeFactory.make("group." + primary));
user.getPrimaryGroup().setStoredValue(primary);
}
plugin.getUserManager().cleanup(user);
plugin.getStorage().saveUser(user);
log.logProgress("Migrated {} users so far.", userCount.incrementAndGet());
}
// All groups are migrated, but there may still be some users being migrated.
// This block will wait for all users to be completed.
log.log("Waiting for user migration to complete. This may take some time");
boolean sleep = true;
while (sleep) {
sleep = false;
for (Map.Entry<UUID, CountDownLatch> e : progress.entrySet()) {
if (e.getValue().getCount() != 0) {
sleep = true;
break;
}
}
if (sleep) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// We done.
log.log("Migrated " + userCount.get() + " users.");
log.log("Success! Migration complete.");
return CommandResult.SUCCESS;
}
private void applyPerm(PermissionHolder holder, Permission p, ProgressLogger log) {
private void applyPerm(PermissionHolder holder, Permission p) {
String node = p.getPermissionString();
boolean value = true;
if (node.startsWith("!")) {
if (node.startsWith("!") || node.startsWith("-")) {
node = node.substring(1);
value = false;
}
String server = p.getServer();
if (server != null && server.equalsIgnoreCase("all")) {
if (server != null && (server.equalsIgnoreCase("all") || server.equalsIgnoreCase("*"))) {
server = null;
}
String world = p.getWorld();
if (world != null && world.equalsIgnoreCase("all")) {
if (world != null && (world.equalsIgnoreCase("all") || world.equalsIgnoreCase("*"))) {
world = null;
}
long expireAt = 0L;
if (!superLegacy) {
if (p.willExpire()) {
expireAt = p.getExpirationDate().getTime() / 1000L;
}
if (p.willExpire()) {
expireAt = p.getExpirationDate().getTime() / 1000L;
}
if (world != null && server == null) {
server = "global";
}
if (world != null) {
if (expireAt == 0L) {
try {
holder.setPermission(node, value, server, world);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
holder.setPermission(node, value, server, world, expireAt);
} catch (Exception ex) {
log.handleException(ex);
}
}
Node.Builder nb = NodeFactory.newBuilder(node).setValue(value);
} else if (server != null) {
if (expireAt == 0L) {
try {
holder.setPermission(node, value, server);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
holder.setPermission(node, value, server, expireAt);
} catch (Exception ex) {
log.handleException(ex);
}
}
} else {
if (expireAt == 0L) {
try {
holder.setPermission(node, value);
} catch (Exception ex) {
log.handleException(ex);
}
} else {
try {
holder.setPermission(node, value, expireAt);
} catch (Exception ex) {
log.handleException(ex);
}
}
if (expireAt != 0) {
nb.setExpiry(expireAt);
}
if (server != null) {
nb.setServer(server);
}
if (world != null) {
nb.setWorld(world);
}
holder.setPermission(nb.build());
}
private static void getPlayerPermissions(PermissionManager manager, UUID uuid, Consumer<List<Permission>> callback) {
if (legacy) {
try {
getPlayerPermissionsMethod.invoke(manager, uuid, new LPResultRunnable<List<Permission>>() {
@Override
public void run() {
callback.accept(getResult());
}
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} else {
try {
ListenableFuture<List<Permission>> lf = (ListenableFuture<List<Permission>>) getPlayerPermissionsMethod.invoke(manager, uuid);
try {
if (lf.isDone()) {
callback.accept(lf.get());
} else {
lf.addListener(() -> {
try {
callback.accept(lf.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}, Runnable::run);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
private void applyGroup(PermissionManager pm, PermissionHolder holder, CachedGroup g, String server) {
Group group = pm.getGroup(g.getGroupId());
String node = "group." + MigrationUtils.standardizeName(group.getName());
long expireAt = 0L;
if (g.willExpire()) {
expireAt = g.getExpirationDate().getTime() / 1000L;
}
Node.Builder nb = NodeFactory.newBuilder(node);
if (expireAt != 0) {
nb.setExpiry(expireAt);
}
if (server != null) {
nb.setServer(server);
}
holder.setPermission(nb.build());
}
@SneakyThrows
private static <T> T joinFuture(Future<T> future) {
return future.get();
}
}
@@ -25,7 +25,8 @@ package me.lucko.luckperms.bukkit.migration;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
@@ -36,7 +37,6 @@ import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import org.bukkit.Bukkit;
import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService;
@@ -59,19 +59,19 @@ public class MigrationZPermissions extends SubCommand<Object> {
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, List<String> args, String label) throws CommandException {
ProgressLogger log = new ProgressLogger("PermissionManager");
ProgressLogger log = new ProgressLogger("zPermissions");
log.addListener(plugin.getConsoleSender());
log.addListener(sender);
log.log("Starting.");
if (!Bukkit.getPluginManager().isPluginEnabled("zPermissions")) {
log.logErr("zPermissions is not loaded.");
log.logErr("Plugin not loaded.");
return CommandResult.STATE_ERROR;
}
if (!Bukkit.getServicesManager().isProvidedFor(ZPermissionsService.class)) {
log.logErr("zPermissions is not loaded.");
log.logErr("Plugin not loaded.");
return CommandResult.STATE_ERROR;
}
@@ -91,8 +91,10 @@ public class MigrationZPermissions extends SubCommand<Object> {
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
for (String g : service.getAllGroups()) {
plugin.getStorage().createAndLoadGroup(g.toLowerCase(), CreationCause.INTERNAL).join();
Group group = plugin.getGroupManager().getIfLoaded(g.toLowerCase());
String groupName = MigrationUtils.standardizeName(g);
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
Group group = plugin.getGroupManager().getIfLoaded(groupName);
PermissionEntity entity = internalService.getEntity(g, null, true);
migrateEntity(group, entity, null);
@@ -106,10 +108,13 @@ public class MigrationZPermissions extends SubCommand<Object> {
log.log("Starting track migration.");
AtomicInteger trackCount = new AtomicInteger(0);
for (String t : service.getAllTracks()) {
plugin.getStorage().createAndLoadTrack(t.toLowerCase(), CreationCause.INTERNAL).join();
Track track = plugin.getTrackManager().getIfLoaded(t.toLowerCase());
String trackName = MigrationUtils.standardizeName(t);
plugin.getStorage().createAndLoadTrack(trackName, CreationCause.INTERNAL).join();
Track track = plugin.getTrackManager().getIfLoaded(trackName);
track.setGroups(service.getTrackGroups(t));
plugin.getStorage().saveTrack(track);
log.logAllProgress("Migrated {} tracks so far.", trackCount.incrementAndGet());
}
log.log("Migrated " + trackCount.get() + " tracks");
@@ -118,18 +123,17 @@ public class MigrationZPermissions extends SubCommand<Object> {
log.log("Starting user migration.");
AtomicInteger userCount = new AtomicInteger(0);
for (UUID u : service.getAllPlayersUUID()) {
plugin.getStorage().loadUser(u, "null").join();
User user = plugin.getUserManager().get(u);
PermissionEntity entity = internalService.getEntity(null, u, false);
migrateEntity(user, entity, internalService.getGroups(u));
user.setPrimaryGroup(service.getPlayerPrimaryGroup(u));
String username = null;
if (!entity.isGroup()) {
user.setName(entity.getDisplayName());
username = entity.getDisplayName();
}
plugin.getStorage().loadUser(u, username).join();
User user = plugin.getUserManager().get(u);
migrateEntity(user, entity, internalService.getGroups(u));
user.getPrimaryGroup().setStoredValue(MigrationUtils.standardizeName(service.getPlayerPrimaryGroup(u)));
plugin.getUserManager().cleanup(user);
plugin.getStorage().saveUser(user);
log.logProgress("Migrated {} users so far.", userCount.incrementAndGet());
@@ -140,41 +144,36 @@ public class MigrationZPermissions extends SubCommand<Object> {
return CommandResult.SUCCESS;
}
private void migrateEntity(PermissionHolder group, PermissionEntity entity, List<Membership> memberships) {
private void migrateEntity(PermissionHolder holder, PermissionEntity entity, List<Membership> memberships) {
for (Entry e : entity.getPermissions()) {
if (e.getWorld() != null) {
try {
group.setPermission(e.getPermission(), true, "global", e.getWorld().getName());
} catch (ObjectAlreadyHasException ignored) {}
if (e.getWorld() != null && !e.getWorld().getName().equals("")) {
holder.setPermission(MigrationUtils.parseNode(e.getPermission(), true).setWorld(e.getWorld().getName()).build());
} else {
try {
group.setPermission(e.getPermission(), true); // TODO handle negated.
} catch (ObjectAlreadyHasException ignored) {}
holder.setPermission(MigrationUtils.parseNode(e.getPermission(), true).build());
}
}
if (entity.isGroup()) {
// entity.getMemberships() doesn't work for groups (always returns 0 records)
for (Inheritance inheritance : entity.getInheritancesAsChild()) {
try {
if (!inheritance.getParent().getName().equals(group.getObjectName())) {
group.setPermission("group." + inheritance.getParent().getName(), true);
}
} catch (ObjectAlreadyHasException ignored) {}
if (!inheritance.getParent().getName().equals(holder.getObjectName())) {
holder.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(inheritance.getParent().getName())));
}
}
} else {
// entity.getMemberships() doesn't work (always returns 0 records)
for (Membership membership : memberships) {
try {
group.setPermission("group." + membership.getGroup().getDisplayName(), true);
} catch (ObjectAlreadyHasException ignored) {}
holder.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(membership.getGroup().getDisplayName())));
}
}
int weight = entity.isGroup() ? 50 : 100;
for (EntityMetadata metadata : entity.getMetadata()) {
try {
group.setPermission(NodeFactory.makeMetaNode(metadata.getName(), metadata.getStringValue()).build());
} catch (Exception e) {
e.printStackTrace();
String key = metadata.getName().toLowerCase();
if (key.equals("prefix") || key.equals("suffix")) {
holder.setPermission(NodeFactory.makeChatMetaNode(key.equals("prefix"), weight, metadata.getStringValue()).build());
} else {
holder.setPermission(NodeFactory.makeMetaNode(key, metadata.getStringValue()).build());
}
}
}
@@ -38,6 +38,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Injects a {@link LPPermissible} into a {@link Player}
*/
@SuppressWarnings("unchecked")
@UtilityClass
public class Injector {
private static final Map<UUID, LPPermissible> INJECTED_PERMISSIBLES = new ConcurrentHashMap<>();
@@ -63,7 +64,7 @@ public class Injector {
PermissibleBase existing = (PermissibleBase) HUMAN_ENTITY_FIELD.get(player);
if (existing instanceof LPPermissible) {
// uh oh
throw new IllegalStateException();
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
}
// Move attachments over from the old permissible.
@@ -72,7 +73,8 @@ public class Injector {
attachments.clear();
existing.clearPermissions();
lpPermissible.recalculatePermissions();
lpPermissible.getActive().set(true);
lpPermissible.recalculatePermissions(false);
lpPermissible.setOldPermissible(existing);
lpPermissible.updateSubscriptionsAsync();
@@ -97,6 +99,8 @@ public class Injector {
((LPPermissible) permissible).unsubscribeFromAllAsync();
}
((LPPermissible) permissible).getActive().set(false);
if (dummy) {
HUMAN_ENTITY_FIELD.set(player, new DummyPermissibleBase());
} else {
@@ -49,6 +49,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -66,6 +67,8 @@ public class LPPermissible extends PermissibleBase {
@Setter
private PermissibleBase oldPermissible = null;
private final AtomicBoolean active = new AtomicBoolean(false);
// Attachment stuff.
private final Map<String, PermissionAttachmentInfo> attachmentPermissions = new ConcurrentHashMap<>();
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new LinkedList<>());
@@ -81,10 +84,18 @@ public class LPPermissible extends PermissibleBase {
}
public void updateSubscriptionsAsync() {
if (!active.get()) {
return;
}
plugin.doAsync(this::updateSubscriptions);
}
public void updateSubscriptions() {
if (!active.get()) {
return;
}
UserCache cache = user.getUserData();
if (cache == null) {
return;
@@ -110,9 +121,7 @@ public class LPPermissible extends PermissibleBase {
}
public void addAttachments(List<PermissionAttachment> attachments) {
for (PermissionAttachment attachment : attachments) {
this.attachments.add(attachment);
}
this.attachments.addAll(attachments);
}
public Contexts calculateContexts() {
@@ -128,7 +137,7 @@ public class LPPermissible extends PermissibleBase {
}
private boolean hasData() {
return user.getUserData() != null;
return user != null && user.getUserData() != null;
}
@Override
@@ -262,6 +271,10 @@ public class LPPermissible extends PermissibleBase {
@Override
public void recalculatePermissions() {
recalculatePermissions(true);
}
public void recalculatePermissions(boolean invalidate) {
if (attachmentPermissions == null) {
return;
}
@@ -272,7 +285,7 @@ public class LPPermissible extends PermissibleBase {
calculateChildPermissions(attachment.getPermissions(), false, attachment);
}
if (hasData()) {
if (hasData() && invalidate) {
user.getUserData().invalidatePermissionCalculators();
}
}
@@ -28,21 +28,17 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.caching.MetaData;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.caching.MetaHolder;
import me.lucko.luckperms.common.caching.MetaAccumulator;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.utils.ExtractedContexts;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import net.milkbowl.vault.chat.Chat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static me.lucko.luckperms.api.MetaUtils.escapeCharacters;
import static me.lucko.luckperms.api.MetaUtils.unescapeCharacters;
@@ -75,15 +71,7 @@ public class VaultChatHook extends Chat {
perms.log("Setting meta: '" + node + "' for " + holder.getObjectName() + " on world " + world + ", server " + perms.getServer());
perms.getScheduler().execute(() -> {
List<Node> toRemove = holder.getNodes().stream()
.filter(n -> n.isMeta() && n.getMeta().getKey().equals(node))
.collect(Collectors.toList());
toRemove.forEach(n -> {
try {
holder.unsetPermission(n);
} catch (ObjectLacksException ignored) {}
});
holder.removeIf(n -> n.isMeta() && n.getMeta().getKey().equals(node));
Node.Builder metaNode = NodeFactory.makeMetaNode(node, value).setValue(true);
if (!perms.getServer().equalsIgnoreCase("global")) {
@@ -93,10 +81,7 @@ public class VaultChatHook extends Chat {
metaNode.setWorld(finalWorld);
}
try {
holder.setPermission(metaNode.build());
} catch (ObjectAlreadyHasException ignored) {}
holder.setPermission(metaNode.build());
perms.save(holder);
});
}
@@ -111,19 +96,11 @@ public class VaultChatHook extends Chat {
perms.getScheduler().execute(() -> {
// remove all prefixes/suffixes directly set on the user/group
List<Node> toRemove = holder.getNodes().stream()
.filter(n -> prefix ? n.isPrefix() : n.isSuffix())
.collect(Collectors.toList());
toRemove.forEach(n -> {
try {
holder.unsetPermission(n);
} catch (ObjectLacksException ignored) {}
});
holder.removeIf(n -> prefix ? n.isPrefix() : n.isSuffix());
// find the max inherited priority & add 10
MetaHolder metaHolder = holder.accumulateMeta(null, null, ExtractedContexts.generate(perms.createContextForWorld(finalWorld)));
int priority = (prefix ? metaHolder.getPrefixes() : metaHolder.getSuffixes()).keySet().stream()
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, ExtractedContexts.generate(perms.createContextForWorld(finalWorld)));
int priority = (prefix ? metaAccumulator.getPrefixes() : metaAccumulator.getSuffixes()).keySet().stream()
.mapToInt(e -> e).max().orElse(0) + 10;
Node.Builder chatMetaNode = NodeFactory.makeChatMetaNode(prefix, priority, value);
@@ -134,10 +111,7 @@ public class VaultChatHook extends Chat {
chatMetaNode.setWorld(finalWorld);
}
try {
holder.setPermission(chatMetaNode.build());
} catch (ObjectAlreadyHasException ignored) {}
holder.setPermission(chatMetaNode.build());
perms.save(holder);
});
}
@@ -184,7 +158,7 @@ public class VaultChatHook extends Chat {
perms.log("Getting meta: '" + node + "' for group " + group.getName() + " on world " + world + ", server " + perms.getServer());
for (Node n : group.getPermissions(true)) {
for (Node n : group.mergePermissionsToList()) {
if (!n.getValue()) continue;
if (!n.isMeta()) continue;
if (!n.shouldApplyOnServer(perms.getServer(), perms.isIncludeGlobal(), false)) continue;
@@ -215,7 +189,7 @@ public class VaultChatHook extends Chat {
}
ExtractedContexts ec = ExtractedContexts.generate(new Contexts(ContextSet.fromMap(context), perms.isIncludeGlobal(), true, true, true, true, false));
for (Node n : group.getAllNodes(null, ec)) {
for (Node n : group.getAllNodes(ec)) {
if (!n.getValue()) continue;
if (prefix ? !n.isPrefix() : !n.isSuffix()) continue;
if (!n.shouldApplyOnServer(perms.getServer(), perms.isIncludeGlobal(), false)) continue;
@@ -26,25 +26,28 @@ import lombok.Getter;
import lombok.NonNull;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import me.lucko.luckperms.common.utils.ExtractedContexts;
import net.milkbowl.vault.permission.Permission;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* LuckPerms Vault Permission implementation
@@ -90,15 +93,16 @@ public class VaultPermissionHook extends Permission {
*/
private CompletableFuture<Void> add(String world, PermissionHolder holder, String permission) {
return CompletableFuture.runAsync(() -> {
try {
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
holder.setPermission(permission, true, getServer(), world);
} else {
holder.setPermission(permission, true, getServer());
}
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.setPermission(NodeFactory.make(permission, true, getServer(), world));
} else {
result = holder.setPermission(NodeFactory.make(permission, true, getServer()));
}
if (result.asBoolean()) {
save(holder);
} catch (ObjectAlreadyHasException ignored) {}
}
}, scheduler);
}
@@ -111,15 +115,16 @@ public class VaultPermissionHook extends Permission {
*/
private CompletableFuture<Void> remove(String world, PermissionHolder holder, String permission) {
return CompletableFuture.runAsync(() -> {
try {
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
holder.unsetPermission(permission, getServer(), world);
} else {
holder.unsetPermission(permission, getServer());
}
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.unsetPermission(NodeFactory.make(permission, getServer(), world));
} else {
result = holder.unsetPermission(NodeFactory.make(permission, getServer()));
}
if (result.asBoolean()) {
save(holder);
} catch (ObjectLacksException ignored) {}
}
}, scheduler);
}
@@ -197,7 +202,7 @@ public class VaultPermissionHook extends Permission {
if (group == null) return false;
// This is a nasty call. Groups aren't cached. :(
Map<String, Boolean> permissions = group.exportNodes(createContextForWorld(world), true);
Map<String, Boolean> permissions = group.exportNodes(ExtractedContexts.generate(createContextForWorld(world)), true);
return permissions.containsKey(permission.toLowerCase()) && permissions.get(permission.toLowerCase());
}
@@ -234,7 +239,7 @@ public class VaultPermissionHook extends Permission {
if (user == null) return false;
String w = world; // screw effectively final
return user.getNodes().stream()
return user.getNodes().values().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), false))
.filter(n -> n.shouldApplyOnWorld(w, true, false))
@@ -255,15 +260,16 @@ public class VaultPermissionHook extends Permission {
String w = world;
scheduler.execute(() -> {
try {
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
user.setInheritGroup(group, getServer(), w);
} else {
user.setInheritGroup(group, getServer());
}
DataMutateResult result;
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
result = user.setInheritGroup(group, ImmutableContextSet.of("server", getServer(), "world", w));
} else {
result = user.setInheritGroup(group, ImmutableContextSet.singleton("server", getServer()));
}
if (result.asBoolean()) {
save(user);
} catch (ObjectAlreadyHasException ignored) {}
}
});
return true;
}
@@ -281,15 +287,16 @@ public class VaultPermissionHook extends Permission {
String w = world;
scheduler.execute(() -> {
try {
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
user.unsetInheritGroup(group, getServer(), w);
} else {
user.unsetInheritGroup(group, getServer());
}
DataMutateResult result;
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
result = user.unsetInheritGroup(group, ImmutableContextSet.of("server", getServer(), "world", w));
} else {
result = user.unsetInheritGroup(group, ImmutableContextSet.singleton("server", getServer()));
}
if (result.asBoolean()) {
save(user);
} catch (ObjectLacksException ignored) {}
}
});
return true;
}
@@ -303,7 +310,7 @@ public class VaultPermissionHook extends Permission {
if (user == null) return new String[0];
String w = world; // screw effectively final
return user.getNodes().stream()
return user.getNodes().values().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), false))
.filter(n -> n.shouldApplyOnWorld(w, true, false))
@@ -323,7 +330,7 @@ public class VaultPermissionHook extends Permission {
// nothing special, just return the value.
if (!isPgo()) {
String g = user.getPrimaryGroup();
String g = user.getPrimaryGroup().getValue();
return plugin.getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).getOrDefault(g, g);
}
@@ -352,7 +359,7 @@ public class VaultPermissionHook extends Permission {
}
} else {
// we need to check the users permissions only
for (LocalizedNode node : user.getPermissions(true)) {
for (Node node : user.mergePermissionsToList()) {
if (!node.getValue()) continue;
if (!node.getPermission().toLowerCase().startsWith("vault.primarygroup.")) continue;
if (!node.shouldApplyOnServer(getServer(), isIncludeGlobal(), false)) continue;
@@ -366,7 +373,15 @@ public class VaultPermissionHook extends Permission {
}
if (isPgoCheckMemberOf()) {
if (!user.getLocalGroups(getServer(), world, isIncludeGlobal()).contains(group.toLowerCase())) {
String finalWorld = world;
List<String> localGroups = user.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnWorld(finalWorld, isIncludeGlobal(), true))
.filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), true))
.map(Node::getGroupName)
.collect(Collectors.toList());
if (!localGroups.contains(group.toLowerCase())) {
continue;
}
}
@@ -376,7 +391,7 @@ public class VaultPermissionHook extends Permission {
}
// Fallback
String g = user.getPrimaryGroup();
String g = user.getPrimaryGroup().getValue();
return plugin.getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).getOrDefault(g, g);
}
+61 -40
View File
@@ -26,26 +26,38 @@ apply-global-groups: true
# If users on this server should have global (non-world specific) groups applied
apply-global-world-groups: true
# If this server is in offline or online mode.
# This setting allows a player to have the same UUID across a network of offline mode/mixed servers.
# If UUIDs should be pulled from the server, or looked up by username based upon previous connections.
#
# You should generally reflect the setting in server.properties here. Except when...
# This option should be set to true in most cases. When set to false, in order to get a player's UUID, LuckPerms will:
# 1. Check if a player with the given username has joined before, if they have, use the UUID they used on their previous login.
# 2. Save and return the players "current" uuid.
#
# 1. You have Spigot servers connected to a BungeeCord proxy, with online-mode set to false, but 'bungeecord' set to true in the spigot.yml
# AND 'ip-forward' set to true in the BungeeCord config.yml
# In this case, set online-mode in LuckPerms to true, despite the server being in offline mode.
# For offline mode (cracked) servers, a players UUID is generated based upon their username.
#
# 2. You are only running one server instance using LuckPerms, (not a network)
# In this case, set online-mode to true no matter what is set in server.properties. (we can just fallback to the servers uuid cache)
# IMPORTANT:
# If you are using BungeeCord proxy running in online mode, it is important that "online-mode=false" is set in server.properties, but
# "bungeecord: true" is set in the spigot.yml. You also need to set "ip_forward: true" in BungeeCord's config.yml.
# If for whatever reason you are not able to do this, and do not have ip-forward enabled, then you may need to set "use-server-uuids" to false.
#
# 3. If your proxy is running in offline mode, and you are using PaperSpigot (https://ci.destroystokyo.com/job/PaperSpigot/),
# you should set "bungee-online-mode" to false in the paper.yml, and set "online-mode" to true in all LuckPerms configs.
# This approach is thoroughly recommended for offline mode networks.
online-mode: true
# If your proxy is running in offline mode, you should still be setting up ip-forwarding as described above, but may also find that you need to set
# "bungee-online-mode" to false in paper.yml, if you are using PaperSpigot. (https://ci.destroystokyo.com/job/PaperSpigot/)
#
# This option only really exists for networks who for whatever reason cannot setup proper ip forwarding.
use-server-uuids: true
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
# Mirrors world names. Whenever LuckPerms checks what world a user is in, if the world name is in this list, the value assigned
# will be sent forward for permission calculation instead.
world-rewrite:
# world_nether: world
# world_the_end: world
# Rewrites group names. The underlying name of the group does not change, just the output in commands / placeholders / Vault.
group-name-rewrite:
# default: Member
# Controls how temporary permissions/parents/meta should be accumulated
#
# The default behaviour is "deny"
@@ -54,6 +66,14 @@ log-notify: true
# If "deny": the command will just fail if you try to add another node with the same expiry
temporary-add-behaviour: deny
# How should LuckPerms determine a users "primary" group.
#
# Available Options:
# -> stored use the value stored against the users record in the file/database
# -> parents-by-weight just use the users most highly weighted parent
# -> all-parents-by-weight same as above, but calculates based upon all parents inherits from both directly and indirectly
primary-group-calculation: stored
@@ -144,8 +164,14 @@ commands-allow-op: true
# | Vault | #
# +------------------------------------------------------------------------+ #
# If the vault-server option below should be used.
# When this option is set to false, the server value defined above under "server" is used.
use-vault-server: false
# The name of the server used within Vault operations. If you don't want Vault operations to be server specific, set this
# to "global".
#
# Will only take effect if use-vault-server is set to true above.
vault-server: global
# If global permissions should be considered when retrieving meta or player groups
@@ -154,32 +180,9 @@ vault-include-global: true
# If Vault operations should ignore any world arguments if supplied.
vault-ignore-world: false
# This block controls the Primary Group override feature
# See the wiki for more information.
vault-primary-groups-overrides:
# If the feature is enabled
enabled: false
# If the check should query the user's inherited permissions.
# (a value of false only checks the permissions they explicitly have)
check-inherited-permissions: false
# If LuckPerms should check if the group exists
check-group-exists: true
# If LuckPerms should check if the user is actually a member of the group
check-user-member-of: true
# If LuckPerms should print debugging info to console when a plugin uses a Vault function
vault-debug: false
# Mirrors world names. Whenever LuckPerms checks what world a user is in, if the world name is in this list, the value assigned
# will be sent forward for permission calculation instead.
world-rewrite:
# world_nether: world
# world_the_end: world
# Rewrites group names. The underlying name of the group does not change, just the output in commands / placeholders / Vault.
group-name-rewrite:
# default: Member
@@ -188,10 +191,20 @@ group-name-rewrite:
# +------------------------------------------------------------------------+ #
# Which storage method the plugin should use.
# Currently supported: mysql, postgresql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL, PostgreSQL or MongoDB
#
# See: https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL, MariaDB, PostgreSQL or MongoDB
# If your MySQL server supports it, the "mariadb" option is preferred over "mysql".
storage-method: h2
# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then schedule automatic
# updates when changes are detected.
#
# If you don't want this to happen, set this option to false.
watch-files: true
# This block enables support for split datastores.
split-storage:
enabled: false
@@ -217,9 +230,14 @@ data:
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
table_prefix: 'luckperms_'
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3
# This option controls how frequently LuckPerms will perform a sync task.
# A sync task will refresh all data from the storage, and ensure that the most up-to-date data is being used by the plugin.
#
# This is disabled by default, as most users will not need it. However, if you're using a remote storage type
# without a messaging service setup, you may wish to set this value to something like 3.
#
# Set to -1 to disable the task completely.
sync-minutes: -1
# Settings for the messaging service
#
@@ -237,6 +255,9 @@ data:
# none ==> nothing
messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
+5 -12
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -33,7 +33,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -42,7 +42,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<version>${shade.version}</version>
<executions>
<execution>
<phase>package</phase>
@@ -70,7 +70,7 @@
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.9-SNAPSHOT</version>
<version>1.11-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- LuckPerms Common -->
@@ -87,18 +87,11 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<!-- slf4j library -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.9</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- **************Migration Plugins************** -->
@@ -29,8 +29,6 @@ import me.lucko.luckperms.api.context.MutableContextSet;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Map;
public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer> {
private static final String WORLD_KEY = "world";
@@ -43,19 +41,9 @@ public class BackendServerCalculator implements ContextCalculator<ProxiedPlayer>
String server = getServer(subject);
if (server != null) {
accumulator.add(Maps.immutableEntry(WORLD_KEY, server));
accumulator.add(Maps.immutableEntry(WORLD_KEY, server.toLowerCase()));
}
return accumulator;
}
@Override
public boolean isContextApplicable(ProxiedPlayer subject, Map.Entry<String, String> context) {
if (!context.getKey().equals(WORLD_KEY)) {
return false;
}
String server = getServer(subject);
return server != null && server.equals(context.getValue());
}
}
@@ -35,11 +35,11 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class BungeeConfig extends AbstractConfiguration {
@@ -72,6 +72,11 @@ public class BungeeConfig extends AbstractConfiguration {
}
}
@Override
public boolean contains(String path) {
return configuration.contains(path);
}
@Override
public String getString(String path, String def) {
return configuration.getString(path, def);
@@ -99,7 +104,7 @@ public class BungeeConfig extends AbstractConfiguration {
return def;
}
return Optional.ofNullable(section.getKeys().stream().collect(Collectors.toList())).orElse(def);
return Optional.of((List<String>) new ArrayList<>(section.getKeys())).orElse(def);
}
@Override
@@ -22,13 +22,16 @@
package me.lucko.luckperms.bungee;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.caching.UserData;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.Message;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.defaults.Rule;
import me.lucko.luckperms.common.utils.AbstractListener;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
@@ -37,67 +40,68 @@ import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("WeakerAccess")
public class BungeeListener extends AbstractListener implements Listener {
private static final TextComponent WARN_MESSAGE = new TextComponent(Message.LOADING_ERROR.toString());
@RequiredArgsConstructor
public class BungeeListener implements Listener {
private final LPBungeePlugin plugin;
BungeeListener(LPBungeePlugin plugin) {
super(plugin);
this.plugin = plugin;
}
private final Set<UUID> deniedLogin = Collections.synchronizedSet(new HashSet<>());
@EventHandler
public void onPlayerPermissionCheck(PermissionCheckEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) {
e.setHasPermission(true);
return;
}
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return;
}
if (user.getUserData() == null) {
plugin.getLog().warn("Player " + player.getName() + " does not have any user data setup.");
return;
}
Contexts contexts = new Contexts(
plugin.getContextManager().getApplicableContext(player),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
e.setHasPermission(user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission()).asBoolean());
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.LOW)
public void onPlayerLogin(LoginEvent e) {
/* Delay the login here, as we want to cache UUID data before the player is connected to a backend bukkit server.
/* Called when the player first attempts a connection with the server.
Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins)
Delay the login here, as we want to cache UUID data before the player is connected to a backend bukkit server.
This means that a player will have the same UUID across the network, even if parts of the network are running in
Offline mode. */
e.registerIntent(plugin);
plugin.doAsync(() -> {
final long startTime = System.currentTimeMillis();
final UuidCache cache = plugin.getUuidCache();
final PendingConnection c = e.getConnection();
if (!plugin.getConfiguration().get(ConfigKeys.ONLINE_MODE)) {
// registers the plugins intent to modify this events state going forward.
// this will prevent the event from completing until we're finished handling.
e.registerIntent(plugin);
final long startTime = System.currentTimeMillis();
final PendingConnection c = e.getConnection();
/* either the plugin hasn't finished starting yet, or there was an issue connecting to the DB, performing file i/o, etc.
as this is bungeecord, we will still allow the login, as players can't really do much harm without permissions data.
the proxy will just fallback to using the config file perms. */
if (!plugin.getStorage().isAcceptingLogins()) {
// log that the user tried to login, but was denied at this stage.
deniedLogin.add(c.getUniqueId());
return;
}
/* another plugin (or the proxy itself) has cancelled this connection already */
if (e.isCancelled()) {
plugin.getLog().warn("Connection from " + c.getUniqueId() + " was already denied. No permissions data will be loaded.");
deniedLogin.add(c.getUniqueId());
return;
}
/* Actually process the login for the connection.
We do this here to delay the login until the data is ready.
If the login gets cancelled later on, then this will be cleaned up.
This includes:
- loading uuid data
- loading permissions
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
plugin.doAsync(() -> {
final UuidCache cache = plugin.getUuidCache();
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUIDS)) {
UUID uuid = plugin.getStorage().getUUID(c.getName()).join();
if (uuid != null) {
cache.addToCache(c.getUniqueId(), uuid);
@@ -105,6 +109,8 @@ public class BungeeListener extends AbstractListener implements Listener {
// No previous data for this player
plugin.getApiProvider().getEventFactory().handleUserFirstLogin(c.getUniqueId(), c.getName());
cache.addToCache(c.getUniqueId(), c.getUniqueId());
// Join this call, as we want this to be set for when the player connects to the backend.
plugin.getStorage().force().saveUUIDData(c.getName(), c.getUniqueId()).join();
}
} else {
@@ -114,12 +120,14 @@ public class BungeeListener extends AbstractListener implements Listener {
}
// Online mode, no cache needed. This is just for name -> uuid lookup.
// Again, join this call so the data is available for the backend.
plugin.getStorage().force().saveUUIDData(c.getName(), c.getUniqueId()).join();
}
// We have to make a new user on this thread whilst the connection is being held, or we get concurrency issues as the Bukkit server
// and the BungeeCord server try to make a new user at the same time.
/* We have to make a new user on this thread whilst the connection is being held, or we get concurrency issues
as the Bukkit server and the BungeeCord server try to make a new user at the same time. */
plugin.getStorage().force().loadUser(cache.getUUID(c.getUniqueId()), c.getName()).join();
User user = plugin.getUserManager().get(cache.getUUID(c.getUniqueId()));
if (user == null) {
plugin.getLog().warn("Failed to load user: " + c.getName());
@@ -144,7 +152,13 @@ public class BungeeListener extends AbstractListener implements Listener {
if (time >= 1000) {
plugin.getLog().warn("Processing login for " + c.getName() + " took " + time + "ms.");
}
// finally, complete out intent to modify state, so the proxy can continue handling the connection.
e.completeIntent(plugin);
// schedule a cleanup of the users data in a few seconds.
// this should cover the eventuality that the login fails.
plugin.getUserManager().scheduleUnload(c.getUniqueId());
});
}
@@ -154,12 +168,87 @@ public class BungeeListener extends AbstractListener implements Listener {
final User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId()));
if (user == null) {
plugin.getProxy().getScheduler().schedule(plugin, () -> player.sendMessage(WARN_MESSAGE), 3, TimeUnit.SECONDS);
plugin.getProxy().getScheduler().schedule(plugin, () -> {
if (!player.isConnected()) {
return;
}
player.sendMessage(new TextComponent(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
}, 3, TimeUnit.SECONDS);
}
}
@EventHandler
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerQuit(PlayerDisconnectEvent e) {
onLeave(e.getPlayer().getUniqueId());
// Request that the users data is unloaded.
plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerPermissionCheck(PermissionCheckEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) {
return;
}
final ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return;
}
UserData userData = user.getUserData();
if (userData == null) {
plugin.getLog().warn("Player " + player.getName() + " does not have any user data setup.");
plugin.doAsync(() -> user.setupData(false));
return;
}
Contexts contexts = new Contexts(
plugin.getContextManager().getApplicableContext(player),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
e.setHasPermission(userData.getPermissionData(contexts).getPermissionValue(e.getPermission()).asBoolean());
}
// We don't pre-process all servers, so we have to do it here.
@EventHandler(priority = EventPriority.LOWEST)
public void onServerSwitch(ServerConnectEvent e) {
String serverName = e.getTarget().getName();
UUID uuid = e.getPlayer().getUniqueId();
plugin.doAsync(() -> {
MutableContextSet set = MutableContextSet.create();
set.add("server", plugin.getConfiguration().get(ConfigKeys.SERVER));
set.add("world", serverName);
User user = plugin.getUserManager().get(plugin.getUuidCache().getUUID(uuid));
if (user == null) {
return;
}
UserData userData = user.getUserData();
if (userData == null) {
return;
}
Contexts contexts = new Contexts(
set.makeImmutable(),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
userData.preCalculate(contexts);
});
}
}
@@ -64,7 +64,7 @@ public class BungeeSenderFactory extends SenderFactory<CommandSender> {
@Override
protected void sendMessage(CommandSender sender, FancyMessage message) {
try {
sender.sendMessage(ComponentSerializer.parse(message.toJSONString()));
sender.sendMessage(ComponentSerializer.parse(message.exportToJson()));
} catch (Exception e) {
sendMessage(sender, message.toOldMessageFormat());
}
@@ -28,7 +28,6 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Logger;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.bungee.messaging.BungeeMessagingService;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.api.ApiProvider;
@@ -39,11 +38,10 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.contexts.ServerCalculator;
import me.lucko.luckperms.common.contexts.StaticCalculator;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.debug.DebugHandler;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
@@ -55,6 +53,7 @@ import me.lucko.luckperms.common.managers.impl.GenericGroupManager;
import me.lucko.luckperms.common.managers.impl.GenericTrackManager;
import me.lucko.luckperms.common.managers.impl.GenericUserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.LuckPermsScheduler;
@@ -66,14 +65,16 @@ import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
import me.lucko.luckperms.common.treeview.PermissionVault;
import me.lucko.luckperms.common.utils.BufferedRequest;
import me.lucko.luckperms.common.utils.FileWatcher;
import me.lucko.luckperms.common.utils.LoggerImpl;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import java.io.File;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -85,33 +86,41 @@ import java.util.stream.Collectors;
public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private final Set<UUID> ignoringLogs = ConcurrentHashMap.newKeySet();
private LuckPermsScheduler scheduler;
private CommandManager commandManager;
private LuckPermsConfiguration configuration;
private UserManager userManager;
private GroupManager groupManager;
private TrackManager trackManager;
private Storage storage;
private FileWatcher fileWatcher = null;
private InternalMessagingService messagingService = null;
private UuidCache uuidCache;
private ApiProvider apiProvider;
private Logger log;
private Importer importer;
private LocaleManager localeManager;
private CachedStateManager cachedStateManager;
private ContextManager<ProxiedPlayer> contextManager;
private CalculatorFactory calculatorFactory;
private BufferedRequest<Void> updateTaskBuffer;
private DebugHandler debugHandler;
private VerboseHandler verboseHandler;
private BungeeSenderFactory senderFactory;
private PermissionVault permissionVault;
@Override
public void onEnable() {
public void onLoad() {
// setup minimal functionality in order to load initial dependencies
scheduler = new LPBungeeScheduler(this);
localeManager = new NoopLocaleManager();
senderFactory = new BungeeSenderFactory(this);
log = new LoggerImpl(getConsoleSender());
DependencyManager.loadDependencies(this, Collections.singletonList(Dependency.CAFFEINE));
}
@Override
public void onEnable() {
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
debugHandler = new DebugHandler(scheduler.getAsyncExecutor(), getVersion());
verboseHandler = new VerboseHandler(scheduler.getAsyncExecutor(), getVersion());
permissionVault = new PermissionVault(scheduler.getAsyncExecutor());
getLog().info("Loading configuration...");
@@ -125,6 +134,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// register events
getProxy().getPluginManager().registerListener(this, new BungeeListener(this));
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
fileWatcher = new FileWatcher(this);
getScheduler().doAsyncRepeating(fileWatcher, 30L);
}
// initialise datastore
storage = StorageFactory.getInstance(this, StorageType.H2);
@@ -158,6 +172,10 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
@@ -181,7 +199,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// register commands
getLog().info("Registering commands...");
CommandManager commandManager = new CommandManager(this);
commandManager = new CommandManager(this);
getProxy().getPluginManager().registerCommand(this, new BungeeCommand(this, commandManager));
// disable the default Bungee /perms command so it gets handled by the Bukkit plugin
@@ -193,14 +211,13 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
userManager = new GenericUserManager(this);
groupManager = new GenericGroupManager(this);
trackManager = new GenericTrackManager(this);
importer = new Importer(commandManager);
calculatorFactory = new BungeeCalculatorFactory(this);
cachedStateManager = new CachedStateManager(this);
contextManager = new ContextManager<>();
BackendServerCalculator serverCalculator = new BackendServerCalculator();
contextManager.registerCalculator(serverCalculator);
contextManager.registerCalculator(new ServerCalculator<>(configuration));
contextManager.registerCalculator(new StaticCalculator<>(configuration));
// register with the LP API
getLog().info("Registering API...");
@@ -230,6 +247,10 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
getLog().info("Closing datastore...");
storage.shutdown();
if (fileWatcher != null) {
fileWatcher.close();
}
if (messagingService != null) {
getLog().info("Closing messaging service...");
messagingService.close();
@@ -311,7 +332,8 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
@Override
public boolean isPlayerOnline(UUID external) {
return getProxy().getPlayer(external) != null;
ProxiedPlayer player = getProxy().getPlayer(external);
return player != null && player.isConnected();
}
@Override
@@ -331,17 +353,6 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
Set<ContextSet> c = new HashSet<>();
c.add(ContextSet.empty());
c.add(ContextSet.singleton("server", getConfiguration().get(ConfigKeys.SERVER)));
c.addAll(getProxy().getServers().values().stream()
.map(ServerInfo::getName)
.map(s -> {
MutableContextSet set = MutableContextSet.create();
set.add("server", getConfiguration().get(ConfigKeys.SERVER));
set.add("world", s);
return set.makeImmutable();
})
.collect(Collectors.toList())
);
return c.stream()
.map(set -> new Contexts(
set,
@@ -354,9 +365,4 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
))
.collect(Collectors.toSet());
}
@Override
public UUID getUuidFromUsername(String playerName) {
return null; // Not needed on Bungee
}
}
@@ -22,13 +22,14 @@
package me.lucko.luckperms.bungee.migration;
import me.lucko.luckperms.api.MetaUtils;
import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.abstraction.SubCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.ProgressLogger;
@@ -63,53 +64,66 @@ public class MigrationBungeePerms extends SubCommand<Object> {
return CommandResult.STATE_ERROR;
}
List<Group> groups = bp.getPermissionsManager().getBackEnd().loadGroups();
log.log("Calculating group weightings.");
int maxWeight = 0;
for (Group group : groups) {
maxWeight = Math.max(maxWeight, group.getRank());
}
maxWeight += 5;
// Migrate all groups.
log.log("Starting group migration.");
AtomicInteger groupCount = new AtomicInteger(0);
for (Group g : bp.getPermissionsManager().getBackEnd().loadGroups()) {
for (Group g : groups) {
int groupWeight = maxWeight - g.getRank();
// Make a LuckPerms group for the one being migrated
plugin.getStorage().createAndLoadGroup(g.getName().toLowerCase(), CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(g.getName().toLowerCase());
String groupName = MigrationUtils.standardizeName(g.getName());
plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
me.lucko.luckperms.common.core.model.Group group = plugin.getGroupManager().getIfLoaded(groupName);
MigrationUtils.setGroupWeight(group, groupWeight);
// Migrate global perms
for (String perm : g.getPerms()) {
try {
group.setPermission(perm, true);
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
group.setPermission(MigrationUtils.parseNode(perm, true).build());
}
// Migrate per-server perms
for (Map.Entry<String, Server> e : g.getServers().entrySet()) {
for (String perm : e.getValue().getPerms()) {
try {
group.setPermission(perm, true, e.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
group.setPermission(MigrationUtils.parseNode(perm, true).setWorld(e.getKey()).build());
}
// Migrate per-world perms
for (Map.Entry<String, World> we : e.getValue().getWorlds().entrySet()) {
for (String perm : we.getValue().getPerms()) {
try {
group.setPermission(perm, true, e.getKey(), we.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
group.setPermission(MigrationUtils.parseNode(perm, true).setServer(e.getKey()).setWorld(we.getKey()).build());
}
}
}
// Migrate any parent groups
for (String inherit : g.getInheritances()) {
try {
group.setPermission("group." + inherit, true);
} catch (Exception ex) {
log.handleException(ex);
if (inherit.isEmpty()) {
continue;
}
group.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(inherit)));
}
// Migrate prefix and suffix
@@ -117,25 +131,13 @@ public class MigrationBungeePerms extends SubCommand<Object> {
String suffix = g.getSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
group.setPermission("prefix.50." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
group.setPermission(NodeFactory.makePrefixNode(groupWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
group.setPermission("suffix.50." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
group.setPermission(NodeFactory.makeSuffixNode(groupWeight, suffix).build());
}
plugin.getStorage().saveGroup(group);
log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet());
}
log.log("Migrated " + groupCount.get() + " groups");
@@ -143,6 +145,10 @@ public class MigrationBungeePerms extends SubCommand<Object> {
// Migrate all users.
log.log("Starting user migration.");
AtomicInteger userCount = new AtomicInteger(0);
// Increment the max weight from the group migrations. All user meta should override.
maxWeight += 5;
for (User u : bp.getPermissionsManager().getBackEnd().loadUsers()) {
if (u.getUUID() == null) {
log.logErr("Could not parse UUID for user: " + u.getName());
@@ -150,47 +156,47 @@ public class MigrationBungeePerms extends SubCommand<Object> {
}
// Make a LuckPerms user for the one being migrated.
plugin.getStorage().loadUser(u.getUUID(), "null").join();
plugin.getStorage().loadUser(u.getUUID(), u.getName()).join();
me.lucko.luckperms.common.core.model.User user = plugin.getUserManager().get(u.getUUID());
// Migrate global perms
for (String perm : u.getPerms()) {
try {
user.setPermission(perm, true);
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
user.setPermission(MigrationUtils.parseNode(perm, true).build());
}
// Migrate per-server perms
for (Map.Entry<String, Server> e : u.getServers().entrySet()) {
for (String perm : e.getValue().getPerms()) {
try {
user.setPermission(perm, true, e.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
user.setPermission(MigrationUtils.parseNode(perm, true).setWorld(e.getKey()).build());
}
// Migrate per-world perms
for (Map.Entry<String, World> we : e.getValue().getWorlds().entrySet()) {
for (String perm : we.getValue().getPerms()) {
try {
user.setPermission(perm, true, e.getKey(), we.getKey());
} catch (Exception ex) {
log.handleException(ex);
if (perm.isEmpty()) {
continue;
}
user.setPermission(MigrationUtils.parseNode(perm, true).setServer(e.getKey()).setWorld(we.getKey()).build());
}
}
}
// Migrate groups
for (String group : u.getGroupsString()) {
try {
user.setPermission("group." + group, true);
} catch (Exception ex) {
log.handleException(ex);
if (group.isEmpty()) {
continue;
}
user.setPermission(NodeFactory.make("group." + MigrationUtils.standardizeName(group)));
}
// Migrate prefix & suffix
@@ -198,21 +204,10 @@ public class MigrationBungeePerms extends SubCommand<Object> {
String suffix = u.getSuffix();
if (prefix != null && !prefix.equals("")) {
prefix = MetaUtils.escapeCharacters(prefix);
try {
user.setPermission("prefix.100." + prefix, true);
} catch (Exception ex) {
log.handleException(ex);
}
user.setPermission(NodeFactory.makePrefixNode(maxWeight, prefix).build());
}
if (suffix != null && !suffix.equals("")) {
suffix = MetaUtils.escapeCharacters(suffix);
try {
user.setPermission("suffix.100." + suffix, true);
} catch (Exception ex) {
log.handleException(ex);
}
user.setPermission(NodeFactory.makeSuffixNode(maxWeight, suffix).build());
}
plugin.getStorage().saveUser(user);
+45 -17
View File
@@ -26,22 +26,24 @@ apply-global-groups: true
# If users on this server should have global (non-world specific) groups applied
apply-global-world-groups: true
# If this server is in offline or online mode.
# This setting allows a player to have the same UUID across a network of offline mode/mixed servers.
# If UUIDs should be pulled from the server, or looked up by username based upon previous connections.
#
# You should generally reflect the setting in server.properties here. Except when...
# This option should be set to true in most cases. When set to false, in order to get a player's UUID, LuckPerms will:
# 1. Check if a player with the given username has joined before, if they have, use the UUID they used on their previous login.
# 2. Save and return the players "current" uuid.
#
# 1. You have Spigot servers connected to a BungeeCord proxy, with online-mode set to false, but 'bungeecord' set to true in the spigot.yml
# AND 'ip-forward' set to true in the BungeeCord config.yml
# In this case, set online-mode in LuckPerms to true, despite the server being in offline mode.
# For offline mode (cracked) servers, a players UUID is generated based upon their username.
#
# 2. You are only running one server instance using LuckPerms, (not a network)
# In this case, set online-mode to true no matter what is set in server.properties. (we can just fallback to the servers uuid cache)
# IMPORTANT:
# If you are using BungeeCord proxy running in online mode, it is important that "online-mode=false" is set all backend server.properties, but
# "bungeecord: true" is set in the spigot.yml. You also need to set "ip_forward: true" in BungeeCord's config.yml.
# If for whatever reason you are not able to do this, and do not have ip-forward enabled, then you may need to set "use-server-uuids" to false.
#
# 3. If your proxy is running in offline mode, and you are using PaperSpigot (https://ci.destroystokyo.com/job/PaperSpigot/),
# you should set "bungee-online-mode" to false in the paper.yml, and set "online-mode" to true in all LuckPerms configs.
# This approach is thoroughly recommended for offline mode networks.
online-mode: true
# If your proxy is running in offline mode, you should still be setting up ip-forwarding as described above, but may also find that you need to set
# "bungee-online-mode" to false in paper.yml, if you are using PaperSpigot. (https://ci.destroystokyo.com/job/PaperSpigot/)
#
# This option should always be set to true on the BungeeCord side. Don't change unless you're sure you know what you're doing.
use-server-uuids: true
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
@@ -64,6 +66,14 @@ group-name-rewrite:
# If "deny": the command will just fail if you try to add another node with the same expiry
temporary-add-behaviour: deny
# How should LuckPerms determine a users "primary" group.
#
# Available Options:
# -> stored use the value stored against the users record in the file/database
# -> parents-by-weight just use the users most highly weighted parent
# -> all-parents-by-weight same as above, but calculates based upon all parents inherits from both directly and indirectly
primary-group-calculation: stored
@@ -130,10 +140,20 @@ meta-formatting:
# +------------------------------------------------------------------------+ #
# Which storage method the plugin should use.
# Currently supported: mysql, postgresql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL, PostgreSQL or MongoDB
#
# See: https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type
# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, mongodb
# Fill out connection info below if you're using MySQL, MariaDB, PostgreSQL or MongoDB
# If your MySQL server supports it, the "mariadb" option is preferred over "mysql".
storage-method: h2
# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then schedule automatic
# updates when changes are detected.
#
# If you don't want this to happen, set this option to false.
watch-files: true
# This block enables support for split datastores.
split-storage:
enabled: false
@@ -159,9 +179,14 @@ data:
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
table_prefix: 'luckperms_'
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3
# This option controls how frequently LuckPerms will perform a sync task.
# A sync task will refresh all data from the storage, and ensure that the most up-to-date data is being used by the plugin.
#
# This is disabled by default, as most users will not need it. However, if you're using a remote storage type
# without a messaging service setup, you may wish to set this value to something like 3.
#
# Set to -1 to disable the task completely.
sync-minutes: -1
# Settings for the messaging service
#
@@ -178,6 +203,9 @@ data:
# none ==> nothing
messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
-1
View File
@@ -3,4 +3,3 @@ version: ${release.version}.${patch.version}
description: A permissions plugin
author: Luck
main: me.lucko.luckperms.bungee.LPBungeePlugin
softdepends: [PowerfulPerms] # For migration
+15 -5
View File
@@ -5,17 +5,20 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.0-SNAPSHOT</version>
<version>3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>luckperms-common</artifactId>
<packaging>jar</packaging>
<build>
<finalName>LuckPerms-Common-${release.version}.${patch.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>${compiler.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -36,7 +39,7 @@
<dependency>
<groupId>io.github.mkremins</groupId>
<artifactId>fanciful</artifactId>
<version>1.2.0</version>
<version>1.2.5</version>
<scope>compile</scope>
</dependency>
<!-- MySQL -->
@@ -50,7 +53,7 @@
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.5.1</version>
<version>2.6.1</version>
<scope>provided</scope>
</dependency>
<!-- Jedis -->
@@ -116,11 +119,18 @@
<version>19.0</version>
<scope>provided</scope>
</dependency>
<!-- caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.4.0</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -42,10 +42,11 @@ import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.api.delegates.NodeFactoryDelegate;
import me.lucko.luckperms.common.api.delegates.UserDelegate;
import me.lucko.luckperms.common.core.NodeBuilder;
import me.lucko.luckperms.common.core.UserIdentifier;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.event.LuckPermsEventBus;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.Optional;
@@ -78,7 +79,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public double getApiVersion() {
return 3.0;
return 3.1;
}
@Override
@@ -103,7 +104,8 @@ public class ApiProvider implements LuckPermsApi {
@Override
public Optional<MessagingService> getMessagingService() {
return Optional.ofNullable(plugin.getMessagingService());
InternalMessagingService service = plugin.getMessagingService();
return service instanceof NoopMessagingService ? Optional.empty() : Optional.of(service);
}
@Override
@@ -150,8 +152,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public void cleanupUser(@NonNull User user) {
ApiUtils.checkUser(user);
plugin.getUserManager().cleanup(((UserDelegate) user).getMaster());
plugin.getUserManager().cleanup(UserDelegate.cast(user));
}
@Override
@@ -203,7 +204,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public Node.Builder buildNode(@NonNull String permission) throws IllegalArgumentException {
return new NodeBuilder(permission);
return me.lucko.luckperms.common.core.NodeFactory.newBuilder(permission);
}
@SuppressWarnings("unchecked")
@@ -214,8 +215,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public Optional<Contexts> getContextForUser(User user) {
ApiUtils.checkUser(user);
return Optional.ofNullable(plugin.getContextForUser(((UserDelegate) user).getMaster()));
return Optional.ofNullable(plugin.getContextForUser(UserDelegate.cast(user)));
}
@SuppressWarnings("unchecked")
@@ -26,29 +26,11 @@ import lombok.experimental.UtilityClass;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.Track;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.common.api.delegates.GroupDelegate;
import me.lucko.luckperms.common.api.delegates.TrackDelegate;
import me.lucko.luckperms.common.api.delegates.UserDelegate;
import me.lucko.luckperms.common.utils.ArgumentChecker;
@UtilityClass
public class ApiUtils {
public static void checkUser(User user) {
Preconditions.checkState(user instanceof UserDelegate, "User instance cannot be handled by this implementation.");
}
public static void checkGroup(Group group) {
Preconditions.checkState(group instanceof GroupDelegate, "Group instance cannot be handled by this implementation.");
}
public static void checkTrack(Track track) {
Preconditions.checkState(track instanceof TrackDelegate, "Track instance cannot be handled by this implementation.");
}
public static String checkUsername(String s) {
Preconditions.checkArgument(
!ArgumentChecker.checkUsername(s),
@@ -23,150 +23,166 @@
package me.lucko.luckperms.common.api.delegates;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.Collectors;
import static me.lucko.luckperms.common.api.ApiUtils.checkGroup;
import static me.lucko.luckperms.common.api.ApiUtils.checkTime;
/**
* Provides a link between {@link Group} and {@link me.lucko.luckperms.common.core.model.Group}
*/
@EqualsAndHashCode(of = {"name"}, callSuper = false)
public class GroupDelegate extends PermissionHolderDelegate implements Group {
public final class GroupDelegate extends PermissionHolderDelegate implements Group {
public static me.lucko.luckperms.common.core.model.Group cast(Group g) {
Preconditions.checkState(g instanceof GroupDelegate, "Illegal instance " + g.getClass() + " cannot be handled by this implementation.");
return ((GroupDelegate) g).getHandle();
}
@Getter(AccessLevel.PACKAGE)
private final me.lucko.luckperms.common.core.model.Group master;
private final me.lucko.luckperms.common.core.model.Group handle;
@Getter
private final String name;
public GroupDelegate(@NonNull me.lucko.luckperms.common.core.model.Group handle) {
super(handle);
this.handle = handle;
}
public GroupDelegate(@NonNull me.lucko.luckperms.common.core.model.Group master) {
super(master);
this.master = master;
this.name = master.getName();
@Override
public String getName() {
return handle.getName();
}
@Override
public boolean inheritsGroup(@NonNull Group group) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster());
return handle.inheritsGroup(cast(group));
}
@Override
public boolean inheritsGroup(@NonNull Group group, @NonNull String server) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster(), server);
return handle.inheritsGroup(((GroupDelegate) group).getHandle(), server);
}
@Override
public boolean inheritsGroup(@NonNull Group group, @NonNull String server, @NonNull String world) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster(), server, world);
return handle.inheritsGroup(cast(group), server, world);
}
@Override
public void setInheritGroup(@NonNull Group group) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster());
handle.setPermission(NodeFactory.make(cast(group))).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server);
handle.setPermission(NodeFactory.make(cast(group), server)).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, world);
handle.setPermission(NodeFactory.make(cast(group), server, world)).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), checkTime(expireAt));
handle.setPermission(NodeFactory.make(cast(group), checkTime(expireAt))).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, checkTime(expireAt));
handle.setPermission(NodeFactory.make(cast(group), server, checkTime(expireAt))).throwException();
}
@Override
public void setInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, world, checkTime(expireAt));
handle.setPermission(NodeFactory.make(cast(group), server, world, checkTime(expireAt))).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster());
handle.unsetPermission(NodeFactory.make(cast(group))).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), temporary);
handle.unsetPermission(NodeFactory.make(cast(group), temporary)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server);
handle.unsetPermission(NodeFactory.make(cast(group), server)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, world);
handle.unsetPermission(NodeFactory.make(cast(group), server, world)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, temporary);
handle.unsetPermission(NodeFactory.make(cast(group), server, temporary)).throwException();
}
@Override
public void unsetInheritGroup(@NonNull Group group, @NonNull String server, @NonNull String world, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, world, temporary);
handle.unsetPermission(NodeFactory.make(cast(group), server, world, temporary)).throwException();
}
@Override
public void clearNodes() {
master.clearNodes();
handle.clearNodes();
}
@Override
public List<String> getGroupNames() {
return master.getGroupNames();
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.map(Node::getGroupName)
.collect(Collectors.toList());
}
@Override
public List<String> getLocalGroups(@NonNull String server, @NonNull String world) {
return master.getLocalGroups(server, world);
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnWorld(world, false, true))
.filter(n -> n.shouldApplyOnServer(server, false, true))
.map(Node::getGroupName)
.collect(Collectors.toList());
}
@Override
public OptionalInt getWeight() {
return master.getWeight();
return handle.getWeight();
}
@Override
public List<String> getLocalGroups(@NonNull String server) {
return master.getLocalGroups(server);
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnServer(server, false, true))
.map(Node::getGroupName)
.collect(Collectors.toList());
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof GroupDelegate)) return false;
GroupDelegate other = (GroupDelegate) o;
return this.getName() == null ? other.getName() == null : this.getName().equals(other.getName());
}
public int hashCode() {
return this.getName().hashCode();
}
}
@@ -36,105 +36,105 @@ import java.util.Map;
*/
@AllArgsConstructor
public class LPConfigurationDelegate implements LPConfiguration {
private final LuckPermsConfiguration master;
private final LuckPermsConfiguration handle;
@Override
public String getServer() {
return master.get(ConfigKeys.SERVER);
return handle.get(ConfigKeys.SERVER);
}
@Override
public int getSyncTime() {
return master.get(ConfigKeys.SYNC_TIME);
return handle.get(ConfigKeys.SYNC_TIME);
}
@Override
public boolean getIncludeGlobalPerms() {
return master.get(ConfigKeys.INCLUDING_GLOBAL_PERMS);
return handle.get(ConfigKeys.INCLUDING_GLOBAL_PERMS);
}
@Override
public boolean getIncludeGlobalWorldPerms() {
return master.get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS);
return handle.get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS);
}
@Override
public boolean getApplyGlobalGroups() {
return master.get(ConfigKeys.APPLYING_GLOBAL_GROUPS);
return handle.get(ConfigKeys.APPLYING_GLOBAL_GROUPS);
}
@Override
public boolean getApplyGlobalWorldGroups() {
return master.get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS);
return handle.get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS);
}
@Override
public boolean getOnlineMode() {
return master.get(ConfigKeys.ONLINE_MODE);
return handle.get(ConfigKeys.USE_SERVER_UUIDS);
}
@Override
public boolean getApplyWildcards() {
return master.get(ConfigKeys.APPLYING_WILDCARDS);
return handle.get(ConfigKeys.APPLYING_WILDCARDS);
}
@Override
public boolean getApplyRegex() {
return master.get(ConfigKeys.APPLYING_REGEX);
return handle.get(ConfigKeys.APPLYING_REGEX);
}
@Override
public boolean getApplyShorthand() {
return master.get(ConfigKeys.APPLYING_SHORTHAND);
return handle.get(ConfigKeys.APPLYING_SHORTHAND);
}
@Override
public boolean getLogNotify() {
return master.get(ConfigKeys.LOG_NOTIFY);
return handle.get(ConfigKeys.LOG_NOTIFY);
}
@Override
public boolean getEnableOps() {
return master.get(ConfigKeys.OPS_ENABLED);
return handle.get(ConfigKeys.OPS_ENABLED);
}
@Override
public boolean getCommandsAllowOp() {
return master.get(ConfigKeys.COMMANDS_ALLOW_OP);
return handle.get(ConfigKeys.COMMANDS_ALLOW_OP);
}
@Override
public boolean getAutoOp() {
return master.get(ConfigKeys.AUTO_OP);
return handle.get(ConfigKeys.AUTO_OP);
}
@Override
public String getVaultServer() {
return master.get(ConfigKeys.VAULT_SERVER);
return handle.get(ConfigKeys.VAULT_SERVER);
}
@Override
public boolean getVaultIncludeGlobal() {
return master.get(ConfigKeys.VAULT_INCLUDING_GLOBAL);
return handle.get(ConfigKeys.VAULT_INCLUDING_GLOBAL);
}
@Override
public DatastoreConfiguration getDatastoreConfig() {
return master.get(ConfigKeys.DATABASE_VALUES);
return handle.get(ConfigKeys.DATABASE_VALUES);
}
@Override
public String getStorageMethod() {
return master.get(ConfigKeys.STORAGE_METHOD);
return handle.get(ConfigKeys.STORAGE_METHOD);
}
@Override
public boolean getSplitStorage() {
return master.get(ConfigKeys.SPLIT_STORAGE);
return handle.get(ConfigKeys.SPLIT_STORAGE);
}
@Override
public Map<String, String> getSplitStorageOptions() {
return master.get(ConfigKeys.SPLIT_STORAGE_OPTIONS);
return handle.get(ConfigKeys.SPLIT_STORAGE_OPTIONS);
}
}
@@ -39,100 +39,100 @@ import static me.lucko.luckperms.common.api.ApiUtils.checkName;
*/
@AllArgsConstructor
public class LogDelegate implements Log {
private final me.lucko.luckperms.common.data.Log master;
private final me.lucko.luckperms.common.data.Log handle;
@Override
public SortedSet<LogEntry> getContent() {
return master.getContent();
return handle.getContent();
}
@Override
public SortedSet<LogEntry> getRecent() {
return master.getRecent();
return handle.getRecent();
}
@Override
public SortedMap<Integer, LogEntry> getRecent(int pageNo) {
return master.getRecent(pageNo);
return handle.getRecent(pageNo);
}
@Override
public int getRecentMaxPages() {
return master.getRecentMaxPages();
return handle.getRecentMaxPages();
}
@Override
public SortedSet<LogEntry> getRecent(@NonNull UUID actor) {
return master.getRecent(actor);
return handle.getRecent(actor);
}
@Override
public SortedMap<Integer, LogEntry> getRecent(int pageNo, @NonNull UUID actor) {
return master.getRecent(pageNo, actor);
return handle.getRecent(pageNo, actor);
}
@Override
public int getRecentMaxPages(@NonNull UUID actor) {
return master.getRecentMaxPages(actor);
return handle.getRecentMaxPages(actor);
}
@Override
public SortedSet<LogEntry> getUserHistory(@NonNull UUID uuid) {
return master.getUserHistory(uuid);
return handle.getUserHistory(uuid);
}
@Override
public SortedMap<Integer, LogEntry> getUserHistory(int pageNo, @NonNull UUID uuid) {
return master.getUserHistory(pageNo, uuid);
return handle.getUserHistory(pageNo, uuid);
}
@Override
public int getUserHistoryMaxPages(@NonNull UUID uuid) {
return master.getUserHistoryMaxPages(uuid);
return handle.getUserHistoryMaxPages(uuid);
}
@Override
public SortedSet<LogEntry> getGroupHistory(@NonNull String name) {
return master.getGroupHistory(checkName(name));
return handle.getGroupHistory(checkName(name));
}
@Override
public SortedMap<Integer, LogEntry> getGroupHistory(int pageNo, @NonNull String name) {
return master.getGroupHistory(pageNo, checkName(name));
return handle.getGroupHistory(pageNo, checkName(name));
}
@Override
public int getGroupHistoryMaxPages(@NonNull String name) {
return master.getGroupHistoryMaxPages(checkName(name));
return handle.getGroupHistoryMaxPages(checkName(name));
}
@Override
public SortedSet<LogEntry> getTrackHistory(@NonNull String name) {
return master.getTrackHistory(checkName(name));
return handle.getTrackHistory(checkName(name));
}
@Override
public SortedMap<Integer, LogEntry> getTrackHistory(int pageNo, @NonNull String name) {
return master.getTrackHistory(pageNo, checkName(name));
return handle.getTrackHistory(pageNo, checkName(name));
}
@Override
public int getTrackHistoryMaxPages(@NonNull String name) {
return master.getTrackHistoryMaxPages(checkName(name));
return handle.getTrackHistoryMaxPages(checkName(name));
}
@Override
public SortedSet<LogEntry> getSearch(@NonNull String query) {
return master.getSearch(query);
return handle.getSearch(query);
}
@Override
public SortedMap<Integer, LogEntry> getSearch(int pageNo, @NonNull String query) {
return master.getSearch(pageNo, query);
return handle.getSearch(pageNo, query);
}
@Override
public int getSearchMaxPages(@NonNull String query) {
return master.getSearchMaxPages(query);
return handle.getSearchMaxPages(query);
}
}
@@ -24,6 +24,7 @@ package me.lucko.luckperms.common.api.delegates;
import lombok.NonNull;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.core.NodeFactory;
@@ -32,7 +33,7 @@ public class NodeFactoryDelegate implements me.lucko.luckperms.api.NodeFactory {
@Override
public Node fromSerialisedNode(@NonNull String serialisedPermission, boolean value) {
return NodeFactory.fromSerialisedNode(serialisedPermission, value);
return NodeFactory.fromSerializedNode(serialisedPermission, value);
}
@Override
@@ -47,7 +48,12 @@ public class NodeFactoryDelegate implements me.lucko.luckperms.api.NodeFactory {
@Override
public Node.Builder newBuilderFromSerialisedNode(@NonNull String serialisedPermission, boolean value) {
return NodeFactory.builderFromSerialisedNode(serialisedPermission, value);
return NodeFactory.builderFromSerializedNode(serialisedPermission, value);
}
@Override
public Node.Builder makeGroupNode(Group group) {
return NodeFactory.newBuilder("group." + GroupDelegate.cast(group).getName());
}
@Override
@@ -29,10 +29,13 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.PermissionHolder;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.common.utils.ExtractedContexts;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
@@ -49,269 +52,335 @@ import static me.lucko.luckperms.common.api.ApiUtils.checkTime;
/**
* Provides a link between {@link PermissionHolder} and {@link me.lucko.luckperms.common.core.model.PermissionHolder}
*/
@SuppressWarnings("unused")
@AllArgsConstructor
public class PermissionHolderDelegate implements PermissionHolder {
private final me.lucko.luckperms.common.core.model.PermissionHolder master;
private final me.lucko.luckperms.common.core.model.PermissionHolder handle;
@Override
public String getObjectName() {
return master.getObjectName();
return handle.getObjectName();
}
@Override
public SortedSet<? extends Node> getPermissions() {
return ImmutableSortedSet.copyOfSorted(master.getPermissions(false));
return ImmutableSortedSet.copyOfSorted(handle.mergePermissionsToSortedSet());
}
@Override
public Set<Node> getEnduringPermissions() {
return ImmutableSet.copyOf(master.getNodes());
return ImmutableSet.copyOf(handle.getNodes().values());
}
@Override
public Set<Node> getTransientPermissions() {
return ImmutableSet.copyOf(master.getTransientNodes());
return ImmutableSet.copyOf(handle.getTransientNodes().values());
}
@Override
public SortedSet<LocalizedNode> getAllNodes(@NonNull Contexts contexts) {
return new TreeSet<>(master.getAllNodes(null, ExtractedContexts.generate(contexts)));
return new TreeSet<>(handle.resolveInheritancesAlmostEqual(ExtractedContexts.generate(contexts)));
}
@Override
public Set<LocalizedNode> getAllNodesFiltered(@NonNull Contexts contexts) {
return new HashSet<>(master.getAllNodesFiltered(ExtractedContexts.generate(contexts)));
return new HashSet<>(handle.getAllNodes(ExtractedContexts.generate(contexts)));
}
@Override
public Map<String, Boolean> exportNodes(Contexts contexts, boolean lowerCase) {
return new HashMap<>(master.exportNodes(contexts, lowerCase));
return new HashMap<>(handle.exportNodes(ExtractedContexts.generate(contexts), lowerCase));
}
@Override
public Tristate hasPermission(@NonNull Node node) {
return master.hasPermission(node, false);
return handle.hasPermission(node, false);
}
@Override
public Tristate hasTransientPermission(@NonNull Node node) {
return master.hasPermission(node, true);
return handle.hasPermission(node, true);
}
@Override
public boolean hasPermission(@NonNull String node, @NonNull boolean b) {
return master.hasPermission(node, b);
return handle.hasPermission(node, b);
}
@Override
public boolean hasPermission(@NonNull String node, @NonNull boolean b, @NonNull String server) {
return master.hasPermission(node, b, server);
return handle.hasPermission(node, b, server);
}
@Override
public boolean hasPermission(@NonNull String node, @NonNull boolean b, @NonNull String server, @NonNull String world) {
return master.hasPermission(node, b, server, world);
return handle.hasPermission(node, b, server, world);
}
@Override
public boolean hasPermission(@NonNull String node, @NonNull boolean b, @NonNull boolean temporary) {
return master.hasPermission(node, b, temporary);
return handle.hasPermission(node, b, temporary);
}
@Override
public boolean hasPermission(@NonNull String node, @NonNull boolean b, @NonNull String server, @NonNull boolean temporary) {
return master.hasPermission(node, b, server, temporary);
return handle.hasPermission(node, b, server, temporary);
}
@Override
public boolean hasPermission(@NonNull String node, @NonNull boolean b, @NonNull String server, @NonNull String world, @NonNull boolean temporary) {
return master.hasPermission(node, b, server, world, temporary);
return handle.hasPermission(node, b, server, world, temporary);
}
@Override
public Tristate inheritsPermission(@NonNull Node node) {
return master.inheritsPermission(node);
return handle.inheritsPermission(node);
}
@Override
public boolean inheritsPermission(@NonNull String node, @NonNull boolean b) {
return master.inheritsPermission(node, b);
return handle.inheritsPermission(node, b);
}
@Override
public boolean inheritsPermission(@NonNull String node, @NonNull boolean b, @NonNull String server) {
return master.inheritsPermission(node, b, server);
return handle.inheritsPermission(node, b, server);
}
@Override
public boolean inheritsPermission(@NonNull String node, @NonNull boolean b, @NonNull String server, @NonNull String world) {
return master.inheritsPermission(node, b, server, world);
return handle.inheritsPermission(node, b, server, world);
}
@Override
public boolean inheritsPermission(@NonNull String node, @NonNull boolean b, @NonNull boolean temporary) {
return master.inheritsPermission(node, b, temporary);
return handle.inheritsPermission(node, b, temporary);
}
@Override
public boolean inheritsPermission(@NonNull String node, @NonNull boolean b, @NonNull String server, @NonNull boolean temporary) {
return master.inheritsPermission(node, b, server, temporary);
return handle.inheritsPermission(node, b, server, temporary);
}
@Override
public boolean inheritsPermission(@NonNull String node, @NonNull boolean b, @NonNull String server, @NonNull String world, @NonNull boolean temporary) {
return master.inheritsPermission(node, b, server, world, temporary);
return handle.inheritsPermission(node, b, server, world, temporary);
}
@Override
public void setPermission(@NonNull Node node) throws ObjectAlreadyHasException {
master.setPermission(node);
handle.setPermission(node).throwException();
}
@Override
public DataMutateResult setPermissionUnchecked(Node node) {
return handle.setPermission(node);
}
@Override
public void setTransientPermission(@NonNull Node node) throws ObjectAlreadyHasException {
master.setTransientPermission(node);
handle.setTransientPermission(node).throwException();
}
@Override
public DataMutateResult setTransientPermissionUnchecked(Node node) {
return handle.setTransientPermission(node);
}
@Override
public void setPermission(@NonNull String node, @NonNull boolean value) throws ObjectAlreadyHasException {
master.setPermission(node, value);
handle.setPermission(NodeFactory.make(node, value)).throwException();
}
@Override
public void setPermission(@NonNull String node, @NonNull boolean value, @NonNull String server) throws ObjectAlreadyHasException {
master.setPermission(node, value, server);
handle.setPermission(NodeFactory.make(node, value, server)).throwException();
}
@Override
public void setPermission(@NonNull String node, @NonNull boolean value, @NonNull String server, @NonNull String world) throws ObjectAlreadyHasException {
master.setPermission(node, value, server, world);
handle.setPermission(NodeFactory.make(node, value, server, world)).throwException();
}
@Override
public void setPermission(@NonNull String node, @NonNull boolean value, @NonNull long expireAt) throws ObjectAlreadyHasException {
master.setPermission(node, value, checkTime(expireAt));
handle.setPermission(NodeFactory.make(node, value, checkTime(expireAt))).throwException();
}
@Override
public void setPermission(@NonNull String node, @NonNull boolean value, @NonNull String server, @NonNull long expireAt) throws ObjectAlreadyHasException {
master.setPermission(node, value, server, checkTime(expireAt));
handle.setPermission(NodeFactory.make(node, value, server, checkTime(expireAt))).throwException();
}
@Override
public void setPermission(@NonNull String node, @NonNull boolean value, @NonNull String server, @NonNull String world, @NonNull long expireAt) throws ObjectAlreadyHasException {
master.setPermission(node, value, server, world, checkTime(expireAt));
handle.setPermission(NodeFactory.make(node, value, server, world, checkTime(expireAt))).throwException();
}
@Override
public void unsetPermission(@NonNull Node node) throws ObjectLacksException {
master.unsetPermission(node);
handle.unsetPermission(node).throwException();
}
@Override
public DataMutateResult unsetPermissionUnchecked(Node node) {
return handle.unsetPermission(node);
}
@Override
public void unsetTransientPermission(@NonNull Node node) throws ObjectLacksException {
master.unsetTransientPermission(node);
handle.unsetTransientPermission(node).throwException();
}
@Override
public DataMutateResult unsetTransientPermissionUnchecked(Node node) {
return handle.unsetTransientPermission(node);
}
@Override
public void unsetPermission(@NonNull String node, @NonNull boolean temporary) throws ObjectLacksException {
master.unsetPermission(node, temporary);
handle.unsetPermission(NodeFactory.make(node, temporary)).throwException();
}
@Override
public void unsetPermission(@NonNull String node) throws ObjectLacksException {
master.unsetPermission(node);
handle.unsetPermission(NodeFactory.make(node)).throwException();
}
@Override
public void unsetPermission(@NonNull String node, @NonNull String server) throws ObjectLacksException {
master.unsetPermission(node, server);
handle.unsetPermission(NodeFactory.make(node, server)).throwException();
}
@Override
public void unsetPermission(@NonNull String node, @NonNull String server, @NonNull String world) throws ObjectLacksException {
master.unsetPermission(node, server, world);
handle.unsetPermission(NodeFactory.make(node, server, world)).throwException();
}
@Override
public void unsetPermission(@NonNull String node, @NonNull String server, @NonNull boolean temporary) throws ObjectLacksException {
master.unsetPermission(node, server, temporary);
handle.unsetPermission(NodeFactory.make(node, server, temporary)).throwException();
}
@Override
public void unsetPermission(@NonNull String node, @NonNull String server, @NonNull String world, @NonNull boolean temporary) throws ObjectLacksException {
master.unsetPermission(node, server, world, temporary);
handle.unsetPermission(NodeFactory.make(node, server, world, temporary)).throwException();
}
@Override
public void clearNodes() {
master.clearNodes();
handle.clearNodes();
}
@Override
public void clearNodes(String server) {
master.clearNodes(server);
MutableContextSet set = new MutableContextSet();
if (server != null) {
set.add("server", server);
}
handle.clearNodes(set);
}
@Override
public void clearNodes(String server, String world) {
master.clearNodes(server, world);
MutableContextSet set = new MutableContextSet();
if (server != null) {
set.add("server", server);
}
if (world != null) {
set.add("world", world);
}
handle.clearNodes(set);
}
@Override
public void clearParents() {
master.clearParents();
handle.clearParents(true);
}
@Override
public void clearParents(String server) {
master.clearParents(server);
MutableContextSet set = new MutableContextSet();
if (server != null) {
set.add("server", server);
}
handle.clearParents(set, true);
}
@Override
public void clearParents(String server, String world) {
master.clearParents(server, world);
MutableContextSet set = new MutableContextSet();
if (server != null) {
set.add("server", server);
}
if (world != null) {
set.add("world", world);
}
handle.clearParents(set, true);
}
@Override
public void clearMeta() {
master.clearMeta();
handle.clearMeta();
}
@Override
public void clearMeta(String server) {
master.clearMeta(server);
MutableContextSet set = new MutableContextSet();
if (server != null) {
set.add("server", server);
}
handle.clearMeta(set);
}
@Override
public void clearMeta(String server, String world) {
master.clearMeta(server, world);
MutableContextSet set = new MutableContextSet();
if (server != null) {
set.add("server", server);
}
if (world != null) {
set.add("world", world);
}
handle.clearMeta(set);
}
@Override
public void clearMetaKeys(String key, String server, String world, boolean temporary) {
master.clearMetaKeys(key, server, world, temporary);
MutableContextSet set = new MutableContextSet();
if (server != null) {
set.add("server", server);
}
if (world != null) {
set.add("world", world);
}
handle.clearMetaKeys(key, set, temporary);
}
@Override
public void clearTransientNodes() {
master.clearTransientNodes();
handle.clearTransientNodes();
}
@Override
public Set<Node> getTemporaryPermissionNodes() {
return master.getTemporaryNodes();
return handle.getTemporaryNodes();
}
@Override
public Set<Node> getPermanentPermissionNodes() {
return master.getPermanentNodes();
return handle.getPermanentNodes();
}
@Override
public void auditTemporaryPermissions() {
master.auditTemporaryPermissions();
handle.auditTemporaryPermissions();
}
}
@@ -43,10 +43,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import static me.lucko.luckperms.common.api.ApiUtils.checkGroup;
import static me.lucko.luckperms.common.api.ApiUtils.checkName;
import static me.lucko.luckperms.common.api.ApiUtils.checkTrack;
import static me.lucko.luckperms.common.api.ApiUtils.checkUser;
import static me.lucko.luckperms.common.api.ApiUtils.checkUsername;
/**
@@ -55,16 +52,16 @@ import static me.lucko.luckperms.common.api.ApiUtils.checkUsername;
@AllArgsConstructor
public class StorageDelegate implements Storage {
private final LuckPermsPlugin plugin;
private final me.lucko.luckperms.common.storage.Storage master;
private final me.lucko.luckperms.common.storage.Storage handle;
@Override
public String getName() {
return master.getName();
return handle.getName();
}
@Override
public boolean isAcceptingLogins() {
return master.isAcceptingLogins();
return handle.isAcceptingLogins();
}
@Override
@@ -79,114 +76,109 @@ public class StorageDelegate implements Storage {
@Override
public CompletableFuture<Boolean> logAction(@NonNull LogEntry entry) {
return master.force().logAction(entry);
return handle.force().logAction(entry);
}
@Override
public CompletableFuture<Log> getLog() {
return master.force().getLog().thenApply(log -> log == null ? null : new LogDelegate(log));
return handle.force().getLog().thenApply(log -> log == null ? null : new LogDelegate(log));
}
@Override
public CompletableFuture<Boolean> loadUser(UUID uuid, String username) {
return master.force().loadUser(uuid, checkUsername(username));
return handle.force().loadUser(uuid, checkUsername(username));
}
@Override
public CompletableFuture<Boolean> saveUser(User user) {
checkUser(user);
return master.force().saveUser(((UserDelegate) user).getMaster());
return handle.force().saveUser(UserDelegate.cast(user));
}
@Override
public CompletableFuture<Boolean> cleanupUsers() {
return master.force().cleanupUsers();
return handle.force().cleanupUsers();
}
@Override
public CompletableFuture<Set<UUID>> getUniqueUsers() {
return master.force().getUniqueUsers();
return handle.force().getUniqueUsers();
}
@Override
public CompletableFuture<List<HeldPermission<UUID>>> getUsersWithPermission(@NonNull String permission) {
return master.force().getUsersWithPermission(permission);
return handle.force().getUsersWithPermission(permission);
}
@Override
public CompletableFuture<Boolean> createAndLoadGroup(String name) {
return master.force().createAndLoadGroup(checkName(name), CreationCause.API);
return handle.force().createAndLoadGroup(checkName(name), CreationCause.API);
}
@Override
public CompletableFuture<Boolean> loadGroup(String name) {
return master.force().loadGroup(checkName(name));
return handle.force().loadGroup(checkName(name));
}
@Override
public CompletableFuture<Boolean> loadAllGroups() {
return master.force().loadAllGroups();
return handle.force().loadAllGroups();
}
@Override
public CompletableFuture<Boolean> saveGroup(Group group) {
checkGroup(group);
return master.force().saveGroup(((GroupDelegate) group).getMaster());
return handle.force().saveGroup(GroupDelegate.cast(group));
}
@Override
public CompletableFuture<Boolean> deleteGroup(Group group) {
checkGroup(group);
if (group.getName().equalsIgnoreCase(plugin.getConfiguration().get(ConfigKeys.DEFAULT_GROUP_NAME))) {
throw new IllegalArgumentException("Cannot delete the default group.");
}
return master.force().deleteGroup(((GroupDelegate) group).getMaster(), DeletionCause.API);
return handle.force().deleteGroup(GroupDelegate.cast(group), DeletionCause.API);
}
@Override
public CompletableFuture<List<HeldPermission<String>>> getGroupsWithPermission(@NonNull String permission) {
return master.force().getGroupsWithPermission(permission);
return handle.force().getGroupsWithPermission(permission);
}
@Override
public CompletableFuture<Boolean> createAndLoadTrack(String name) {
return master.force().createAndLoadTrack(checkName(name), CreationCause.API);
return handle.force().createAndLoadTrack(checkName(name), CreationCause.API);
}
@Override
public CompletableFuture<Boolean> loadTrack(String name) {
return master.force().loadTrack(checkName(name));
return handle.force().loadTrack(checkName(name));
}
@Override
public CompletableFuture<Boolean> loadAllTracks() {
return master.force().loadAllTracks();
return handle.force().loadAllTracks();
}
@Override
public CompletableFuture<Boolean> saveTrack(Track track) {
checkTrack(track);
return master.force().saveTrack(((TrackDelegate) track).getMaster());
return handle.force().saveTrack(TrackDelegate.cast(track));
}
@Override
public CompletableFuture<Boolean> deleteTrack(Track track) {
checkTrack(track);
return master.force().deleteTrack(((TrackDelegate) track).getMaster(), DeletionCause.API);
return handle.force().deleteTrack(TrackDelegate.cast(track), DeletionCause.API);
}
@Override
public CompletableFuture<Boolean> saveUUIDData(String username, UUID uuid) {
return master.force().saveUUIDData(checkUsername(username), uuid);
return handle.force().saveUUIDData(checkUsername(username), uuid);
}
@Override
public CompletableFuture<UUID> getUUID(String username) {
return master.force().getUUID(checkUsername(username));
return handle.force().getUUID(checkUsername(username));
}
@Override
public CompletableFuture<String> getName(@NonNull UUID uuid) {
return master.force().getName(uuid);
return handle.force().getName(uuid);
}
}
@@ -23,10 +23,12 @@
package me.lucko.luckperms.common.api.delegates;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.Track;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
@@ -34,83 +36,88 @@ import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import static me.lucko.luckperms.common.api.ApiUtils.checkGroup;
/**
* Provides a link between {@link Track} and {@link me.lucko.luckperms.common.core.model.Track}
*/
@EqualsAndHashCode(of = {"name"})
public class TrackDelegate implements Track {
@AllArgsConstructor
public final class TrackDelegate implements Track {
public static me.lucko.luckperms.common.core.model.Track cast(Track g) {
Preconditions.checkState(g instanceof TrackDelegate, "Illegal instance " + g.getClass() + " cannot be handled by this implementation.");
return ((TrackDelegate) g).getHandle();
}
@Getter(AccessLevel.PACKAGE)
private final me.lucko.luckperms.common.core.model.Track master;
private final me.lucko.luckperms.common.core.model.Track handle;
@Getter
private final String name;
public TrackDelegate(@NonNull me.lucko.luckperms.common.core.model.Track master) {
this.master = master;
this.name = master.getName();
@Override
public String getName() {
return handle.getName();
}
@Override
public List<String> getGroups() {
return master.getGroups();
return handle.getGroups();
}
@Override
public int getSize() {
return master.getSize();
return handle.getSize();
}
@Override
public String getNext(@NonNull Group current) throws ObjectLacksException {
checkGroup(current);
return master.getNext(((GroupDelegate) current).getMaster());
return handle.getNext(GroupDelegate.cast(current));
}
@Override
public String getPrevious(@NonNull Group current) throws ObjectLacksException {
checkGroup(current);
return master.getPrevious(((GroupDelegate) current).getMaster());
return handle.getPrevious(GroupDelegate.cast(current));
}
@Override
public void appendGroup(@NonNull Group group) throws ObjectAlreadyHasException {
checkGroup(group);
master.appendGroup(((GroupDelegate) group).getMaster());
handle.appendGroup(GroupDelegate.cast(group));
}
@Override
public void insertGroup(@NonNull Group group, @NonNull int position) throws ObjectAlreadyHasException, IndexOutOfBoundsException {
checkGroup(group);
master.insertGroup(((GroupDelegate) group).getMaster(), position);
handle.insertGroup(GroupDelegate.cast(group), position);
}
@Override
public void removeGroup(@NonNull Group group) throws ObjectLacksException {
checkGroup(group);
master.removeGroup(((GroupDelegate) group).getMaster());
handle.removeGroup(GroupDelegate.cast(group));
}
@Override
public void removeGroup(@NonNull String group) throws ObjectLacksException {
master.removeGroup(group);
handle.removeGroup(group);
}
@Override
public boolean containsGroup(@NonNull Group group) {
checkGroup(group);
return master.containsGroup(((GroupDelegate) group).getMaster());
return handle.containsGroup(GroupDelegate.cast(group));
}
@Override
public boolean containsGroup(@NonNull String group) {
return master.containsGroup(group);
return handle.containsGroup(group);
}
@Override
public void clearGroups() {
master.clearGroups();
handle.clearGroups();
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof TrackDelegate)) return false;
TrackDelegate other = (TrackDelegate) o;
return this.getName() == null ? other.getName() == null : this.getName().equals(other.getName());
}
public int hashCode() {
return this.getName().hashCode();
}
}
@@ -22,49 +22,56 @@
package me.lucko.luckperms.common.api.delegates;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.Group;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.caching.UserData;
import me.lucko.luckperms.common.core.NodeFactory;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import static me.lucko.luckperms.common.api.ApiUtils.checkGroup;
import static me.lucko.luckperms.common.api.ApiUtils.checkTime;
/**
* Provides a link between {@link User} and {@link me.lucko.luckperms.common.core.model.User}
*/
@EqualsAndHashCode(of = {"uuid"}, callSuper = false)
public class UserDelegate extends PermissionHolderDelegate implements User {
public final class UserDelegate extends PermissionHolderDelegate implements User {
public static me.lucko.luckperms.common.core.model.User cast(User g) {
Preconditions.checkState(g instanceof UserDelegate, "Illegal instance " + g.getClass() + " cannot be handled by this implementation.");
return ((UserDelegate) g).getHandle();
}
@Getter
private final me.lucko.luckperms.common.core.model.User master;
private final me.lucko.luckperms.common.core.model.User handle;
@Getter
private final UUID uuid;
public UserDelegate(@NonNull me.lucko.luckperms.common.core.model.User handle) {
super(handle);
this.handle = handle;
}
public UserDelegate(@NonNull me.lucko.luckperms.common.core.model.User master) {
super(master);
this.master = master;
this.uuid = master.getUuid();
@Override
public UUID getUuid() {
return handle.getUuid();
}
@Override
public String getName() {
return master.getName();
return handle.getName();
}
@Override
public String getPrimaryGroup() {
return master.getPrimaryGroup();
return handle.getPrimaryGroup().getValue();
}
@Override
@@ -77,132 +84,140 @@ public class UserDelegate extends PermissionHolderDelegate implements User {
throw new IllegalStateException("User is not a member of that group.");
}
master.setPrimaryGroup(s.toLowerCase());
handle.getPrimaryGroup().setStoredValue(s.toLowerCase());
}
@Override
public void refreshPermissions() {
master.getRefreshBuffer().requestDirectly();
handle.getRefreshBuffer().requestDirectly();
}
@Override
public Optional<UserData> getUserDataCache() {
return Optional.ofNullable(master.getUserData());
return Optional.ofNullable(handle.getUserData());
}
@Override
public void setupDataCache() {
master.setupData(false);
handle.setupData(false);
}
@Override
public boolean isInGroup(@NonNull Group group) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster());
return handle.inheritsGroup(GroupDelegate.cast(group));
}
@Override
public boolean isInGroup(@NonNull Group group, @NonNull String server) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster(), server);
return handle.inheritsGroup(((GroupDelegate) group).getHandle(), server);
}
@Override
public boolean isInGroup(@NonNull Group group, @NonNull String server, @NonNull String world) {
checkGroup(group);
return master.inheritsGroup(((GroupDelegate) group).getMaster(), server, world);
return handle.inheritsGroup(((GroupDelegate) group).getHandle(), server, world);
}
@Override
public void addGroup(@NonNull Group group) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster());
handle.setPermission(NodeFactory.make(GroupDelegate.cast(group))).throwException();
}
@Override
public void addGroup(@NonNull Group group, @NonNull String server) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server);
handle.setPermission(NodeFactory.make(GroupDelegate.cast(group), server)).throwException();
}
@Override
public void addGroup(@NonNull Group group, @NonNull String server, @NonNull String world) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, world);
handle.setPermission(NodeFactory.make(GroupDelegate.cast(group), server, world)).throwException();
}
@Override
public void addGroup(@NonNull Group group, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), checkTime(expireAt));
handle.setPermission(NodeFactory.make(GroupDelegate.cast(group), checkTime(expireAt))).throwException();
}
@Override
public void addGroup(@NonNull Group group, @NonNull String server, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, checkTime(expireAt));
handle.setPermission(NodeFactory.make(GroupDelegate.cast(group), server, checkTime(expireAt))).throwException();
}
@Override
public void addGroup(@NonNull Group group, @NonNull String server, @NonNull String world, @NonNull long expireAt) throws ObjectAlreadyHasException {
checkGroup(group);
master.setInheritGroup(((GroupDelegate) group).getMaster(), server, world, checkTime(expireAt));
handle.setPermission(NodeFactory.make(GroupDelegate.cast(group), server, world, checkTime(expireAt))).throwException();
}
@Override
public void removeGroup(@NonNull Group group) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster());
handle.unsetPermission(NodeFactory.make(GroupDelegate.cast(group))).throwException();
}
@Override
public void removeGroup(@NonNull Group group, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), temporary);
handle.unsetPermission(NodeFactory.make(GroupDelegate.cast(group), temporary)).throwException();
}
@Override
public void removeGroup(@NonNull Group group, @NonNull String server) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server);
handle.unsetPermission(NodeFactory.make(GroupDelegate.cast(group), server)).throwException();
}
@Override
public void removeGroup(@NonNull Group group, @NonNull String server, @NonNull String world) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, world);
handle.unsetPermission(NodeFactory.make(GroupDelegate.cast(group), server, world)).throwException();
}
@Override
public void removeGroup(@NonNull Group group, @NonNull String server, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, temporary);
handle.unsetPermission(NodeFactory.make(GroupDelegate.cast(group), server, temporary)).throwException();
}
@Override
public void removeGroup(@NonNull Group group, @NonNull String server, @NonNull String world, @NonNull boolean temporary) throws ObjectLacksException {
checkGroup(group);
master.unsetInheritGroup(((GroupDelegate) group).getMaster(), server, world, temporary);
handle.unsetPermission(NodeFactory.make(GroupDelegate.cast(group), server, world, temporary)).throwException();
}
@Override
public void clearNodes() {
master.clearNodes();
handle.clearNodes();
}
@Override
public List<String> getGroupNames() {
return master.getGroupNames();
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.map(Node::getGroupName)
.collect(Collectors.toList());
}
@Override
public List<String> getLocalGroups(@NonNull String server, @NonNull String world) {
return master.getLocalGroups(server, world);
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnWorld(world, false, true))
.filter(n -> n.shouldApplyOnServer(server, false, true))
.map(Node::getGroupName)
.collect(Collectors.toList());
}
@Override
public List<String> getLocalGroups(@NonNull String server) {
return master.getLocalGroups(server);
return handle.mergePermissionsToList().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyOnServer(server, false, true))
.map(Node::getGroupName)
.collect(Collectors.toList());
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof UserDelegate)) return false;
UserDelegate other = (UserDelegate) o;
return this.getUuid() == null ? other.getUuid() == null : this.getUuid().equals(other.getUuid());
}
public int hashCode() {
return this.getUuid().hashCode();
}
}
@@ -34,15 +34,15 @@ import java.util.UUID;
*/
@AllArgsConstructor
public class UuidCacheDelegate implements UuidCache {
private final me.lucko.luckperms.common.core.UuidCache master;
private final me.lucko.luckperms.common.core.UuidCache handle;
@Override
public UUID getUUID(@NonNull UUID external) {
return master.getUUID(external);
return handle.getUUID(external);
}
@Override
public UUID getExternalUUID(@NonNull UUID internal) {
return master.getExternalUUID(internal);
return handle.getExternalUUID(internal);
}
}
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import me.lucko.luckperms.common.bulkupdate.action.Action;
import me.lucko.luckperms.common.bulkupdate.constraint.Constraint;
import me.lucko.luckperms.common.core.NodeModel;
import java.util.List;
/**
* Represents a query to be applied to a set of data.
* Queries can either be applied to im-memory sets of data, or converted to SQL syntax to be executed remotely.
*/
@Getter
@ToString
@EqualsAndHashCode
@AllArgsConstructor
public class BulkUpdate {
// the data types which this query should apply to
private final DataType dataType;
// the action to apply to the data which matches the constraints
private final Action action;
// a set of constraints which data must match to be acted upon
private final List<Constraint> constraints;
/**
* Check to see if a Node instance satisfies the constrints of this query
*
* @param node the node to check
* @return true if satisfied
*/
public boolean satisfiesConstraints(NodeModel node) {
for (Constraint constraint : constraints) {
if (!constraint.isSatisfiedBy(node)) {
return false;
}
}
return true;
}
/**
* Applies this query to the given NodeModel, and returns the result.
*
* @param from the node to base changes from
* @return the new nodemodel instance, or null if the node should be deleted.
*/
public NodeModel apply(NodeModel from) {
if (!satisfiesConstraints(from)) {
return from; // make no change
}
return action.apply(from);
}
/**
* Converts this {@link BulkUpdate} to SQL syntax
*
* @return this query in SQL form
*/
public String buildAsSql() {
// DELETE FROM {table} WHERE ...
// UPDATE {table} SET ... WHERE ...
StringBuilder sb = new StringBuilder();
// add the action
// (DELETE FROM or UPDATE)
sb.append(action.getAsSql());
// if there are no constraints, just return without a WHERE clause
if (constraints.isEmpty()) {
return sb.append(";").toString();
}
// append constraints
sb.append(" WHERE");
for (int i = 0; i < constraints.size(); i++) {
Constraint constraint = constraints.get(i);
sb.append(" ");
if (i != 0) {
sb.append("AND ");
}
sb.append(constraint.getAsSql());
}
return sb.append(";").toString();
}
/**
* Utility to appropriately escape a string for use in a query.
*
* @param s the string to escape
* @return an escaped string
*/
public static String escapeStringForSql(String s) {
if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")) {
return s.toLowerCase();
}
try {
Integer.parseInt(s);
return s;
} catch (NumberFormatException e) {
// ignored
}
return "\"" + s + "\"";
}
}
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate;
import lombok.NoArgsConstructor;
import lombok.ToString;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.bulkupdate.action.Action;
import me.lucko.luckperms.common.bulkupdate.constraint.Constraint;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Responsible for building a {@link BulkUpdate}
*/
@ToString
@NoArgsConstructor(staticName = "create")
public class BulkUpdateBuilder {
// the data type this query should affect
private DataType dataType = DataType.ALL;
// the action to apply to the data which matches the constraints
private Action action = null;
// a set of constraints which data must match to be acted upon
private Set<Constraint> constraints = new LinkedHashSet<>();
public BulkUpdateBuilder action(Action action) {
this.action = action;
return this;
}
public BulkUpdateBuilder dataType(DataType dataType) {
this.dataType = dataType;
return this;
}
public BulkUpdateBuilder constraint(Constraint constraint) {
constraints.add(constraint);
return this;
}
public BulkUpdate build() {
if (action == null) {
throw new IllegalStateException("no action specified");
}
return new BulkUpdate(dataType, action, ImmutableList.copyOf(constraints));
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Represents the data sets a query should apply to
*/
@Getter
@AllArgsConstructor
public enum DataType {
ALL("all", true, true),
USERS("users", true, false),
GROUPS("groups", false, true);
private final String name;
private final boolean includingUsers;
private final boolean includingGroups;
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.action;
import me.lucko.luckperms.common.core.NodeModel;
public interface Action {
String getName();
/**
* Applies this action to the given NodeModel, and returns the result.
*
* @param from the node to base changes from
* @return the new nodemodel instance, or null if the node should be deleted.
*/
NodeModel apply(NodeModel from);
String getAsSql();
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.action;
import lombok.AllArgsConstructor;
import me.lucko.luckperms.common.core.NodeModel;
@AllArgsConstructor(staticName = "create")
public class DeleteAction implements Action {
@Override
public String getName() {
return "delete";
}
@Override
public NodeModel apply(NodeModel from) {
return null;
}
@Override
public String getAsSql() {
return "DELETE FROM {table}";
}
}
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.action;
import lombok.AllArgsConstructor;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.constraint.QueryField;
import me.lucko.luckperms.common.core.NodeModel;
@AllArgsConstructor(staticName = "of")
public class UpdateAction implements Action {
private final QueryField field;
private final String value;
@Override
public String getName() {
return "update";
}
@Override
public NodeModel apply(NodeModel from) {
switch (field) {
case PERMISSION:
return from.setPermission(value);
case SERVER:
return from.setServer(value);
case WORLD:
return from.setWorld(value);
default:
throw new RuntimeException();
}
}
@Override
public String getAsSql() {
return "UPDATE {table} SET " + field.getSqlName() + "=" + BulkUpdate.escapeStringForSql(value);
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.comparisons;
public interface Comparison {
String getSymbol();
boolean matches(String str, String expr);
}
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.comparisons;
import lombok.AllArgsConstructor;
import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonEqual;
import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonNotEqual;
import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonNotSimilar;
import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonSimilar;
@AllArgsConstructor
public enum ComparisonType {
EQUAL("==", new ComparisonEqual()),
NOT_EQUAL("!=", new ComparisonNotEqual()),
SIMILAR("~~", new ComparisonSimilar()),
NOT_SIMILAR("!~", new ComparisonNotSimilar());
private final String symbol;
private final Comparison instance;
public String getSymbol() {
return symbol;
}
public Comparison get() {
return instance;
}
public static ComparisonType parseComparison(String s) {
for (ComparisonType t : values()) {
if (t.getSymbol().equals(s)) {
return t;
}
}
return null;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.comparisons.impl;
import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison;
public class ComparisonEqual implements Comparison {
@Override
public String getSymbol() {
return "==";
}
@Override
public boolean matches(String str, String expr) {
return str.equalsIgnoreCase(expr);
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.comparisons.impl;
import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison;
public class ComparisonNotEqual implements Comparison {
@Override
public String getSymbol() {
return "!=";
}
@Override
public boolean matches(String str, String expr) {
return !str.equalsIgnoreCase(expr);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.comparisons.impl;
import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison;
public class ComparisonNotSimilar implements Comparison {
@Override
public String getSymbol() {
return "!~";
}
@Override
public boolean matches(String str, String expr) {
// form expression
expr = expr.toLowerCase();
expr = expr.replace(".", "\\.");
// convert from SQL LIKE syntax to regex
expr = expr.replace("_", ".");
expr = expr.replace("%", ".*");
return !str.toLowerCase().matches(expr);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -20,25 +20,26 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.utils;
package me.lucko.luckperms.common.bulkupdate.comparisons.impl;
import java.util.Objects;
import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison;
public interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
default <V> ThrowingFunction<V, R> compose(ThrowingFunction<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
public class ComparisonSimilar implements Comparison {
@Override
public String getSymbol() {
return "~~";
}
default <V> ThrowingFunction<T, V> andThen(ThrowingFunction<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
@Override
public boolean matches(String str, String expr) {
// form expression
expr = expr.toLowerCase();
expr = expr.replace(".", "\\.");
static <T> ThrowingFunction<T, T> identity() {
return t -> t;
// convert from SQL LIKE syntax to regex
expr = expr.replace("_", ".");
expr = expr.replace("%", ".*");
return str.toLowerCase().matches(expr);
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.constraint;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.comparisons.ComparisonType;
import me.lucko.luckperms.common.core.NodeModel;
/**
* Represents a query constraint
*/
@Getter
@AllArgsConstructor(staticName = "of")
public class Constraint {
// the field this constraint is comparing against
private final QueryField field;
// the comparison type being used in this constraint
private final ComparisonType comparison;
// the expression being compared against
private final String value;
/**
* Returns if the given node satisfies this constraint
*
* @param node the node
* @return true if satisfied
*/
public boolean isSatisfiedBy(NodeModel node) {
switch (field) {
case PERMISSION:
return comparison.get().matches(node.getPermission(), value);
case SERVER:
return comparison.get().matches(node.getServer(), value);
case WORLD:
return comparison.get().matches(node.getWorld(), value);
default:
throw new RuntimeException();
}
}
public String getAsSql() {
// WHERE permission = "thing"
// WHERE permission != ""
// WHERE permission LIKE ""
// WHERE permission NOT LIKE ""
switch (comparison) {
case EQUAL:
return field.getSqlName() + " = " + BulkUpdate.escapeStringForSql(value);
case NOT_EQUAL:
return field.getSqlName() + " != " + BulkUpdate.escapeStringForSql(value);
case SIMILAR:
return field.getSqlName() + " LIKE " + BulkUpdate.escapeStringForSql(value);
case NOT_SIMILAR:
return field.getSqlName() + " NOT LIKE " + BulkUpdate.escapeStringForSql(value);
default:
throw new RuntimeException();
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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.bulkupdate.constraint;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Represents a field being used in an update
*/
@Getter
@AllArgsConstructor
public enum QueryField {
PERMISSION("permission"),
SERVER("server"),
WORLD("world");
private final String sqlName;
public static QueryField of(String s) {
try {
return valueOf(s.toUpperCase());
} catch (IllegalArgumentException e) {
return null;
}
}
}
@@ -22,12 +22,12 @@
package me.lucko.luckperms.common.caching;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.ToString;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.common.caching.stacking.MetaStack;
import me.lucko.luckperms.common.caching.stacking.NoopMetaStack;
import java.util.Comparator;
import java.util.HashMap;
@@ -40,16 +40,18 @@ import java.util.TreeMap;
*/
@Getter
@ToString
public class MetaHolder {
public class MetaAccumulator {
@Getter(AccessLevel.NONE)
private final Map<String, String> meta;
private final SortedMap<Integer, String> prefixes;
private final SortedMap<Integer, String> suffixes;
private int weight = 0;
private final MetaStack prefixStack;
private final MetaStack suffixStack;
public MetaHolder(MetaStack prefixStack, MetaStack suffixStack) {
public MetaAccumulator(MetaStack prefixStack, MetaStack suffixStack) {
this.meta = new HashMap<>();
this.prefixes = new TreeMap<>(Comparator.reverseOrder());
this.suffixes = new TreeMap<>(Comparator.reverseOrder());
@@ -57,10 +59,6 @@ public class MetaHolder {
this.suffixStack = suffixStack;
}
public MetaHolder() {
this(NoopMetaStack.INSTANCE, NoopMetaStack.INSTANCE);
}
public void accumulateNode(LocalizedNode n) {
if (n.isMeta()) {
Map.Entry<String, String> entry = n.getMeta();
@@ -88,4 +86,18 @@ public class MetaHolder {
}
}
public void accumulateWeight(int weight) {
this.weight = Math.max(this.weight, weight);
}
// We can assume that if this method is being called, this holder is effectively finalized. (it's not going to accumulate more nodes)
// Therefore, it should be ok to set the weight meta key, if not already present.
public Map<String, String> getMeta() {
if (!this.meta.containsKey("weight") && this.weight != 0) {
this.meta.put("weight", String.valueOf(this.weight));
}
return this.meta;
}
}
@@ -59,7 +59,7 @@ public class MetaCache implements MetaData {
@Getter
private MetaStack suffixStack = NoopMetaStack.INSTANCE;
public void loadMeta(MetaHolder meta) {
public void loadMeta(MetaAccumulator meta) {
lock.writeLock().lock();
try {
this.meta = ImmutableMap.copyOf(meta.getMeta());
@@ -25,12 +25,10 @@ package me.lucko.luckperms.common.caching;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.caching.MetaData;
@@ -49,6 +47,7 @@ import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
public class UserCache implements UserData {
/**
* The user whom this data instance is representing
*/
@@ -59,7 +58,7 @@ public class UserCache implements UserData {
*/
private final CalculatorFactory calculatorFactory;
private final LoadingCache<Contexts, PermissionCache> permission = CacheBuilder.newBuilder()
private final LoadingCache<Contexts, PermissionCache> permission = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader<Contexts, PermissionCache>() {
@Override
@@ -68,13 +67,13 @@ public class UserCache implements UserData {
}
@Override
public ListenableFuture<PermissionCache> reload(Contexts contexts, PermissionCache oldData) {
oldData.comparePermissions(user.exportNodes(contexts, true));
return Futures.immediateFuture(oldData);
public PermissionCache reload(Contexts contexts, PermissionCache oldData) {
oldData.comparePermissions(user.exportNodes(ExtractedContexts.generate(contexts), true));
return oldData;
}
});
private final LoadingCache<Contexts, MetaCache> meta = CacheBuilder.newBuilder()
private final LoadingCache<Contexts, MetaCache> meta = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader<Contexts, MetaCache>() {
@Override
@@ -83,26 +82,26 @@ public class UserCache implements UserData {
}
@Override
public ListenableFuture<MetaCache> reload(Contexts contexts, MetaCache oldData) {
public MetaCache reload(Contexts contexts, MetaCache oldData) {
oldData.loadMeta(user.accumulateMeta(null, null, ExtractedContexts.generate(contexts)));
return Futures.immediateFuture(oldData);
return oldData;
}
});
@Override
public PermissionData getPermissionData(@NonNull Contexts contexts) {
return permission.getUnchecked(contexts);
return permission.get(contexts);
}
@Override
public MetaData getMetaData(@NonNull Contexts contexts) {
return meta.getUnchecked(contexts);
return meta.get(contexts);
}
@Override
public PermissionCache calculatePermissions(@NonNull Contexts contexts) {
PermissionCache data = new PermissionCache(contexts, user, calculatorFactory);
data.setPermissions(user.exportNodes(contexts, true));
data.setPermissions(user.exportNodes(ExtractedContexts.generate(contexts), true));
return data;
}
@@ -142,8 +141,8 @@ public class UserCache implements UserData {
@Override
public void preCalculate(@NonNull Contexts contexts) {
permission.getUnchecked(contexts);
meta.getUnchecked(contexts);
permission.get(contexts);
meta.get(contexts);
}
public void invalidateCache() {
@@ -27,24 +27,22 @@ import lombok.RequiredArgsConstructor;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import me.lucko.luckperms.common.core.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
/**
* Manages the cached state of all permission holders
*/
@RequiredArgsConstructor
public class CachedStateManager {
private static final Consumer<PermissionHolder> INVALIDATE_CONSUMER = PermissionHolder::invalidateInheritanceCaches;
// private static final Consumer<PermissionHolder> INVALIDATE_CONSUMER = PermissionHolder::invalidateInheritanceCaches;
private final LuckPermsPlugin plugin;
// Group --> Groups that inherit from that group. (reverse relationship)
// Group --> Groups/Users that inherit from that group. (reverse relationship)
private final Multimap<HolderReference, HolderReference> map = HashMultimap.create();
private final ReentrantLock lock = new ReentrantLock();
@@ -117,6 +115,7 @@ public class CachedStateManager {
}
}
/*
public void invalidateInheritances(HolderReference holder) {
Set<HolderReference> toInvalidate = getInheritances(holder);
invalidateInheritances(plugin, toInvalidate);
@@ -125,5 +124,6 @@ public class CachedStateManager {
public static void invalidateInheritances(LuckPermsPlugin plugin, Set<HolderReference> references) {
references.forEach(hr -> hr.apply(plugin, INVALIDATE_CONSUMER));
}
*/
}
@@ -38,7 +38,7 @@ import java.util.function.Consumer;
@ToString
@EqualsAndHashCode
@AllArgsConstructor(staticName = "of")
public class UserReference implements HolderReference<UserIdentifier> {
public final class UserReference implements HolderReference<UserIdentifier> {
private final UserIdentifier id;
@Override
@@ -23,6 +23,7 @@
package me.lucko.luckperms.common.caching.stacking;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -44,7 +45,7 @@ public interface MetaStackElement {
* @param node the node to check
* @return true if the accumulation should return
*/
static boolean checkMetaType(boolean expectingPrefix, LocalizedNode node) {
static boolean checkMetaType(boolean expectingPrefix, Node node) {
if (expectingPrefix) {
if (!node.isPrefix()) {
return true;
@@ -24,9 +24,8 @@ package me.lucko.luckperms.common.calculators;
import lombok.RequiredArgsConstructor;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -43,13 +42,7 @@ public class PermissionCalculator {
private final String objectName;
private final List<PermissionProcessor> processors;
private final LoadingCache<String, Tristate> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, Tristate>() {
@Override
public Tristate load(String s) {
return lookupPermissionValue(s);
}
});
private final LoadingCache<String, Tristate> cache = Caffeine.newBuilder().build(this::lookupPermissionValue);
public void invalidateCache() {
cache.invalidateAll();
@@ -57,8 +50,8 @@ public class PermissionCalculator {
public Tristate getPermissionValue(String permission) {
permission = permission.toLowerCase();
Tristate t = cache.getUnchecked(permission);
plugin.getDebugHandler().offer(objectName, permission, t);
Tristate t = cache.get(permission);
plugin.getVerboseHandler().offer(objectName, permission, t);
plugin.getPermissionVault().offer(permission);
return t;
}
@@ -26,29 +26,30 @@ import lombok.Getter;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.commands.group.CreateGroup;
import me.lucko.luckperms.common.commands.group.DeleteGroup;
import me.lucko.luckperms.common.commands.group.GroupMainCommand;
import me.lucko.luckperms.common.commands.group.ListGroups;
import me.lucko.luckperms.common.commands.log.LogMainCommand;
import me.lucko.luckperms.common.commands.migration.MigrationMainCommand;
import me.lucko.luckperms.common.commands.misc.CheckCommand;
import me.lucko.luckperms.common.commands.misc.ExportCommand;
import me.lucko.luckperms.common.commands.misc.ImportCommand;
import me.lucko.luckperms.common.commands.misc.InfoCommand;
import me.lucko.luckperms.common.commands.misc.NetworkSyncCommand;
import me.lucko.luckperms.common.commands.misc.ReloadConfigCommand;
import me.lucko.luckperms.common.commands.misc.SearchCommand;
import me.lucko.luckperms.common.commands.misc.SyncCommand;
import me.lucko.luckperms.common.commands.misc.TreeCommand;
import me.lucko.luckperms.common.commands.misc.VerboseCommand;
import me.lucko.luckperms.common.commands.abstraction.Command;
import me.lucko.luckperms.common.commands.impl.group.CreateGroup;
import me.lucko.luckperms.common.commands.impl.group.DeleteGroup;
import me.lucko.luckperms.common.commands.impl.group.GroupMainCommand;
import me.lucko.luckperms.common.commands.impl.group.ListGroups;
import me.lucko.luckperms.common.commands.impl.log.LogMainCommand;
import me.lucko.luckperms.common.commands.impl.migration.MigrationMainCommand;
import me.lucko.luckperms.common.commands.impl.misc.BulkUpdateCommand;
import me.lucko.luckperms.common.commands.impl.misc.CheckCommand;
import me.lucko.luckperms.common.commands.impl.misc.ExportCommand;
import me.lucko.luckperms.common.commands.impl.misc.ImportCommand;
import me.lucko.luckperms.common.commands.impl.misc.InfoCommand;
import me.lucko.luckperms.common.commands.impl.misc.NetworkSyncCommand;
import me.lucko.luckperms.common.commands.impl.misc.ReloadConfigCommand;
import me.lucko.luckperms.common.commands.impl.misc.SearchCommand;
import me.lucko.luckperms.common.commands.impl.misc.SyncCommand;
import me.lucko.luckperms.common.commands.impl.misc.TreeCommand;
import me.lucko.luckperms.common.commands.impl.misc.VerboseCommand;
import me.lucko.luckperms.common.commands.impl.track.CreateTrack;
import me.lucko.luckperms.common.commands.impl.track.DeleteTrack;
import me.lucko.luckperms.common.commands.impl.track.ListTracks;
import me.lucko.luckperms.common.commands.impl.track.TrackMainCommand;
import me.lucko.luckperms.common.commands.impl.user.UserMainCommand;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.track.CreateTrack;
import me.lucko.luckperms.common.commands.track.DeleteTrack;
import me.lucko.luckperms.common.commands.track.ListTracks;
import me.lucko.luckperms.common.commands.track.TrackMainCommand;
import me.lucko.luckperms.common.commands.user.UserMainCommand;
import me.lucko.luckperms.common.commands.usersbulkedit.UsersBulkEditMainCommand;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.Message;
@@ -75,14 +76,14 @@ public class CommandManager {
private final ExecutorService executor;
@Getter
private final List<BaseCommand> mainCommands;
private final List<Command> mainCommands;
public CommandManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.executor = Executors.newSingleThreadExecutor();
ImmutableList.Builder<BaseCommand> l = ImmutableList.builder();
l.add(new UserMainCommand())
mainCommands = ImmutableList.<Command>builder()
.add(new UserMainCommand())
.add(new GroupMainCommand())
.add(new TrackMainCommand())
.addAll(plugin.getExtraCommands())
@@ -97,40 +98,50 @@ public class CommandManager {
.add(new ImportCommand())
.add(new ExportCommand())
.add(new ReloadConfigCommand())
.add(new BulkUpdateCommand())
.add(new MigrationMainCommand())
.add(new UsersBulkEditMainCommand())
.add(new CreateGroup())
.add(new DeleteGroup())
.add(new ListGroups())
.add(new CreateTrack())
.add(new DeleteTrack())
.add(new ListTracks());
mainCommands = l.build();
.add(new ListTracks())
.build();
}
/**
* Generic on command method to be called from the command executor object of the platform
* Unlike {@link #onCommand(Sender, String, List)}, this method is called in a new thread
* @param sender who sent the command
* Unlike {@link #execute(Sender, String, List)}, this method is called in a new thread
* @param sender who sent the command
* @param label the command label used
* @param args the arguments provided
*/
public Future<CommandResult> onCommand(Sender sender, String label, List<String> args) {
return executor.submit(() -> execute(sender, label, args));
return executor.submit(() -> {
try {
return execute(sender, label, args);
} catch (Exception e) {
plugin.getLog().severe("Exception whilst executing command: " + args.toString());
e.printStackTrace();
return null;
}
});
}
@SuppressWarnings("unchecked")
private CommandResult execute(Sender sender, String label, List<String> args) {
List<String> arguments = new ArrayList<>(args);
handleRewrites(arguments);
// Handle no arguments
if (args.size() == 0) {
if (arguments.size() == 0) {
sendCommandUsage(sender, label);
return CommandResult.INVALID_ARGS;
}
// Look for the main command.
Optional<BaseCommand> o = mainCommands.stream()
.filter(m -> m.getName().equalsIgnoreCase(args.get(0)))
Optional<Command> o = mainCommands.stream()
.filter(m -> m.getName().equalsIgnoreCase(arguments.get(0)))
.limit(1)
.findAny();
@@ -147,13 +158,11 @@ public class CommandManager {
return CommandResult.NO_PERMISSION;
}
List<String> arguments = new ArrayList<>(args);
handleRewrites(arguments);
arguments.remove(0); // remove the main command arg.
// Check the correct number of args were given for the main command
if (main.getArgumentCheck().test(arguments.size())) {
main.sendUsage(sender, label);
main.sendDetailedUsage(sender, label);
return CommandResult.INVALID_ARGS;
}
@@ -180,15 +189,18 @@ public class CommandManager {
*/
@SuppressWarnings("unchecked")
public List<String> onTabComplete(Sender sender, List<String> args) {
List<String> arguments = new ArrayList<>(args);
handleRewrites(arguments);
final List<Command> mains = mainCommands.stream()
.filter(m -> m.isAuthorized(sender))
.collect(Collectors.toList());
// Not yet past the point of entering a main command
if (args.size() <= 1) {
if (arguments.size() <= 1) {
// Nothing yet entered
if (args.isEmpty() || args.get(0).equalsIgnoreCase("")) {
if (arguments.isEmpty() || arguments.get(0).equalsIgnoreCase("")) {
return mains.stream()
.map(m -> m.getName().toLowerCase())
.collect(Collectors.toList());
@@ -197,22 +209,24 @@ public class CommandManager {
// Started typing a main command
return mains.stream()
.map(m -> m.getName().toLowerCase())
.filter(s -> s.startsWith(args.get(0).toLowerCase()))
.filter(s -> s.startsWith(arguments.get(0).toLowerCase()))
.collect(Collectors.toList());
}
// Find a main command matching the first arg
Optional<Command> o = mains.stream()
.filter(m -> m.getName().equalsIgnoreCase(args.get(0)))
.filter(m -> m.getName().equalsIgnoreCase(arguments.get(0)))
.limit(1)
.findAny();
arguments.remove(0); // remove the main command arg.
if (!o.isPresent()) {
return Collections.emptyList();
}
// Pass the processing onto the main command
return o.get().tabComplete(plugin, sender, args.subList(1, args.size()));
return o.get().tabComplete(plugin, sender, arguments);
}
private void sendCommandUsage(Sender sender, String label) {
@@ -226,19 +240,23 @@ public class CommandManager {
@SuppressWarnings("unchecked")
String permission = (String) c.getPermission().map(p -> ((Permission) p).getExample()).orElse("None");
FancyMessage msg = new FancyMessage("> ").color(ChatColor.getByChar('3')).then().text(String.format(c.getUsage(), label)).color(ChatColor.getByChar('a'))
FancyMessage msg = new FancyMessage("> ").color(c('3')).then().text(String.format(c.getUsage(), label)).color(c('a'))
.formattedTooltip(
new FancyMessage("Command: ").color(ChatColor.getByChar('b')).then().text(c.getName()).color(ChatColor.getByChar('2')),
new FancyMessage("Description: ").color(ChatColor.getByChar('b')).then().text(c.getDescription()).color(ChatColor.getByChar('2')),
new FancyMessage("Usage: ").color(ChatColor.getByChar('b')).then().text(String.format(c.getUsage(), label)).color(ChatColor.getByChar('2')),
new FancyMessage("Permission: ").color(ChatColor.getByChar('b')).then().text(permission).color(ChatColor.getByChar('2')),
new FancyMessage("Command: ").color(c('b')).then().text(c.getName()).color(c('2')),
new FancyMessage("Description: ").color(c('b')).then().text(c.getDescription()).color(c('2')),
new FancyMessage("Usage: ").color(c('b')).then().text(String.format(c.getUsage(), label)).color(c('2')),
new FancyMessage("Permission: ").color(c('b')).then().text(permission).color(c('2')),
new FancyMessage(" "),
new FancyMessage("Click to auto-complete.").color(ChatColor.getByChar('7'))
new FancyMessage("Click to auto-complete.").color(c('7'))
)
.suggest(String.format(c.getUsage(), label));
sender.sendMessage(msg);
});
}
private static ChatColor c(char c) {
return ChatColor.getByChar(c);
}
public static CommandResult handleException(CommandException e, Sender sender, String label, Command command) {
if (e instanceof ArgumentUtils.ArgumentException) {
@@ -279,6 +297,17 @@ public class CommandManager {
}
private static void handleRewrites(List<String> args) {
if (args.size() >= 1) {
if (args.get(0).equalsIgnoreCase("u")) {
args.remove(0);
args.add(0, "user");
}
if (args.get(0).equalsIgnoreCase("g")) {
args.remove(0);
args.add(0, "group");
}
}
if (args.size() >= 3) {
if (!args.get(0).equalsIgnoreCase("user") && !args.get(0).equalsIgnoreCase("group")) {
return;
@@ -299,6 +328,11 @@ public class CommandManager {
args.add(2, "meta");
break;
case "i":
case "about":
case "list":
args.remove(2);
args.add(2, "info");
break;
case "inherit":
case "inheritances":
case "group":
@@ -389,12 +423,24 @@ public class CommandManager {
break;
}
// provide lazy info
boolean lazyInfo = (
args.size() >= 4 &&
(args.get(2).equalsIgnoreCase("permission") || args.get(2).equalsIgnoreCase("parent") || args.get(2).equalsIgnoreCase("meta")) &&
(args.get(3).equalsIgnoreCase("i") || args.get(3).equalsIgnoreCase("about") || args.get(3).equalsIgnoreCase("list"))
);
if (lazyInfo) {
args.remove(3);
args.add(3, "info");
}
// Provide lazy set rewrite
boolean lazySet = (
args.size() >= 6 &&
args.get(2).equalsIgnoreCase("permission") &&
args.get(3).toLowerCase().startsWith("set") &&
(args.get(5).equalsIgnoreCase("none") || args.get(5).equalsIgnoreCase("0"))
args.get(2).equalsIgnoreCase("permission") &&
args.get(3).toLowerCase().startsWith("set") &&
(args.get(5).equalsIgnoreCase("none") || args.get(5).equalsIgnoreCase("0"))
);
if (lazySet) {
@@ -20,12 +20,15 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.commands;
package me.lucko.luckperms.common.commands.abstraction;
import lombok.Getter;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.commands.Arg;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.Permission;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -35,24 +38,46 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
/**
* An abstract command class
*
* @param <T> the type required by the {@link #execute(LuckPermsPlugin, Sender, Object, List, String)} method of this command
* @param <S> the type of any child commands
*/
public abstract class Command<T, S> {
/**
* The name of the command. Should be properly capitalised.
*/
@Getter
private final String name;
/**
* A brief description of this command
*/
@Getter
private final String description;
// Could be null
/**
* The permission required to use this command. Nullable.
*/
private final Permission permission;
/**
* A predicate used for testing the size of the arguments list passed to this command
*/
@Getter
private final Predicate<Integer> argumentCheck;
// Could be null
/**
* A list of arguments required for the command. These are just here for informational purposes, and aren't used
* for argument validation.
*/
private final List<Arg> args;
// Could be null
/**
* Child commands. Nullable.
*/
private final List<Command<S, ?>> children;
public Command(String name, String description, Permission permission, Predicate<Integer> argumentCheck, List<Arg> args, List<Command<S, ?>> children) {
@@ -78,14 +103,56 @@ public abstract class Command<T, S> {
return Collections.emptyList();
}
/**
* Sends a brief command usage message to the Sender.
* If this command has child commands, the children are listed. Otherwise, a basic usage message is sent.
*
* @param sender the sender to send the usage to
* @param label the label used when executing the command
*/
public abstract void sendUsage(Sender sender, String label);
/**
* Sends a detailed command usage message to the Sender.
* If this command has child commands, nothing is sent. Otherwise, a detailed messaging containing a description
* and argument usage is sent.
*
* @param sender the sender to send the usage to
* @param label the label used when executing the command
*/
public abstract void sendDetailedUsage(Sender sender, String label);
/**
* Returns true if the sender is authorised to use this command
*
* Commands with children are likely to override this method to check for permissions based upon whether
* a sender has access to any sub commands.
*
* @param sender the sender
* @return true if the sender has permission to use this command
*/
public boolean isAuthorized(Sender sender) {
return permission == null || permission.isAuthorized(sender);
}
/**
* Returns if this command should be displayed in command listings, or "hidden"
*
* @return if this command should be displayed in command listings, or "hidden"
*/
public boolean shouldDisplay() {
return true;
}
/**
* Returns the usage of this command. Will only return a non empty result for main commands.
*
* @return the usage of this command.
*/
public String getUsage() {
return "";
}
public Optional<Permission> getPermission() {
return Optional.ofNullable(permission);
}
@@ -20,10 +20,13 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.commands;
package me.lucko.luckperms.common.commands.abstraction;
import lombok.Getter;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.Message;
@@ -36,7 +39,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public abstract class MainCommand<T> extends BaseCommand<Void, T> {
public abstract class MainCommand<T> extends Command<Void, T> {
@Getter
private final String usage;
@@ -55,7 +58,7 @@ public abstract class MainCommand<T> extends BaseCommand<Void, T> {
return CommandResult.INVALID_ARGS;
}
Optional<Command<T, ?>> o = getSubCommands().stream()
Optional<Command<T, ?>> o = getChildren().get().stream()
.filter(s -> s.getName().equalsIgnoreCase(args.get(minArgs - 1)))
.limit(1)
.findAny();
@@ -112,7 +115,7 @@ public abstract class MainCommand<T> extends BaseCommand<Void, T> {
.collect(Collectors.toList());
}
final List<Command<T, ?>> subs = getSubCommands().stream()
final List<Command<T, ?>> subs = getChildren().get().stream()
.filter(s -> s.isAuthorized(sender))
.collect(Collectors.toList());
@@ -149,7 +152,7 @@ public abstract class MainCommand<T> extends BaseCommand<Void, T> {
@Override
public void sendUsage(Sender sender, String label) {
List<Command> subs = getSubCommands().stream()
List<Command> subs = getChildren().get().stream()
.filter(s -> s.isAuthorized(sender))
.collect(Collectors.toList());
@@ -167,23 +170,16 @@ public abstract class MainCommand<T> extends BaseCommand<Void, T> {
@Override
public void sendDetailedUsage(Sender sender, String label) {
sendUsage(sender, label);
}
@Override
public boolean isAuthorized(Sender sender) {
return getSubCommands().stream().filter(sc -> sc.isAuthorized(sender)).count() != 0;
return getChildren().get().stream().filter(sc -> sc.isAuthorized(sender)).count() != 0;
}
@Deprecated
@Override
public Optional<List<Command<T, ?>>> getChildren() {
return super.getChildren();
}
@SuppressWarnings("deprecation")
public List<Command<T, ?>> getSubCommands() {
return getChildren().get();
}
}
@@ -20,11 +20,10 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.commands.generic;
package me.lucko.luckperms.common.commands.abstraction;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.SubCommand;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
@@ -39,46 +38,18 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* A shared main command. Shared meaning it can apply to both users and groups.
* This extends sub command as they're actually sub commands of the main user/group commands.
* @param <T>
*/
public class SharedMainCommand<T extends PermissionHolder> extends SubCommand<T> {
private static CommandResult handleException(CommandException e, Sender sender, SharedSubCommand command) {
if (e instanceof ArgumentUtils.ArgumentException) {
if (e instanceof ArgumentUtils.DetailedUsageException) {
command.sendDetailedUsage(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.UseInheritException) {
Message.USE_INHERIT_COMMAND.send(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.InvalidServerException) {
Message.SERVER_INVALID_ENTRY.send(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.PastDateException) {
Message.PAST_DATE_ERROR.send(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.InvalidDateException) {
Message.ILLEGAL_DATE_ERROR.send(sender, ((ArgumentUtils.InvalidDateException) e).getInvalidDate());
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.InvalidPriorityException) {
Message.META_INVALID_PRIORITY.send(sender, ((ArgumentUtils.InvalidPriorityException) e).getInvalidPriority());
return CommandResult.INVALID_ARGS;
}
}
// Not something we can catch.
e.printStackTrace();
return CommandResult.FAILURE;
}
private final List<SharedSubCommand> secondaryCommands;
/**
* If this instance of the shared command is targeting a user. Otherwise, it targets a group.
*/
private boolean user;
public SharedMainCommand(String name, String description, boolean user, List<SharedSubCommand> secondaryCommands) {
@@ -162,12 +133,7 @@ public class SharedMainCommand<T extends PermissionHolder> extends SubCommand<T>
@Override
public boolean isAuthorized(Sender sender) {
for (SharedSubCommand subCommand : secondaryCommands) {
if (subCommand.isAuthorized(sender, user)) {
return true;
}
}
return false;
return secondaryCommands.stream().filter(sc -> sc.isAuthorized(sender, user)).count() != 0;
}
private void sendUsageDetailed(Sender sender, boolean user, String label) {
@@ -190,4 +156,42 @@ public class SharedMainCommand<T extends PermissionHolder> extends SubCommand<T>
Message.COMMAND_NO_PERMISSION.send(sender);
}
}
private static CommandResult handleException(CommandException e, Sender sender, SharedSubCommand command) {
if (e instanceof ArgumentUtils.ArgumentException) {
if (e instanceof ArgumentUtils.DetailedUsageException) {
command.sendDetailedUsage(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.UseInheritException) {
Message.USE_INHERIT_COMMAND.send(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.InvalidServerException) {
Message.SERVER_INVALID_ENTRY.send(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.PastDateException) {
Message.PAST_DATE_ERROR.send(sender);
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.InvalidDateException) {
Message.ILLEGAL_DATE_ERROR.send(sender, ((ArgumentUtils.InvalidDateException) e).getInvalidDate());
return CommandResult.INVALID_ARGS;
}
if (e instanceof ArgumentUtils.InvalidPriorityException) {
Message.META_INVALID_PRIORITY.send(sender, ((ArgumentUtils.InvalidPriorityException) e).getInvalidPriority());
return CommandResult.INVALID_ARGS;
}
}
// Not something we can catch.
e.printStackTrace();
return CommandResult.FAILURE;
}
}
Loaded 100 of 328 files, more files were not shown because too many files have changed in this diff. Show more