Compare commits

...
36 Commits
Author SHA1 Message Date
Luck 096885d91f Add tracing to /lp verbose, API updates/cleanup, add login process event, and utilise string interning for faster context/node comparisons 2017-09-10 21:25:00 +01:00
Frank Zhang 368700156c Added uranium server support to DependencyManager (#464) 2017-09-08 14:25:49 +01:00
Luck 81cf50520b Fix NodeFactory#nodeAsCommand when unsetting meta nodes, fix meta unset command 2017-09-06 21:56:41 +01:00
Luck 649748c2d3 Bump caffeine and hikari versions (#462) 2017-09-05 23:56:45 +01:00
Luck bf7a708440 Send some extra data to the editor data object 2017-09-05 14:54:22 +01:00
Luck 8faa4d137b Fix some issues with the recent import/export changes 2017-09-04 23:20:21 +01:00
Luck 479bd91b74 Fix build 2017-09-04 22:32:02 +01:00
Luck 6bc7f1fca2 Refactor Importer/Exporter to use multiple threads (#317) 2017-09-04 22:24:07 +01:00
Luck dadd8e2c50 Don't publish/broadcast log entries from the import process - towards #317 2017-09-04 20:47:45 +01:00
Luck 03f342a21c Refactor command execution to use Locks per target instead of (effectively) one for all commands - towards #317 2017-09-04 20:42:21 +01:00
Luck 7fe5397e21 Fix incorrect usage of CompletableFuture#thenCombineAsync 2017-09-04 17:56:01 +01:00
Luck b6e1fd179e Add type argument to 'meta clear' command (#457) 2017-09-04 17:25:59 +01:00
Luck 754c7d8e3b Add config option to prevent primary group removal (#405) 2017-09-04 17:03:17 +01:00
Luck 925d3ccd1b Remove "list" alias for holder info (#461) 2017-09-04 16:55:10 +01:00
Luck d60d0ac9c5 Reduce buffer times, refactor MessagingService init 2017-09-04 16:49:43 +01:00
Luck e1b51dd6af Catch Throwable instead of Exception when setting up BukkitJsonMessageHandler 2017-09-04 13:03:09 +01:00
Luck fbe9d9f128 Run user cleanup task async and not on Storage init 2017-09-03 16:16:21 +01:00
Luck 509826949d Add login debug option 2017-08-27 13:54:26 +01:00
Luck 39aaa8a292 Update for latest Text version 2017-08-22 22:51:49 +01:00
Luck 069c76c53a Fix time test for node expiry times 2017-08-22 16:30:46 +01:00
Luck f45c0caa45 Refactor metastacks & primary group calculation 2017-08-21 15:45:29 +02:00
EuropeJing 41b0e10709 Update zh-CN locale (#450) 2017-08-21 10:38:30 +02:00
EuropeJing 8e8da20c13 Update zh-CN locale (#449) 2017-08-20 19:43:58 +02:00
Luck 269aa39378 Fix comparator and ensure @Nonnull annotated methods never return null in LogEntry 2017-08-20 14:51:46 +02:00
Luck ae8be97db7 Dispatch log entries via the messaging service 2017-08-20 13:32:52 +02:00
Luck f0ad40825b Refactor and cleanup Log classes 2017-08-20 01:35:03 +02:00
Luck 36d292747a Format times in /lp log into a shorter form 2017-08-19 11:38:08 +02:00
Luck b65d2262bf Remove "... data was saved to storage" messages 2017-08-19 11:08:28 +02:00
Luck a7ab0c6b36 Send a more helpful message when the non legacy version of LP is installed on Bukkit 1.7 (#437) 2017-08-18 13:58:41 +02:00
Luck c02fcb6508 Refactor weight lookups 2017-08-18 11:14:03 +02:00
Luck 07c38de44b Allow permission info command to be filtered by context (#442) 2017-08-18 10:44:54 +02:00
Luck 188d62c99b Fix issue with casting UUIDs in MongoDB backing (#443) 2017-08-18 10:35:56 +02:00
Luck 76b203ad10 Fix text dependencies being shaded 2017-08-17 10:12:08 +02:00
Luck 909844aef5 Update KyoriPowered/text dependency 2017-08-16 10:56:35 +02:00
Luck 046202719a Correctly export meta/prefix/suffix nodes to command strings 2017-08-14 18:58:12 +02:00
Luck ed15e2838d Fix checkinherits command not returning the inheritance data 2017-08-14 09:38:08 +02:00
225 changed files with 5331 additions and 2251 deletions

No files matched your search

+24 -36
View File
@@ -26,13 +26,10 @@ 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-save-success: "&7(User data was saved to storage)"
user-save-error: "There was an error whilst saving the 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."
@@ -95,34 +92,22 @@ update-task-push-success: "&aOther servers were notified via &b{0} Messaging &as
update-task-push-failure: "&cError whilst pushing changes to other servers."
update-task-push-failure-not-setup: "&cError whilst pushing changes to other servers. A messaging service has not been configured."
reload-config-success: "&aThe configuration file was reloaded. &7(some options will only apply after the server has restarted.)"
info: >
info-top: >
{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}
{PREFIX}&f- &3Server Brand: &f{2}\n
{PREFIX}&f- &3Server Version: &f{3}
info-middle: >
{PREFIX}&f- &bMessaging Type: &f{0}\n
{PREFIX}&f- &bInstance:\n
{PREFIX}&f- &3Server Name: &f{1}\n
{PREFIX}&f- &3Online Players: &a{2}\n
{PREFIX}&f- &3Unique Connections: &a{3}\n
{PREFIX}&f- &3Uptime: &7{4}\n
{PREFIX}&f- &3Local Data: &a{5} &7users, &a{6} &7groups, &a{7} &7tracks\n
{PREFIX}&f- &3Context Calculators: &a{8}\n
{PREFIX}&f- &3Known permissions: &a{9}\n
{PREFIX}&f- &3Active processors: &7{10}
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."
@@ -147,6 +132,7 @@ context-pair--global-inline: "&eglobal"
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-inherits-permission: "&b{0}&a has permission &b{1}&a set to {2}&a in context {3}&a. &7(inherited from &a{4}&7)"
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."
@@ -159,12 +145,12 @@ unset-inherit-success: "&b{0}&a no longer inherits permissions from &b{1}&a in c
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 in context {1}&a. (&b{2}&a nodes were removed.)"
clear-success-singular: "&b{0}&a's permissions were cleared in context {1}&a. (&b{2}&a node was removed.)"
parent-clear-success: "&b{0}&a's parents were cleared in context {1}&a. &b{2}&a nodes were removed.)"
parent-clear-success-singular: "&b{0}&a's parents were cleared in context {1}&a. &b{2}&a node was removed.)"
parent-clear-track-success: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. &b{3}&a nodes were removed.)"
parent-clear-track-success-singular: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. &b{3}&a node was removed.)"
meta-clear-success: "&b{0}&a's meta was cleared in context {1}&a. &b{2}&a nodes were removed.)"
meta-clear-success-singular: "&b{0}&a's meta was cleared in context {1}&a. &b{2}&a node was removed.)"
parent-clear-success: "&b{0}&a's parents were cleared in context {1}&a. (&b{2}&a nodes were removed.)"
parent-clear-success-singular: "&b{0}&a's parents were cleared in context {1}&a. (&b{2}&a node was removed.)"
parent-clear-track-success: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. (&b{3}&a nodes were removed.)"
parent-clear-track-success-singular: "&b{0}&a's parents on track &b{1}&a were cleared in context {2}&a. (&b{3}&a node was removed.)"
meta-clear-success: "&b{0}&a's meta matching type &b{1}&a was cleared in context {2}&a. (&b{3}&a nodes were removed.)"
meta-clear-success-singular: "&b{0}&a's meta matching type &b{1}&a was cleared in context {2}&a. (&b{3}&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"
@@ -261,6 +247,7 @@ 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-console: "&cCannot toggle notifications for console."
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."
@@ -606,8 +593,9 @@ command-specs:
"suffix": "the suffix string"
"context...": "the contexts to remove the suffix in"
meta-clear:
description: "Clears all chat meta"
description: "Clears all meta"
args:
"type": "the type of meta to remove"
"context...": "the contexts to filter by"
track-info:
description: "Gives info about the track"
+625 -190
View File
@@ -9,85 +9,94 @@ log-error: "&7&l[&bLuck&3Perms&7&l] &4[错误] {0}"
empty: "{0}"
player-online: "&a在线"
player-offline: "&c离线"
loading-error: "无法加载权限数据,请稍后再试."
op-disabled: "&b此服务器上禁用了vanilla OP系统."
op-disabled-sponge: "&2服务器管理员状态在安装权限插件时无效,请直接编辑用户数据."
loading-error: "无法加载权限数据,请稍后再试。"
op-disabled: "&b此服务器上禁用了vanilla OP系统。"
op-disabled-sponge: "&2安装了权限插件时,服务器OP状态无效,请直接编辑用户数据。"
log: "&3日志 &3&l> {0}"
verbose-log: "&3详细 &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-not-recognised: "命令未被识别。"
command-no-permission: "你没有权限使用这个命令!"
already-haspermission: "{0}已经有这个权限!"
does-not-havepermission: "{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任何数据."
does-not-have-temp-permission: "{0}没有设置此临时权限。"
user-not-found: "&b找不到用户。"
user-not-online: "&b用户&a{0}&b不在线。"
user-save-success: "&7(保存用户数据到文件)"
user-save-error: "保存用户数据时出错."
user-create-fail: "创建新用户时出错."
group-not-found: "&b找不到组."
user-save-error: "保存用户数据时出错。"
group-not-found: "&b找不到组。"
group-save-success: "&7(保存组数据到文件)"
group-save-error: "保存组数据时出错."
track-not-found: "&b无法找到轨道."
track-save-success: "&7(保存轨道到文件)"
track-save-error: "保存轨道时出错."
user-invalid-entry: "&d{0}&c不是有效的用户名/uuid."
group-invalid-entry: "组名只能包含字母数字字符."
track-invalid-entry: "轨道名称只能包含字母数字字符."
server-invalid-entry: "服务器名称只能包含字母数字字符."
use-inherit-command: "使用'parent add'和'parent remove'命令,而不是指定节点."
group-save-error: "保存组数据时出错。"
track-not-found: "&b无法找到权限组轨道。"
track-save-success: "&7(保存权限组轨道到文件)"
track-save-error: "保存权限组轨道时出错。"
user-invalid-entry: "&d{0}&c不是有效的用户名/uuid。"
group-invalid-entry: "组名只能包含字母数字字符。"
track-invalid-entry: "权限组轨道名称只能包含字母数字字符。"
server-world-invalid-entry: "服务器/世界名称只能包含字母数字字符,长度不能超过36个字符。"
use-inherit-command: "使用'parent add'和'parent remove'命令,而不是指定节点。"
verbose-invalid-filter: "&c无效的详细过滤器: &f{0}"
verbose-on: "&b所有权限的详细输出设置为&a开&b."
verbose-on-query: "&b将匹配过滤器:&f{0}&b的权限详细输出设置为&a开&b."
verbose-off: "&b所有权限的详细输出设置为&c关&b."
verbose-recording-on: "&b所有权限的详细记录设置为&a开&b."
verbose-recording-on-query: "&b将匹配过滤器:&f{0}&b的权限详细记录设置为&a开&b."
verbose-recording-upload-start: "&b详细记录被禁用. 正在上传结果..."
verbose-on: "&b所有权限的详细输出设置为&a开&b。"
verbose-on-query: "&b将匹配过滤器:&f{0}&b的权限详细输出设置为&a开&b。"
verbose-off: "&b所有权限的详细输出设置为&c关&b。"
verbose-recording-on: "&b所有权限的详细记录设置为&a开&b。"
verbose-recording-on-query: "&b将匹配过滤器:&f{0}&b的权限详细记录设置为&a开&b。"
verbose-recording-upload-start: "&b详细记录被禁用。正在上传结果..."
verbose-recording-url: "&a详细结果的网址:"
tree-upload-start: "&b生成权限树..."
tree-empty: "&a无法生成树,没有找到结果."
tree-empty: "&a无法生成树,没有找到结果。"
tree-url: "&a权限树网址:"
search-searching: "&a使用&b{0}&a搜索用户和组..."
search-searching-members: "&a正在搜索继承自&b{0}&a的用户和组..."
search-result: "&a从&b{1}&a用户和&b{2}&a组找到&b{0}&a个条目."
search-result: "&a从&b{1}&a用户和&b{2}&a组找到&b{0}&a个条目。"
search-showing-users: "&b显示用户条目:"
search-showing-groups: "&b显示组条目:"
search-showing-users-with-page: "&b显示用户条目: {0}"
search-showing-groups-with-page: "&b显示组条目: {0}"
apply-edits-invalid-code: "&a无效的代码。&7({0})"
apply-edits-unable-to-read: "&a无法使用给定代码读取数据。&7({0})"
apply-edits-no-target: "&a无法解析编辑的目标。请提供它作为一个额外的参数。"
apply-edits-target-group-not-exists: "&a目标组&b{0}&a不存在。"
apply-edits-target-user-not-uuid: "&a目标用户&b{0}&a不是一个有效的uuid。"
apply-edits-target-user-unable-to-load: "&a无法加载目标用户&b{0}&a。"
apply-edits-target-unknown: "&a目标无效。&7({0})"
apply-edits-success: "&a成功应用&b{0}&a节点到&b{1}&a。"
editor-upload-failure: "&c无法将权限数据上传到编辑器。"
editor-url: "&a编辑器URL:"
check-result: "&a用户&b{0}&a获得允许&b{1}&a权限的检查结果&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: "轨道{0}已经包含组'{1}'."
track-does-not-contain: "轨道{0}不包含组'{1}'."
track-ambiguous-call: "指定的用户是此轨道上多个组的成员,无法确定他们的位置."
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: "权限组轨道{0}已经包含组'{1}'。"
track-does-not-contain: "权限组轨道{0}不包含组'{1}'。"
track-ambiguous-call: "指定的用户是此权限组轨道上多个组的成员,无法确定他们的位置。"
group-already-exists: "该组已存在!"
group-does-not-exist: "组不存在!"
group-load-error: "发生意外错误,组未加载."
groups-load-error: "发生意外错误,无法加载所有组."
track-already-exists: "轨道已存在!"
track-does-not-exist: "轨道不存在!"
track-load-error: "发生意外错误. 轨道未加载."
tracks-load-error: "出现意外错误,无法加载所有轨道."
track-empty: "轨道不能使用,因为它是空的或只包含一个组."
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(一些选项将仅在服务器重新启动后才应用.)"
group-load-error: "发生意外错误,组未加载。"
groups-load-error: "发生意外错误,无法加载所有组。"
track-already-exists: "权限组轨道已存在!"
track-does-not-exist: "权限组轨道不存在!"
track-load-error: "发生意外错误。权限组轨道未加载。"
tracks-load-error: "出现意外错误,无法加载所有权限组轨道。"
track-empty: "权限组轨道不能使用,因为它是空的或只包含一个组。"
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}&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
@@ -97,7 +106,7 @@ info: >
{PREFIX}&f- &3在线玩家: &a{6}\n
{PREFIX}&f- &3加载用户: &a{7}\n
{PREFIX}&f- &3加载组: &a{8}\n
{PREFIX}&f- &3加载轨道: &a{9}\n
{PREFIX}&f- &3加载权限组轨道: &a{9}\n
{PREFIX}&f- &3日志大小: &a{10}\n
{PREFIX}&f- &3UUID缓存大小: &a{11}\n
{PREFIX}&f- &3翻译加载: &a{12}\n
@@ -114,57 +123,49 @@ info: >
{PREFIX}&f- &3应用通配符: {21}\n
{PREFIX}&f- &3应用正则表达式: {22}\n
{PREFIX}&f- &3应用速记: {23}
create-group-error: "创建组时出错."
delete-group-error: "删除组时出错."
delete-group-error-default: "您不能删除默认组."
groups-list: "&aGroups: {0}"
create-track-error: "创建轨道时出错."
delete-track-error: "删除轨道时出错."
tracks-list: "&a轨道: {0}"
create-group-error: "创建组时出错。"
delete-group-error: "删除组时出错。"
delete-group-error-default: "您不能删除默认组。"
groups-list: "&a组: &7(名字,权重,权限组轨道)"
groups-list-entry: "&f- &3{0} &7- &b{1}"
groups-list-entry-with-tracks: "&f- &3{0} &7- &b{1} &7- [&3{2}&7]"
create-track-error: "创建权限组轨道时出错。"
delete-track-error: "删除权限组轨道时出错。"
tracks-list: "&a权限组轨道: {0}"
listnodes: "&b{0}的节点:"
listnodes-with-page: "&b{0}的节点: {1}"
listnodes-temp: >
&b{0}的临时节点:\n
{1}
listparents: >
&b{0}的父组:\n
{1}
listparents-temp: >
&b{0}的临时父组:\n
{1}
listgroups: >
&b{0}的组:\n
{1}
listgroups-temp: >
&b{0}的临时组:\n
{1}
listnodes-temp: "&b{0}的临时节点:"
listnodes-temp-with-page: "&b{0}的临时节点: {1}"
listparents: "&b{0}的父组"
listparents-temp: "&b{0}的临时父组:"
list-tracks: >
&b{0}的轨道:\n
&b{0}的权限组轨道:\n
{1}
list-tracks-empty: "{0}不在任何轨道上."
list-tracks-empty: "{0}不在任何权限组轨道上。"
context-pair-inline: "&3{0}=&b{1}"
context-pair--global-inline: "&e全局"
context-pair-end: "&a."
context-pair-sep: "&a, "
context-pair-sep: "&a,"
context-pair: "&8(&7{0}=&f{1}&8)"
check-permission: "&b在环境&b{3}&b中,&b{0}&b的权限&b{1}&b被设置为&b{2}&b."
check-permission-inherited: "&b{0}&a在环境&b{3}&a中的&b{1}&a被设置为&b{2}&a,继承自&b{4}&a."
setpermission-success: "&a在&b{3}&a中为&b{2}&a设置&b{0}&a为&b{1}&a."
setpermission-temp-success: "&a在环境&b{4}&a将&b{0}&a的&b{2}&a设为&b{1}&a,持续时间&b{3}&a."
unsetpermission-success: "&a在环境&b{2}&a中取消&b{1}&a的&b{0}&a."
unset-temp-permission-success: "&a在环境&b{2}&a中取消设置&b{1}&a的临时许可&b{0}&a."
set-inherit-success: "&b{0}&a现在在环境&b{2}&a中继承&b{1}&a的权限&a."
set-temp-inherit-success: "&b{0}&a现在在环境&b{3}&a中继承&b{1}&a的权限,持续时间&b{2}&a."
set-parent-success: "&b{0}&a现有的父组被清除,现在只在&b{2}&a中继承&b{1}&a."
unset-inherit-success: "&b{0}&a不再继承环境&b{2}&a中的&b{1}&a权限."
unset-temp-inherit-success: "&b{0}&a在环境{2}中不再临时从&b{1}&a继承权限."
clear-success: "&b{0}&a的权限被清除. (&b{1}&a节点被删除.)"
clear-success-singular: "&b{0}&a的权限被清除. (&b{1}&a节点已被删除.)"
parent-clear-success: "&b{0}&a的组被清除. (&b{1}&a节点被删除.)"
parent-clear-success-singular: "&b{0}&a的组被清除. (&b{1}&a节点已被删除.)"
meta-clear-success: "&b{0}&a的父组被清除. (&b{1}&a节点被删除.)"
meta-clear-success-singular: "已经清除了这个&b{0}&a的元数据. (&b{1}&a节点已被删除.)"
illegal-date-error: "无法解析日期'{0}'."
check-permission: "&b在环境&b{3}&b中,&b{0}&b的权限&b{1}&b被设置为&b{2}&b。"
setpermission-success: "&a在环境&b{3}&a中将&b{2}&a的&b{0}&a权限设置为&b{1}&a。"
setpermission-temp-success: "&a在环境&b{4}&a中将&b{2}&a的&b{0}&a临时权限设为&b{1}&a,持续时间&b{3}&a。"
unsetpermission-success: "&a在环境&b{2}&a中取消&b{1}&a的&b{0}&a权限。"
unset-temp-permission-success: "&a在环境&b{2}&a中取消&b{1}&a的临时权限&b{0}&a。"
set-inherit-success: "&b{0}&a现在在环境&b{2}&a中继承&b{1}&a的权限&a。"
set-temp-inherit-success: "&b{0}&a现在在环境&b{3}&a中继承&b{1}&a的权限,持续时间&b{2}&a。"
set-parent-success: "&b{0}&a现有的父组被清除,现在只在&b{2}&a中继承&b{1}&a。"
set-track-parent-success: "在权限组轨道&b{1}&a上&b{0}&a现有的父组被清除,现在只在&b{3}&a中继承&b{2}&a。"
unset-inherit-success: "&b{0}&a不再继承环境&b{2}&a中&b{1}&a的权限。"
unset-temp-inherit-success: "&b{0}&a在环境&b{2}&a中不再继承临时权限&b{1}&a。"
clear-success: "&b{0}&a的权限在环境&b{1}&a中被清除。(&b{2}&a节点已被删除。)"
clear-success-singular: "&b{0}&a的权限在环境&b{1}&a中被清除。(&b{2}&a节点已被删除。)"
parent-clear-success: "&b{0}&a的父组在环境&b{1}&a中被清除。&b{2}&a节点被删除。)"
parent-clear-success-singular: "&b{0}&a的父组在环境&b{1}&a中被清除。&b{2}&a节点被删除。)"
parent-clear-track-success: "在权限组轨道&b{1}&a上,&b{0}&a的父组在环境&b{2}&a中被清除。&b{3}&a节点被删除。)"
parent-clear-track-success-singular: "在权限组轨道&b{1}&a上,&b{0}&a的父组在环境&b{2}&a中被清除。&b{3}&a节点被删除。)"
meta-clear-success: "&b{0}&a的元数据在环境&b{1}&a中被清除。&b{2}&a节点已被删除。)"
meta-clear-success-singular: "&b{0}&a的元数据在环境&b{1}&a中被清除。&b{2}&a节点已被删除。)"
illegal-date-error: "无法解析日期'{0}'。"
past-date-error: "您不能设定一个过去的日期!"
chat-meta-prefix-header: "&b{0}的前缀"
chat-meta-suffix-header: "&b{0}的后缀"
@@ -173,36 +174,34 @@ 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-invalid-priority: "优先级'{0}'无效. 应该是一个数字."
already-has-chat-meta: "{0}已经设置了{1}."
does-not-have-chat-meta: "{0}没有设置{1}."
add-chatmeta-success: "&b{0}&a在环境&b{4}&a中将优先级&b{3}&a设置为 {1} &f\"{2}&f\"."
add-temp-chatmeta-success: "&b{0}&a在环境&b{5}&a中,设置为优先级&b{3}&a的{1} &f\"{2}&f\"&a,持续时间为&b{4}&a."
remove-chatmeta-success: "&b{0}&a在&b{3}&a中删除了优先级&b{3}&a的{1} &f\"{2}&f\"&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在环境&b{4}&a中删除了优先级&b{3}&a的临时{1} &f\"{2}&f\"&a."
bulk-remove-temp-chatmeta-success: "&b{0}&a在环境&b{4}&a中移除了优先级&b{3}&a的所有临时&b{1}&a.."
already-has-meta: "{0}已经有了这个元信息集."
set-meta-success: "将环境&b{3}&a中的&b{2}&a的键&f\"{0}&f\"&a的元信息设置为&f\"{1}&f\"&a."
set-meta-temp-success: "&a在环境&b{4}&a中将&b{2}&a的键&f\"{0}&f\"&a的值设置为&f\"{1}&f\"&a,持续时间为&b{3}&a."
unset-meta-success: "&a在环境{2}中取消设置&b{1}&a的键&f\"{0}&f\"&a的元信息."
unset-meta-temp-success: "&a在环境{2}中取消设置&b{1}&a的键&f\"{0}&f\"&a的临时元信息."
bulk-update-invalid-data-type: "Invalid type. Was expecting 'all', 'users' or 'groups'."
bulk-update-invalid-constraint: "无效的约束&4{0}&c.约束的格式应该是'&f<field> <comparison operator> <value>&c'."
bulk-update-invalid-comparison: "比较运算符无效'&4{0}&c'. 应该以下之一: &f== != ~~ ~!"
bulk-update-queued: "&a批量更新操作已排队.&7(&f{0}&7)"
bulk-update-confirm: "&a运行&b/{0} bulkupdate &a确认{1}执行更新."
bulk-update-unknown-id: "&a使用id&b{0}&a的操作不存在或已过期."
bulk-update-starting: "&a运行批量更新."
bulk-update-success: "&b量更新已成功完成."
bulk-update-failure: "&cBulk update failed. Check the console for errors."
bulk-change-type-error: "类型无效. 应该是 'server' 或 'world'."
bulk-change-success: "&a应用批量更改成功. {0}记录已更改."
chat-meta-prefix-none: "&b{0}没有前缀。"
chat-meta-suffix-none: "&b{0}没有后缀。"
meta-none: "&b{0}没有元信息。"
meta-invalid-priority: "优先级'{0}'无效。应该是一个数字。"
already-has-chat-meta: "{0}已经设置了{1}。"
does-not-have-chat-meta: "{0}没有设置{1}。"
add-chatmeta-success: "在环境&b{4}&a中将&b{0}&a的{1}设置为&f\"{2}&f\",优先级&b{3}&a。"
add-temp-chatmeta-success: "在环境&b{5}&a中将&b{0}&a的{1}设置为&f\"{2}&f\"&a,优先级&b{3}&a,持续时间为&b{4}&a。"
remove-chatmeta-success: "在环境&b{3}&a中删除了&b{0}&a优先级为&b{3}&a的{1} &f\"{2}&f\"&a。"
bulk-remove-chatmeta-success: "在环境&b{3}&a中删除了&b{0}&a优先级&b{2}&a的所有&b{1}&a。"
remove-temp-chatmeta-success: "在环境&b{4}&a中删除了&b{0}&a优先级&b{3}&a的临时{1} &f\"{2}&f\"&a。"
bulk-remove-temp-chatmeta-success: "在环境&b{3}&a中删除了&b{0}&a优先级&b{2}&a的所有临时&b{1}&a。"
already-has-meta: "{0}已经有了这个元信息集。"
set-meta-success: "将环境&b{3}&a中&b{2}&a的键&f\"{0}&f\"&a的元信息设置为&f\"{1}&f\"&a。"
set-meta-temp-success: "&a在环境&b{4}&a中将&b{2}&a的键&f\"{0}&f\"&a的值设置为&f\"{1}&f\"&a,持续时间为&b{3}&a。"
unset-meta-success: "&a在环境{2}中取消设置&b{1}&a的键&f\"{0}&f\"&a的元信息。"
unset-meta-temp-success: "&a在环境{2}中取消设置&b{1}&a的键&f\"{0}&f\"&a的临时元信息。"
bulk-update-invalid-data-type: "类型无效。期望类型是'全部'、'用户'或'组'。"
bulk-update-invalid-constraint: "无效的约束&4{0}&c。约束的格式应该是'&f<field> <comparison operator> <value>&c'。"
bulk-update-invalid-comparison: "比较运算符无效'&4{0}&c'。应该以下之一: &f== != ~~ ~!"
bulk-update-queued: "&a批量更新操作已排队。&7(&f{0}&7)"
bulk-update-confirm: "&a运行&b/{0} bulkupdate &a确认{1}执行更新。"
bulk-update-unknown-id: "&a使用id&b{0}&a的操作不存在或已过期。"
bulk-update-starting: "&a运行批量更新。"
bulk-update-success: "&b批量更新已成功完成。"
bulk-update-failure: "&c批量更新失败。检查控制台是否有错误。"
user-info-general: >
{PREFIX}&b&l> &bUser Info: &f{0}\n
{PREFIX}&b&l> &b用户信息: &f{0}\n
{PREFIX}&f- &3UUID: &f{1}\n
{PREFIX}&f- &3状态: {2}\n
{PREFIX}&f- &3主组: &f{3}\n
@@ -213,34 +212,30 @@ user-info-general: >
{PREFIX}&f- &3后缀: &a{7}\n
{PREFIX}&f- &3元信息: &a{8}
user-info-data: >
{PREFIX}&f- &aCached Data:\n
{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: "&b{0}&b的UUID是&b{1}&b."
user-removegroup-error-primary: "您不能从其主组中删除用户."
user-primarygroup-success: "&b{0}&的主组已设为&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: "指定的用户尚未在此轨道上的任何组中."
user-track-added-to-first: "&b{0}&a不在此轨道上的任何组中, 因此在环境&b{2}&a中将其添加到第一组&b{2}&a."
user-promote-success: "&a在环境&b{3}&a中将用户沿轨道&b{0}&a从&b{1}&a提升到&b{2}&a."
user-promote-error-endoftrack: "已达到轨道&4{0}&c的结尾,所以无法提升用户."
user-removegroup-error-primary: "您不能从其主组中删除用户。"
user-primarygroup-success: "&b{0}&a的主组已设为&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: "指定的用户尚未在此权限组轨道上的任何组中。"
user-track-added-to-first: "&b{0}&a不在此权限组轨道上的任何组中,因此在环境&b{2}&a中将其添加到第一组&b{2}&a。"
user-promote-success: "&a在环境&b{3}&a中将用户沿权限组轨道&b{0}&a从&b{1}&a提升到&b{2}&a。"
user-promote-error-endoftrack: "已达到权限组轨道&4{0}&c的结尾,所以无法提升用户。"
user-promote-error-malformed: >
{PREFIX}轨道上的下一个组{0}不存在. 无法提升用户.\n
{PREFIX}创建组,或将其从轨道中删除,然后重试.
user-demote-success: "&a在环境&b{3}&a中将用户沿轨道&b{0}&a从&b{1}&a转移到&b{2}&a."
user-demote-endoftrack: "已达到轨道&4{0}&c的结尾,所以&4{1}&c已从&4{2}&c中移除."
{PREFIX}权限组轨道上的下一个组{0}不存在。无法提升用户。\n
{PREFIX}创建组,或将其从权限组轨道中删除,然后重试。
user-demote-success: "&a在环境&b{3}&a中将用户沿权限组轨道&b{0}&a从&b{1}&a下降到&b{2}&a。"
user-demote-endoftrack: "已达到权限组轨道&4{0}&c的结尾,所以&4{1}&c已从&4{2}&c中移除。"
user-demote-error-malformed: >
{PREFIX}轨道上的上一组{0}不存在.无法降级用户.\n
{PREFIX}创建组,或将其从轨道中删除,然后重试.
user-showpos: >
&a显示&b{0}&a在轨道&b{1}&a的位置.\n
{2}
{PREFIX}权限组轨道上的上一组{0}不存在。无法降级用户。\n
{PREFIX}创建组,或将其从权限组轨道中删除,然后重试。
group-info-general: >
{PREFIX}&b&l> &b组信息: &f{0}\n
{PREFIX}&f- &3显示名称: &f{1}\n
@@ -251,53 +246,493 @@ group-info-general: >
{PREFIX}&f- &3前缀: &a{5}\n
{PREFIX}&f- &3后缀: &a{6}\n
{PREFIX}&f- &3元信息: &a{7}
group-set-weight: "&a将组&b{1}&a的权重设置为&b{0}&a."
group-set-weight: "&a将组&b{1}&a的权重设置为&b{0}&a。"
track-info: >
{PREFIX}&b&l> &b显示轨道: &f{0}\n
{PREFIX}&b&l> &b显示权限组轨道: &f{0}\n
{PREFIX}&f- &7路径: &f{1}
track-clear: "&b{0}&a's groups track was cleared."
track-append-success: "&a组&b{0}&a已成功添加到轨道&b{1}&a."
track-insert-success: "&a组&b{0}&a已成功插入轨道&b{1}&a的&b{2}&a位置."
track-insert-error-number: "没有预期的数量,而是收到: {0}"
track-insert-error-invalid-pos: "无法插入位置{0}. 索引超出范围."
track-remove-success: "&aGroup &b{0}&a已成功从轨道&b{1}&a移除."
log-load-error: "日志无法加载."
log-invalid-page: "页码无效."
log-invalid-page-range: "页码无效,请输入1和{0}之间的值."
log-no-entries: "&b没有日志条目显示."
track-clear: "&b{0}&a的权限组轨道已被清除。"
track-append-success: "&a组&b{0}&a已成功添加到权限组轨道&b{1}&a。"
track-insert-success: "&a组&b{0}&a已成功插入权限组轨道&b{1}&a的&b{2}&a位置。"
track-insert-error-number: "没有预期的数量,而是收到: {0}"
track-insert-error-invalid-pos: "无法插入位置{0}。索引超出范围。"
track-remove-success: "&a组&b{0}&a已成功从权限组轨道&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显示轨道的历史记录 &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}完成 &7- &b{1}&f/&b{2} &f完成 &c{3}&f错误."
import-progress-sin: "&b(导入) &b-> &f百分之{0}完成 &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}错误."
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(&f{1}&a/&f{2}&a)"
log-recent-header: "&a显示最近操作 (&f{0}&a/&f{1}&a)"
log-recent-by-header: "&a显示&b{0}&a的最近操作 (&f{1}&a/&f{2}&a)"
log-history-user-header: "&a显示用户的历史记录&b{0} &a(&f{1}&a/&f{2}&a)"
log-history-group-header: "&a显示组的历史记录 &b{0} &a(&f{1}&a/&f{2}&a)"
log-history-track-header: "&a显示权限组轨道的历史记录 &b{0} &a(&f{1}&a/&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}完成 &7- &b{1}&f/&b{2} &f完成 &c{3}&f错误。"
import-progress-sin: "&b(导入) &b-> &f百分之{0}完成 &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(导入) &7------------> &f显示错误 #&b{0} &7<------------\n
{PREFIX}&b(导入) &f执行时: &3Command #{1}\n
{PREFIX}&b(导入) &f执行: &3命令 #{1}\n
{PREFIX}&b(导入) &f命令: &7{2}\n
{PREFIX}&b(导入) &f类型: &3{3}\n
{PREFIX}&b(导入) &f输出:
import-end-error-content: "&b(导入) &b-> &c{0}"
import-end-error-footer: "&b(导入) &7<------------------------------------------>"
import-end-error-footer: "&b(导入) &7<------------------------------------------>"
command-specs:
user:
description: "用户命令"
usage: "/%s user <用户>"
group:
description: "组命令"
usage: "/%s group <组>"
track:
description: "权限组轨道指令"
usage: "/%s track <权限组轨道>"
log:
description: "日志命令"
usage: "/%s log"
sync:
description: "与存储同步更改"
usage: "/%s sync"
info:
description: "打印通用插件信息"
usage: "/%s info"
verbose:
description: "管理详细权限检查"
usage: "/%s verbose <true|false> [filter]"
args:
"on|record|off|paste": "是否启用/禁用日志记录,或粘贴记录的输出"
"filter": "过滤器匹配项"
tree:
description: "生成权限的树视图"
usage: "/%s tree [selection] [max level] [player]"
args:
"selection": "树的根,指定\".\"包括所有权限"
"max level": "应该返回多少分支级别"
"player": "要检查的在线玩家的名称"
search:
description: "搜索具有特定权限的用户/组"
usage: "/%s search <permission>"
args:
"permission": "权限搜索"
"page": "页面查看"
check:
description: "对用户执行标准的权限检查"
usage: "/%s check <user> <permission>"
args:
"user": "检查用户"
"permission": "允许检查"
network-sync:
description: "与存储同步更改并请求网络上所有其他服务器都同步"
usage: "/%s networksync"
import:
description: "从文件导入数据"
usage: "/%s import <file>"
args:
"file": "要导入的文件"
export:
description: "将数据导出到文件"
usage: "/%s export <file>"
args:
"file": "要导出的文件"
reload-config:
description: "重新加载部分配置选项"
usage: "/%s reloadconfig"
bulk-update:
description: "对所有数据执行批量更改查询"
usage: "/%s bulkupdate"
args:
"data type": "正在更改的数据类型。('all'、'users'、'groups')"
"action": "对数据执行的操作。('update' or 'delete')"
"action field": "要采取行动的领域。只需要'update'。('permission'、'server'、'world')"
"action value": "替换的值。只需要'update'。"
"constraint...": "更新所需的限制"
migration:
description: "迁移命令"
usage: "/%s migration"
apply-edits:
description: "应用从Web编辑器进行的权限更改"
usage: "/%s applyedits <code> [target]"
args:
"code": "数据的唯一代码"
"target": "应用数据到谁"
create-group:
description: "创建新组"
usage: "/%s creategroup <group>"
args:
"name": "组的名称"
delete-group:
description: "删除组"
usage: "/%s deletegroup <group>"
args:
"name": "组的名称"
list-groups:
description: "列出平台上的所有组"
usage: "/%s listgroups"
create-track:
description: "创建新权限组轨道"
usage: "/%s createtrack <track>"
args:
"name": "权限组轨道的名称"
delete-track:
description: "删除权限组轨道"
usage: "/%s deletetrack <track>"
args:
"name": "权限组轨道的名称"
list-tracks:
description: "列出平台上的所有权限组轨道"
usage: "/%s listtracks"
user-info:
description: "显示有关用户的信息"
user-switchprimarygroup:
description: "切换用户的主组"
args:
"group": "组切换到"
user-promote:
description: "在权限组轨道提升用户"
args:
"track": "提升用户的权限组轨道"
"context...": "提升用户使用的环境"
user-demote:
description: "在权限组轨道降低用户"
args:
"track": "降低用户的权限组轨道"
"context...": "降低用户使用的环境"
group-info:
description: "提供有关组的信息"
group-listmembers:
description: "显示从该组继承的用户/组"
args:
"page": "页面查看"
group-setweight:
description: "设置组权重"
args:
"weight": "设定权重"
group-rename:
description: "重命名组"
args:
"name": "新名字"
group-clone:
description: "克隆组"
args:
"name": "要克隆到的组的名称"
holder-editor:
description: "打开网页权限编辑器"
holder-showtracks:
description: "列出对象所在的权限组轨道"
holder-clear:
description: "删除所有权限,父组和元"
args:
"context...": "过滤的环境"
permission:
description: "编辑权限"
parent:
description: "编辑继承"
meta:
description: "编辑元数据值"
permission-info:
description: "列出对象拥有的权限节点"
args:
"page": "页面查看"
"filter": "要过滤的字符串"
permission-set:
description: "设置对象的权限"
args:
"node": "要添加的权限"
"true|false": "节点的值"
"context...": "添加权限的环境"
permission-unset:
description: "取消对象的权限"
args:
"node": "取消的权限节点"
"context...": "删除权限的环境"
permission-settemp:
description: "设置对象的临时权限"
args:
"node": "要添加的权限"
"true|false": "节点的值"
"duration": "到期时间"
"context...": "权限的环境"
permission-unsettemp:
description: "取消对对象的临时权限"
args:
"node": "要取消的权限"
"context...": "权限的环境"
permission-check:
description: "检查对象是否具有某个权限节点"
args:
"node": "要检查的权限"
"context...": "权限的环境"
permission-check-inherits:
description: "检查对象是否继承某个权限节点"
args:
"node": "要检查的权限"
"context...": "权限的环境"
parent-info:
description: "列出此对象从哪里继承的组"
parent-set:
description: "删除对象已经继承的所有其他组,并将其添加到给定的对象组中。"
args:
"group": "组设为"
"context...": "设置组的环境"
parent-add:
description: "为该对象设置另一个组来继承权限"
args:
"group": "要继承的组"
"context...": "继承该组的环境"
parent-remove:
description: "删除先前设置的继承规则"
args:
"group": "删除的组"
"context...": "删除组的环境"
parent-set-track:
description: "删除对象在给定权限组轨道上继承的所有其他组,并将其添加到给定的权限组轨道上。"
args:
"track": "设置的权限组轨道"
"group": "组中设置的组,或与组中在给定权限组轨道上的位置相关的数字"
"context...": "设置组的环境"
parent-add-temp:
description: "为对象设置另一个组,以便从临时继承权限"
args:
"group": "要继承的组"
"duration": "持续时间"
"context...": "继承该组的环境"
parent-remove-temp:
description: "删除以前设置的临时继承规则"
args:
"group": "组删除"
"context...": "删除组的环境"
parent-clear:
description: "清除所有的父组"
args:
"context...": "过滤的环境"
parent-clear-track:
description: "清除给定权限组轨道上的所有父组"
args:
"track": "删除的权限组轨道"
"contexts...": "选择的环境"
meta-info:
description: "显示所有聊天元"
meta-set:
description: "设置元值"
args:
"key": "设定的键"
"value": "要设置的值"
"context...": "将元添加到"
meta-unset:
description: "取消设定元值"
args:
"key": "要取消的键"
"context...": "删除元的环境"
meta-settemp:
description: "暂时设置一个元值"
args:
"key": "设定的键"
"value": "要设置的值"
"duration": "持续时间"
"context...": "将元组添加到"
meta-unsettemp:
description: "取消设置临时元值"
args:
"key": "要取消的键"
"context...": "删除元的环境"
meta-addprefix:
description: "添加前缀"
args:
"priority": "添加的前缀优先级"
"prefix": "前缀字符串"
"context...": "添加前缀的环境"
meta-addsuffix:
description: "添加后缀"
args:
"priority": "添加的后缀优先级"
"suffix": "后缀字符串"
"context...": "添加后缀的环境"
meta-removeprefix:
description: "删除前缀"
args:
"priority": "删除的前缀优先级"
"prefix": "前缀字符串"
"context...": "删除前缀的环境"
meta-removesuffix:
description: "删除后缀"
args:
"priority": "删除的后缀优先级"
"suffix": "后缀字符串"
"context...": "删除后缀的环境"
meta-addtemp-prefix:
description: "添加临时前缀"
args:
"priority": "删除的前缀优先级"
"prefix": "前缀字符串"
"duration": "持续时间"
"context...": "添加前缀的环境"
meta-addtemp-suffix:
description: "添加临时后缀"
args:
"priority": "添加的后缀优先级"
"suffix": "后缀字符串"
"duration": "持续时间"
"context...": "添加后缀的环境"
meta-removetemp-prefix:
description: "删除临时前缀"
args:
"priority": "删除的前缀优先级"
"prefix": "前缀字符串"
"context...": "删除前缀的环境"
meta-removetemp-suffix:
description: "删除临时后缀"
args:
"priority": "删除的后缀优先级"
"suffix": "后缀字符串"
"context...": "删除后缀的环境"
meta-clear:
description: "清除所有聊天元"
args:
"context...": "过滤的环境"
track-info:
description: "提供有关权限组轨道的信息"
track-append:
description: "追加一个组到权限组轨道的最后"
args:
"group": "追加的组"
track-insert:
description: "在权限组轨道上的给定位置插入一个组"
args:
"group": "插入的组"
"position": "插入组的位置(权限组轨道上的第一个位置为1)"
track-remove:
description: "从权限组轨道中删除一个组"
args:
"group": "删除的组"
track-clear:
description: "清除权限组轨道上的组"
track-rename:
description: "重命名权限组轨道"
args:
"name": "新名字"
track-clone:
description: "克隆权限组轨道"
args:
"name": "要克隆的权限组轨道的名称"
log-recent:
description: "查看最近的登陆"
args:
"user": "用户的名称/uuid"
"page": "要查看的页码"
log-search:
description: "搜索日志中的条目"
args:
"query": "搜索的查询"
"page": "要查看的页码"
log-notify:
description: "切换日志通知"
args:
"on|off": "是否打开或关闭"
log-user-history:
description: "查看用户的历史记录"
args:
"user": "用户名/ uuid"
"page": "要查看的页码"
log-group-history:
description: "查看组的历史"
args:
"group": "组名"
"page": "要查看的页码"
log-track-history:
description: "查看权限组轨道的历史"
args:
"track": "权限组轨道的名称"
"page": "要查看的页码"
sponge:
description: "编辑额外的Sponge数据"
usage: "/%s sponge <collection> <subject>"
args:
"collection": "要查询的集合"
"subject": "修改的主题"
sponge-permission-info:
description: "显示有关主题权限的信息"
args:
"contexts...": "选择的环境"
sponge-permission-set:
description: "设置一个主题的权限"
args:
"node": "要设置的权限节点"
"tristate": "设置允许值"
"contexts...": "选择的环境"
sponge-permission-clear:
description: "清除一个主题的权限"
args:
"contexts...": "清除的环境"
sponge-parent-info:
description: "显示关于该主题父项的信息"
args:
"contexts...": "选择的环境"
sponge-parent-add:
description: "向主题添加父项"
args:
"collection": "父主题的主题集合"
"subject": "父主题的名称"
"contexts...": "添加父项的环境"
sponge-parent-remove:
description: "从主题中删除父项"
args:
"collection": "父主题的主题集合"
"subject": "父主题的名称"
"contexts...": "删除父项的环境"
sponge-parent-clear:
description: "清除主题父项"
args:
"contexts...": "清除父项的主题"
sponge-option-info:
description: "显示有关主题选项的信息"
args:
"contexts...": "选择的环境"
sponge-option-set:
description: "为主题设置一个选项"
args:
"key": "设定键"
"value": "设置的键"
"contexts...": "设置选项的环境"
sponge-option-unset:
description: "取消主题一个选项"
args:
"key": "取消的键"
"contexts...": "取消选项的环境"
sponge-option-clear:
description: "清除对象的选择"
args:
"contexts...": "清除选项的环境"
migration-command:
description: "迁移命令"
migration-groupmanager:
description: "迁移命令"
args:
"migrate as global": "如果世界权限应该被忽略,并且刚刚迁移为全局"
migration-powerfulperms:
description: "迁移命令"
args:
"address": "PP数据库的地址"
"database": "PP数据库的名称"
"username": "登录DB的用户名"
"password": "登录DB的用密码"
"db table": "存储用户数据的PP表的名称"
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>3.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -32,49 +32,59 @@ import java.util.Optional;
import javax.annotation.Nonnull;
/**
* Singleton for the {@link LuckPermsApi}.
* Provides static access to 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>
* <p>Ideally, the ServiceManager for the platform should be used to obtain an instance,
* however, this provider can be used if you need static access.</p>
*/
public final class LuckPerms {
private static LuckPermsApi api = null;
private static LuckPermsApi instance = null;
/**
* Gets an instance of {@link LuckPermsApi}, throwing {@link IllegalStateException} if the API is not loaded.
* Gets an instance of the {@link LuckPermsApi},
* throwing {@link IllegalStateException} if an instance is not yet loaded.
*
* <p>Will never return null.</p>
*
* @return an api instance
* @throws IllegalStateException if the api is not loaded
*/
@Nonnull
public static LuckPermsApi getApi() {
if (api == null) {
if (instance == null) {
throw new IllegalStateException("API is not loaded.");
}
return api;
return instance;
}
/**
* Gets an instance of {@link LuckPermsApi}, if it is loaded.
*
* <p> Unlike {@link LuckPerms#getApi}, this method will not throw an {@link IllegalStateException} if the API is
* not loaded, rather return an empty {@link Optional}.
* <p>Unlike {@link LuckPerms#getApi}, this method will not throw an
* {@link IllegalStateException} if an instance is not yet loaded, rather return
* an empty {@link Optional}.
*
* @return an optional api instance
*/
@Nonnull
public static Optional<LuckPermsApi> getApiSafe() {
return Optional.ofNullable(api);
return Optional.ofNullable(instance);
}
/* method used by the implementation to set the singleton instance */
static void registerProvider(LuckPermsApi luckPermsApi) {
api = luckPermsApi;
/**
* Registers an instance of the {@link LuckPermsApi} with this provider.
*
* @param instance the instance
*/
static void registerProvider(LuckPermsApi instance) {
LuckPerms.instance = instance;
}
/* method used by the implementation to remove any previous instance */
/**
* Removes the current instance from this provider.
*/
static void unregisterProvider() {
api = null;
LuckPerms.instance = null;
}
private LuckPerms() {
@@ -28,6 +28,7 @@ package me.lucko.luckperms.api;
import com.google.common.base.Preconditions;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
@@ -104,4 +105,22 @@ public enum ChatMetaType {
@Nonnull
public abstract Map.Entry<Integer, String> getEntry(@Nonnull Node node);
/**
* Parses a ChatMetaType from the given node.
*
* @param node the node
* @return the parsed chat meta type
* @since 3.4
*/
@Nonnull
public static Optional<ChatMetaType> ofNode(@Nonnull Node node) {
if (node.isPrefix()) {
return Optional.of(PREFIX);
} else if (node.isSuffix()) {
return Optional.of(SUFFIX);
} else {
return Optional.empty();
}
}
}
@@ -30,11 +30,29 @@ import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.function.Supplier;
/**
* Represents the result of a mutation call.
*/
public enum DataMutateResult {
/**
* Indicates the mutation was a success
*/
SUCCESS(true, null),
/**
* Indicates the mutation failed because the subject already has something
*/
ALREADY_HAS(false, ObjectAlreadyHasException::new),
/**
* Indicates the mutation failed because the subject lacks something
*/
LACKS(false, ObjectLacksException::new),
/**
* Indicates the mutation failed
*/
FAIL(false, RuntimeException::new);
private boolean value;
@@ -51,10 +69,35 @@ public enum DataMutateResult {
}
}
/**
* Gets a boolean representation of the result.
*
* @return a boolean representation
*/
public boolean asBoolean() {
return value;
}
/**
* Gets if the result indicates a success
*
* @return if the result indicates a success
* @since 3.4
*/
public boolean wasSuccess() {
return value;
}
/**
* Gets if the result indicates a failure
*
* @return if the result indicates a failure
* @since 3.4
*/
public boolean wasFailure() {
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) {
@@ -1,3 +1,28 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.api;
import me.lucko.luckperms.api.caching.MetaContexts;
@@ -9,13 +34,14 @@ import javax.annotation.Nonnull;
/**
* A special instance of {@link Contexts}, which when passed to:
*
* <p><ul>
* <li>{@link UserData#getPermissionData(Contexts)}</li>
* <li>{@link UserData#getMetaData(Contexts)}</li>
* <li>{@link UserData#getMetaData(MetaContexts)}</li>
* </ul></p>
* <p></p>
* <ul>
* <li>{@link UserData#getPermissionData(Contexts)}</li>
* <li>{@link UserData#getMetaData(Contexts)}</li>
* <li>{@link UserData#getMetaData(MetaContexts)}</li>
* </ul>
*
* <p>will always satisfy all contextual requirements.</p>
* <p>... will always satisfy all contextual requirements.</p>
*
* <p>This effectively allows you to do lookups which ignore context.</p>
*
@@ -35,7 +35,7 @@ import java.util.OptionalInt;
import javax.annotation.Nonnull;
/**
* A group which holds permission data.
* An inheritable holder of permission data.
*/
public interface Group extends PermissionHolder {
@@ -27,13 +27,15 @@ package me.lucko.luckperms.api;
import com.google.common.collect.Multimap;
import me.lucko.luckperms.api.context.ContextSet;
import java.util.Optional;
import java.util.OptionalLong;
import javax.annotation.Nonnull;
/**
* A relationship between a Holder and a permission
* A relationship between a PermissionHolder and a permission
*
* @param <T> the identifier type of the holder
* @since 2.17
@@ -90,10 +92,19 @@ public interface HeldPermission<T> {
* Gets the context for the permission.
*
* @return the context
* @deprecated in favour of {@link #getContexts()}.
*/
@Nonnull
@Deprecated
Multimap<String, String> getContext();
/**
* Gets the extra context for the permission.
*
* @return the extra context
*/
ContextSet getContexts();
/**
* Converts this permission into a Node
*
@@ -32,104 +32,119 @@ import java.util.Map;
import javax.annotation.Nonnull;
/**
* Read-only access to the LuckPerms configuration settings
* Wrapper around parts of the LuckPerms configuration file
*/
public interface LPConfiguration {
/**
* Returns the name of this server
* Gets the name of this server
*
* @return the name of this server
*/
@Nonnull
String getServer();
/**
* Returns how often a sync task will run in minutes
* Gets how often a sync task will run in minutes
*
* @return how often a sync task will run in minutes
*/
int getSyncTime();
/**
* Returns if the users on this server will have their global permissions applied
* Gets if the users on this server will have their global permissions applied
*
* @return if the users on this server will have their global permissions applied
*/
boolean getIncludeGlobalPerms();
/**
* Returns if the users on this server will have their global world permissions applied
* Gets if the users on this server will have their global world permissions applied
*
* @return if the users on this server will have their global world permissions applied
* @since 2.9
*/
boolean getIncludeGlobalWorldPerms();
/**
* Returns true if the platform is applying global groups
* Gets if the platform is applying global groups
*
* @return true if the platform is applying global groups
* @since 2.9
*/
boolean getApplyGlobalGroups();
/**
* Returns true if the platform is applying global world groups
* Gets if the platform is applying global world groups
*
* @return true if the platform is applying global world groups
* @since 2.9
*/
boolean getApplyGlobalWorldGroups();
/**
* Returns the online mode setting
* Gets the online mode setting
*
* @return the online mode setting
*/
boolean getOnlineMode();
/**
* Returns if LuckPerms is applying wildcard permissions
* Gets if LuckPerms is applying wildcard permissions
*
* @return if LuckPerms is applying wildcard permissions
*/
boolean getApplyWildcards();
/**
* Returns if LuckPerms is resolving and applying regex permissions
*
* @return if LuckPerms is resolving and applying regex permissions
*/
boolean getApplyRegex();
/**
* Returns if LuckPerms is expanding shorthand permissions
* Gets if LuckPerms is expanding shorthand permissions
*
* @return if LuckPerms is expanding shorthand permissions
*/
boolean getApplyShorthand();
/**
* Returns if LuckPerms will send notifications to users when permissions are modified
* Gets if LuckPerms will send notifications to users when permissions are modified
*
* @return if LuckPerms will send notifications to users when permissions are modified
* @since 2.7
*/
boolean getLogNotify();
/**
* Returns true if the vanilla op system is enabled
* Gets if the vanilla op system is enabled
*
* @return true if the vanilla op system is enabled
* @since 2.8
*/
boolean getEnableOps();
/**
* Returns true if opped players are allowed to use LuckPerms commands
* Gets if opped players are allowed to use LuckPerms commands
*
* @return true if opped players are allowed to use LuckPerms commands
* @since 2.8
*/
boolean getCommandsAllowOp();
/**
* Returns true if auto op is enabled
* Gets if auto op is enabled
*
* @return true if auto op is enabled
* @since 2.9
*/
boolean getAutoOp();
/**
* Returns the name of the server used within Vault operations
* Gets the name of the server used within Vault operations
*
* @return the name of the server used within Vault operations
* @since 2.7
*/
@@ -137,35 +152,40 @@ public interface LPConfiguration {
String getVaultServer();
/**
* Returns true if global permissions should be considered when retrieving meta or player groups
* Gets if global permissions should be considered when retrieving meta or player groups
*
* @return true if global permissions should be considered when retrieving meta or player groups
* @since 2.7
*/
boolean getVaultIncludeGlobal();
/**
* Returns the values set for data storage in the configuration
* Gets the values set for data storage in the configuration
*
* @return the values set for data storage in the configuration
*/
@Nonnull
DatastoreConfiguration getDatastoreConfig();
/**
* Returns the storage method string from the configuration
* Gets the storage method string from the configuration
*
* @return the storage method string from the configuration
*/
@Nonnull
String getStorageMethod();
/**
* Returns true if split storage is enabled
* Gets true if split storage is enabled
*
* @return true if split storage is enabled
* @since 2.7
*/
boolean getSplitStorage();
/**
* Returns a map of split storage options
* Gets a map of split storage options
*
* @return a map of split storage options, where the key is the storage section, and the value is the storage
* method. For example: key = user, value = json
* @since 2.7
@@ -34,18 +34,26 @@ import javax.annotation.Nonnull;
/**
* 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>
* <p>The returned instance provides a copy of the data at the time of retrieval.</p>
*
* <p>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>
*
* <p>All methods are thread safe, and return immutable & thread safe collections.</p>
*/
public interface Log {
/**
* Get the content of the log
*
* @return a {@link SortedSet} of all of the {@link LogEntry} objects in this {@link Log}
*/
@Nonnull
SortedSet<LogEntry> getContent();
/**
* Get the content of the log
*
* @return all content in this log
*/
@Nonnull
@@ -57,18 +65,22 @@ public interface Log {
* @param pageNo the page number
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages()}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages()}
*/
@Nonnull
SortedMap<Integer, LogEntry> getRecent(int pageNo);
/**
* Gets the max page number for the recent pages.
*
* @return the max page number allowed in the {@link #getRecent(int)} method
*/
int getRecentMaxPages();
/**
* Gets the recent content for a given actor
*
* @param actor the uuid of the actor to filter by
* @return all content in this log where is actor = uuid
*/
@@ -76,18 +88,20 @@ public interface Log {
SortedSet<LogEntry> getRecent(@Nonnull UUID actor);
/**
* Gets the recent content for the uuid, separated into pages
* Gets the recent content for a given actor, separated by page
*
* @param pageNo the page number
* @param actor the uuid of the actor to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages(UUID)}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getRecentMaxPages(UUID)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getRecent(int pageNo, @Nonnull UUID actor);
/**
* Gets the max page number for the recent pages.
*
* @param actor the actor to filter by
* @return the max page number allowed in the {@link #getRecent(int, UUID)} method
*/
@@ -95,6 +109,8 @@ public interface Log {
/**
* Gets the log content for a given user
*
* @param uuid the uuid to filter by
* @return all content in this log where the user = uuid
*/
@@ -102,18 +118,20 @@ public interface Log {
SortedSet<LogEntry> getUserHistory(@Nonnull UUID uuid);
/**
* Gets the user history content, separated by pages
* Gets the log content for a given user, separated by page
*
* @param pageNo the page number
* @param uuid the uuid of the acted user to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getUserHistoryMaxPages(UUID)}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getUserHistoryMaxPages(UUID)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getUserHistory(int pageNo, @Nonnull UUID uuid);
/**
* Gets the max page number for the user history pages.
*
* @param uuid the uuid to filter by
* @return the max page number allowed in the {@link #getUserHistory(int, UUID)} method
*/
@@ -121,6 +139,8 @@ public interface Log {
/**
* Gets the log content for a given group
*
* @param name the name to filter by
* @return all content in this log where the group = name
*/
@@ -128,18 +148,20 @@ public interface Log {
SortedSet<LogEntry> getGroupHistory(@Nonnull String name);
/**
* Gets the group history content, separated by pages
* Gets the log content for a given group, separated by page
*
* @param pageNo the page number
* @param name the name of the acted group to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getGroupHistoryMaxPages(String)}}
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getGroupHistoryMaxPages(String)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getGroupHistory(int pageNo, @Nonnull String name);
/**
* Gets the max page number for the group history pages.
*
* @param name the name to filter by
* @return the max page number allowed in the {@link #getGroupHistory(int, String)} method
*/
@@ -147,6 +169,8 @@ public interface Log {
/**
* Gets the log content for a given track
*
* @param name the name to filter by
* @return all content in this log where the track = name
*/
@@ -154,25 +178,29 @@ public interface Log {
SortedSet<LogEntry> getTrackHistory(@Nonnull String name);
/**
* Gets the track history content, separated by pages
* Gets the log content for a given track, separated by page
*
* @param pageNo the page number
* @param name the name of the acted track to filter by
* @return the page content
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getTrackHistoryMaxPages(String)}
*/
@Nonnull
SortedMap<Integer, LogEntry> getTrackHistory(int pageNo, @Nonnull String name);
/**
* Gets the max page number for the track history pages.
*
* @param name the name to filter by
* @return the max page number allowed in the {@link #getTrackHistory(int, String)} method
* @throws IllegalArgumentException if the pageNo is less than 1
* @throws IllegalStateException if the log doesn't contain enough entries to populate the page. See {@link #getTrackHistoryMaxPages(String)}}
*/
int getTrackHistoryMaxPages(@Nonnull String name);
/**
* Gets the log content for a given search query
*
* @param query the query to filter by
* @return all content in this log where the content matches query
*/
@@ -180,7 +208,7 @@ public interface Log {
SortedSet<LogEntry> getSearch(@Nonnull String query);
/**
* Gets the search content, separated by pages
* Gets the log content for a given search query, separated by page
*
* @param pageNo the page number
* @param query the query to filter by
@@ -192,8 +220,11 @@ public interface Log {
SortedMap<Integer, LogEntry> getSearch(int pageNo, @Nonnull String query);
/**
* Gets the max page number for the search query pages.
*
* @param query the query to filter by
* @return the max page number allowed in the {@link #getSearch(int, String)} method
*/
int getSearchMaxPages(@Nonnull String query);
}
@@ -25,45 +25,115 @@
package me.lucko.luckperms.api;
import com.google.common.base.Preconditions;
import java.util.Comparator;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A single entry in the log
*
* <p>Implements {@link Comparable} ordering based upon the timestamp of the entry.</p>
* A single entry in the log.
*/
public class LogEntry implements Comparable<LogEntry> {
/**
* Compares two LogEntries
*
* @since 3.3
*/
public static final Comparator<LogEntry> COMPARATOR = Comparator
.comparingLong(LogEntry::getTimestamp)
.thenComparing(LogEntry::getActor)
.thenComparing(LogEntry::getActorName, String.CASE_INSENSITIVE_ORDER)
.thenComparing(LogEntry::getEntryType)
.thenComparing(e -> {
UUID u = e.getActed();
return u == null ? "" : u.toString();
})
.thenComparing(LogEntry::getActedName, String.CASE_INSENSITIVE_ORDER)
.thenComparing(LogEntry::getAction);
/**
* Compares two LogEntries in reverse order
*
* @since 3.3
* @see #COMPARATOR
*/
public static final Comparator<LogEntry> REVERSE_ORDER = COMPARATOR.reversed();
private static final String FORMAT = "&8(&e%s&8) [&a%s&8] (&b%s&8) &7--> &f%s";
/**
* Creates a new LogEntry builder
*
* @return a new builder
*/
@Nonnull
public static LogEntryBuilder builder() {
return new LogEntryBuilder();
}
/**
* The time when the log event occurred in unix seconds.
*/
private long timestamp;
/**
* The player who enacted the change
*/
@Nonnull
private UUID actor;
/**
* The name of the player who enacted the change
*/
@Nonnull
private String actorName;
private char type;
/**
* The entry type.
*/
@Nonnull
private Type type;
/**
* The user who was acted upon
*/
@Nullable
private UUID acted;
/**
* The name of the object who was acted upon
*/
@Nonnull
private String actedName;
/**
* A description of the action
*/
@Nonnull
private String action;
public LogEntry(long timestamp, @Nonnull UUID actor, @Nonnull String actorName, char type, @Nullable UUID acted, @Nonnull String actedName, @Nonnull String action) {
if (actor == null) {
throw new NullPointerException("actor");
}
if (actorName == null) {
throw new NullPointerException("actorName");
}
if (actedName == null) {
throw new NullPointerException("actedName");
}
if (action == null) {
throw new NullPointerException("action");
}
/**
* Creates a new log entry
*
* @param timestamp the timestamp
* @param actor the actor
* @param actorName the actorName
* @param type the type
* @param acted the acted object, or null
* @param actedName the acted object name
* @param action the action
* @since 3.3
*/
public LogEntry(long timestamp, @Nonnull UUID actor, @Nonnull String actorName, @Nonnull Type type, @Nullable UUID acted, @Nonnull String actedName, @Nonnull String action) {
Preconditions.checkNotNull(actor, "actor");
Preconditions.checkNotNull(actorName, "actorName");
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(actedName, "actedName");
Preconditions.checkNotNull(action, "action");
this.timestamp = timestamp;
this.actor = actor;
@@ -74,35 +144,72 @@ public class LogEntry implements Comparable<LogEntry> {
this.action = action;
}
/**
* Creates a new log entry
*
* @param timestamp the timestamp
* @param actor the actor
* @param actorName the actorName
* @param type the type code
* @param acted the acted object, or null
* @param actedName the acted object name
* @param action the action
*/
public LogEntry(long timestamp, @Nonnull UUID actor, @Nonnull String actorName, char type, @Nullable UUID acted, @Nonnull String actedName, @Nonnull String action) {
Preconditions.checkNotNull(actor, "actor");
Preconditions.checkNotNull(actorName, "actorName");
Preconditions.checkNotNull(actedName, "actedName");
Preconditions.checkNotNull(action, "action");
this.timestamp = timestamp;
this.actor = actor;
this.actorName = actorName;
this.type = Type.valueOf(type);
this.acted = acted;
this.actedName = actedName;
this.action = action;
}
/**
* Creates a new LogEntry and copies the values from another
*
* @param other the entry to copy values from
* @since 3.3
*/
protected LogEntry(@Nonnull LogEntry other) {
this.timestamp = other.timestamp;
this.actor = other.actor;
this.actorName = other.actorName;
this.type = other.type;
this.acted = other.acted;
this.actedName = other.actedName;
this.action = other.action;
}
protected LogEntry() {
this.timestamp = 0L;
this.actor = null;
this.actorName = null;
this.type = Character.MIN_VALUE;
this.type = null;
this.acted = null;
this.actedName = null;
this.action = null;
}
@Override
public int compareTo(LogEntry o) {
return equals(o) ? 0 : (Long.compare(timestamp, o.getTimestamp()) == 0 ? 1 : Long.compare(timestamp, o.getTimestamp()));
public int compareTo(@Nonnull LogEntry other) {
Preconditions.checkNotNull(other, "other");
return COMPARATOR.compare(this, other);
}
public boolean matchesSearch(@Nonnull String query) {
query = query.toLowerCase();
return actorName.toLowerCase().contains(query) || actedName.toLowerCase().contains(query)
|| action.toLowerCase().contains(query);
}
@Nonnull
public String getFormatted() {
return String.format(FORMAT,
String.valueOf(actorName).equals("null") ? actor.toString() : actorName,
Character.toString(type),
String.valueOf(actedName).equals("null") && acted != null ? acted.toString() : actedName,
action
);
/**
* Creates a copy of this log entry
*
* @return a copy of this log entry
* @since 3.3
*/
public LogEntry copy() {
return new LogEntry(this);
}
public long getTimestamp() {
@@ -115,28 +222,63 @@ public class LogEntry implements Comparable<LogEntry> {
@Nonnull
public UUID getActor() {
return actor;
return Preconditions.checkNotNull(actor, "actor");
}
void setActor(@Nonnull UUID actor) {
this.actor = actor;
this.actor = Preconditions.checkNotNull(actor, "actor");
}
@Nonnull
public String getActorName() {
return actorName;
return Preconditions.checkNotNull(actorName, "actorName");
}
void setActorName(@Nonnull String actorName) {
this.actorName = actorName;
this.actorName = Preconditions.checkNotNull(actorName, "actorName");
}
/**
* Gets the type of this entry
*
* @return the type of this entry
* @since 3.3
*/
@Nonnull
public Type getEntryType() {
return Preconditions.checkNotNull(type, "type");
}
/**
* Sets the type of this entry
*
* @param type the new type
* @since 3.3
*/
public void setEntryType(@Nonnull Type type) {
this.type = Preconditions.checkNotNull(type, "type");
}
/**
* Gets the code representing this entry type
*
* @return the code representing this entry type
* @deprecated in favour of {@link #getEntryType()}
*/
@Deprecated
public char getType() {
return type;
return getEntryType().getCode();
}
void setType(char type) {
this.type = type;
/**
* Sets the type of this entry by code
*
* @param code the code type
* @deprecated in favour of {@link #setEntryType(Type)}
*/
@Deprecated
void setType(char code) {
setEntryType(Type.valueOf(code));
}
@Nullable
@@ -150,20 +292,37 @@ public class LogEntry implements Comparable<LogEntry> {
@Nonnull
public String getActedName() {
return actedName;
return Preconditions.checkNotNull(actedName, "actedName");
}
void setActedName(@Nonnull String actedName) {
this.actedName = actedName;
this.actedName = Preconditions.checkNotNull(actedName, "actedName");
}
@Nonnull
public String getAction() {
return action;
return Preconditions.checkNotNull(action, "action");
}
void setAction(@Nonnull String action) {
this.action = action;
this.action = Preconditions.checkNotNull(action, "action");
}
public boolean matchesSearch(@Nonnull String query) {
query = Preconditions.checkNotNull(query, "query").toLowerCase();
return actorName.toLowerCase().contains(query) ||
actedName.toLowerCase().contains(query) ||
action.toLowerCase().contains(query);
}
@Nonnull
public String getFormatted() {
return String.format(FORMAT,
String.valueOf(actorName).equals("null") ? actor.toString() : actorName,
Character.toString(type.getCode()),
String.valueOf(actedName).equals("null") && acted != null ? acted.toString() : actedName,
action
);
}
@Override
@@ -172,11 +331,10 @@ public class LogEntry implements Comparable<LogEntry> {
"timestamp=" + this.getTimestamp() + ", " +
"actor=" + this.getActor() + ", " +
"actorName=" + this.getActorName() + ", " +
"type=" + this.getType() + ", " +
"type=" + this.getEntryType() + ", " +
"acted=" + this.getActed() + ", " +
"actedName=" + this.getActedName() + ", " +
"action=" + this.getAction() +
")";
"action=" + this.getAction() + ")";
}
@Override
@@ -184,13 +342,14 @@ public class LogEntry implements Comparable<LogEntry> {
if (o == this) return true;
if (!(o instanceof LogEntry)) return false;
final LogEntry other = (LogEntry) o;
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.getActor().equals(other.getActor()) &&
this.getActorName().equals(other.getActorName()) &&
this.getEntryType() == other.getEntryType() &&
(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()));
this.getActedName().equals(other.getActedName()) &&
this.getAction().equals(other.getAction());
}
@Override
@@ -198,118 +357,183 @@ public class LogEntry implements Comparable<LogEntry> {
final int PRIME = 59;
int result = 1;
result = result * PRIME + (int) (this.getTimestamp() >>> 32 ^ this.getTimestamp());
result = result * PRIME + (this.getActor() == null ? 43 : this.getActor().hashCode());
result = result * PRIME + (this.getActorName() == null ? 43 : this.getActorName().hashCode());
result = result * PRIME + this.getType();
result = result * PRIME + this.getActor().hashCode();
result = result * PRIME + this.getActorName().hashCode();
result = result * PRIME + this.getEntryType().hashCode();
result = result * PRIME + (this.getActed() == null ? 43 : this.getActed().hashCode());
result = result * PRIME + (this.getActedName() == null ? 43 : this.getActedName().hashCode());
result = result * PRIME + (this.getAction() == null ? 43 : this.getAction().hashCode());
result = result * PRIME + this.getActedName().hashCode();
result = result * PRIME + this.getAction().hashCode();
return result;
}
/**
* The LogEntry type
* @since 3.3
*/
public enum Type {
USER('U'),
GROUP('G'),
TRACK('T');
private final char code;
Type(char code) {
this.code = code;
}
public char getCode() {
return this.code;
}
public static Type valueOf(char code) {
switch (code) {
case 'U':
case 'u':
return USER;
case 'G':
case 'g':
return GROUP;
case 'T':
case 't':
return TRACK;
default:
throw new IllegalArgumentException("Unknown code: " + code);
}
}
}
/**
* Builds LogEntry instances
*/
public static class LogEntryBuilder extends AbstractLogEntryBuilder<LogEntry, LogEntry.LogEntryBuilder> {
@Override
protected LogEntry createObj() {
protected LogEntry createEmptyLog() {
return new LogEntry();
}
@Override
protected LogEntryBuilder getThis() {
protected LogEntryBuilder getThisBuilder() {
return this;
}
}
/**
* An abstract log entry builder
*
* @param <T> the log type
* @param <B> the log builder type
*/
public static abstract class AbstractLogEntryBuilder<T extends LogEntry, B extends AbstractLogEntryBuilder<T, B>> {
private T obj;
private B thisObj;
public AbstractLogEntryBuilder() {
obj = createObj();
thisObj = getThis();
obj = createEmptyLog();
thisObj = getThisBuilder();
}
protected abstract T createObj();
protected abstract T createEmptyLog();
protected abstract B getThis();
protected abstract B getThisBuilder();
public long getTimestamp() {
return obj.getTimestamp();
}
@Nonnull
public UUID getActor() {
return obj.getActor();
}
@Nonnull
public String getActorName() {
return obj.getActorName();
}
@Nonnull
public Type getEntryType() {
return obj.getEntryType();
}
@Nonnull
@Deprecated
public char getType() {
return obj.getType();
}
@Nullable
public UUID getActed() {
return obj.getActed();
}
@Nonnull
public String getActedName() {
return obj.getActedName();
}
@Nonnull
public String getAction() {
return obj.getAction();
}
@Nonnull
public B timestamp(long timestamp) {
obj.setTimestamp(timestamp);
return thisObj;
}
public B actor(UUID actor) {
@Nonnull
public B actor(@Nonnull UUID actor) {
obj.setActor(actor);
return thisObj;
}
public B actorName(String actorName) {
@Nonnull
public B actorName(@Nonnull String actorName) {
obj.setActorName(actorName);
return thisObj;
}
@Nonnull
public B entryType(@Nonnull Type type) {
obj.setEntryType(type);
return thisObj;
}
@Nonnull
@Deprecated
public B type(char type) {
obj.setType(type);
return thisObj;
}
public B acted(UUID acted) {
@Nonnull
public B acted(@Nullable UUID acted) {
obj.setActed(acted);
return thisObj;
}
public B actedName(String actedName) {
@Nonnull
public B actedName(@Nonnull String actedName) {
obj.setActedName(actedName);
return thisObj;
}
public B action(String action) {
@Nonnull
public B action(@Nonnull String action) {
obj.setAction(action);
return thisObj;
}
@Nonnull
public T build() {
if (getActor() == null) {
throw new NullPointerException("actor");
}
if (getActorName() == null) {
throw new NullPointerException("actorName");
}
if (getActedName() == null) {
throw new NullPointerException("actedName");
}
if (getAction() == null) {
throw new NullPointerException("action");
}
Preconditions.checkNotNull(getActor(), "actor");
Preconditions.checkNotNull(getActorName(), "actorName");
Preconditions.checkNotNull(getEntryType(), "type");
Preconditions.checkNotNull(getActedName(), "actedName");
Preconditions.checkNotNull(getAction(), "action");
return obj;
}
@@ -320,11 +544,10 @@ public class LogEntry implements Comparable<LogEntry> {
"timestamp=" + this.getTimestamp() + ", " +
"actor=" + this.getActor() + ", " +
"actorName=" + this.getActorName() + ", " +
"type=" + this.getType() + ", " +
"type=" + this.getEntryType() + ", " +
"acted=" + this.getActed() + ", " +
"actedName=" + this.getActedName() + ", " +
"action=" + this.getAction() +
")";
"action=" + this.getAction() + ")";
}
}
@@ -25,6 +25,7 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.LuckPerms;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.event.EventBus;
@@ -38,7 +39,13 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The root API interface for LuckPerms
* The LuckPerms API.
*
* <p>This interface is the base of the entire API package. All API functions
* are accessed via this interface.</p>
*
* <p>An instance can be obtained via {@link LuckPerms#getApi()}, or the platforms
* Services Manager.</p>
*/
public interface LuckPermsApi {
@@ -41,6 +41,7 @@ import java.util.Set;
*/
@Deprecated
public class MetaUtils {
private static final String[] DELIMS = new String[]{".", "/", "-", "$"};
private static String escapeDelimiters(String s, String... delims) {
for (String delim : delims) {
@@ -68,7 +69,7 @@ public class MetaUtils {
throw new NullPointerException();
}
return escapeDelimiters(s, ".", "/", "-", "$");
return escapeDelimiters(s, DELIMS);
}
/**
@@ -86,7 +87,7 @@ public class MetaUtils {
s = s.replace("{SEP}", ".");
s = s.replace("{FSEP}", "/");
s = s.replace("{DSEP}", "$");
s = unescapeDelimiters(s, ".", "/", "-", "$");
s = unescapeDelimiters(s, DELIMS);
return s;
}
@@ -161,7 +162,7 @@ public class MetaUtils {
node = escapeCharacters(node);
for (Node n : holder.getPermissions()) {
if (!n.getValue() || !n.isMeta()) continue;
if (!n.getValuePrimitive() || !n.isMeta()) continue;
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
@@ -233,7 +234,7 @@ public class MetaUtils {
int priority = Integer.MIN_VALUE;
String meta = null;
for (Node n : holder.getAllNodes(Contexts.allowAll())) {
if (!n.getValue()) continue;
if (!n.getValuePrimitive()) continue;
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
@@ -37,16 +37,18 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An immutable permission node
* Represents a permission node.
*
* <p>Use {@link LuckPermsApi#buildNode(String)} to get an instance.</p>
* <p>All implementations of this interface must be immutable.</p>
*
* <p>Use the {@link NodeFactory} to obtain and construct instances.</p>
*
* @since 2.6
*/
public interface Node extends Map.Entry<String, Boolean> {
/**
* Returns the actual permission string
* Gets the permission string
*
* @return the actual permission node
*/
@@ -54,36 +56,50 @@ public interface Node extends Map.Entry<String, Boolean> {
String getPermission();
/**
* Gets what value the permission is set to. A negated node would return <code>false</code>.
* Gets the value.
*
* <p>A negated node would return a value of <code>false</code>.</p>
*
* @return the permission's value
*/
@Override
@Nonnull
Boolean getValue();
default Boolean getValue() {
return getValuePrimitive();
}
/**
* Returns the value of this node as a tristate
* Gets the value.
*
* <p>A negated node would return a value of <code>false</code>.</p>
*
* @return the permission's value
*/
boolean getValuePrimitive();
/**
* Gets the value of this node as a {@link Tristate}
*
* @return the value of this node as a Tristate
*/
@Nonnull
default Tristate getTristate() {
return Tristate.fromBoolean(getValue());
return Tristate.fromBoolean(getValuePrimitive());
}
/**
* Returns if the node is negated
* Gets if the node is negated
*
* @return true if the node is negated
*/
default boolean isNegated() {
return !getValue();
return !getValuePrimitive();
}
/**
* If this node is set to override explicitly.
* This value does not persist across saves, and is therefore only useful for transient nodes
* Gets if this node is set to override explicitly.
*
* <p>This value does not persist across saves, and is therefore only useful for transient nodes</p>
*
* @return true if this node is set to override explicitly
*/
@@ -106,21 +122,21 @@ public interface Node extends Map.Entry<String, Boolean> {
Optional<String> getWorld();
/**
* Returns if this node is server specific
* Gets if this node is server specific
*
* @return true if this node is server specific
*/
boolean isServerSpecific();
/**
* Returns if this node is server specific
* Gets 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
* Gets if this node applies globally, and therefore has no specific context
*
* @return true if this node applies globally, and has no specific context
* @since 3.1
@@ -128,7 +144,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean appliesGlobally();
/**
* Returns if this node has any specific context in order to apply.
* Gets if this node has any specific context in order for it to apply
*
* @return true if this node has specific context
* @since 3.1
@@ -136,7 +152,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean hasSpecificContext();
/**
* Returns if this node is able to apply in the given context
* Gets 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
@@ -150,7 +166,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApply(boolean includeGlobal, boolean includeGlobalWorld, @Nullable String server, @Nullable String world, @Nullable ContextSet context, boolean applyRegex);
/**
* If this node should apply on a specific server
* Gets if this node should apply on a specific server
*
* @param server the name of the server
* @param includeGlobal if global permissions should apply
@@ -160,7 +176,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApplyOnServer(@Nullable String server, boolean includeGlobal, boolean applyRegex);
/**
* If this node should apply on a specific world
* Gets if this node should apply on a specific world
*
* @param world the name of the world
* @param includeGlobal if global permissions should apply
@@ -170,7 +186,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApplyOnWorld(@Nullable String world, boolean includeGlobal, boolean applyRegex);
/**
* If this node should apply in the given context
* Gets if this node should apply in the given context
*
* @param context the context key value pairs
* @param worldAndServer if world and server contexts should be checked
@@ -180,7 +196,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApplyWithContext(@Nonnull ContextSet context, boolean worldAndServer);
/**
* If this node should apply in the given context
* Gets if this node should apply in the given context
*
* @param context the context key value pairs
* @return true if the node should apply
@@ -211,8 +227,10 @@ public interface Node extends Map.Entry<String, Boolean> {
*
* @param possibleNodes a list of possible permission nodes
* @return a list of permissions that match this wildcard
* @deprecated as this is no longer used internally to resolve wildcards
*/
@Nonnull
@Deprecated
List<String> resolveWildcard(@Nonnull List<String> possibleNodes);
/**
@@ -224,14 +242,14 @@ public interface Node extends Map.Entry<String, Boolean> {
List<String> resolveShorthand();
/**
* Returns if this node will expire in the future
* Gets if this node will expire in the future
*
* @return true if this node will expire in the future
*/
boolean isTemporary();
/**
* Returns if this node will not expire
* Gets if this node will not expire
*
* @return true if this node will not expire
*/
@@ -240,7 +258,7 @@ public interface Node extends Map.Entry<String, Boolean> {
}
/**
* Returns a unix timestamp in seconds when this node will expire
* Gets a unix timestamp in seconds when this node will expire
*
* @return the time in Unix time when this node will expire
* @throws IllegalStateException if the node is not temporary
@@ -248,7 +266,7 @@ public interface Node extends Map.Entry<String, Boolean> {
long getExpiryUnixTime() throws IllegalStateException;
/**
* Returns the date when this node will expire
* Gets the date when this node will expire
*
* @return the {@link Date} when this node will expire
* @throws IllegalStateException if the node is not temporary
@@ -257,7 +275,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Date getExpiry() throws IllegalStateException;
/**
* Return the number of seconds until this permission will expire
* Gets the number of seconds until this permission will expire
*
* @return the number of seconds until this permission will expire
* @throws IllegalStateException if the node is not temporary
@@ -265,8 +283,9 @@ public interface Node extends Map.Entry<String, Boolean> {
long getSecondsTilExpiry() throws IllegalStateException;
/**
* Return true if the node has expired.
* This also returns false if the node is not temporary
* Gets if the node has expired.
*
* <p>This also returns false if the node is not temporary.</p>
*
* @return true if this node has expired
*/
@@ -301,14 +320,14 @@ public interface Node extends Map.Entry<String, Boolean> {
String toSerializedNode();
/**
* Returns if this is a group node
* Gets if this is a group node
*
* @return true if this is a group node
*/
boolean isGroupNode();
/**
* Returns the name of the group
* Gets the name of the group, if this is a group node.
*
* @return the name of the group
* @throws IllegalStateException if this is not a group node. See {@link #isGroupNode()}
@@ -317,7 +336,7 @@ public interface Node extends Map.Entry<String, Boolean> {
String getGroupName() throws IllegalStateException;
/**
* Returns if this node is a wildcard node
* Gets if this node is a wildcard node
*
* @return true if this node is a wildcard node
*/
@@ -332,7 +351,7 @@ public interface Node extends Map.Entry<String, Boolean> {
int getWildcardLevel() throws IllegalStateException;
/**
* Returns if this node is a meta node
* Gets if this node is a meta node
*
* @return true if this node is a meta node
*/
@@ -348,7 +367,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<String, String> getMeta() throws IllegalStateException;
/**
* Returns if this node is a prefix node
* Gets if this node is a prefix node
*
* @return true if this node is a prefix node
*/
@@ -364,7 +383,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<Integer, String> getPrefix() throws IllegalStateException;
/**
* Returns if this node is a suffix node
* Gets if this node is a suffix node
*
* @return true if this node is a suffix node
*/
@@ -28,27 +28,12 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* Builds {@link Node} instances
* Assists with constructing {@link Node} instances.
*
* @since 2.17
*/
public interface NodeFactory {
/**
* Creates a node from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node fromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a new node builder from a given base permission string
*
@@ -69,20 +54,6 @@ public interface NodeFactory {
@Nonnull
Node.Builder newBuilderFromExisting(@Nonnull Node other);
/**
* Creates a node builder from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node builder instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node.Builder newBuilderFromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a node builder from a group
@@ -141,4 +112,35 @@ public interface NodeFactory {
@Nonnull
Node.Builder makeSuffixNode(int priority, @Nonnull String suffix);
/**
* Creates a node from a serialised node string
*
* <p>This format is what was previously used in YAML/JSON storage files.</p>
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node fromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a node builder from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node builder instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node.Builder newBuilderFromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
}
@@ -43,14 +43,15 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An object capable of holding permissions
* An object which holds permissions.
*
* <p>Any changes made will be lost unless the instance is saved back to the {@link Storage}.</p>
* <p>Any changes made to permission holding objects will be lost unless the
* instance is saved back to the {@link Storage}.</p>
*/
public interface PermissionHolder {
/**
* Gets the objects name
* Gets the objects name.
*
* <p>{@link User#getUuid()}, {@link User#getName()} or {@link Group#getName()} should normally be used instead of
* this method.</p>
@@ -123,7 +124,7 @@ public interface PermissionHolder {
SortedSet<? extends Node> getPermissions();
/**
* Similar to {@link #getPermissions()}, except without transient permissions
* Similar to {@link #getPermissions()}, except without transient permissions.
*
* <p>Unlike transient permissions, enduring permissions will be saved to storage, and exist after the session.</p>
*
@@ -134,7 +135,7 @@ public interface PermissionHolder {
Set<? extends Node> getEnduringPermissions();
/**
* Similar to {@link #getPermissions()}, except without enduring permissions
* Similar to {@link #getPermissions()}, except without enduring permissions.
*
* <p>Transient permissions only exist for the duration of the session.</p>
*
@@ -28,7 +28,7 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* The platforms which LuckPerms can run on
* Represents a type of platform which LuckPerms can run on.
*
* @since 2.7
*/
@@ -44,6 +44,11 @@ public enum PlatformType {
this.friendlyName = friendlyName;
}
/**
* Gets a readable name for the platform type.
*
* @return a readable name
*/
@Nonnull
public String getFriendlyName() {
return friendlyName;
@@ -36,14 +36,14 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A means of loading and saving data to/from the Storage provider.
* A means of loading and saving permission data to/from the backend.
*
* <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
* <p>All blocking methods return {@link CompletableFuture}s, which will be populated with the result once the data has been
* loaded/saved asynchronously. Care should be taken when using such methods to ensure that the main server thread is not
* 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
* server thread. If you need to use the result of these operations on the main server thread, register a
* callback using {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} and {@link #getSyncExecutor()}.</p>
*
* @since 2.14
@@ -59,7 +59,7 @@ public interface Storage {
String getName();
/**
* Return whether the storage instance is allowing logins on the platform.
* Gets whether the storage instance is allowing logins on the platform.
*
* @return true if logins are enabled
*/
@@ -144,7 +144,8 @@ public interface Storage {
/**
* Gets a set all "unique" user UUIDs.
* "Unique" meaning the user isn't just a member of the "default" group.
*
* <p>"Unique" meaning the user isn't just a member of the "default" group.</p>
*
* @return a set of uuids, or null if the operation failed.
*/
@@ -34,21 +34,24 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An ordered collection of groups for easy promotions and demotions
* An ordered chain of {@link Group}s.
*/
public interface Track {
/**
* Gets the name of this track
*
* @return the name of this track
*/
@Nonnull
String getName();
/**
* Gets an ordered list of the groups on this track
* Gets a list of the groups on this track
*
* <p>Index 0 is the first/lowest group in (or start of) the track</p>
* <p>Index 0 is the first/lowest group in (or start of) the track.</p>
*
* <p>The returned collection is immutable, and cannot be modified.</p>
*
* @return an ordered {@link List} of the groups on this track
*/
@@ -28,37 +28,42 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* Represents a permission setting.
* Represents three different states of a setting.
*
* <p>Consider a value of {@link #TRUE} to be a positive setting, {@link #FALSE} to be a "negated" setting,
* and a value of {@link #UNDEFINED} to be a non-existent value.</p>
* <p>Possible values:</p>
* <p></p>
* <ul>
* <li>{@link #TRUE} - a positive setting</li>
* <li>{@link #FALSE} - a negative (negated) setting</li>
* <li>{@link #UNDEFINED} - a non-existent setting</li>
* </ul>
*/
public enum Tristate {
/**
* A value indicating a holder has a permission set.
* A value indicating a positive setting
*/
TRUE(true),
/**
* A value indicating a holder has a negated value for a permission.
* A value indicating a negative (negated) setting
*/
FALSE(false),
/**
* A value indicating a holder doesn't have a value for a permission set.
* A value indicating a non-existent setting
*/
UNDEFINED(false);
/**
* Converts from {@link Boolean} a boolean
* Returns a {@link Tristate} from a boolean
*
* @param b the boolean
* @param val the boolean value
* @return {@link #TRUE} or {@link #FALSE}, depending on the value of the boolean.
*/
@Nonnull
public static Tristate fromBoolean(boolean b) {
return b ? TRUE : FALSE;
public static Tristate fromBoolean(boolean val) {
return val ? TRUE : FALSE;
}
private final boolean booleanValue;
@@ -38,7 +38,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A player holding permission data
* A player which holds permission data.
*/
public interface User extends PermissionHolder {
@@ -51,7 +51,9 @@ public interface User extends PermissionHolder {
UUID getUuid();
/**
* Gets the users username, or null if no username is associated with this user
* Gets the users username
*
* <p>Returns null if no username is associated with this user.</p>
*
* @return the users username
*/
@@ -96,6 +98,16 @@ public interface User extends PermissionHolder {
@Nonnull
UserData getCachedData();
/**
* Pre-calculates some values in the user's data cache.
*
* <p>Is it <b>not</b> necessary to call this method before
* using {@link #getCachedData()}.</p>
*
* @since 2.17
*/
void setupDataCache();
/**
* Check to see if the user is a direct member of a group
*
@@ -128,16 +140,6 @@ public interface User extends PermissionHolder {
@Nonnull
Optional<UserData> getUserDataCache();
/**
* Pre-calculates some values in the user's data cache.
*
* <p>Is it <b>not</b> necessary to call this method before
* using {@link #getCachedData()}.</p>
*
* @since 2.17
*/
void setupDataCache();
/**
* Check to see if a user is a member of a group on a specific server
*
@@ -32,34 +32,34 @@ import javax.annotation.Nonnull;
/**
* 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
* 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>
* <p>A user's internal LuckPerms UUID is always the same as their Mojang one,
* unless <code>use-server-uuids</code> is disabled.</p>
*
* <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>
* <p>When this setting is disabled, this cache becomes active, and allows you to convert
* between 'internal' and 'server provided' uuids.</p>
*
* <p><strong>This is only effective for online players. Use {@link Storage#getUUID(String)} for offline players.</strong></p>
*/
public interface UuidCache {
/**
* Gets a users internal "LuckPerms" UUID, from the one given by the server.
* Gets a users "internal" LuckPerms UUID, from the one given by the server.
*
* @param external the UUID assigned by the server, through Player#getUniqueId or ProxiedPlayer#getUniqueId
* <p>When <code>use-server-uuids</code> is true, this returns the same UUID instance.</p>
*
* @param mojangUuid the UUID assigned by the server, through <code>Player#getUniqueId</code> or <code>ProxiedPlayer#getUniqueId</code>
* @return the corresponding internal UUID
*/
@Nonnull
UUID getUUID(@Nonnull UUID external);
UUID getUUID(@Nonnull UUID mojangUuid);
/**
* Gets a users external, server assigned or Mojang assigned unique id, from the internal one used within LuckPerms.
* Gets a users "external", server assigned unique id, from the internal one used within LuckPerms.
*
* @param internal the UUID used within LuckPerms, through User#getUuid
* @param internalUuid the UUID used within LuckPerms, through <code>User#getUuid</code>
* @return the corresponding external UUID
*/
@Nonnull
UUID getExternalUUID(@Nonnull UUID internal);
UUID getExternalUUID(@Nonnull UUID internalUuid);
}
@@ -33,8 +33,10 @@ import me.lucko.luckperms.api.metastacking.MetaStackDefinition;
import javax.annotation.Nonnull;
/**
* Represents the context for a meta lookup, consisting of a standard {@link Contexts} element, plus options to define how
* the meta stack should be constructed.
* Represents the context for a meta lookup.
*
* <p>Consisting of a standard {@link Contexts} element, plus options to define how
* the meta stack should be constructed.</p>
*
* @since 3.2
*/
@@ -35,7 +35,13 @@ import java.util.Set;
import javax.annotation.Nonnull;
/**
* Holder of contexts.
* A set of context pairs.
*
* <p>You can think of ContextSets as a wrapped <code>Multimap&lt;String, String&gt;</code>.
* Each key can be mapped to multiple values.</p>
*
* <p>Keys are automatically converted to lowercase when added, and are therefore
* case-insensitive. Values however are not.</p>
*
* <p>Implementations may be either mutable or immutable.</p>
*
@@ -72,18 +78,6 @@ public interface ContextSet {
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
* @throws NullPointerException if the map is null
*/
@Nonnull
static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return ImmutableContextSet.fromMap(map);
}
/**
* Creates an ImmutableContextSet from an existing iterable of Map Entries
*
@@ -96,6 +90,18 @@ public interface ContextSet {
return ImmutableContextSet.fromEntries(iterable);
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return ImmutableContextSet.fromMap(map);
}
/**
* Creates an ImmutableContextSet from an existing multimap
*
@@ -133,7 +139,7 @@ public interface ContextSet {
}
/**
* Check to see if this set is in an immutable form
* Gets if this set is in an immutable form
*
* @return true if the set is immutable
*/
@@ -167,12 +173,14 @@ public interface ContextSet {
/**
* Converts this ContextSet to an immutable {@link Map}
*
* <b>NOTE: Use of this method may result in data being lost. ContextSets can contain lots of different values for
* <b>IMPORTANT: Use of this method may result in data being lost. ContextSets can contain lots of different values for
* one key.</b>
*
* @return an immutable map
* @deprecated because the resultant map may not contain all data in the ContextSet
*/
@Nonnull
@Deprecated
Map<String, String> toMap();
/**
@@ -225,6 +233,19 @@ public interface ContextSet {
*/
boolean has(@Nonnull String key, @Nonnull String value);
/**
* Check if thr set contains a given key mapped to a given value
*
* @param entry the entry to look for
* @return true if the set contains the KV pair
* @throws NullPointerException if the key or value is null
* @since 3.4
*/
default boolean has(@Nonnull Map.Entry<String, String> entry) {
Preconditions.checkNotNull(entry, "entry");
return has(entry.getKey(), entry.getValue());
}
/**
* Same as {@link #has(String, String)}, except ignores the case of the value.
*
@@ -235,6 +256,19 @@ public interface ContextSet {
*/
boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value);
/**
* Same as {@link #has(Map.Entry)}, except ignores the case of the value.
*
* @param entry the entry to look for
* @return true if the set contains the KV pair
* @throws NullPointerException if the key or value is null
* @since 3.4
*/
default boolean hasIgnoreCase(@Nonnull Map.Entry<String, String> entry) {
Preconditions.checkNotNull(entry, "entry");
return hasIgnoreCase(entry.getKey(), entry.getValue());
}
/**
* Checks to see if all entries in this context set are also included in another set.
*
@@ -243,6 +277,18 @@ public interface ContextSet {
* @since 3.1
*/
default boolean isSatisfiedBy(@Nonnull ContextSet other) {
return isSatisfiedBy(other, true);
}
/**
* Checks to see if all entries in this context set are also included in another set.
*
* @param other the other set to check
* @param caseSensitive if the lookup should be case sensitive. see {@link #has(Map.Entry)} and {@link #hasIgnoreCase(Map.Entry)}.
* @return true if all entries in this set are also in the other set
* @since 3.4
*/
default boolean isSatisfiedBy(@Nonnull ContextSet other, boolean caseSensitive) {
Preconditions.checkNotNull(other, "other");
if (this.isEmpty()) {
// this is empty, so is therefore always satisfied.
@@ -256,8 +302,14 @@ public interface ContextSet {
} 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;
if (caseSensitive) {
if (!other.has(pair)) {
return false;
}
} else {
if (!other.hasIgnoreCase(pair)) {
return false;
}
}
}
@@ -25,12 +25,10 @@
package me.lucko.luckperms.api.context;
import com.google.common.base.Preconditions;
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.SetMultimap;
import java.util.Collection;
import java.util.Map;
@@ -38,9 +36,14 @@ import java.util.Set;
import javax.annotation.Nonnull;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* An immutable implementation of {@link ContextSet}.
*
* <p>On construction, all keys/values are {@link String#intern()}ed, in order to increase
* comparison speed.</p>
*
* @since 2.16
*/
public final class ImmutableContextSet implements ContextSet {
@@ -56,9 +59,10 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet singleton(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
return new ImmutableContextSet(ImmutableSetMultimap.of(key.toLowerCase(), value));
return new ImmutableContextSet(ImmutableSetMultimap.of(
checkNotNull(key, "key").toLowerCase().intern(),
checkNotNull(value, "value").intern()
));
}
/**
@@ -74,30 +78,12 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet of(@Nonnull String key1, @Nonnull String value1, @Nonnull String key2, @Nonnull String value2) {
Preconditions.checkNotNull(key1, "key1");
Preconditions.checkNotNull(value1, "value1");
Preconditions.checkNotNull(key2, "key2");
Preconditions.checkNotNull(value2, "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
* @throws NullPointerException if the map is null
*/
@Nonnull
public static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
Preconditions.checkNotNull(map, "map");
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : map.entrySet()) {
b.put(e.getKey().toLowerCase(), e.getValue());
}
return new ImmutableContextSet(b.build());
return new ImmutableContextSet(ImmutableSetMultimap.of(
checkNotNull(key1, "key1").toLowerCase().intern(),
checkNotNull(value1, "value1").intern(),
checkNotNull(key2, "key2").toLowerCase().intern(),
checkNotNull(value2, "value2").intern()
));
}
/**
@@ -109,8 +95,26 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromEntries(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
Preconditions.checkNotNull(iterable, "iterable");
return MutableContextSet.fromEntries(iterable).makeImmutable();
checkNotNull(iterable, "iterable");
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : iterable) {
b.put(checkNotNull(e.getKey()).toLowerCase().intern(), checkNotNull(e.getValue()).intern());
}
return new ImmutableContextSet(b.build());
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return fromEntries(checkNotNull(map, "map").entrySet());
}
/**
@@ -122,8 +126,7 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromMultimap(@Nonnull Multimap<String, String> multimap) {
Preconditions.checkNotNull(multimap, "multimap");
return MutableContextSet.fromMultimap(multimap).makeImmutable();
return fromEntries(checkNotNull(multimap, "multimap").entries());
}
/**
@@ -136,7 +139,7 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromSet(@Nonnull ContextSet contextSet) {
return Preconditions.checkNotNull(contextSet, "contextSet").makeImmutable();
return checkNotNull(contextSet, "contextSet").makeImmutable();
}
/**
@@ -149,10 +152,10 @@ public final class ImmutableContextSet implements ContextSet {
return EMPTY;
}
private final SetMultimap<String, String> map;
private final ImmutableSetMultimap<String, String> map;
ImmutableContextSet(Multimap<String, String> contexts) {
this.map = ImmutableSetMultimap.copyOf(contexts);
ImmutableContextSet(ImmutableSetMultimap<String, String> contexts) {
this.map = contexts;
}
@Override
@@ -176,7 +179,7 @@ public final class ImmutableContextSet implements ContextSet {
@Override
@Nonnull
public Set<Map.Entry<String, String>> toSet() {
return ImmutableSet.copyOf(map.entries());
return map.entries();
}
@Override
@@ -197,36 +200,35 @@ public final class ImmutableContextSet implements ContextSet {
}
@Override
@Nonnull
public boolean containsKey(@Nonnull String key) {
Preconditions.checkNotNull(key, "key");
return map.containsKey(key);
return map.containsKey(checkNotNull(key, "key").toLowerCase().intern());
}
@Override
@Nonnull
public Set<String> getValues(@Nonnull String key) {
Preconditions.checkNotNull(key, "key");
Collection<String> c = map.get(key);
return c != null && !c.isEmpty() ? ImmutableSet.copyOf(c) : ImmutableSet.of();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
return values != null ? ImmutableSet.copyOf(values) : ImmutableSet.of();
}
@Override
@Nonnull
public boolean has(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
return map.containsEntry(key, value);
return map.containsEntry(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
@Override
@Nonnull
public boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
value = checkNotNull(value, "value").intern();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
Collection<String> c = map.get(key);
if (c == null || c.isEmpty()) {
if (values == null || values.isEmpty()) {
return false;
}
for (String val : c) {
for (String val : values) {
if (val.equalsIgnoreCase(value)) {
return true;
}
@@ -250,6 +252,11 @@ public final class ImmutableContextSet implements ContextSet {
if (!(o instanceof ContextSet)) return false;
final ContextSet other = (ContextSet) o;
// saves on copying the multimap
if (other instanceof MutableContextSet) {
return other.equals(this);
}
final Multimap<String, String> thisContexts = this.toMultimap();
final Multimap<String, String> otherContexts = other.toMultimap();
return thisContexts.equals(otherContexts);
@@ -257,7 +264,7 @@ public final class ImmutableContextSet implements ContextSet {
@Override
public int hashCode() {
return 59 + (this.map == null ? 43 : this.map.hashCode());
return map.hashCode();
}
@Override
@@ -40,9 +40,14 @@ import java.util.Set;
import javax.annotation.Nonnull;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A mutable implementation of {@link ContextSet}.
*
* <p>On construction, all keys/values are {@link String#intern()}ed, in order to increase
* comparison speed.</p>
*
* @since 2.16
*/
public final class MutableContextSet implements ContextSet {
@@ -57,9 +62,9 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet singleton(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
MutableContextSet set = new MutableContextSet();
checkNotNull(key, "key");
checkNotNull(value, "value");
MutableContextSet set = MutableContextSet.create();
set.add(key, value);
return set;
}
@@ -77,27 +82,13 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet of(@Nonnull String key1, @Nonnull String value1, @Nonnull String key2, @Nonnull String value2) {
Preconditions.checkNotNull(key1, "key1");
Preconditions.checkNotNull(value1, "value1");
Preconditions.checkNotNull(key2, "key2");
Preconditions.checkNotNull(value2, "value2");
MutableContextSet ret = singleton(key1, value1);
ret.add(key2, value2);
return ret;
}
/**
* Creates a MutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new MutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static MutableContextSet fromMap(@Nonnull Map<String, String> map) {
Preconditions.checkNotNull(map, "map");
MutableContextSet set = new MutableContextSet();
set.addAll(map);
checkNotNull(key1, "key1");
checkNotNull(value1, "value1");
checkNotNull(key2, "key2");
checkNotNull(value2, "value2");
MutableContextSet set = MutableContextSet.create();
set.add(key1, value1);
set.add(key2, value2);
return set;
}
@@ -110,12 +101,27 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet fromEntries(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
Preconditions.checkNotNull(iterable, "iterable");
MutableContextSet set = new MutableContextSet();
checkNotNull(iterable, "iterable");
MutableContextSet set = MutableContextSet.create();
set.addAll(iterable);
return set;
}
/**
* Creates a MutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new MutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static MutableContextSet fromMap(@Nonnull Map<String, String> map) {
checkNotNull(map, "map");
MutableContextSet set = MutableContextSet.create();
set.addAll(map);
return set;
}
/**
* Creates a MutableContextSet from an existing multimap
*
@@ -125,8 +131,10 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet fromMultimap(@Nonnull Multimap<String, String> multimap) {
Preconditions.checkNotNull(multimap, "multimap");
return fromEntries(multimap.entries());
checkNotNull(multimap, "multimap");
MutableContextSet set = MutableContextSet.create();
set.addAll(multimap);
return set;
}
/**
@@ -161,8 +169,8 @@ public final class MutableContextSet implements ContextSet {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create());
}
private MutableContextSet(Multimap<String, String> contexts) {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(contexts));
private MutableContextSet(MutableContextSet other) {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(other.map));
}
@Override
@@ -170,21 +178,25 @@ public final class MutableContextSet implements ContextSet {
return false;
}
@Nonnull
@Override
public ImmutableContextSet makeImmutable() {
return new ImmutableContextSet(map);
return new ImmutableContextSet(ImmutableSetMultimap.copyOf(map));
}
@Nonnull
@Override
public MutableContextSet mutableCopy() {
return new MutableContextSet(map);
return new MutableContextSet(this);
}
@Nonnull
@Override
public Set<Map.Entry<String, String>> toSet() {
return ImmutableSet.copyOf(map.entries());
}
@Nonnull
@Override
public Map<String, String> toMap() {
ImmutableMap.Builder<String, String> m = ImmutableMap.builder();
@@ -195,57 +207,42 @@ public final class MutableContextSet implements ContextSet {
return m.build();
}
@Nonnull
@Override
public Multimap<String, String> toMultimap() {
return ImmutableSetMultimap.copyOf(map);
}
@Nonnull
@Override
public boolean containsKey(String key) {
if (key == null) {
throw new NullPointerException("key");
}
return map.containsKey(key);
public boolean containsKey(@Nonnull String key) {
return map.containsKey(checkNotNull(key, "key").toLowerCase().intern());
}
@Nonnull
@Override
public Set<String> getValues(String key) {
if (key == null) {
throw new NullPointerException("key");
}
Collection<String> c = map.get(key);
return c != null && !c.isEmpty() ? ImmutableSet.copyOf(c) : ImmutableSet.of();
public Set<String> getValues(@Nonnull String key) {
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
return values != null ? ImmutableSet.copyOf(values) : ImmutableSet.of();
}
@Nonnull
@Override
public boolean has(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
return map.containsEntry(key, value);
public boolean has(@Nonnull String key, @Nonnull String value) {
return map.containsEntry(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
@Nonnull
@Override
public boolean hasIgnoreCase(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
public boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value) {
value = checkNotNull(value, "value").intern();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
Collection<String> c = map.get(key);
if (c == null || c.isEmpty()) {
if (values == null || values.isEmpty()) {
return false;
}
for (String val : c) {
for (String val : values) {
if (val.equalsIgnoreCase(value)) {
return true;
}
@@ -270,15 +267,8 @@ public final class MutableContextSet implements ContextSet {
* @param value the value to add
* @throws NullPointerException if the key or value is null
*/
public void add(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
map.put(key.toLowerCase(), value);
public void add(@Nonnull String key, @Nonnull String value) {
map.put(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
/**
@@ -287,12 +277,9 @@ public final class MutableContextSet implements ContextSet {
* @param entry the entry to add
* @throws NullPointerException if the entry is null
*/
public void add(Map.Entry<String, String> entry) {
if (entry == null) {
throw new NullPointerException("context");
}
map.put(entry.getKey().toLowerCase(), entry.getValue());
public void add(@Nonnull Map.Entry<String, String> entry) {
checkNotNull(entry, "entry");
add(entry.getKey(), entry.getValue());
}
/**
@@ -301,13 +288,9 @@ public final class MutableContextSet implements ContextSet {
* @param iterable an iterable of key value context pairs
* @throws NullPointerException if iterable is null
*/
public void addAll(Iterable<? extends Map.Entry<String, String>> iterable) {
if (iterable == null) {
throw new NullPointerException("iterable");
}
for (Map.Entry<String, String> e : iterable) {
this.map.put(e.getKey().toLowerCase(), e.getValue());
public void addAll(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
for (Map.Entry<String, String> e : checkNotNull(iterable, "iterable")) {
add(e);
}
}
@@ -317,11 +300,19 @@ public final class MutableContextSet implements ContextSet {
* @param map the map to add from
* @throws NullPointerException if the map is null
*/
public void addAll(Map<String, String> map) {
if (map == null) {
throw new NullPointerException("map");
}
addAll(map.entrySet());
public void addAll(@Nonnull Map<String, String> map) {
addAll(checkNotNull(map, "map").entrySet());
}
/**
* Adds the entries of a multimap to the set
*
* @param multimap the multimap to add from
* @throws NullPointerException if the map is null
* @since 3.4
*/
public void addAll(@Nonnull Multimap<String, String> multimap) {
addAll(checkNotNull(multimap, "multimap").entries());
}
/**
@@ -330,12 +321,14 @@ public final class MutableContextSet implements ContextSet {
* @param contextSet the set to add from
* @throws NullPointerException if the contextSet is null
*/
public void addAll(ContextSet contextSet) {
if (contextSet == null) {
throw new NullPointerException("contextSet");
public void addAll(@Nonnull ContextSet contextSet) {
checkNotNull(contextSet, "contextSet");
if (contextSet instanceof MutableContextSet) {
MutableContextSet other = ((MutableContextSet) contextSet);
this.map.putAll(other.map);
} else {
addAll(contextSet.toMultimap());
}
this.map.putAll(contextSet.toMultimap());
}
/**
@@ -345,15 +338,12 @@ public final class MutableContextSet implements ContextSet {
* @param value the value to remove (case sensitive)
* @throws NullPointerException if the key or value is null
*/
public void remove(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
public void remove(@Nonnull String key, @Nonnull String value) {
String k = checkNotNull(key, "key").toLowerCase().intern();
String v = checkNotNull(value, "value").intern();
map.entries().removeIf(entry -> entry.getKey().equals(key) && entry.getValue().equals(value));
//noinspection StringEquality
map.entries().removeIf(entry -> entry.getKey() == k && entry.getValue() == v);
}
/**
@@ -363,15 +353,12 @@ public final class MutableContextSet implements ContextSet {
* @param value the value to remove
* @throws NullPointerException if the key or value is null
*/
public void removeIgnoreCase(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
public void removeIgnoreCase(@Nonnull String key, @Nonnull String value) {
String k = checkNotNull(key, "key").toLowerCase().intern();
String v = checkNotNull(value, "value").intern();
map.entries().removeIf(e -> e.getKey().equalsIgnoreCase(key) && e.getValue().equalsIgnoreCase(value));
//noinspection StringEquality
map.entries().removeIf(e -> e.getKey() == k && e.getValue().equalsIgnoreCase(v));
}
/**
@@ -380,12 +367,8 @@ public final class MutableContextSet implements ContextSet {
* @param key the key to remove
* @throws NullPointerException if the key is null
*/
public void removeAll(String key) {
if (key == null) {
throw new NullPointerException("key");
}
map.removeAll(key.toLowerCase());
public void removeAll(@Nonnull String key) {
map.removeAll(checkNotNull(key, "key").toLowerCase());
}
/**
@@ -401,14 +384,20 @@ public final class MutableContextSet implements ContextSet {
if (!(o instanceof ContextSet)) return false;
final ContextSet other = (ContextSet) o;
final Multimap<String, String> thisContexts = this.toMultimap();
final Multimap<String, String> otherContexts = other.toMultimap();
return thisContexts == null ? otherContexts == null : thisContexts.equals(otherContexts);
final Multimap<String, String> otherContexts;
if (other instanceof MutableContextSet) {
otherContexts = ((MutableContextSet) other).map;
} else {
otherContexts = other.toMultimap();
}
return this.map.equals(otherContexts);
}
@Override
public int hashCode() {
return 59 + (this.map == null ? 43 : this.map.hashCode());
return map.hashCode();
}
@Override
@@ -44,4 +44,31 @@ public interface LogBroadcastEvent extends LuckPermsEvent, Cancellable {
@Nonnull
LogEntry getEntry();
/**
* Gets where the log entry originated from.
*
* @return the origin of the log
* @since 3.3
*/
@Nonnull
Origin getOrigin();
/**
* Represents where a log entry is from
*
* @since 3.3
*/
enum Origin {
/**
* Represents a log entry which originated from the current server instance
*/
LOCAL,
/**
* Represents a log entry which was sent to this server via the messaging service
*/
REMOTE
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.api.event.log;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.event.Cancellable;
import me.lucko.luckperms.api.event.LuckPermsEvent;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called when a log is about to be published to the network via the MessagingService
*
* @since 3.3
*/
public interface LogNetworkPublishEvent extends LuckPermsEvent, Cancellable {
/**
* Gets the ID of the log entry being published
*
* @return the id of the log entry being published
*/
@Nonnull
UUID getLogId();
/**
* Gets the log entry to be published
*
* @return the log entry to be published
*/
@Nonnull
LogEntry getEntry();
}
@@ -0,0 +1,58 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.api.event.log;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.event.LuckPermsEvent;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called when a log entry is received via the MessagingService
*
* @since 3.3
*/
public interface LogReceiveEvent extends LuckPermsEvent {
/**
* Gets the ID of the log entry being received
*
* @return the id of the log entry being received
*/
@Nonnull
UUID getLogId();
/**
* Gets the log entry being received
*
* @return the log entry being received
*/
@Nonnull
LogEntry getEntry();
}
@@ -33,7 +33,7 @@ import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called before a network sync task runs
* Called before a received network sync task runs
*/
public interface PreNetworkSyncEvent extends LuckPermsEvent, Cancellable {
@@ -0,0 +1,72 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.api.event.user;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.event.LuckPermsEvent;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called when LuckPerms has finished processing a certain Player's connection.
*
* <p>This event will always execute during the platforms async login/auth event.
* All handlers will be called instantly.</p>
*
* <p>This, among other things, allows you to wait until permission data is loaded
* for a User during the BungeeCord 'LoginEvent', as event priorities are ignored
* by the current implementation.</p>
*
* @since 3.4
*/
public interface UserLoginProcessEvent extends LuckPermsEvent {
/**
* Gets the UUID of the connection which was processed
*
* @return the uuid of the connection which was processed
*/
@Nonnull
UUID getUuid();
/**
* Gets the username of the connection which was processed
*
* @return the username of the connection which was processed
*/
@Nonnull
String getUsername();
/**
* Gets the resultant User instance which was loaded.
*
* @return the user instance
*/
User getUser();
}
@@ -26,8 +26,16 @@
package me.lucko.luckperms.exceptions;
/**
* Thrown when a permission holding object doesn't / already has a permission or isn't / is already is a member of a
* group
* Thrown when a certain membership state is / isn't met.
*
* For example, when:
* <p></p>
* <ul>
* <li>a permission holding object doesn't have a permission</li>
* <li>a permission holding object already has a permission</li>
* <li>a permission holding object is already a member of a group</li>
* <li>a permission holding object isn't already a member of a group</li>
* </ul>
*
* @since 2.7
*/
@@ -26,8 +26,15 @@
package me.lucko.luckperms.exceptions;
/**
* Thrown when a permission holding object already has a permission, is already a member of a group, or when a track
* already contains a group.
* Thrown when an object already has something.
*
* <p>For example, when:</p>
* <p></p>
* <ul>
* <li>a permission holding object already has a permission</li>
* <li>a permission holding object is already a member of a group</li>
* <li>a track already contains a group</li>
* </ul>
*/
public class ObjectAlreadyHasException extends MembershipException {
}
@@ -26,8 +26,15 @@
package me.lucko.luckperms.exceptions;
/**
* Thrown when a permission holding object does not already have a permission, is not already a member of a group,
* or when a track doesn't contain a group.
* Thrown when an object lacks something.
*
* <p>For example, when:</p>
* <p></p>
* <ul>
* <li>a permission holding object doesn't have a permission</li>
* <li>a permission holding object isn't already a member of a group</li>
* <li>a track doesn't contain a group</li>
* </ul>
*/
public class ObjectLacksException extends MembershipException {
}
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>3.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>3.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -71,6 +71,10 @@ public class BukkitListener implements Listener {
ex.printStackTrace();
}
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing pre-login for " + e.getUniqueId() + " - " + e.getName());
}
/* 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
@@ -98,7 +102,8 @@ public class BukkitListener implements Listener {
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
try {
LoginHelper.loadUser(plugin, e.getUniqueId(), e.getName(), false);
User user = LoginHelper.loadUser(plugin, e.getUniqueId(), e.getName(), false);
plugin.getApiProvider().getEventFactory().handleUserLoginProcess(e.getUniqueId(), e.getName(), user);
} catch (Exception ex) {
ex.printStackTrace();
@@ -139,6 +144,11 @@ public class BukkitListener implements Listener {
At this point, the users data should be present and loaded. */
final Player player = e.getPlayer();
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing login for " + player.getUniqueId() + " - " + player.getName());
}
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
@@ -32,20 +32,21 @@ import me.lucko.luckperms.api.Logger;
import me.lucko.luckperms.api.LuckPermsApi;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.bukkit.calculators.BukkitCalculatorFactory;
import me.lucko.luckperms.bukkit.contexts.BukkitContextManager;
import me.lucko.luckperms.bukkit.contexts.WorldCalculator;
import me.lucko.luckperms.bukkit.messaging.BungeeMessagingService;
import me.lucko.luckperms.bukkit.messaging.LilyPadMessagingService;
import me.lucko.luckperms.bukkit.messaging.BukkitMessagingFactory;
import me.lucko.luckperms.bukkit.model.Injector;
import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.bukkit.processors.ChildPermissionProvider;
import me.lucko.luckperms.bukkit.processors.DefaultsProvider;
import me.lucko.luckperms.bukkit.vault.VaultHookManager;
import me.lucko.luckperms.common.actionlog.LogDispatcher;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.api.ApiProvider;
import me.lucko.luckperms.common.buffers.BufferedRequest;
import me.lucko.luckperms.common.buffers.UpdateTaskBuffer;
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
import me.lucko.luckperms.common.calculators.CalculatorFactory;
import me.lucko.luckperms.common.commands.sender.Sender;
@@ -67,8 +68,6 @@ import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.UserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessagingService;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.Storage;
@@ -77,7 +76,6 @@ import me.lucko.luckperms.common.storage.StorageType;
import me.lucko.luckperms.common.storage.backing.file.FileWatcher;
import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
import me.lucko.luckperms.common.treeview.PermissionVault;
import me.lucko.luckperms.common.utils.LoginHelper;
import me.lucko.luckperms.common.utils.UuidCache;
@@ -109,8 +107,7 @@ import java.util.stream.Collectors;
@Getter
public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private Set<UUID> ignoringLogs;
private Set<UUID> uniqueConnections;
private long startTime;
private LPBukkitScheduler scheduler;
private BukkitCommand commandManager;
@@ -131,7 +128,6 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private LocaleManager localeManager;
private CachedStateManager cachedStateManager;
private ContextManager<Player> contextManager;
private WorldCalculator worldCalculator;
private CalculatorFactory calculatorFactory;
private BufferedRequest<Void> updateTaskBuffer;
private boolean started = false;
@@ -139,9 +135,15 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private VerboseHandler verboseHandler;
private BukkitSenderFactory senderFactory;
private PermissionVault permissionVault;
private LogDispatcher logDispatcher;
private Set<UUID> uniqueConnections = ConcurrentHashMap.newKeySet();
@Override
public void onLoad() {
if (checkInvalidVersion()) {
return;
}
// setup minimal functionality in order to load initial dependencies
scheduler = new LPBukkitScheduler(this);
localeManager = new NoopLocaleManager();
@@ -153,6 +155,18 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public void onEnable() {
if (checkInvalidVersion()) {
getLogger().severe("----------------------------------------------------------------------");
getLogger().severe("Your server version is not compatible with this build of LuckPerms. :(");
getLogger().severe("");
getLogger().severe("If your server is running 1.8, please update to 1.8.8 or higher.");
getLogger().severe("If your server is running 1.7.10, please download the Bukkit-Legacy version of LuckPerms from here:");
getLogger().severe("==> https://ci.lucko.me/job/LuckPerms/");
getLogger().severe("----------------------------------------------------------------------");
getServer().getPluginManager().disablePlugin(this);
return;
}
try {
enable();
started = true;
@@ -166,11 +180,9 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private void enable() {
startTime = System.currentTimeMillis();
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
ignoringLogs = ConcurrentHashMap.newKeySet();
uniqueConnections = ConcurrentHashMap.newKeySet();
verboseHandler = new VerboseHandler(scheduler.getAsyncBukkitExecutor(), getVersion());
permissionVault = new PermissionVault(scheduler.getAsyncBukkitExecutor());
verboseHandler = new VerboseHandler(scheduler.asyncBukkit(), getVersion());
permissionVault = new PermissionVault(scheduler.asyncBukkit());
logDispatcher = new LogDispatcher(this);
getLog().info("Loading configuration...");
configuration = new BukkitConfig(this);
@@ -211,68 +223,14 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
storage = StorageFactory.getInstance(this, StorageType.H2);
// initialise messaging
String messagingType = getConfiguration().get(ConfigKeys.MESSAGING_SERVICE).toLowerCase();
if (messagingType.equals("none") && getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
messagingType = "redis";
}
if (!messagingType.equals("none")) {
getLog().info("Loading messaging service... [" + messagingType.toUpperCase() + "]");
}
if (messagingType.equals("redis")) {
if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
RedisMessagingService redis = new RedisMessagingService(this);
try {
redis.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD));
messagingService = redis;
} catch (Exception e) {
getLog().warn("Couldn't load redis...");
e.printStackTrace();
}
} else {
getLog().warn("Messaging Service was set to redis, but redis is not enabled!");
}
} else if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(this);
bungeeMessaging.init();
messagingService = bungeeMessaging;
} else if (messagingType.equals("lilypad")) {
if (getServer().getPluginManager().getPlugin("LilyPad-Connect") == null) {
getLog().warn("LilyPad-Connect plugin not present.");
} else {
LilyPadMessagingService lilyPadMessaging = new LilyPadMessagingService(this);
lilyPadMessaging.init();
messagingService = lilyPadMessaging;
}
} else if (!messagingType.equals("none")) {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
messagingService = new BukkitMessagingFactory(this).getInstance();
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
protected Void perform() {
new UpdateTask(LPBukkitPlugin.this).run();
return null;
}
};
updateTaskBuffer = new UpdateTaskBuffer(this);
// load locale
localeManager = new SimpleLocaleManager();
File locale = new File(getDataFolder(), "lang.yml");
if (locale.exists()) {
getLog().info("Found lang.yml - loading messages...");
try {
localeManager.loadFromFile(locale);
} catch (Exception e) {
e.printStackTrace();
}
}
localeManager.tryLoad(this, new File(getDataFolder(), "lang.yml"));
// register commands
commandManager = new BukkitCommand(this);
@@ -291,26 +249,10 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
calculatorFactory = new BukkitCalculatorFactory(this);
cachedStateManager = new CachedStateManager();
contextManager = new ContextManager<Player>() {
@Override
public Contexts formContexts(Player player, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
player.isOp()
);
}
};
worldCalculator = new WorldCalculator(this);
contextManager.registerCalculator(worldCalculator);
LuckPermsCalculator<Player> staticCalculator = new LuckPermsCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator, true);
// setup context manager
contextManager = new BukkitContextManager(this);
contextManager.registerCalculator(new WorldCalculator(this));
contextManager.registerCalculator(new LuckPermsCalculator<>(getConfiguration()), true);
// Provide vault support
tryVaultHook(false);
@@ -374,6 +316,10 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public void onDisable() {
if (checkInvalidVersion()) {
return;
}
// Switch back to the LP executor, the bukkit one won't allow new tasks
scheduler.setUseBukkitAsync(false);
@@ -428,7 +374,6 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
HandlerList.unregisterAll(this);
// Null everything
ignoringLogs = null;
vaultHookManager = null;
configuration = null;
userManager = null;
@@ -446,12 +391,12 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
localeManager = null;
cachedStateManager = null;
contextManager = null;
worldCalculator = null;
calculatorFactory = null;
updateTaskBuffer = null;
verboseHandler = null;
senderFactory = null;
permissionVault = null;
logDispatcher = null;
}
public void tryVaultHook(boolean force) {
@@ -671,4 +616,13 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
pm.addPermission(new org.bukkit.permissions.Permission(p.getPermission(), def));
}
}
private static boolean checkInvalidVersion() {
try {
Class.forName("com.google.gson.JsonElement");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
}
@@ -27,6 +27,7 @@ package me.lucko.luckperms.bukkit;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import me.lucko.luckperms.common.plugin.LuckPermsScheduler;
@@ -43,10 +44,16 @@ public class LPBukkitScheduler implements LuckPermsScheduler {
private final LPBukkitPlugin plugin;
@Getter
private ExecutorService asyncLpExecutor;
@Accessors(fluent = true)
private ExecutorService asyncLp;
@Getter
private Executor asyncBukkitExecutor;
private Executor syncExecutor;
@Accessors(fluent = true)
private Executor asyncBukkit;
@Getter
@Accessors(fluent = true)
private Executor sync;
@Getter
@Setter
@@ -57,19 +64,14 @@ public class LPBukkitScheduler implements LuckPermsScheduler {
public LPBukkitScheduler(LPBukkitPlugin plugin) {
this.plugin = plugin;
this.asyncLpExecutor = Executors.newCachedThreadPool();
this.asyncBukkitExecutor = r -> plugin.getServer().getScheduler().runTaskAsynchronously(plugin, r);
this.syncExecutor = r -> plugin.getServer().getScheduler().runTask(plugin, r);
this.asyncLp = Executors.newCachedThreadPool();
this.asyncBukkit = r -> plugin.getServer().getScheduler().runTaskAsynchronously(plugin, r);
this.sync = r -> plugin.getServer().getScheduler().runTask(plugin, r);
}
@Override
public Executor async() {
return useBukkitAsync ? asyncBukkitExecutor : asyncLpExecutor;
}
@Override
public Executor sync() {
return syncExecutor;
return useBukkitAsync ? asyncBukkit : asyncLp;
}
@Override
@@ -108,9 +110,9 @@ public class LPBukkitScheduler implements LuckPermsScheduler {
public void shutdown() {
tasks.forEach(BukkitTask::cancel);
// wait for executor
asyncLpExecutor.shutdown();
asyncLp.shutdown();
try {
asyncLpExecutor.awaitTermination(30, TimeUnit.SECONDS);
asyncLp.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -38,6 +38,7 @@ import me.lucko.luckperms.bukkit.processors.ChildProcessor;
import me.lucko.luckperms.bukkit.processors.DefaultsProcessor;
import me.lucko.luckperms.common.calculators.AbstractCalculatorFactory;
import me.lucko.luckperms.common.calculators.PermissionCalculator;
import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.processors.MapProcessor;
@@ -82,7 +83,11 @@ public class BukkitCalculatorFactory extends AbstractCalculatorFactory {
processors.add(new DefaultsProcessor(contexts.isOp(), plugin.getDefaultsProvider()));
}
return registerCalculator(new PermissionCalculator(plugin, user.getFriendlyName(), processors.build()));
return registerCalculator(new PermissionCalculator(
plugin,
PermissionCalculatorMetadata.of(user.getFriendlyName(), contexts.getContexts()),
processors.build()
));
}
@Override
@@ -91,7 +91,7 @@ public class BukkitJsonMessageHandler {
setup(player);
setup = true;
return true;
} catch (Exception e) {
} catch (Throwable e) {
triedAndFailed = true;
return false;
}
@@ -25,10 +25,10 @@
package me.lucko.luckperms.bukkit.compat;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializer;
import net.kyori.text.serializer.ComponentSerializers;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -48,7 +48,7 @@ public class MessageHandler {
public void sendJsonMessage(CommandSender sender, Component message) {
if (ReflectionUtil.isChatCompatible() && sender instanceof Player) {
Player player = (Player) sender;
String json = ComponentSerializer.serialize(message);
String json = ComponentSerializers.JSON.serialize(message);
// Try Bukkit.
if (bukkitHandler.sendJsonMessage(player, json)) {
@@ -62,7 +62,7 @@ public class MessageHandler {
}
// Fallback to Bukkit
sender.sendMessage(ComponentSerializer.toLegacy(message, Constants.COLOR_CHAR));
sender.sendMessage(TextUtils.toLegacy(message));
}
private static boolean isSpigot() {
@@ -0,0 +1,54 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.contexts;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.Contexts;
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.contexts.ContextManager;
import org.bukkit.entity.Player;
@RequiredArgsConstructor
public class BukkitContextManager extends ContextManager<Player> {
private final LPBukkitPlugin plugin;
@Override
public Contexts formContexts(Player subject, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
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),
subject.isOp()
);
}
}
@@ -0,0 +1,55 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.messaging;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.MessagingFactory;
public class BukkitMessagingFactory extends MessagingFactory<LPBukkitPlugin> {
public BukkitMessagingFactory(LPBukkitPlugin plugin) {
super(plugin);
}
@Override
protected InternalMessagingService getServiceFor(String messagingType) {
if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(getPlugin());
bungeeMessaging.init();
return bungeeMessaging;
} else if (messagingType.equals("lilypad")) {
if (getPlugin().getServer().getPluginManager().getPlugin("LilyPad-Connect") == null) {
getPlugin().getLog().warn("LilyPad-Connect plugin not present.");
} else {
LilyPadMessagingService lilyPadMessaging = new LilyPadMessagingService(getPlugin());
lilyPadMessaging.init();
return lilyPadMessaging;
}
}
return super.getServiceFor(messagingType);
}
}
@@ -35,6 +35,7 @@ import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.caching.UserCache;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -110,13 +111,13 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean isPermissionSet(@NonNull String permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission);
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_LOOKUP_CHECK);
return ts != Tristate.UNDEFINED || Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean isPermissionSet(@NonNull Permission permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName());
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_LOOKUP_CHECK);
if (ts != Tristate.UNDEFINED) {
return true;
}
@@ -130,13 +131,13 @@ public class LPPermissible extends PermissibleBase {
@Override
public boolean hasPermission(@NonNull String permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission);
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_PERMISSION_CHECK);
return ts != Tristate.UNDEFINED ? ts.asBoolean() : Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean hasPermission(@NonNull Permission permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName());
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_PERMISSION_CHECK);
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
@@ -331,7 +331,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.getOwnNodes()) {
if (!n.getValue()) continue;
if (!n.getValuePrimitive()) continue;
if (!n.isMeta()) continue;
if (!n.shouldApplyWithContext(perms.createContextForWorldLookup(world).getContexts())) continue;
@@ -357,7 +357,7 @@ public class VaultChatHook extends Chat {
ExtractedContexts ec = ExtractedContexts.generate(Contexts.of(perms.createContextForWorldLookup(world).getContexts(), perms.isIncludeGlobal(), true, true, true, true, false));
for (Node n : group.getAllNodes(ec)) {
if (!n.getValue()) continue;
if (!n.getValuePrimitive()) continue;
if (type.shouldIgnore(n)) continue;
if (!n.shouldApplyWithContext(perms.createContextForWorldLookup(world).getContexts())) continue;
@@ -32,16 +32,17 @@ import me.lucko.luckperms.api.Contexts;
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.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.caching.PermissionCache;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.contexts.ExtractedContexts;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import net.milkbowl.vault.permission.Permission;
@@ -147,7 +148,7 @@ public class VaultPermissionHook extends Permission {
}
// Effectively fallback to the standard Bukkit #hasPermission check.
return user.getUserData().getPermissionData(createContextForWorldLookup(player, world)).getPermissionValue(permission).asBoolean();
return user.getUserData().getPermissionData(createContextForWorldLookup(player, world)).getPermissionValue(permission, CheckOrigin.INTERNAL).asBoolean();
}
@Override
@@ -396,7 +397,7 @@ public class VaultPermissionHook extends Permission {
// we need to do the complex PGO checking. (it's been enabled in the config.)
if (isPgoCheckInherited()) {
// we can just check the cached data
PermissionData data = user.getUserData().getPermissionData(createContextForWorldLookup(plugin.getPlayer(user), world));
PermissionCache data = user.getUserData().getPermissionData(createContextForWorldLookup(plugin.getPlayer(user), world));
for (Map.Entry<String, Boolean> e : data.getImmutableBacking().entrySet()) {
if (!e.getValue()) continue;
if (!e.getKey().toLowerCase().startsWith("vault.primarygroup.")) continue;
@@ -409,7 +410,7 @@ public class VaultPermissionHook extends Permission {
}
if (isPgoCheckMemberOf()) {
if (data.getPermissionValue("group." + group) != Tristate.TRUE) {
if (data.getPermissionValue("group." + group, CheckOrigin.INTERNAL) != Tristate.TRUE) {
continue;
}
}
@@ -419,7 +420,7 @@ public class VaultPermissionHook extends Permission {
} else {
// we need to check the users permissions only
for (Node node : user.getOwnNodes()) {
if (!node.getValue()) continue;
if (!node.getValuePrimitive()) continue;
if (!node.getPermission().toLowerCase().startsWith("vault.primarygroup.")) continue;
if (!node.shouldApplyOnServer(getServer(), isIncludeGlobal(), false)) continue;
if (!node.shouldApplyOnWorld(world, true, false)) continue;
+18
View File
@@ -70,6 +70,10 @@ use-server-uuids: true
# in the LuckPerms cache.
use-server-uuid-cache: false
# If LuckPerms should produce extra logging output when it handles logins.
# Useful if you're having issues with UUID forwarding or data not being loaded.
debug-logins: false
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
@@ -101,6 +105,10 @@ temporary-add-behaviour: deny
# directly and indirectly
primary-group-calculation: stored
# If set to false, the plugin will allow a Users primary group to be removed with the
# 'parent remove' command, and will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should check for "extra" permissions with users run LP commands.
#
# These extra permissions allow finer control over what users can do with each command, and
@@ -328,6 +336,16 @@ messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# If LuckPerms should push logging entries to connected servers via the messaging service.
push-log-entries: true
# If LuckPerms should broadcast received logging entries to players on this platform.
#
# If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you should
# set this option to false on either your backends or your proxies, to avoid players being messaged
# twice about log entries.
broadcast-received-log-entries: true
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>3.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -35,6 +35,7 @@ import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.utils.LoginHelper;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
@@ -70,6 +71,10 @@ public class BungeeListener implements Listener {
final PendingConnection c = e.getConnection();
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing pre-login for " + c.getUniqueId() + " - " + c.getName());
}
/* 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. */
@@ -96,7 +101,8 @@ public class BungeeListener implements Listener {
- creating a user instance in the UserManager for this connection.
- setting up cached data. */
try {
LoginHelper.loadUser(plugin, c.getUniqueId(), c.getName(), true);
User user = LoginHelper.loadUser(plugin, c.getUniqueId(), c.getName(), true);
plugin.getApiProvider().getEventFactory().handleUserLoginProcess(c.getUniqueId(), c.getName(), user);
} catch (Exception ex) {
ex.printStackTrace();
@@ -118,6 +124,10 @@ public class BungeeListener implements Listener {
final ProxiedPlayer player = e.getPlayer();
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(e.getPlayer().getUniqueId()));
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing post-login for " + player.getUniqueId() + " - " + player.getName());
}
if (user == null) {
plugin.getProxy().getScheduler().schedule(plugin, () -> {
if (!player.isConnected()) {
@@ -151,7 +161,7 @@ public class BungeeListener implements Listener {
}
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission());
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission(), CheckOrigin.PLATFORM_PERMISSION_CHECK);
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
}
@@ -174,7 +184,7 @@ public class BungeeListener implements Listener {
}
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission());
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission(), CheckOrigin.PLATFORM_LOOKUP_CHECK);
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
}
@@ -30,9 +30,10 @@ import me.lucko.luckperms.bungee.event.TristateCheckEvent;
import me.lucko.luckperms.common.commands.sender.SenderFactory;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializer;
import net.kyori.text.serializer.ComponentSerializers;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -68,9 +69,10 @@ public class BungeeSenderFactory extends SenderFactory<CommandSender> {
@Override
protected void sendMessage(CommandSender sender, Component message) {
try {
sender.sendMessage(net.md_5.bungee.chat.ComponentSerializer.parse(ComponentSerializer.serialize(message)));
sender.sendMessage(net.md_5.bungee.chat.ComponentSerializer.parse(ComponentSerializers.JSON.serialize(message)));
} catch (Exception e) {
sendMessage(sender, ComponentSerializer.toLegacy(message, Constants.COLOR_CHAR));
//noinspection deprecation
sendMessage(sender, TextUtils.toLegacy(message));
}
}
@@ -34,12 +34,14 @@ import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bungee.calculators.BungeeCalculatorFactory;
import me.lucko.luckperms.bungee.contexts.BackendServerCalculator;
import me.lucko.luckperms.bungee.messaging.BungeeMessagingService;
import me.lucko.luckperms.bungee.messaging.RedisBungeeMessagingService;
import me.lucko.luckperms.bungee.contexts.BungeeContextManager;
import me.lucko.luckperms.bungee.messaging.BungeeMessagingFactory;
import me.lucko.luckperms.bungee.util.RedisBungeeUtil;
import me.lucko.luckperms.common.actionlog.LogDispatcher;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.api.ApiProvider;
import me.lucko.luckperms.common.buffers.BufferedRequest;
import me.lucko.luckperms.common.buffers.UpdateTaskBuffer;
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
import me.lucko.luckperms.common.calculators.CalculatorFactory;
import me.lucko.luckperms.common.commands.CommandManager;
@@ -61,8 +63,6 @@ import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.UserManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.messaging.RedisMessagingService;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.LuckPermsScheduler;
@@ -72,7 +72,6 @@ import me.lucko.luckperms.common.storage.StorageType;
import me.lucko.luckperms.common.storage.backing.file.FileWatcher;
import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.UpdateTask;
import me.lucko.luckperms.common.treeview.PermissionVault;
import me.lucko.luckperms.common.utils.UuidCache;
import me.lucko.luckperms.common.verbose.VerboseHandler;
@@ -93,8 +92,7 @@ import java.util.stream.Collectors;
@Getter
public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private final Set<UUID> ignoringLogs = ConcurrentHashMap.newKeySet();
private Set<UUID> uniqueConnections = ConcurrentHashMap.newKeySet();
private long startTime;
private LuckPermsScheduler scheduler;
private CommandManager commandManager;
@@ -116,6 +114,8 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private VerboseHandler verboseHandler;
private BungeeSenderFactory senderFactory;
private PermissionVault permissionVault;
private LogDispatcher logDispatcher;
private Set<UUID> uniqueConnections = ConcurrentHashMap.newKeySet();
@Override
public void onLoad() {
@@ -134,6 +134,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
verboseHandler = new VerboseHandler(scheduler.async(), getVersion());
permissionVault = new PermissionVault(scheduler.async());
logDispatcher = new LogDispatcher(this);
getLog().info("Loading configuration...");
configuration = new BungeeConfig(this);
@@ -155,68 +156,14 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
storage = StorageFactory.getInstance(this, StorageType.H2);
// initialise messaging
String messagingType = getConfiguration().get(ConfigKeys.MESSAGING_SERVICE).toLowerCase();
if (messagingType.equals("none") && getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
messagingType = "redis";
}
if (!messagingType.equals("none")) {
getLog().info("Loading messaging service... [" + messagingType.toUpperCase() + "]");
}
if (messagingType.equals("redis")) {
if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) {
RedisMessagingService redis = new RedisMessagingService(this);
try {
redis.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD));
messagingService = redis;
} catch (Exception e) {
getLog().warn("Couldn't load redis...");
e.printStackTrace();
}
} else {
getLog().warn("Messaging Service was set to redis, but redis is not enabled!");
}
} else if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(this);
bungeeMessaging.init();
messagingService = bungeeMessaging;
} else if (messagingType.equals("redisbungee")) {
if (getProxy().getPluginManager().getPlugin("RedisBungee") == null) {
getLog().warn("RedisBungee plugin not present.");
} else {
RedisBungeeMessagingService redisBungeeMessaging = new RedisBungeeMessagingService(this);
redisBungeeMessaging.init();
messagingService = redisBungeeMessaging;
}
} else if (!messagingType.equals("none")) {
getLog().warn("Messaging service '" + messagingType + "' not recognised.");
}
if (messagingService == null) {
messagingService = new NoopMessagingService();
}
messagingService = new BungeeMessagingFactory(this).getInstance();
// setup the update task buffer
updateTaskBuffer = new BufferedRequest<Void>(1000L, this::doAsync) {
@Override
protected Void perform() {
new UpdateTask(LPBungeePlugin.this).run();
return null;
}
};
updateTaskBuffer = new UpdateTaskBuffer(this);
// load locale
localeManager = new SimpleLocaleManager();
File locale = new File(getDataFolder(), "lang.yml");
if (locale.exists()) {
getLog().info("Found lang.yml - loading messages...");
try {
localeManager.loadFromFile(locale);
} catch (Exception e) {
e.printStackTrace();
}
}
localeManager.tryLoad(this, new File(getDataFolder(), "lang.yml"));
// register commands
commandManager = new CommandManager(this);
@@ -234,26 +181,10 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
calculatorFactory = new BungeeCalculatorFactory(this);
cachedStateManager = new CachedStateManager();
contextManager = new ContextManager<ProxiedPlayer>() {
@Override
public Contexts formContexts(ProxiedPlayer player, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
}
};
BackendServerCalculator serverCalculator = new BackendServerCalculator(this);
contextManager.registerCalculator(serverCalculator);
LuckPermsCalculator<ProxiedPlayer> staticCalculator = new LuckPermsCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator, true);
// setup context manager
contextManager = new BungeeContextManager(this);
contextManager.registerCalculator(new BackendServerCalculator(this));
contextManager.registerCalculator(new LuckPermsCalculator<>(getConfiguration()), true);
// register with the LP API
apiProvider = new ApiProvider(this);
@@ -33,6 +33,7 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.calculators.AbstractCalculatorFactory;
import me.lucko.luckperms.common.calculators.PermissionCalculator;
import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.processors.MapProcessor;
@@ -60,7 +61,11 @@ public class BungeeCalculatorFactory extends AbstractCalculatorFactory {
processors.add(new WildcardProcessor());
}
return registerCalculator(new PermissionCalculator(plugin, user.getFriendlyName(), processors.build()));
return registerCalculator(new PermissionCalculator(
plugin,
PermissionCalculatorMetadata.of(user.getFriendlyName(), contexts.getContexts()),
processors.build()
));
}
@Override
@@ -0,0 +1,54 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bungee.contexts;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.contexts.ContextManager;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@RequiredArgsConstructor
public class BungeeContextManager extends ContextManager<ProxiedPlayer> {
private final LPBungeePlugin plugin;
@Override
public Contexts formContexts(ProxiedPlayer subject, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
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
);
}
}
@@ -0,0 +1,55 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bungee.messaging;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.MessagingFactory;
public class BungeeMessagingFactory extends MessagingFactory<LPBungeePlugin> {
public BungeeMessagingFactory(LPBungeePlugin plugin) {
super(plugin);
}
@Override
protected InternalMessagingService getServiceFor(String messagingType) {
if (messagingType.equals("bungee")) {
BungeeMessagingService bungeeMessaging = new BungeeMessagingService(getPlugin());
bungeeMessaging.init();
return bungeeMessaging;
} else if (messagingType.equals("redisbungee")) {
if (getPlugin().getProxy().getPluginManager().getPlugin("RedisBungee") == null) {
getPlugin().getLog().warn("RedisBungee plugin not present.");
} else {
RedisBungeeMessagingService redisBungeeMessaging = new RedisBungeeMessagingService(getPlugin());
redisBungeeMessaging.init();
return redisBungeeMessaging;
}
}
return super.getServiceFor(messagingType);
}
}
@@ -89,7 +89,7 @@ public class BungeeMessagingService extends AbstractMessagingService implements
onMessage(e.getTag(), msg, u -> {
// Forward to other servers
plugin.doAsync(() -> sendMessage(CHANNEL, "update:" + u.toString()));
plugin.doAsync(() -> sendMessage(CHANNEL, u));
});
}
}
+18
View File
@@ -73,6 +73,10 @@ use-server-uuids: true
# try to find a uuid for a username using RedisBungee, if installed.
use-server-uuid-cache: false
# If LuckPerms should produce extra logging output when it handles logins.
# Useful if you're having issues with UUID forwarding or data not being loaded.
debug-logins: false
# If the plugin should send log notifications to users whenever permissions are modified.
log-notify: true
@@ -104,6 +108,10 @@ temporary-add-behaviour: deny
# directly and indirectly
primary-group-calculation: stored
# If set to false, the plugin will allow a Users primary group to be removed with the
# 'parent remove' command, and will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should check for "extra" permissions with users run LP commands.
#
# These extra permissions allow finer control over what users can do with each command, and
@@ -263,6 +271,16 @@ messaging-service: none
# If LuckPerms should automatically push updates after a change has been made with a command.
auto-push-updates: true
# If LuckPerms should push logging entries to connected servers via the messaging service.
push-log-entries: true
# If LuckPerms should broadcast received logging entries to players on this platform.
#
# If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you should
# set this option to false on either your backends or your proxies, to avoid players being messaged
# twice about log entries.
broadcast-received-log-entries: false
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
redis:
+18 -4
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>3.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -39,14 +39,28 @@
<dependency>
<groupId>net.kyori</groupId>
<artifactId>text</artifactId>
<version>1.12-SNAPSHOT</version>
<version>1.11-1.0.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.6.3</version>
<version>2.7.0</version>
<scope>provided</scope>
</dependency>
<!-- Jedis -->
@@ -88,7 +102,7 @@
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.5.2</version>
<version>2.5.5</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
@@ -0,0 +1,139 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog;
import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.DateUtil;
import java.util.Map;
import java.util.UUID;
/**
* An extended version of {@link LogEntry}, with helper methods for
* populating and using the entry using internal LuckPerms classes.
*/
public class ExtendedLogEntry extends LogEntry {
public static ExtendedLogEntryBuilder build() {
return new ExtendedLogEntryBuilder();
}
public ExtendedLogEntry copy() {
return (ExtendedLogEntry) super.copy();
}
public void submit(LuckPermsPlugin plugin, Sender sender) {
plugin.getLogDispatcher().dispatch(this, sender);
}
public static JsonObject serializeWithId(UUID id, LogEntry entry) {
JsonObject data = new JsonObject();
data.add("id", new JsonPrimitive(id.toString()));
data.add("actor", new JsonPrimitive(entry.getActor().toString()));
data.add("actorName", new JsonPrimitive(entry.getActorName()));
data.add("type", new JsonPrimitive(entry.getEntryType().name()));
if (entry.getActed() != null) {
data.add("acted", new JsonPrimitive(entry.getActed().toString()));
}
data.add("actedName", new JsonPrimitive(entry.getActedName()));
data.add("action", new JsonPrimitive(entry.getAction()));
return data;
}
public static Map.Entry<UUID, LogEntry> deserialize(JsonObject object) {
LogEntry.LogEntryBuilder builder = LogEntry.builder();
UUID id = UUID.fromString(object.get("id").getAsString());
builder.actor(UUID.fromString(object.get("actor").getAsString()));
builder.actorName(object.get("actorName").getAsString());
builder.entryType(Type.valueOf(object.get("type").getAsString()));
if (object.has("acted")) {
builder.actor(UUID.fromString(object.get("acted").getAsString()));
}
builder.actedName(object.get("actedName").getAsString());
builder.action(object.get("action").getAsString());
return Maps.immutableEntry(id, builder.build());
}
public static class ExtendedLogEntryBuilder extends AbstractLogEntryBuilder<ExtendedLogEntry, ExtendedLogEntryBuilder> {
@Override
protected ExtendedLogEntry createEmptyLog() {
return new ExtendedLogEntry();
}
@Override
protected ExtendedLogEntryBuilder getThisBuilder() {
return this;
}
public ExtendedLogEntryBuilder actor(Sender actor) {
super.actorName(actor.getName());
super.actor(actor.getUuid());
return this;
}
public ExtendedLogEntryBuilder acted(PermissionHolder acted) {
if (acted instanceof User) {
super.actedName(((User) acted).getName().orElse("null"));
super.acted(((User) acted).getUuid());
super.entryType(Type.USER);
} else if (acted instanceof Group) {
super.actedName(((Group) acted).getName());
super.entryType(Type.GROUP);
}
return this;
}
public ExtendedLogEntryBuilder acted(Track track) {
super.actedName(track.getName());
super.entryType(Type.TRACK);
return this;
}
@Override
public ExtendedLogEntry build() {
if (getTimestamp() == 0L) {
super.timestamp(DateUtil.unixSecondsNow());
}
return super.build();
}
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.data;
package me.lucko.luckperms.common.actionlog;
import lombok.Getter;
@@ -119,7 +119,8 @@ public class Log {
public SortedSet<LogEntry> getUserHistory(UUID uuid) {
return content.stream()
.filter(e -> e.getType() == 'U')
.filter(e -> e.getEntryType() == LogEntry.Type.USER)
.filter(e -> e.getActed() != null)
.filter(e -> e.getActed().equals(uuid))
.collect(Collectors.toCollection(TreeSet::new));
}
@@ -130,14 +131,15 @@ public class Log {
public int getUserHistoryMaxPages(UUID uuid) {
return getMaxPages(content.stream()
.filter(e -> e.getType() == 'U')
.filter(e -> e.getEntryType() == LogEntry.Type.USER)
.filter(e -> e.getActed() != null)
.filter(e -> e.getActed().equals(uuid))
.count(), PAGE_ENTRIES);
}
public SortedSet<LogEntry> getGroupHistory(String name) {
return content.stream()
.filter(e -> e.getType() == 'G')
.filter(e -> e.getEntryType() == LogEntry.Type.GROUP)
.filter(e -> e.getActedName().equals(name))
.collect(Collectors.toCollection(TreeSet::new));
}
@@ -148,14 +150,14 @@ public class Log {
public int getGroupHistoryMaxPages(String name) {
return getMaxPages(content.stream()
.filter(e -> e.getType() == 'G')
.filter(e -> e.getEntryType() == LogEntry.Type.GROUP)
.filter(e -> e.getActedName().equals(name))
.count(), PAGE_ENTRIES);
}
public SortedSet<LogEntry> getTrackHistory(String name) {
return content.stream()
.filter(e -> e.getType() == 'T')
.filter(e -> e.getEntryType() == LogEntry.Type.TRACK)
.filter(e -> e.getActedName().equals(name))
.collect(Collectors.toCollection(TreeSet::new));
}
@@ -166,7 +168,7 @@ public class Log {
public int getTrackHistoryMaxPages(String name) {
return getMaxPages(content.stream()
.filter(e -> e.getType() == 'T')
.filter(e -> e.getEntryType() == LogEntry.Type.TRACK)
.filter(e -> e.getActedName().equals(name))
.count(), PAGE_ENTRIES);
}
@@ -0,0 +1,94 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.event.log.LogBroadcastEvent;
import me.lucko.luckperms.common.commands.impl.log.LogNotify;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.messaging.NoopMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.List;
@RequiredArgsConstructor
public class LogDispatcher {
private final LuckPermsPlugin plugin;
public void dispatch(LogEntry entry, Sender sender) {
// set the event to cancelled if the sender is import
if (!plugin.getApiProvider().getEventFactory().handleLogPublish(sender.isImport(), entry)) {
plugin.getStorage().logAction(entry);
}
// don't dispatch log entries sent by an import process
if (sender.isImport()) {
return;
}
InternalMessagingService messagingService = plugin.getMessagingService();
if (!sender.isImport() && !(messagingService instanceof NoopMessagingService)) {
messagingService.pushLog(entry);
}
if (!plugin.getApiProvider().getEventFactory().handleLogBroadcast(!plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY), entry, LogBroadcastEvent.Origin.LOCAL)) {
final String msg = entry.getFormatted();
List<Sender> senders = plugin.getOnlineSenders();
senders.add(plugin.getConsoleSender());
senders.stream()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
.filter(s -> !LogNotify.isIgnoring(plugin, s.getUuid()))
.filter(s -> !s.getUuid().equals(sender.getUuid()))
.forEach(s -> Message.LOG.send(s, msg));
}
}
public void dispatchFromRemote(LogEntry entry) {
if (!plugin.getConfiguration().get(ConfigKeys.BROADCAST_RECEIVED_LOG_ENTRIES)) {
return;
}
if (!plugin.getApiProvider().getEventFactory().handleLogBroadcast(!plugin.getConfiguration().get(ConfigKeys.LOG_NOTIFY), entry, LogBroadcastEvent.Origin.REMOTE)) {
final String msg = entry.getFormatted();
List<Sender> senders = plugin.getOnlineSenders();
senders.add(plugin.getConsoleSender());
senders.stream()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
.filter(s -> !LogNotify.isIgnoring(plugin, s.getUuid()))
.forEach(s -> Message.LOG.send(s, msg));
}
}
}
@@ -31,18 +31,23 @@ import me.lucko.luckperms.api.LuckPermsApi;
import java.lang.reflect.Method;
public class ApiHandler {
private static Method REGISTER;
private static Method UNREGISTER;
private static final Method REGISTER;
private static final Method UNREGISTER;
static {
Method register = null;
Method unregister = null;
try {
REGISTER = LuckPerms.class.getDeclaredMethod("registerProvider", LuckPermsApi.class);
REGISTER.setAccessible(true);
register = LuckPerms.class.getDeclaredMethod("registerProvider", LuckPermsApi.class);
register.setAccessible(true);
UNREGISTER = LuckPerms.class.getDeclaredMethod("unregisterProvider");
UNREGISTER.setAccessible(true);
unregister = LuckPerms.class.getDeclaredMethod("unregisterProvider");
unregister.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
REGISTER = register;
UNREGISTER = unregister;
}
public static void registerProvider(LuckPermsApi luckPermsApi) {
@@ -88,7 +88,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public double getApiVersion() {
return 3.3;
return 3.4;
}
@Override
@@ -38,11 +38,11 @@ import java.util.UUID;
import static me.lucko.luckperms.common.api.ApiUtils.checkName;
/**
* Provides a link between {@link Log} and {@link me.lucko.luckperms.common.data.Log}
* Provides a link between {@link Log} and {@link me.lucko.luckperms.common.actionlog.Log}
*/
@AllArgsConstructor
public class LogDelegate implements Log {
private final me.lucko.luckperms.common.data.Log handle;
private final me.lucko.luckperms.common.actionlog.Log handle;
@Override
public SortedSet<LogEntry> getContent() {
@@ -33,8 +33,8 @@ import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.api.metastacking.MetaStackDefinition;
import me.lucko.luckperms.api.metastacking.MetaStackElement;
import me.lucko.luckperms.api.metastacking.MetaStackFactory;
import me.lucko.luckperms.common.metastacking.definition.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.definition.StandardStackElements;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.List;
@@ -43,6 +43,7 @@ import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.contexts.ExtractedContexts;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.MetaType;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
@@ -381,12 +382,12 @@ public class PermissionHolderDelegate implements PermissionHolder {
@Override
public void clearMeta() {
handle.clearMeta();
handle.clearMeta(MetaType.ANY);
}
@Override
public void clearMeta(@NonNull ContextSet contextSet) {
handle.clearMeta(contextSet);
handle.clearMeta(MetaType.ANY, contextSet);
}
@Override
@@ -396,7 +397,7 @@ public class PermissionHolderDelegate implements PermissionHolder {
set.add("server", server);
}
handle.clearMeta(set);
handle.clearMeta(MetaType.ANY, set);
}
@Override
@@ -409,7 +410,7 @@ public class PermissionHolderDelegate implements PermissionHolder {
set.add("world", world);
}
handle.clearMeta(set);
handle.clearMeta(MetaType.ANY, set);
}
@Override
@@ -79,57 +79,57 @@ public class StorageDelegate implements Storage {
@Override
public CompletableFuture<Boolean> logAction(@NonNull LogEntry entry) {
return handle.force().logAction(entry);
return handle.noBuffer().logAction(entry);
}
@Override
public CompletableFuture<Log> getLog() {
return handle.force().getLog().thenApply(log -> log == null ? null : new LogDelegate(log));
return handle.noBuffer().getLog().thenApply(log -> log == null ? null : new LogDelegate(log));
}
@Override
public CompletableFuture<Boolean> loadUser(@NonNull UUID uuid, String username) {
return handle.force().loadUser(uuid, username == null ? null : checkUsername(username));
return handle.noBuffer().loadUser(uuid, username == null ? null : checkUsername(username));
}
@Override
public CompletableFuture<Boolean> saveUser(@NonNull User user) {
return handle.force().saveUser(UserDelegate.cast(user));
return handle.noBuffer().saveUser(UserDelegate.cast(user));
}
@Override
public CompletableFuture<Boolean> cleanupUsers() {
return handle.force().cleanupUsers();
return handle.noBuffer().cleanupUsers();
}
@Override
public CompletableFuture<Set<UUID>> getUniqueUsers() {
return handle.force().getUniqueUsers();
return handle.noBuffer().getUniqueUsers();
}
@Override
public CompletableFuture<List<HeldPermission<UUID>>> getUsersWithPermission(@NonNull String permission) {
return handle.force().getUsersWithPermission(permission);
return handle.noBuffer().getUsersWithPermission(permission);
}
@Override
public CompletableFuture<Boolean> createAndLoadGroup(@NonNull String name) {
return handle.force().createAndLoadGroup(checkName(name), CreationCause.API);
return handle.noBuffer().createAndLoadGroup(checkName(name), CreationCause.API);
}
@Override
public CompletableFuture<Boolean> loadGroup(@NonNull String name) {
return handle.force().loadGroup(checkName(name));
return handle.noBuffer().loadGroup(checkName(name));
}
@Override
public CompletableFuture<Boolean> loadAllGroups() {
return handle.force().loadAllGroups();
return handle.noBuffer().loadAllGroups();
}
@Override
public CompletableFuture<Boolean> saveGroup(@NonNull Group group) {
return handle.force().saveGroup(GroupDelegate.cast(group));
return handle.noBuffer().saveGroup(GroupDelegate.cast(group));
}
@Override
@@ -137,51 +137,51 @@ public class StorageDelegate implements Storage {
if (group.getName().equalsIgnoreCase(plugin.getConfiguration().get(ConfigKeys.DEFAULT_GROUP_NAME))) {
throw new IllegalArgumentException("Cannot delete the default group.");
}
return handle.force().deleteGroup(GroupDelegate.cast(group), DeletionCause.API);
return handle.noBuffer().deleteGroup(GroupDelegate.cast(group), DeletionCause.API);
}
@Override
public CompletableFuture<List<HeldPermission<String>>> getGroupsWithPermission(@NonNull String permission) {
return handle.force().getGroupsWithPermission(permission);
return handle.noBuffer().getGroupsWithPermission(permission);
}
@Override
public CompletableFuture<Boolean> createAndLoadTrack(@NonNull String name) {
return handle.force().createAndLoadTrack(checkName(name), CreationCause.API);
return handle.noBuffer().createAndLoadTrack(checkName(name), CreationCause.API);
}
@Override
public CompletableFuture<Boolean> loadTrack(@NonNull String name) {
return handle.force().loadTrack(checkName(name));
return handle.noBuffer().loadTrack(checkName(name));
}
@Override
public CompletableFuture<Boolean> loadAllTracks() {
return handle.force().loadAllTracks();
return handle.noBuffer().loadAllTracks();
}
@Override
public CompletableFuture<Boolean> saveTrack(@NonNull Track track) {
return handle.force().saveTrack(TrackDelegate.cast(track));
return handle.noBuffer().saveTrack(TrackDelegate.cast(track));
}
@Override
public CompletableFuture<Boolean> deleteTrack(@NonNull Track track) {
return handle.force().deleteTrack(TrackDelegate.cast(track), DeletionCause.API);
return handle.noBuffer().deleteTrack(TrackDelegate.cast(track), DeletionCause.API);
}
@Override
public CompletableFuture<Boolean> saveUUIDData(@NonNull String username, @NonNull UUID uuid) {
return handle.force().saveUUIDData(checkUsername(username), uuid);
return handle.noBuffer().saveUUIDData(checkUsername(username), uuid);
}
@Override
public CompletableFuture<UUID> getUUID(@NonNull String username) {
return handle.force().getUUID(checkUsername(username));
return handle.noBuffer().getUUID(checkUsername(username));
}
@Override
public CompletableFuture<String> getName(@NonNull UUID uuid) {
return handle.force().getName(uuid);
return handle.noBuffer().getName(uuid);
}
}
@@ -23,18 +23,20 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.data;
package me.lucko.luckperms.common.backup;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.logging.ProgressLogger;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.Storage;
import me.lucko.luckperms.common.utils.Cycle;
import java.io.BufferedWriter;
import java.io.IOException;
@@ -44,7 +46,6 @@ import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@@ -54,7 +55,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Handles export operations
@@ -62,15 +63,6 @@ import java.util.function.Function;
public class Exporter implements Runnable {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
// number of users --> the value to divide the list by. base value of 1000 per thread, with a max of 10 threads. so, 3000 = 3 threads, 10000+ = 10 threads
private static final Function<Integer, Integer> THREAD_COUNT_FUNCTION = usersCount -> {
// how many threads to make. must be 1 <= x <= 15
int i = Math.max(1, Math.min(15, usersCount / 1000));
// then work out the value to split at. e.g. if we have 1,000 users to export and 2 threads, we should split at every 500 users.
return Math.max(1, usersCount / i);
};
private static void write(BufferedWriter writer, String s) {
try {
writer.write(s);
@@ -109,34 +101,34 @@ public class Exporter implements Runnable {
// Create the actual groups first
write(writer, "# Create groups");
plugin.getGroupManager().getAll().values().stream()
.sorted((o1, o2) -> {
int i = Integer.compare(o2.getWeight().orElse(0), o1.getWeight().orElse(0));
return i != 0 ? i : o1.getName().compareToIgnoreCase(o2.getName());
})
.filter(g -> !g.getName().equals("default"))
.forEach(group -> {
write(writer, "/luckperms creategroup " + group.getName());
});
write(writer, "");
AtomicInteger groupCount = new AtomicInteger(0);
// export groups in order of weight
plugin.getGroupManager().getAll().values().stream()
List<? extends Group> groups = plugin.getGroupManager().getAll().values().stream()
// export groups in order of weight
.sorted((o1, o2) -> {
int i = Integer.compare(o2.getWeight().orElse(0), o1.getWeight().orElse(0));
return i != 0 ? i : o1.getName().compareToIgnoreCase(o2.getName());
})
.forEach(group -> {
write(writer, "# Export group: " + group.getName());
for (Node node : group.getEnduringNodes().values()) {
write(writer, NodeFactory.nodeAsCommand(node, group.getName(), true, true));
}
write(writer, "");
log.logAllProgress("Exported {} groups so far.", groupCount.incrementAndGet());
});
}).collect(Collectors.toList());
for (Group group : groups) {
if (!group.getName().equals("default")) {
write(writer, "/luckperms creategroup " + group.getName());
}
}
for (Group group : groups) {
if (groupCount.get() == 0) {
write(writer, "");
}
write(writer, "# Export group: " + group.getName());
for (Node node : group.getEnduringNodes().values()) {
write(writer, NodeFactory.nodeAsCommand(node, group.getName(), true, true));
}
write(writer, "");
log.logAllProgress("Exported {} groups so far.", groupCount.incrementAndGet());
}
log.log("Exported " + groupCount.get() + " groups.");
@@ -187,17 +179,13 @@ public class Exporter implements Runnable {
write(writer, "# Export users");
List<List<UUID>> subUsers;
AtomicInteger userCount = new AtomicInteger(0);
// not really that many users, so it's not really worth spreading the load.
if (users.size() < 1500) {
subUsers = Collections.singletonList(new ArrayList<>(users));
} else {
subUsers = Util.divideList(users, THREAD_COUNT_FUNCTION.apply(users.size()));
// divide into 16 pools.
Cycle<List<UUID>> userPools = new Cycle<>(Util.nInstances(32, ArrayList::new));
for (UUID uuid : users) {
userPools.next().add(uuid);
}
log.log("Split users into " + subUsers.size() + " threads for export.");
log.log("Split users into " + userPools.getBacking().size() + " threads for export.");
// Setup a file writing lock. We don't want multiple threads writing at the same time.
// The write function accepts a list of strings, as we want a user's data to be grouped together.
@@ -217,8 +205,10 @@ public class Exporter implements Runnable {
// A set of futures, which are really just the threads we need to wait for.
Set<CompletableFuture<Void>> futures = new HashSet<>();
AtomicInteger userCount = new AtomicInteger(0);
// iterate through each user sublist.
for (List<UUID> subList : subUsers) {
for (List<UUID> subList : userPools.getBacking()) {
// register and start a new thread to process the sublist
futures.add(CompletableFuture.runAsync(() -> {
@@ -229,7 +219,7 @@ public class Exporter implements Runnable {
// actually export the user. this output will be fed to the writing function when we have all of the user's data.
List<String> output = new ArrayList<>();
plugin.getStorage().loadUser(uuid, "null").join();
plugin.getStorage().loadUser(uuid, null).join();
User user = plugin.getUserManager().getIfLoaded(uuid);
output.add("# Export user: " + user.getUuid().toString() + " - " + user.getName().orElse("unknown username"));
@@ -0,0 +1,293 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.backup;
import lombok.Getter;
import lombok.Setter;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
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.locale.Message;
import me.lucko.luckperms.common.utils.Cycle;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* Handles import operations
*/
public class Importer implements Runnable {
private final CommandManager commandManager;
private final Set<Sender> notify;
private final List<String> commands;
private final List<ImportCommand> toExecute;
public Importer(CommandManager commandManager, Sender executor, List<String> commands) {
this.commandManager = commandManager;
if (executor.isConsole()) {
this.notify = ImmutableSet.of(executor);
} else {
this.notify = ImmutableSet.of(executor, commandManager.getPlugin().getConsoleSender());
}
this.commands = commands.stream()
.map(String::trim)
.filter(s -> !s.isEmpty())
.filter(s -> !s.startsWith("#"))
.filter(s -> !s.startsWith("//"))
.map(s -> s.startsWith("/") ? s.substring("/".length()) : s)
.map(s -> s.startsWith("perms ") ? s.substring("perms ".length()) : s)
.map(s -> s.startsWith("lp ") ? s.substring("lp ".length()) : s)
.map(s -> s.startsWith("luckperms ") ? s.substring("luckperms ".length()) : s)
.collect(Collectors.toList());
this.toExecute = new ArrayList<>();
}
@Override
public void run() {
long startTime = System.currentTimeMillis();
notify.forEach(s -> Message.IMPORT_START.send(s));
// form instances for all commands, and register them
int index = 1;
for (String command : commands) {
ImportCommand cmd = new ImportCommand(commandManager, index, command);
toExecute.add(cmd);
if (cmd.getCommand().startsWith("creategroup ") || cmd.getCommand().startsWith("createtrack")) {
cmd.process(); // process immediately
}
index++;
}
// divide commands up into pools
Cycle<List<ImportCommand>> commandPools = new Cycle<>(Util.nInstances(128, ArrayList::new));
String lastTarget = null;
for (ImportCommand cmd : toExecute) {
// if the last target isn't the same, skip to a new pool
if (lastTarget == null || !lastTarget.equals(cmd.getTarget())) {
commandPools.next();
}
commandPools.current().add(cmd);
lastTarget = cmd.getTarget();
}
// A set of futures, which are really just the threads we need to wait for.
Set<CompletableFuture<Void>> futures = new HashSet<>();
AtomicInteger processedCount = new AtomicInteger(0);
// iterate through each user sublist.
for (List<ImportCommand> subList : commandPools.getBacking()) {
// register and start a new thread to process the sublist
futures.add(CompletableFuture.runAsync(() -> {
// iterate through each user in the sublist, and grab their data.
for (ImportCommand cmd : subList) {
cmd.process();
processedCount.incrementAndGet();
}
}, commandManager.getPlugin().getScheduler().async()));
}
// all of the threads have been scheduled now and are running. we just need to wait for them all to complete
CompletableFuture<Void> overallFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
while (true) {
try {
overallFuture.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
// abnormal error - just break
e.printStackTrace();
break;
} catch (TimeoutException e) {
// still executing - send a progress report and continue waiting
sendProgress(processedCount.get());
continue;
}
// process is complete
break;
}
long endTime = System.currentTimeMillis();
double seconds = (endTime - startTime) / 1000;
int errors = (int) toExecute.stream().filter(v -> !v.getResult().asBoolean()).count();
if (errors == 0) {
notify.forEach(s -> Message.IMPORT_END_COMPLETE.send(s, seconds));
} else if (errors == 1) {
notify.forEach(s -> Message.IMPORT_END_COMPLETE_ERR_SIN.send(s, seconds, errors));
} else {
notify.forEach(s -> Message.IMPORT_END_COMPLETE_ERR.send(s, seconds, errors));
}
AtomicInteger errIndex = new AtomicInteger(1);
for (ImportCommand e : toExecute) {
if (e.getResult() != null && !e.getResult().asBoolean()) {
notify.forEach(s -> {
Message.IMPORT_END_ERROR_HEADER.send(s, errIndex.get(), e.getId(), e.getCommand(), e.getResult().toString());
for (String out : e.getOutput()) {
Message.IMPORT_END_ERROR_CONTENT.send(s, out);
}
Message.IMPORT_END_ERROR_FOOTER.send(s);
});
errIndex.incrementAndGet();
}
}
}
private void sendProgress(int processedCount) {
int percent = (processedCount * 100) / commands.size();
int errors = (int) toExecute.stream().filter(v -> v.isCompleted() && !v.getResult().asBoolean()).count();
if (errors == 1) {
notify.forEach(s -> Message.IMPORT_PROGRESS_SIN.send(s, percent, processedCount, commands.size(), errors));
} else {
notify.forEach(s -> Message.IMPORT_PROGRESS.send(s, percent, processedCount, commands.size(), errors));
}
}
@Getter
private static class ImportCommand extends ImporterSender {
private static final Splitter SPACE_SPLIT = Splitter.on(" ");
private final CommandManager commandManager;
private final int id;
private final String command;
private final String target;
@Setter
private boolean completed = false;
private final List<String> output = new ArrayList<>();
@Setter
private CommandResult result = CommandResult.FAILURE;
ImportCommand(CommandManager commandManager, int id, String command) {
super(commandManager.getPlugin());
this.commandManager = commandManager;
this.id = id;
this.command = command;
this.target = determineTarget(command);
}
@Override
protected void consumeMessage(String s) {
output.add(s);
}
public void process() {
if (isCompleted()) {
return;
}
try {
CommandResult result = commandManager.onCommand(
this,
"lp",
Util.stripQuotes(Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN).omitEmptyStrings().splitToList(getCommand()))
).get();
setResult(result);
} catch (Exception e) {
setResult(CommandResult.FAILURE);
e.printStackTrace();
}
setCompleted(true);
}
private static String determineTarget(String command) {
if (command.startsWith("user ") && command.length() > "user ".length()) {
String subCmd = command.substring("user ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetUser = SPACE_SPLIT.split(subCmd).iterator().next();
return "u:" + targetUser;
}
if (command.startsWith("group ") && command.length() > "group ".length()) {
String subCmd = command.substring("group ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetGroup = SPACE_SPLIT.split(subCmd).iterator().next();
return "g:" + targetGroup;
}
if (command.startsWith("track ") && command.length() > "track ".length()) {
String subCmd = command.substring("track ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetTrack = SPACE_SPLIT.split(subCmd).iterator().next();
return "t:" + targetTrack;
}
if (command.startsWith("creategroup ") && command.length() > "creategroup ".length()) {
String targetGroup = command.substring("creategroup ".length());
return "g:" + targetGroup;
}
if (command.startsWith("createtrack ") && command.length() > "createtrack ".length()) {
String targetTrack = command.substring("createtrack ".length());
return "t:" + targetTrack;
}
return null;
}
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.data;
package me.lucko.luckperms.common.backup;
import lombok.AllArgsConstructor;
@@ -32,17 +32,17 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.UUID;
import java.util.function.Consumer;
@AllArgsConstructor
public class ImporterSender implements Sender {
public abstract class ImporterSender implements Sender {
private final LuckPermsPlugin plugin;
private final Consumer<String> messageConsumer;
protected abstract void consumeMessage(String s);
@Override
public LuckPermsPlugin getPlatform() {
@@ -61,12 +61,13 @@ public class ImporterSender implements Sender {
@Override
public void sendMessage(String s) {
messageConsumer.accept(s);
consumeMessage(s);
}
@SuppressWarnings("deprecation")
@Override
public void sendMessage(Component message) {
messageConsumer.accept(ComponentSerializer.toLegacy(message, Constants.COLOR_CHAR));
consumeMessage(TextUtils.toLegacy(message));
}
@Override
@@ -45,6 +45,7 @@ import java.util.function.Supplier;
@RequiredArgsConstructor
public abstract class BufferedRequest<T> {
private final long bufferTimeMillis;
private final long sleepInterval;
private final Executor executor;
private WeakReference<Processor<T>> processor = null;
@@ -60,7 +61,7 @@ public abstract class BufferedRequest<T> {
}
}
Processor<T> p = new Processor<>(bufferTimeMillis, this::perform);
Processor<T> p = new Processor<>(bufferTimeMillis, sleepInterval, this::perform);
executor.execute(p);
processor = new WeakReference<>(p);
return p.get();
@@ -79,6 +80,7 @@ public abstract class BufferedRequest<T> {
@RequiredArgsConstructor
private static class Processor<R> implements Runnable {
private final long delayMillis;
private final long sleepMillis;
private final Supplier<R> supplier;
private final ReentrantLock lock = new ReentrantLock();
private final CompletableFuture<R> future = new CompletableFuture<>();
@@ -108,7 +110,7 @@ public abstract class BufferedRequest<T> {
}
try {
Thread.sleep(500);
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -0,0 +1,44 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.buffers;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.tasks.UpdateTask;
public class UpdateTaskBuffer extends BufferedRequest<Void> {
private final LuckPermsPlugin plugin;
public UpdateTaskBuffer(LuckPermsPlugin plugin) {
super(250L, 50L, plugin::doAsync);
this.plugin = plugin;
}
@Override
protected Void perform() {
new UpdateTask(plugin).run();
return null;
}
}
@@ -36,8 +36,8 @@ import com.google.common.collect.ListMultimap;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.metastacking.GenericMetaStack;
import me.lucko.luckperms.common.metastacking.MetaStack;
import me.lucko.luckperms.common.metastacking.SimpleMetaStack;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.Comparator;
@@ -46,15 +46,16 @@ import java.util.SortedMap;
import java.util.TreeMap;
/**
* Holds temporary mutable meta whilst this object is passed up the inheritance tree to accumulate meta from parents
* Holds temporary mutable meta whilst this object is passed up the
* inheritance tree to accumulate meta from parents
*/
@Getter
@ToString
public class MetaAccumulator {
public static MetaAccumulator makeFromConfig(LuckPermsPlugin plugin) {
return new MetaAccumulator(
new GenericMetaStack(plugin.getConfiguration().get(ConfigKeys.PREFIX_FORMATTING_OPTIONS), ChatMetaType.PREFIX),
new GenericMetaStack(plugin.getConfiguration().get(ConfigKeys.SUFFIX_FORMATTING_OPTIONS), ChatMetaType.SUFFIX)
new SimpleMetaStack(plugin.getConfiguration().get(ConfigKeys.PREFIX_FORMATTING_OPTIONS), ChatMetaType.PREFIX),
new SimpleMetaStack(plugin.getConfiguration().get(ConfigKeys.SUFFIX_FORMATTING_OPTIONS), ChatMetaType.SUFFIX)
);
}
@@ -104,7 +105,8 @@ public class MetaAccumulator {
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)
// 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 ListMultimap<String, String> getMeta() {
if (!this.meta.containsKey("weight") && this.weight != 0) {
@@ -33,6 +33,7 @@ import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.common.calculators.CalculatorFactory;
import me.lucko.luckperms.common.calculators.PermissionCalculator;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import java.util.Collections;
import java.util.Map;
@@ -93,6 +94,10 @@ public class PermissionCache implements PermissionData {
@Override
public Tristate getPermissionValue(@NonNull String permission) {
return calculator.getPermissionValue(permission);
return calculator.getPermissionValue(permission, CheckOrigin.API);
}
public Tristate getPermissionValue(@NonNull String permission, CheckOrigin origin) {
return calculator.getPermissionValue(permission, origin);
}
}
@@ -36,12 +36,11 @@ import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.caching.MetaContexts;
import me.lucko.luckperms.api.caching.MetaData;
import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.api.caching.UserData;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.contexts.ExtractedContexts;
import me.lucko.luckperms.common.metastacking.GenericMetaStack;
import me.lucko.luckperms.common.metastacking.SimpleMetaStack;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -68,17 +67,17 @@ public class UserCache implements UserData {
.build(new MetaCacheLoader());
@Override
public PermissionData getPermissionData(@NonNull Contexts contexts) {
public PermissionCache getPermissionData(@NonNull Contexts contexts) {
return permission.get(contexts);
}
@Override
public MetaData getMetaData(@NonNull MetaContexts contexts) {
public MetaCache getMetaData(@NonNull MetaContexts contexts) {
return meta.get(contexts);
}
@Override
public MetaData getMetaData(@NonNull Contexts contexts) {
public MetaCache getMetaData(@NonNull Contexts contexts) {
// just create a MetaContexts instance using the values in the config
return getMetaData(makeFromMetaContextsConfig(contexts, user.getPlugin()));
}
@@ -219,8 +218,8 @@ public class UserCache implements UserData {
private static MetaAccumulator newAccumulator(MetaContexts contexts) {
return new MetaAccumulator(
new GenericMetaStack(contexts.getPrefixStackDefinition(), ChatMetaType.PREFIX),
new GenericMetaStack(contexts.getSuffixStackDefinition(), ChatMetaType.SUFFIX)
new SimpleMetaStack(contexts.getPrefixStackDefinition(), ChatMetaType.PREFIX),
new SimpleMetaStack(contexts.getSuffixStackDefinition(), ChatMetaType.SUFFIX)
);
}
@@ -33,6 +33,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.processors.PermissionProcessor;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import java.util.List;
import java.util.Map;
@@ -43,7 +44,7 @@ import java.util.Map;
@RequiredArgsConstructor
public class PermissionCalculator {
private final LuckPermsPlugin plugin;
private final String objectName;
private final PermissionCalculatorMetadata metadata;
private final List<PermissionProcessor> processors;
// caches lookup calls.
@@ -54,7 +55,7 @@ public class PermissionCalculator {
lookupCache.invalidateAll();
}
public Tristate getPermissionValue(String permission) {
public Tristate getPermissionValue(String permission, CheckOrigin origin) {
// convert the permission to lowercase, as all values in the backing map are also lowercase.
// this allows fast case insensitive lookups
@@ -64,7 +65,7 @@ public class PermissionCalculator {
Tristate result = lookupCache.get(permission);
// log this permission lookup to the verbose handler
plugin.getVerboseHandler().offerCheckData(objectName, permission, result);
plugin.getVerboseHandler().offerCheckData(origin, metadata.getObjectName(), metadata.getContext(), permission, result);
// return the result
return result;
@@ -0,0 +1,47 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.calculators;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.lucko.luckperms.api.context.ContextSet;
@Getter
@AllArgsConstructor(staticName = "of")
public class PermissionCalculatorMetadata {
/**
* The name of the object which owns the permission calculator
*/
private final String objectName;
/**
* The context the permission calculator works with
*/
private final ContextSet context;
}
@@ -63,18 +63,15 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -84,14 +81,11 @@ public class CommandManager {
@Getter
private final LuckPermsPlugin plugin;
private final ExecutorService executor;
@Getter
private final List<Command> mainCommands;
public CommandManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.executor = Executors.newSingleThreadExecutor();
LocaleManager locale = plugin.getLocaleManager();
@@ -130,8 +124,8 @@ public class CommandManager {
* @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(() -> {
public CompletableFuture<CommandResult> onCommand(Sender sender, String label, List<String> args) {
return CompletableFuture.supplyAsync(() -> {
try {
return execute(sender, label, args);
} catch (Throwable e) {
@@ -139,7 +133,7 @@ public class CommandManager {
e.printStackTrace();
return null;
}
});
}, plugin.getScheduler().async());
}
@SuppressWarnings("unchecked")
@@ -255,18 +249,18 @@ public class CommandManager {
@SuppressWarnings("unchecked")
String permission = (String) c.getPermission().map(p -> ((CommandPermission) p).getPermission()).orElse("None");
Component component = ComponentSerializer.parseFromLegacy("&3> &a" + String.format(c.getUsage(), label), Constants.FORMAT_CHAR)
.applyRecursively(comp -> {
comp.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(TextUtils.joinNewline(
TextComponent component = TextUtils.fromLegacy("&3> &a" + String.format(c.getUsage(), label), Constants.FORMAT_CHAR)
.toBuilder().applyDeep(comp -> {
comp.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(
"&bCommand: &2" + c.getName(),
"&bDescription: &2" + c.getDescription(),
"&bUsage: &2" + String.format(c.getUsage(), label),
"&bPermission: &2" + permission,
" ",
"&7Click to auto-complete."
), Constants.FORMAT_CHAR)));
), Constants.FORMAT_CHAR)));
comp.clickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, String.format(c.getUsage(), label)));
});
}).build();
sender.sendMessage(component);
});
}
@@ -310,6 +304,7 @@ public class CommandManager {
}
private static void handleRewrites(List<String> args) {
// Provide aliases
if (args.size() >= 1) {
if (args.get(0).equalsIgnoreCase("u")) {
args.remove(0);
@@ -319,6 +314,14 @@ public class CommandManager {
args.remove(0);
args.add(0, "group");
}
if (args.get(0).equalsIgnoreCase("t")) {
args.remove(0);
args.add(0, "track");
}
if (args.get(0).equalsIgnoreCase("i")) {
args.remove(0);
args.add(0, "info");
}
}
if (args.size() >= 3) {
@@ -342,7 +345,6 @@ public class CommandManager {
break;
case "i":
case "about":
case "list":
args.remove(2);
args.add(2, "info");
break;
@@ -350,6 +352,7 @@ public class CommandManager {
case "inheritances":
case "group":
case "groups":
case "g":
case "rank":
case "ranks":
case "parents":
@@ -440,7 +443,7 @@ public class CommandManager {
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"))
(args.get(3).equalsIgnoreCase("i") || args.get(3).equalsIgnoreCase("about"))
);
if (lazyInfo) {
@@ -39,11 +39,14 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
public abstract class MainCommand<T> extends Command<Void, T> {
public abstract class MainCommand<T, I> extends Command<Void, T> {
private final int minArgs; // equals 1 if the command doesn't take a mid argument, e.g. /lp user <USER> sub-command....
// equals 1 if the command doesn't take a mid argument, e.g. /lp log sub-command....
// equals 2 if the command does take a mid argument, e.g. /lp user <USER> sub-command....
private final int minArgs;
public MainCommand(LocalizedSpec spec, String name, int minArgs, List<Command<T, ?>> children) {
super(spec, name, null, Predicates.alwaysFalse(), children);
@@ -84,17 +87,28 @@ public abstract class MainCommand<T> extends Command<Void, T> {
}
final String name = args.get(0);
T t = getTarget(name, plugin, sender);
if (t != null) {
CommandResult result;
try {
result = sub.execute(plugin, sender, t, strippedArgs, label);
} catch (CommandException e) {
result = CommandManager.handleException(e, sender, label, sub);
}
I targetId = parseTarget(name, plugin, sender);
if (targetId == null) {
return CommandResult.LOADING_ERROR;
}
cleanup(t, plugin);
return result;
ReentrantLock lock = getLockForTarget(targetId);
lock.lock();
try {
T target = getTarget(targetId, plugin, sender);
if (target != null) {
CommandResult result;
try {
result = sub.execute(plugin, sender, target, strippedArgs, label);
} catch (CommandException e) {
result = CommandManager.handleException(e, sender, label, sub);
}
cleanup(target, plugin);
return result;
}
} finally {
lock.unlock();
}
return CommandResult.LOADING_ERROR;
@@ -145,7 +159,11 @@ public abstract class MainCommand<T> extends Command<Void, T> {
protected abstract List<String> getTargets(LuckPermsPlugin plugin);
protected abstract T getTarget(String target, LuckPermsPlugin plugin, Sender sender);
protected abstract I parseTarget(String target, LuckPermsPlugin plugin, Sender sender);
protected abstract ReentrantLock getLockForTarget(I target);
protected abstract T getTarget(I target, LuckPermsPlugin plugin, Sender sender);
protected abstract void cleanup(T t, LuckPermsPlugin plugin);
@@ -173,7 +173,7 @@ public abstract class SubCommand<T> extends Command<T, Void> {
}
public static void save(User user, Sender sender, LuckPermsPlugin plugin) {
boolean success = plugin.getStorage().force().saveUser(user).join();
boolean success = plugin.getStorage().noBuffer().saveUser(user).join();
if (sender.isImport()) {
user.getRefreshBuffer().request();
@@ -181,20 +181,20 @@ public abstract class SubCommand<T> extends Command<T, Void> {
user.getRefreshBuffer().requestDirectly();
}
InternalMessagingService messagingService = plugin.getMessagingService();
if (!sender.isImport() && !(messagingService instanceof NoopMessagingService) && plugin.getConfiguration().get(ConfigKeys.AUTO_PUSH_UPDATES)) {
messagingService.getUpdateBuffer().request();
if (!sender.isImport()) {
InternalMessagingService messagingService = plugin.getMessagingService();
if (!(messagingService instanceof NoopMessagingService) && plugin.getConfiguration().get(ConfigKeys.AUTO_PUSH_UPDATES)) {
messagingService.getUpdateBuffer().request();
}
}
if (success) {
Message.USER_SAVE_SUCCESS.send(sender);
} else {
if (!success) {
Message.USER_SAVE_ERROR.send(sender);
}
}
public static void save(Group group, Sender sender, LuckPermsPlugin plugin) {
boolean success = plugin.getStorage().force().saveGroup(group).join();
boolean success = plugin.getStorage().noBuffer().saveGroup(group).join();
if (sender.isImport()) {
plugin.getUpdateTaskBuffer().request();
@@ -202,20 +202,20 @@ public abstract class SubCommand<T> extends Command<T, Void> {
plugin.getUpdateTaskBuffer().requestDirectly();
}
InternalMessagingService messagingService = plugin.getMessagingService();
if (!sender.isImport() && !(messagingService instanceof NoopMessagingService) && plugin.getConfiguration().get(ConfigKeys.AUTO_PUSH_UPDATES)) {
messagingService.getUpdateBuffer().request();
if (!sender.isImport()) {
InternalMessagingService messagingService = plugin.getMessagingService();
if (!(messagingService instanceof NoopMessagingService) && plugin.getConfiguration().get(ConfigKeys.AUTO_PUSH_UPDATES)) {
messagingService.getUpdateBuffer().request();
}
}
if (success) {
Message.GROUP_SAVE_SUCCESS.send(sender);
} else {
if (!success) {
Message.GROUP_SAVE_ERROR.send(sender);
}
}
public static void save(Track track, Sender sender, LuckPermsPlugin plugin) {
boolean success = plugin.getStorage().force().saveTrack(track).join();
boolean success = plugin.getStorage().noBuffer().saveTrack(track).join();
if (sender.isImport()) {
plugin.getUpdateTaskBuffer().request();
@@ -223,14 +223,14 @@ public abstract class SubCommand<T> extends Command<T, Void> {
plugin.getUpdateTaskBuffer().requestDirectly();
}
InternalMessagingService messagingService = plugin.getMessagingService();
if (!sender.isImport() && !(messagingService instanceof NoopMessagingService) && plugin.getConfiguration().get(ConfigKeys.AUTO_PUSH_UPDATES)) {
messagingService.getUpdateBuffer().request();
if (!sender.isImport()) {
InternalMessagingService messagingService = plugin.getMessagingService();
if (!(messagingService instanceof NoopMessagingService) && plugin.getConfiguration().get(ConfigKeys.AUTO_PUSH_UPDATES)) {
messagingService.getUpdateBuffer().request();
}
}
if (success) {
Message.TRACK_SAVE_SUCCESS.send(sender);
} else {
if (!success) {
Message.TRACK_SAVE_ERROR.send(sender);
}
}
@@ -28,6 +28,7 @@ package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -37,7 +38,6 @@ import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -45,10 +45,10 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.List;
import java.util.stream.Collectors;
@@ -85,15 +85,15 @@ public class MetaAddChatMeta extends SharedSubCommand {
DataMutateResult result = holder.setPermission(NodeFactory.makeChatMetaNode(type, priority, meta).withExtraContext(context).build());
if (result.asBoolean()) {
Component component = ComponentSerializer.parseFromLegacy(Message.ADD_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, Util.contextSetToString(context)), Constants.COLOR_CHAR);
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(
TextComponent.Builder builder = TextUtils.fromLegacy(Message.ADD_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, Util.contextSetToString(context)), Constants.COLOR_CHAR).toBuilder();
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(
"¥3Raw " + type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
component.applyRecursively(c -> c.hoverEvent(event));
sender.sendMessage(component);
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta add" + type.name().toLowerCase() + " " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -29,6 +29,7 @@ import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -39,7 +40,6 @@ import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -49,10 +49,10 @@ import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.DateUtil;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.List;
import java.util.Map;
@@ -95,15 +95,15 @@ public class MetaAddTempChatMeta extends SharedSubCommand {
if (ret.getKey().asBoolean()) {
duration = ret.getValue().getExpiryUnixTime();
Component component = ComponentSerializer.parseFromLegacy(Message.ADD_TEMP_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, DateUtil.formatDateDiff(duration), Util.contextSetToString(context)), Constants.COLOR_CHAR);
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(
TextComponent.Builder builder = TextUtils.fromLegacy(Message.ADD_TEMP_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, DateUtil.formatDateDiff(duration), Util.contextSetToString(context)), Constants.COLOR_CHAR).toBuilder();
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(
"¥3Raw " + type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
component.applyRecursively(c -> c.hoverEvent(event));
sender.sendMessage(component);
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta addtemp" + type.name().toLowerCase() + " " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -34,11 +35,11 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.node.MetaType;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
@@ -57,6 +58,34 @@ public class MetaClear extends SharedSubCommand {
return CommandResult.NO_PERMISSION;
}
MetaType type = null;
if (args.size() > 0) {
String typeId = args.get(0).toLowerCase();
if (typeId.equals("any") || typeId.equals("all") || typeId.equals("*")) {
type = MetaType.ANY;
}
if (typeId.equals("chat") || typeId.equals("chatmeta")) {
type = MetaType.CHAT;
}
if (typeId.equals("meta")) {
type = MetaType.META;
}
if (typeId.equals("prefix") || typeId.equals("prefixes")) {
type = MetaType.PREFIX;
}
if (typeId.equals("suffix") || typeId.equals("suffixes")) {
type = MetaType.SUFFIX;
}
if (type != null) {
args.remove(0);
}
}
if (type == null) {
type = MetaType.ANY;
}
int before = holder.getEnduringNodes().size();
MutableContextSet context = ArgumentUtils.handleContext(0, args, plugin);
@@ -67,19 +96,19 @@ public class MetaClear extends SharedSubCommand {
}
if (context.isEmpty()) {
holder.clearMeta();
holder.clearMeta(type);
} else {
holder.clearMeta(context);
holder.clearMeta(type, context);
}
int changed = before - holder.getEnduringNodes().size();
if (changed == 1) {
Message.META_CLEAR_SUCCESS_SINGULAR.send(sender, holder.getFriendlyName(), Util.contextSetToString(context), changed);
Message.META_CLEAR_SUCCESS_SINGULAR.send(sender, holder.getFriendlyName(), type.name().toLowerCase(), Util.contextSetToString(context), changed);
} else {
Message.META_CLEAR_SUCCESS.send(sender, holder.getFriendlyName(), Util.contextSetToString(context), changed);
Message.META_CLEAR_SUCCESS.send(sender, holder.getFriendlyName(), type.name().toLowerCase(), Util.contextSetToString(context), changed);
}
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta clear " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -49,10 +49,10 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.BuildableComponent;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.HashSet;
import java.util.List;
@@ -126,13 +126,13 @@ public class MetaInfo extends SharedSubCommand {
String location = processLocation(m, holder);
if (m.hasSpecificContext()) {
String context = Util.getAppendableNodeContextString(m);
Component component = ComponentSerializer.parseFromLegacy(Message.META_ENTRY_WITH_CONTEXT.asString(sender.getPlatform().getLocaleManager(), m.getMeta().getKey(), m.getMeta().getValue(), location, context), Constants.COLOR_CHAR);
component.applyRecursively(makeFancy(holder, label, m));
sender.sendMessage(component);
TextComponent.Builder builder = TextUtils.fromLegacy(Message.META_ENTRY_WITH_CONTEXT.asString(sender.getPlatform().getLocaleManager(), m.getMeta().getKey(), m.getMeta().getValue(), location, context), Constants.COLOR_CHAR).toBuilder();
builder.applyDeep(makeFancy(holder, label, m));
sender.sendMessage(builder.build());
} else {
Component component = ComponentSerializer.parseFromLegacy(Message.META_ENTRY.asString(sender.getPlatform().getLocaleManager(), m.getMeta().getKey(), m.getMeta().getValue(), location), Constants.COLOR_CHAR);
component.applyRecursively(makeFancy(holder, label, m));
sender.sendMessage(component);
TextComponent.Builder builder = TextUtils.fromLegacy(Message.META_ENTRY.asString(sender.getPlatform().getLocaleManager(), m.getMeta().getKey(), m.getMeta().getValue(), location), Constants.COLOR_CHAR).toBuilder();
builder.applyDeep(makeFancy(holder, label, m));
sender.sendMessage(builder.build());
}
}
}
@@ -142,18 +142,18 @@ public class MetaInfo extends SharedSubCommand {
String location = processLocation(e.getValue(), holder);
if (e.getValue().hasSpecificContext()) {
String context = Util.getAppendableNodeContextString(e.getValue());
Component component = ComponentSerializer.parseFromLegacy(Message.CHAT_META_ENTRY_WITH_CONTEXT.asString(sender.getPlatform().getLocaleManager(), e.getKey(), type.getEntry(e.getValue()).getValue(), location, context), Constants.COLOR_CHAR);
component.applyRecursively(makeFancy(type, holder, label, e.getValue()));
sender.sendMessage(component);
TextComponent.Builder builder = TextUtils.fromLegacy(Message.CHAT_META_ENTRY_WITH_CONTEXT.asString(sender.getPlatform().getLocaleManager(), e.getKey(), type.getEntry(e.getValue()).getValue(), location, context), Constants.COLOR_CHAR).toBuilder();
builder.applyDeep(makeFancy(type, holder, label, e.getValue()));
sender.sendMessage(builder.build());
} else {
Component component = ComponentSerializer.parseFromLegacy(Message.CHAT_META_ENTRY.asString(sender.getPlatform().getLocaleManager(), e.getKey(), type.getEntry(e.getValue()).getValue(), location), Constants.COLOR_CHAR);
component.applyRecursively(makeFancy(type, holder, label, e.getValue()));
sender.sendMessage(component);
TextComponent.Builder builder = TextUtils.fromLegacy(Message.CHAT_META_ENTRY.asString(sender.getPlatform().getLocaleManager(), e.getKey(), type.getEntry(e.getValue()).getValue(), location), Constants.COLOR_CHAR).toBuilder();
builder.applyDeep(makeFancy(type, holder, label, e.getValue()));
sender.sendMessage(builder.build());
}
}
}
private static Consumer<Component> makeFancy(ChatMetaType type, PermissionHolder holder, String label, LocalizedNode node) {
private static Consumer<BuildableComponent.Builder<?, ?>> makeFancy(ChatMetaType type, PermissionHolder holder, String label, LocalizedNode node) {
if (!node.getLocation().equals(holder.getObjectName())) {
// inherited.
Group group = holder.getPlugin().getGroupManager().getIfLoaded(node.getLocation());
@@ -162,7 +162,7 @@ public class MetaInfo extends SharedSubCommand {
}
}
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(TextUtils.joinNewline(
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(
"¥3> ¥a" + type.getEntry(node).getKey() + " ¥7- ¥r" + type.getEntry(node).getValue(),
" ",
"¥7Click to remove this " + type.name().toLowerCase() + " from " + holder.getFriendlyName()
@@ -178,7 +178,7 @@ public class MetaInfo extends SharedSubCommand {
};
}
private static Consumer<Component> makeFancy(PermissionHolder holder, String label, LocalizedNode node) {
private static Consumer<BuildableComponent.Builder<?, ?>> makeFancy(PermissionHolder holder, String label, LocalizedNode node) {
if (!node.getLocation().equals(holder.getObjectName())) {
// inherited.
Group group = holder.getPlugin().getGroupManager().getIfLoaded(node.getLocation());
@@ -187,7 +187,7 @@ public class MetaInfo extends SharedSubCommand {
}
}
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(TextUtils.joinNewline(
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(
"¥3> ¥r" + node.getMeta().getKey() + " ¥7- ¥r" + node.getMeta().getValue(),
" ",
"¥7Click to remove this meta pair from " + holder.getFriendlyName()
@@ -28,6 +28,7 @@ package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -37,7 +38,6 @@ import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -45,10 +45,10 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.List;
import java.util.stream.Collectors;
@@ -99,15 +99,15 @@ public class MetaRemoveChatMeta extends SharedSubCommand {
DataMutateResult result = holder.unsetPermission(NodeFactory.makeChatMetaNode(type, priority, meta).withExtraContext(context).build());
if (result.asBoolean()) {
Component component = ComponentSerializer.parseFromLegacy(Message.REMOVE_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, Util.contextSetToString(context)), Constants.COLOR_CHAR);
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(
TextComponent.Builder builder = TextUtils.fromLegacy(Message.REMOVE_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, Util.contextSetToString(context)), Constants.COLOR_CHAR).toBuilder();
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(
"¥3Raw " + type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
component.applyRecursively(c -> c.hoverEvent(event));
sender.sendMessage(component);
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta remove" + type.name().toLowerCase() + " " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -28,6 +28,7 @@ package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -37,7 +38,6 @@ import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -45,10 +45,10 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.List;
import java.util.stream.Collectors;
@@ -99,15 +99,15 @@ public class MetaRemoveTempChatMeta extends SharedSubCommand {
DataMutateResult result = holder.unsetPermission(NodeFactory.makeChatMetaNode(type, priority, meta).setExpiry(10L).withExtraContext(context).build());
if (result.asBoolean()) {
Component component = ComponentSerializer.parseFromLegacy(Message.REMOVE_TEMP_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, Util.contextSetToString(context)), Constants.COLOR_CHAR);
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(
TextComponent.Builder builder = TextUtils.fromLegacy(Message.REMOVE_TEMP_CHATMETA_SUCCESS.asString(plugin.getLocaleManager(), holder.getFriendlyName(), type.name().toLowerCase(), meta, priority, Util.contextSetToString(context)), Constants.COLOR_CHAR).toBuilder();
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(
"¥3Raw " + type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
component.applyRecursively(c -> c.hoverEvent(event));
sender.sendMessage(component);
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta removetemp" + type.name().toLowerCase() + " " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -35,8 +36,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -46,9 +45,8 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.List;
import java.util.stream.Collectors;
@@ -89,15 +87,15 @@ public class MetaSet extends SharedSubCommand {
holder.clearMetaKeys(key, context, false);
holder.setPermission(n);
Component component = ComponentSerializer.parseFromLegacy(Message.SET_META_SUCCESS.asString(plugin.getLocaleManager(), key, value, holder.getFriendlyName(), Util.contextSetToString(context)), Constants.COLOR_CHAR);
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(
TextComponent.Builder builder = TextUtils.fromLegacy(Message.SET_META_SUCCESS.asString(plugin.getLocaleManager(), key, value, holder.getFriendlyName(), Util.contextSetToString(context))).toBuilder();
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(
TextUtils.joinNewline("¥3Raw key: ¥r" + key, "¥3Raw value: ¥r" + value),
'¥'
));
component.applyRecursively(c -> c.hoverEvent(event));
sender.sendMessage(component);
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta set " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -37,7 +38,6 @@ import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -49,9 +49,8 @@ import me.lucko.luckperms.common.utils.DateUtil;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import java.util.List;
import java.util.stream.Collectors;
@@ -94,15 +93,15 @@ public class MetaSetTemp extends SharedSubCommand {
holder.clearMetaKeys(key, context, true);
duration = holder.setPermission(n, modifier).getValue().getExpiryUnixTime();
Component component = ComponentSerializer.parseFromLegacy(Message.SET_META_TEMP_SUCCESS.asString(plugin.getLocaleManager(), key, value, holder.getFriendlyName(), DateUtil.formatDateDiff(duration), Util.contextSetToString(context)), Constants.COLOR_CHAR);
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(
TextComponent.Builder builder = TextUtils.fromLegacy(Message.SET_META_TEMP_SUCCESS.asString(plugin.getLocaleManager(), key, value, holder.getFriendlyName(), DateUtil.formatDateDiff(duration), Util.contextSetToString(context)), Constants.COLOR_CHAR).toBuilder();
HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(
TextUtils.joinNewline("¥3Raw key: ¥r" + key, "¥3Raw value: ¥r" + value),
'¥'
));
component.applyRecursively(c -> c.hoverEvent(event));
sender.sendMessage(component);
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta settemp " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -34,7 +35,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -70,14 +70,18 @@ public class MetaUnset extends SharedSubCommand {
return CommandResult.NO_PERMISSION;
}
holder.clearMetaKeys(key, context, false);
Message.UNSET_META_SUCCESS.send(sender, key, holder.getFriendlyName(), Util.contextSetToString(context));
if (holder.clearMetaKeys(key, context, false)) {
Message.UNSET_META_SUCCESS.send(sender, key, holder.getFriendlyName(), Util.contextSetToString(context));
LogEntry.build().actor(sender).acted(holder)
.action("meta unset " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta unset " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
save(holder, sender, plugin);
return CommandResult.SUCCESS;
save(holder, sender, plugin);
return CommandResult.SUCCESS;
} else {
Message.DOESNT_HAVE_META.send(sender, holder.getFriendlyName());
return CommandResult.STATE_ERROR;
}
}
}
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.commands.impl.generic.meta;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -34,7 +35,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -70,14 +70,18 @@ public class MetaUnsetTemp extends SharedSubCommand {
return CommandResult.NO_PERMISSION;
}
holder.clearMetaKeys(key, context, true);
Message.UNSET_META_TEMP_SUCCESS.send(sender, key, holder.getFriendlyName(), Util.contextSetToString(context));
if (holder.clearMetaKeys(key, context, true)) {
Message.UNSET_META_TEMP_SUCCESS.send(sender, key, holder.getFriendlyName(), Util.contextSetToString(context));
LogEntry.build().actor(sender).acted(holder)
.action("meta unsettemp " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("meta unsettemp " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
save(holder, sender, plugin);
return CommandResult.SUCCESS;
save(holder, sender, plugin);
return CommandResult.SUCCESS;
} else {
Message.DOESNT_HAVE_META.send(sender, holder.getFriendlyName());
return CommandResult.STATE_ERROR;
}
}
}
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.commands.impl.generic.other;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -34,7 +35,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -81,7 +81,7 @@ public class HolderClear<T extends PermissionHolder> extends SubCommand<T> {
Message.CLEAR_SUCCESS.send(sender, holder.getFriendlyName(), Util.contextSetToString(context), changed);
}
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("clear " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -53,6 +53,7 @@ import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.format.TextColor;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -84,8 +85,11 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
JsonObject data = new JsonObject();
Set<NodeModel> nodes = holder.getEnduringNodes().values().stream().map(NodeModel::fromNode).collect(Collectors.toCollection(LinkedHashSet::new));
data.add("nodes", serializePermissions(nodes));
data.addProperty("who", id(holder));
data.addProperty("cmdAlias", label);
data.addProperty("uploadedBy", sender.getName());
data.addProperty("time", System.currentTimeMillis());
data.add("nodes", serializePermissions(nodes));
String dataUrl = paste(new GsonBuilder().setPrettyPrinting().create().toJson(data));
if (dataUrl == null) {
@@ -99,9 +103,10 @@ public class HolderEditor<T extends PermissionHolder> extends SubCommand<T> {
Message.EDITOR_URL.send(sender);
Component message = new TextComponent(url).color('b')
Component message = TextComponent.builder(url).color(TextColor.AQUA)
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TextComponent("Click to open the editor.").color('7')));
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to open the editor.").color(TextColor.GRAY)))
.build();
sender.sendMessage(message);
return CommandResult.SUCCESS;
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.commands.impl.generic.parent;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -35,7 +36,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -90,7 +90,7 @@ public class ParentAdd extends SharedSubCommand {
if (result.asBoolean()) {
Message.SET_INHERIT_SUCCESS.send(sender, holder.getFriendlyName(), group.getDisplayName(), Util.contextSetToString(context));
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("parent add " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -28,6 +28,7 @@ package me.lucko.luckperms.common.commands.impl.generic.parent;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -37,7 +38,6 @@ import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -104,7 +104,7 @@ public class ParentAddTemp extends SharedSubCommand {
duration = ret.getValue().getExpiryUnixTime();
Message.SET_TEMP_INHERIT_SUCCESS.send(sender, holder.getFriendlyName(), group.getDisplayName(), DateUtil.formatDateDiff(duration), Util.contextSetToString(context));
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("parent addtemp " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.commands.impl.generic.parent;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -34,7 +35,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -79,7 +79,7 @@ public class ParentClear extends SharedSubCommand {
Message.PARENT_CLEAR_SUCCESS.send(sender, holder.getFriendlyName(), Util.contextSetToString(context), changed);
}
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("parent clear " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.commands.impl.generic.parent;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -35,7 +36,6 @@ import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.constants.DataConstraints;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -116,7 +116,7 @@ public class ParentClearTrack extends SharedSubCommand {
Message.PARENT_CLEAR_TRACK_SUCCESS.send(sender, holder.getFriendlyName(), track.getName(), Util.contextSetToString(context), changed);
}
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("parent cleartrack " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -46,11 +46,12 @@ import me.lucko.luckperms.common.utils.DateUtil;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.BuildableComponent;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.ComponentSerializer;
import net.kyori.text.format.TextColor;
import java.util.ArrayList;
import java.util.List;
@@ -89,15 +90,15 @@ public class ParentInfo extends SharedSubCommand {
}
if (page.isEmpty()) {
return new TextComponent("None").color('3');
return TextComponent.builder("None").color(TextColor.DARK_AQUA).build();
}
TextComponent message = new TextComponent("");
TextComponent.Builder message = TextComponent.builder("");
for (Node node : page) {
String s = "&3> &a" + node.getGroupName() + Util.getAppendableNodeContextString(node) + "\n";
message.append(ComponentSerializer.parseFromLegacy(s, Constants.FORMAT_CHAR).applyRecursively(makeFancy(holder, label, node)));
message.append(TextUtils.fromLegacy(s, Constants.FORMAT_CHAR).toBuilder().applyDeep(makeFancy(holder, label, node)).build());
}
return message;
return message.build();
}
private static Component tempGroupsToMessage(SortedSet<LocalizedNode> nodes, PermissionHolder holder, String label) {
@@ -109,19 +110,19 @@ public class ParentInfo extends SharedSubCommand {
}
if (page.isEmpty()) {
return new TextComponent("None").color('3');
return TextComponent.builder("None").color(TextColor.DARK_AQUA).build();
}
TextComponent message = new TextComponent("");
TextComponent.Builder message = TextComponent.builder("");
for (Node node : page) {
String s = "&3> &a" + node.getPermission() + Util.getAppendableNodeContextString(node) + "\n&2- expires in " + DateUtil.formatDateDiff(node.getExpiryUnixTime()) + "\n";
message.append(ComponentSerializer.parseFromLegacy(s, Constants.FORMAT_CHAR).applyRecursively(makeFancy(holder, label, node)));
message.append(TextUtils.fromLegacy(s, Constants.FORMAT_CHAR).toBuilder().applyDeep(makeFancy(holder, label, node)).build());
}
return message;
return message.build();
}
private static Consumer<Component> makeFancy(PermissionHolder holder, String label, Node node) {
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentSerializer.parseFromLegacy(TextUtils.joinNewline(
private static Consumer<BuildableComponent.Builder<? ,?>> makeFancy(PermissionHolder holder, String label, Node node) {
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(
"&3> &f" + node.getGroupName(),
" ",
"&7Click to remove this parent from " + holder.getFriendlyName()
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.commands.impl.generic.parent;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -36,7 +37,6 @@ import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -79,7 +79,8 @@ public class ParentRemove extends SharedSubCommand {
if (holder instanceof User) {
User user = (User) holder;
boolean shouldPrevent = (context.isEmpty() || context.has("server", "global")) &&
boolean shouldPrevent = plugin.getConfiguration().get(ConfigKeys.PREVENT_PRIMARY_GROUP_REMOVAL) &&
context.isEmpty() &&
plugin.getConfiguration().get(ConfigKeys.PRIMARY_GROUP_CALCULATION_METHOD).equals("stored") &&
user.getPrimaryGroup().getStoredValue().equalsIgnoreCase(groupName);
@@ -93,7 +94,7 @@ public class ParentRemove extends SharedSubCommand {
if (result.asBoolean()) {
Message.UNSET_INHERIT_SUCCESS.send(sender, holder.getFriendlyName(), groupName, Util.contextSetToString(context));
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("parent remove " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.commands.impl.generic.parent;
import me.lucko.luckperms.api.DataMutateResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.commands.ArgumentPermissions;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
@@ -35,7 +36,6 @@ import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.ArgumentUtils;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.CommandPermission;
import me.lucko.luckperms.common.data.LogEntry;
import me.lucko.luckperms.common.locale.CommandSpec;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.Message;
@@ -79,7 +79,7 @@ public class ParentRemoveTemp extends SharedSubCommand {
if (result.asBoolean()) {
Message.UNSET_TEMP_INHERIT_SUCCESS.send(sender, holder.getFriendlyName(), groupName, Util.contextSetToString(context));
LogEntry.build().actor(sender).acted(holder)
ExtendedLogEntry.build().actor(sender).acted(holder)
.action("parent removetemp " + args.stream().map(ArgumentUtils.WRAPPER).collect(Collectors.joining(" ")))
.build().submit(plugin, sender);
Loaded 100 of 225 files, more files were not shown because too many files have changed in this diff. Show more