Compare commits

...
56 Commits
Author SHA1 Message Date
NotAlexNoyle bbe60a1bf4 Exclude CODEOWNERS from language statistics 2026-10-27 08:25:12 -04:00
NotAlexNoyle 06aca10e5d Add CODEOWNERS (one staff approval) 2026-10-27 08:25:12 -04:00
NotAlexNoyle 625bb0c6a9 Use live TrueOG Network config file 2026-05-27 11:22:19 -04:00
NotAlexNoyle bb8fe0f528 Fix user list on luckperms web by normalizing version number format to upstream 2026-04-24 16:01:18 -04:00
NotAlexNoyle 863d10273c Disable Vault integration 2026-04-24 15:24:29 -04:00
NotAlexNoyle a57ba166ff Merge branch 'LuckPerms:master' into master 2025-09-22 07:10:41 -04:00
蒟蒻 0b91732cd3 Prevent handleDisconnect after stopped server on Fabric (#4160) 2025-09-21 11:04:58 +01:00
An5w1r a087f14aa2 Fix classExists checks to handle NoClassDefFoundError (#4136) 2025-09-21 11:03:07 +01:00
Luck d666a2a798 Fix command rate limit for command blocks (#4094) 2025-09-21 10:58:24 +01:00
NotAlexNoyle c27cfcb10b Update shadow 2025-09-03 14:22:27 -04:00
NotAlexNoyle 52464c6bec Simplify gradle build, support java 17 2025-09-03 13:39:23 -04:00
Luck 65c42b9b09 Micro-optimize temporary node audit (#4155) 2025-08-23 15:39:49 +01:00
Luck 73d710a89e Update to Minecraft 1.21.8 2025-07-21 22:08:21 +01:00
Luck ed863b275d Update to Minecraft 1.21.7 2025-06-30 19:41:56 +01:00
Luck c20776d6aa Remove Forge capability attachment 2025-06-19 19:53:09 +01:00
Luck 3aca721f96 Rollback bytebuddy dependency version upgrade 2025-06-19 19:35:04 +01:00
Luck 8d9c5bf401 Add workaround for Forge eventbus limitation 2025-06-19 19:09:40 +01:00
Luck 0af1038d81 Update to Minecraft 1.21.6 2025-06-19 18:48:44 +01:00
Luck 51fe574a51 Move config load earlier in startup process (#4083) 2025-06-14 09:31:56 +01:00
Max Barrass bac0edd90e Fix missing closing brackets in .conf files (#4109) 2025-06-14 09:24:52 +01:00
Luck 8ab9910ef8 Downgrade jedis and h2 2025-06-01 19:32:26 +01:00
Luck 9538cb9dad Upgrade dependencies and require Java 11 2025-06-01 16:41:39 +01:00
Luck 0486bc885b Release API v5.5 2025-05-25 08:59:47 +01:00
Luck 27e7551e2b Fix adventure package relocations 2025-05-23 08:10:55 +01:00
Luck b77d5759d3 Upgrade adventure dependencies for 1.21.5 support 2025-05-22 21:15:24 +01:00
Luck 96abc0d4fe Update setup-gradle GitHub action 2025-05-10 15:04:42 +01:00
lucko 1a24a6a155 Add REST storage backend (#3939) 2025-05-10 15:01:08 +01:00
𝑩𝒊𝒒𝒖𝒂𝒕𝒆𝒓𝒏𝒊𝒐𝒏𝒔 95ea491787 Support plugins that 'provide' Vault (#4075) 2025-05-10 14:47:32 +01:00
Remigio 626d08431e Add missing IllegalArgumentExceptions to Javadocs (#4015) 2025-05-10 14:33:38 +01:00
lucko 94e7e11183 Add configurable read-only mode for commands (#4031) 2025-05-10 14:32:47 +01:00
Luck f64bf04ba4 Update mod loader dependencies 2025-05-08 19:45:14 +01:00
Luck 7744913df2 Update to Minecraft 1.21.5 2025-03-27 19:46:43 +00:00
lucko 6b7283ac83 Improve context manager caching (#4050) 2025-03-20 19:20:16 +00:00
Luck 9bc8a61e2d Add file secret config adapter (#3913) 2025-02-22 15:57:44 +00:00
Luck 5e24a7068c Mark plugin msg channel as optional on Forge (#3954) 2025-02-22 15:28:18 +00:00
Luck f351369a51 Update CI action versions 2025-02-13 09:59:59 +00:00
Luck 1bf0deb5a5 Add unit tests for messaging service messages 2025-01-20 22:46:52 +00:00
Luck 5b82837b80 Update Sponge module to API 12 2025-01-05 14:38:15 +00:00
Luck 0fef481d48 Add more unit tests 2024-12-22 09:51:11 +00:00
Luck f2040ab815 Update to Minecraft 1.21.4 2024-12-07 16:21:26 +00:00
Luck b18717edef Remove NeoForge capability attachment 2024-12-04 20:39:55 +00:00
Luck 29c93fac4f Add note about shading LuckPerms API into plugin jars 2024-12-04 11:27:14 +00:00
Luck 2e67011f45 Refactor some unit tests 2024-12-04 11:25:06 +00:00
Luck a92e04f605 Recreate individual sql tables if they are missing (#3995) 2024-11-09 12:09:23 +00:00
Luck 8d65693d9a Update to Minecraft 1.21.3 2024-10-26 10:07:03 +01:00
Luck 8973abd7f1 Support older signature alg if running on Java 8 2024-10-23 23:19:05 +01:00
Luck 9420efd3ac Switch to ECDSA keys for web editor socket 2024-10-23 22:52:20 +01:00
Alex c773126a0f Simplify ExpiringSet implementation (#3969) 2024-10-21 21:09:40 +01:00
Luck 9d80de5d5c Fix MongoStorage nodeFromDoc integer cast (#3846) 2024-08-24 09:55:32 +01:00
3TUSK 9bf30fc839 Fix login configuration phase on NeoForge (#3961) 2024-08-22 22:27:29 +01:00
Luck 136a4859f3 Update to Minecraft 1.21.1 2024-08-12 19:45:38 +01:00
Jason Penilla 642f1d3019 Port to NeoForge (#3950) 2024-08-12 19:11:27 +01:00
Luck b21753f5e9 Use builtin Fabric world change event 2024-07-07 21:31:34 +01:00
Luck 9cc129466b Init VerboseHandler earlier (#3932) 2024-07-07 21:22:40 +01:00
Drex 55440cac01 Fix memory leak in Fabric MixinUser (#3931) 2024-07-07 21:15:41 +01:00
lucko 9e7a3d26e4 Improve efficiency of action log queries (#3917) 2024-06-16 21:11:23 +01:00
329 changed files with 12414 additions and 5192 deletions

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
-2
View File
@@ -1,2 +0,0 @@
github: lucko
patreon: luckdev
-63
View File
@@ -1,63 +0,0 @@
name: 🐛 ​ Bug report
description: Report an issue or problem with the way LuckPerms is functioning.
labels: ["type: issue"]
body:
- type: markdown
attributes:
value: "Before going any further, please check to see if your problem is fixed by updating to a newer version. The latest development builds can be obtained from https://luckperms.net/download"
- type: textarea
id: description
attributes:
label: Description
description: Please provide a short description of the issue in the space below.
placeholder: Description goes here!
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: Reproduction Steps
description: Please provide a clear list of steps we should follow in order to reproduce your issue in the space below.
placeholder: List reproduction steps here, please!
validations:
required: true
- type: textarea
id: expected-behaviour
attributes:
label: Expected Behaviour
description: Please provide a description of what you expected to happen.
placeholder: Describe what you expected to happen here.
validations:
required: true
- type: input
id: server-info
attributes:
label: Server Details
description: Please provide a description of the server details, including type and specific version number.
placeholder: 'git-Paper-124 (MC: 1.17.1)'
validations:
required: true
- type: input
id: luckperms-version
attributes:
label: LuckPerms Version
description: Please provide the specific, precise version number of LuckPerms you are using to reproduce this bug.
placeholder: v5.3.70
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs and Configs
description: If you think it would be helpful, please also include a pastebin of any relevant config files or log output. You should use https://gist.github.com/ to upload, then include the link below.
placeholder: https://gist.github.com/HelpfulBugReporter/ThisIsAVeryUsefulLogOutput
validations:
required: false
- type: textarea
id: extra-info
attributes:
label: Extra Details
description: Please include any other relevant details in the space below.
placeholder: I was able to reproduce this only at the end of a rainbow.
validations:
required: false
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: 💬 ​ Ask a question
url: https://discord.gg/luckperms
about: We use Discord to provide support for the project. If you have a question, need help, or aren't sure if you've found a bug or not, please come and talk to us there!
-31
View File
@@ -1,31 +0,0 @@
name: 💡 ​ Suggestion
description: Suggest an idea for an improvement or change to LuckPerms.
labels: ["type: suggestion"]
body:
- type: markdown
attributes:
value: "Before going any further, please check to see if your suggestion has already been added by checking the [wiki](https://luckperms.net/wiki/Home). The latest development builds can be obtained from https://luckperms.net/download"
- type: textarea
id: description
attributes:
label: Description
description: Please provide a short description of your suggestion in the space below.
placeholder: Hello, perhaps you should...
validations:
required: true
- type: textarea
id: proposed-behaviour
attributes:
label: Proposed Behaviour
description: Please provide a short explanation of how the feature should work / be changed, and how this will affect the project.
placeholder: It would...
validations:
required: true
- type: textarea
id: extra-info
attributes:
label: Extra Details
description: Please include any other relevant details in the space below.
placeholder: I think this should only function at the end of a rainbow.
validations:
required: false
-130
View File
@@ -1,130 +0,0 @@
name: Build Gradle and Publish Docker image
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-gradle:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run build and tests with Gradle wrapper
run: ./gradlew test build -PdockerTests
- name: Publish test report
uses: mikepenz/action-junit-report@v3
if: success() || failure()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
annotate_notice: true
detailed_summary: true
- name: Upload all artifacts
uses: actions/upload-artifact@v3
with:
name: jars
path: |
bukkit/loader/build/libs/LuckPerms-Bukkit-*.jar
bukkit-legacy/loader/build/libs/LuckPerms-Bukkit-Legacy-*.jar
bungee/loader/build/libs/LuckPerms-Bungee-*.jar
sponge/loader/build/libs/LuckPerms-Sponge-*.jar
nukkit/loader/build/libs/LuckPerms-Nukkit-*.jar
velocity/build/libs/LuckPerms-Velocity-*.jar
fabric/build/libs/LuckPerms-Fabric-*.jar
forge/loader/build/libs/LuckPerms-Forge-*.jar
standalone/loader/build/libs/LuckPerms-Standalone-*.jar
- name: Upload standalone artifact
uses: actions/upload-artifact@v3
with:
name: standalone-binary
path: standalone/loader/build/libs/LuckPerms-Standalone-*.jar
build-docker:
needs: build-gradle
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Retrieve saved standalone jar artifact
uses: actions/download-artifact@v3
with:
name: standalone-binary
path: standalone/docker/
- name: Remove version number from jar artifact name
run: mv standalone/docker/LuckPerms-Standalone-*.jar standalone/docker/luckperms-standalone.jar
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine the LuckPerms version
run: |
echo "luckperms_version=$(git describe --tags | awk -F "-" '{print $1 "." $2}')" >> "$GITHUB_ENV"
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=${{ github.ref == 'refs/heads/master' }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=raw,enable=${{ github.ref == 'refs/heads/master' }},value=${{ env.luckperms_version }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: standalone/docker/
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-26
View File
@@ -1,26 +0,0 @@
### :speech_balloon: Looking for support?
Details about support for the project can be found [here, on the Wiki Homepage](https://luckperms.net/wiki/Home#support).
### :bug: Reporting bugs?
Before reporting a bug or issue, please make sure that the issue is actually being caused by or related to LuckPerms. We get a lot of reports which are caused by other software - please double check!
If you're unsure, feel free to ask using the above resources BEFORE making a report.
Bugs or issues should be reported using the [GitHub Issues tab](https://github.com/LuckPerms/LuckPerms/issues).
### :pencil: Want to contribute code?
#### Pull Requests
If you make any changes or improvements to the plugin which you think would be beneficial to others, please consider making a pull request to merge your changes back into the upstream project. (especially if your changes are bug fixes!)
LuckPerms loosely follows the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Generally, try to copy the style of code found in the class you're editing.
If you're considering submitting a substantial pull request, please open an issue so we can discuss the change before starting work on the contribution. Most pull requests are happily accepted, but larger changes may have an impact on the maintainability of the project, and require more consideration.
#### Project Layout
The project is split up into a few separate modules.
* **API** - The public, semantically versioned API used by other plugins wishing to integrate with and retrieve data from LuckPerms. This module (for the most part) does not contain any implementation itself, and is provided by the plugin.
* **Common** - The common module contains most of the code which implements the respective LuckPerms plugins. This abstract module reduces duplicated code throughout the project.
* **Bukkit, BungeeCord, Fabric, Forge, Nukkit, Sponge & Velocity** - Each use the common module to implement plugins on the respective server platforms.
-106
View File
@@ -1,106 +0,0 @@
group = 'net.luckperms'
project.version = '5.4'
jar {
manifest {
attributes('Automatic-Module-Name': 'net.luckperms.api')
}
}
dependencies {
compileOnly 'org.checkerframework:checker-qual:3.21.2'
compileOnly 'org.jetbrains:annotations:23.1.0'
}
// Only used occasionally for deployment - not needed for normal builds.
/*
if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')) {
apply plugin: 'signing'
javadoc {
title = 'LuckPerms API (v' + project.ext.apiVersion + ')'
options.overview = 'javadoc/overview.html'
options.encoding = 'UTF-8'
options.charSet = 'UTF-8'
options.links(
'https://checkerframework.org/api/',
'https://javadoc.io/static/org.jetbrains/annotations/23.0.0/'
)
options.addStringOption('Xdoclint:none', '-quiet')
options.addStringOption('-since', '5.0,5.1,5.2,5.3,5.4,5.5')
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
options.links.add('https://docs.oracle.com/en/java/javase/11/docs/api/')
} else {
options.links.add('https://docs.oracle.com/javase/8/docs/api/')
}
}
java {
withJavadocJar()
withSourcesJar()
}
publishing {
repositories {
maven {
def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username sonatypeUsername
password sonatypePassword
}
}
}
publications {
mavenJava(MavenPublication) {
artifactId = 'api'
from components.java
pom {
name = 'LuckPerms API'
description = 'A permissions plugin for Minecraft servers.'
url = 'https://luckperms.net'
licenses {
license {
name = 'MIT'
url = 'https://opensource.org/licenses/MIT'
}
}
developers {
developer {
id = 'lucko'
name = 'Luck'
url = 'https://lucko.me'
email = 'git@lucko.me'
}
}
scm {
connection = 'scm:git:https://github.com/LuckPerms/LuckPerms.git'
developerConnection = 'scm:git:git@github.com:LuckPerms/LuckPerms.git'
url = 'https://github.com/LuckPerms/LuckPerms'
}
issueManagement {
system = 'GitHub'
url = 'https://github.com/LuckPerms/LuckPerms/issues'
}
}
}
}
}
signing {
useGpgCmd()
sign publishing.publications.mavenJava
required = true
}
}
*/
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<p>
LuckPerms is a permissions plugin for Minecraft servers. It allows server admins to control what
LuckPerms is a permissions manager for Minecraft servers. It allows server admins to control what
features players can use by creating groups and assigning permissions.
</p>
<h4>Useful Links</h4>
@@ -26,6 +26,7 @@
package net.luckperms.api;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.event.EventBus;
@@ -267,4 +268,14 @@ public interface LuckPerms {
@Internal
@NonNull NodeMatcherFactory getNodeMatcherFactory();
/**
* Gets the {@link ActionFilterFactory}.
*
* @return the action filter factory
* @since 5.5
*/
@Internal
@NonNull
ActionFilterFactory getActionFilterFactory();
}
@@ -79,7 +79,8 @@ public final class LuckPermsProvider {
" a) the LuckPerms plugin is not installed or it failed to enable\n" +
" b) the plugin in the stacktrace does not declare a dependency on LuckPerms\n" +
" c) the plugin in the stacktrace is retrieving the API before the plugin 'enable' phase\n" +
" (call the #get method in onEnable, not the constructor!)\n";
" (call the #get method in onEnable, not the constructor!)\n" +
" d) the plugin in the stacktrace is incorrectly 'shading' the LuckPerms API into its jar\n";
NotLoadedException() {
super(MESSAGE);
@@ -28,6 +28,7 @@ package net.luckperms.api.actionlog;
import net.luckperms.api.LuckPermsProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import java.time.Instant;
import java.util.Optional;
@@ -35,7 +36,10 @@ import java.util.UUID;
/**
* Represents a logged action.
*
* <p>API users should not implement this interface directly.</p>
*/
@NonExtendable
public interface Action extends Comparable<Action> {
/**
@@ -81,6 +85,7 @@ public interface Action extends Comparable<Action> {
/**
* Represents the source of an action.
*/
@NonExtendable
interface Source {
/**
@@ -102,6 +107,7 @@ public interface Action extends Comparable<Action> {
/**
* Represents the target of an action.
*/
@NonExtendable
interface Target {
/**
@@ -126,7 +132,7 @@ public interface Action extends Comparable<Action> {
@NonNull Type getType();
/**
* Represents the type of a {@link Target}.
* Represents the type of {@link Target}.
*/
enum Type {
USER, GROUP, TRACK
@@ -25,6 +25,7 @@
package net.luckperms.api.actionlog;
import net.luckperms.api.actionlog.filter.ActionFilter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Unmodifiable;
@@ -40,7 +41,11 @@ import java.util.UUID;
* You can add to the log using the {@link ActionLogger}, and then request an updated copy.</p>
*
* <p>All methods are thread safe, and return immutable and thread safe collections.</p>
*
* @deprecated Use {@link ActionLogger#queryActions(ActionFilter)} or
* {@link ActionLogger#queryActions(ActionFilter, int, int)} instead.
*/
@Deprecated
public interface ActionLog {
/**
@@ -25,9 +25,11 @@
package net.luckperms.api.actionlog;
import net.luckperms.api.messaging.MessagingService;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.util.Page;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -46,43 +48,78 @@ public interface ActionLogger {
* Gets a {@link ActionLog} instance from the plugin storage.
*
* @return a log instance
* @deprecated Use {@link #queryActions(ActionFilter)} or {@link #queryActions(ActionFilter, int, int)} instead. These methods
* are more efficient (they don't load the full action log into memory) and allow for pagination.
*/
@Deprecated
@NonNull CompletableFuture<ActionLog> getLog();
/**
* Submits a log entry to the plugin to be handled.
* Gets all actions from the action log matching the given {@code filter}.
*
* <p>This method submits the log to the storage provider and broadcasts
* it.</p>
* <p>If the filter is {@code null}, all actions will be returned.</p>
*
* <p>It is therefore roughly equivalent to calling
* {@link #submitToStorage(Action)} and {@link #broadcastAction(Action)},
* however, using this method is preferred to making the calls individually.</p>
* <p>Unlike {@link #queryActions(ActionFilter, int, int)}, this method does not implement any pagination and will return
* all entries at once.</p>
*
* <p>If you want to submit a log entry but don't know which method to pick,
* @param filter the filter, optional
* @return the actions
* @since 5.5
*/
@NonNull CompletableFuture<List<Action>> queryActions(@NonNull ActionFilter filter);
/**
* Gets a page of actions from the action log matching the given {@code filter}.
*
* <p>If the filter is {@code null}, all actions will be returned.</p>
*
* @param filter the filter, optional
* @param pageSize the size of the page
* @param pageNumber the page number
* @return the page of actions
* @since 5.5
*/
@NonNull CompletableFuture<Page<Action>> queryActions(@NonNull ActionFilter filter, int pageSize, int pageNumber);
/**
* Submits a logged action to LuckPerms.
*
* <p>This method submits the action to the storage provider to be persisted in the action log.
* It also broadcasts it to administrator players on the current instance and to admins on other
* connected servers if a messaging service is configured.</p>
*
* <p>It is roughly equivalent to calling
* {@link #submitToStorage(Action)} followed by {@link #broadcastAction(Action)},
* however using this method is preferred to making the calls individually.</p>
*
* <p>If you want to submit an action log entry but don't know which method to pick,
* use this one.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is submitted
*/
@NonNull CompletableFuture<Void> submit(@NonNull Action entry);
/**
* Submits a log entry to the plugins storage handler.
* Submits a logged action to LuckPerms and persists it in the storage backend.
*
* <p>This method does not broadcast the action or send it through the messaging service.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is submitted
*/
@NonNull CompletableFuture<Void> submitToStorage(@NonNull Action entry);
/**
* Submits a log entry to the plugins log broadcasting handler.
* Submits a logged action to LuckPerms and broadcasts it to administrators.
*
* <p>If enabled, this method will also dispatch the log entry via the
* plugins {@link MessagingService}.</p>
* <p>The broadcast is made to administrator players on the current instance
* and to admins on other connected servers if a messaging service is configured.</p>
*
* <p>This method does not save the action to the plugin storage backend.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is broadcasted
*/
@NonNull CompletableFuture<Void> broadcastAction(@NonNull Action entry);
@@ -0,0 +1,114 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.actionlog.filter;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.actionlog.Action;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import java.util.UUID;
import java.util.function.Predicate;
/**
* A predicate filter which matches certain {@link Action}s.
*
* <p>API users should not implement this interface directly.</p>
*
* @since 5.5
*/
@NonExtendable
public interface ActionFilter extends Predicate<Action> {
/**
* Gets an {@link ActionFilter} which matches any action.
*
* @return the matcher
*/
static ActionFilter any() {
return LuckPermsProvider.get().getActionFilterFactory().any();
}
/**
* Gets an {@link ActionFilter} which matches actions with a specific source user.
*
* @param uniqueId the source user unique id
* @return the matcher
*/
static ActionFilter source(UUID uniqueId) {
return LuckPermsProvider.get().getActionFilterFactory().source(uniqueId);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific user.
*
* @param uniqueId the target user unique id
* @return the matcher
*/
static ActionFilter user(UUID uniqueId) {
return LuckPermsProvider.get().getActionFilterFactory().user(uniqueId);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific group.
*
* @param name the target group name
* @return the matcher
*/
static ActionFilter group(String name) {
return LuckPermsProvider.get().getActionFilterFactory().group(name);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific track.
*
* @param name the target track name
* @return the matcher
*/
static ActionFilter track(String name) {
return LuckPermsProvider.get().getActionFilterFactory().track(name);
}
/**
* Gets an {@link ActionFilter} which matches actions which contain a specific search query in the source name,
* target name or description.
*
* @param query the search query
* @return the matcher
*/
static ActionFilter search(String query) {
return LuckPermsProvider.get().getActionFilterFactory().search(query);
}
/**
* Tests to see if the given {@link Action} matches the filter.
*
* @param action the action to test
* @return true if the action matched
*/
@Override
boolean test(Action action);
}
@@ -0,0 +1,88 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.actionlog.filter;
import org.jetbrains.annotations.ApiStatus.Internal;
import java.util.UUID;
/**
* A factory which creates {@link ActionFilter}s.
*
* @since 5.5
*/
@Internal
public interface ActionFilterFactory {
/**
* Gets an {@link ActionFilter} which matches any action.
*
* @return the matcher
*/
ActionFilter any();
/**
* Gets an {@link ActionFilter} which matches actions with a specific source user.
*
* @param uniqueId the source user unique id
* @return the matcher
*/
ActionFilter source(UUID uniqueId);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific user.
*
* @param uniqueId the target user unique id
* @return the matcher
*/
ActionFilter user(UUID uniqueId);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific group.
*
* @param name the target group name
* @return the matcher
*/
ActionFilter group(String name);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific track.
*
* @param name the target track name
* @return the matcher
*/
ActionFilter track(String name);
/**
* Gets an {@link ActionFilter} which matches actions which contain a specific search query in the source name,
* target name or description.
*
* @param query the search query
* @return the matcher
*/
ActionFilter search(String query);
}
@@ -0,0 +1,29 @@
/*
* 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.
*/
/**
* {@link net.luckperms.api.actionlog.Action} filters.
*/
package net.luckperms.api.actionlog.filter;
@@ -26,7 +26,6 @@
package net.luckperms.api.event.sync;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -63,6 +63,7 @@ public interface DisplayNameNode extends ScopedNode<DisplayNameNode, DisplayName
*
* @param displayName the display name to set
* @return the builder
* @throws IllegalArgumentException if {@code displayName} is empty
*/
static @NonNull Builder builder(@NonNull String displayName) {
return builder().displayName(displayName);
@@ -67,6 +67,7 @@ public interface InheritanceNode extends ScopedNode<InheritanceNode, Inheritance
*
* @param group the group to set
* @return the builder
* @throws IllegalArgumentException if {@code group} is not a valid group name
*/
static @NonNull Builder builder(@NonNull String group) {
return builder().group(group);
@@ -71,6 +71,7 @@ public interface MetaNode extends ScopedNode<MetaNode, MetaNode.Builder> {
* @param key the meta key to set
* @param value the meta value to set
* @return the builder
* @throws IllegalArgumentException if {@code key} is empty
*/
static @NonNull Builder builder(@NonNull String key, @NonNull String value) {
return builder().key(key).value(value);
@@ -88,6 +88,7 @@ public interface PermissionNode extends ScopedNode<PermissionNode, PermissionNod
*
* @param permission the permission to set
* @return the builder
* @throws IllegalArgumentException if {@code permission} is empty
*/
static @NonNull Builder builder(@NonNull String permission) {
return builder().permission(permission);
@@ -75,6 +75,7 @@ public interface RegexPermissionNode extends ScopedNode<RegexPermissionNode, Reg
*
* @param pattern the pattern to set
* @return the builder
* @throws IllegalArgumentException if {@code pattern} is empty
*/
static @NonNull Builder builder(@NonNull String pattern) {
return builder().pattern(pattern);
@@ -76,6 +76,7 @@ public interface Platform {
NUKKIT("Nukkit"),
VELOCITY("Velocity"),
FABRIC("Fabric"),
NEOFORGE("NeoForge"),
FORGE("Forge"),
STANDALONE("Standalone");
@@ -48,7 +48,7 @@ public interface DataTypeFilterFunction {
/**
* Creates a {@link DataTypeFilterFunction} that always returns the given
* {@code predicate}.
* {@code predicate} (commonly one of the values in {@link DataTypeFilter}).
*
* @param predicate the predicate
* @return the data type filter function
@@ -23,20 +23,31 @@
* SOFTWARE.
*/
package me.lucko.luckperms.fabric.event;
package net.luckperms.api.util;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import org.checkerframework.checker.nullness.qual.NonNull;
// TODO: Use Fabric API alternative when merged.
public interface PlayerChangeWorldCallback {
Event<PlayerChangeWorldCallback> EVENT = EventFactory.createArrayBacked(PlayerChangeWorldCallback.class, callbacks -> (originalWorld, destination, player) -> {
for (PlayerChangeWorldCallback callback : callbacks) {
callback.onChangeWorld(originalWorld, destination, player);
}
});
import java.util.List;
/**
* Represents a page of entries.
*
* @since 5.5
*/
public interface Page<T> {
/**
* Gets the entries on this page.
*
* @return the entries
*/
@NonNull List<T> entries();
/**
* Gets the total/overall number of entries (not just the number of entries on this page).
*
* @return the total number of entries
*/
int overallSize();
void onChangeWorld(ServerWorld originalWorld, ServerWorld destination, ServerPlayerEntity player);
}
-83
View File
@@ -1,83 +0,0 @@
plugins {
alias(libs.plugins.licenser) apply false
}
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
defaultTasks 'licenseFormat', 'build'
subprojects {
apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: 'org.cadixdev.licenser'
group = 'me.lucko.luckperms'
version = '5.4-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}
tasks.withType(Test).configureEach {
testLogging {
events = [TestLogEvent.PASSED, TestLogEvent.FAILED, TestLogEvent.SKIPPED]
exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
}
jar {
from '../LICENSE.txt'
}
def determinePatchVersion = {
// get the name of the last tag
def tagInfo = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags'
standardOutput = tagInfo
}
tagInfo = tagInfo.toString()
if (!tagInfo.contains('-')) {
return 0
}
return tagInfo.split("-")[1]
}
project.ext.majorVersion = '5'
project.ext.minorVersion = '4'
project.ext.patchVersion = determinePatchVersion()
project.ext.apiVersion = project.ext.majorVersion + '.' + project.ext.minorVersion
project.ext.fullVersion = project.ext.apiVersion + '.' + project.ext.patchVersion
license {
header = rootProject.file('HEADER.txt')
include '**/*.java'
newLine = true
}
repositories {
// Fix issue with lwjgl-freetype not being found on macOS / ForgeGradle issue
//
// Could not resolve all files for configuration ':_compileJava_1'.
// Could not find lwjgl-freetype-3.3.3-natives-macos-patch.jar (org.lwjgl:lwjgl-freetype:3.3.3).
maven {
url "https://libraries.minecraft.net"
content {
includeModule("org.lwjgl", "lwjgl-freetype")
}
}
mavenCentral()
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://repo.lucko.me/' }
maven { url 'https://libraries.minecraft.net/' }
}
}
+348
View File
@@ -0,0 +1,348 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.kotlin.dsl.provideDelegate
/* ------------------------------ Plugins ------------------------------ */
plugins {
id("java") // Import Java plugin.
id("java-library") // Import Java Library plugin.
id("com.diffplug.spotless") version "7.0.4" // Import Spotless plugin.
id("com.gradleup.shadow") version "8.3.8" // Import Shadow plugin.
id("checkstyle") // Import Checkstyle plugin.
eclipse // Import Eclipse plugin.
kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin.
}
extra["kotlinAttribute"] = Attribute.of("kotlin-tag", Boolean::class.javaObjectType)
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
/* --------------------------- JDK / Kotlin ---------------------------- */
java {
sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility.
toolchain { // Select Java toolchain.
languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17.
vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE.
}
}
kotlin { jvmToolchain(17) }
/* ----------------------------- Metadata ------------------------------ */
group = "me.lucko.luckperms" // Declare bundle identifier.
version = "5.5.0-SNAPSHOT" // Declare plugin version (will be in .jar).
val apiVersion = "1.19" // Declare minecraft server target version.
/* ----------------------------- Resources ----------------------------- */
tasks.named<ProcessResources>("processResources") {
val props = mapOf("version" to version, "apiVersion" to apiVersion)
inputs.properties(props) // Indicates to rerun if version changes.
filesMatching("plugin.yml") { expand(props) }
from("LICENSE") { into("/") } // Bundle licenses into jarfiles.
}
/* ---------------------------- Repos ---------------------------------- */
repositories {
// Fix issue with lwjgl-freetype not being found on macOS / ForgeGradle issue
maven("https://libraries.minecraft.net") { content { includeModule("org.lwjgl", "lwjgl-freetype") } }
mavenCentral()
maven { url = uri("https://repo.lucko.me/") }
maven { url = uri("https://libraries.minecraft.net/") }
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let {
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
maven { url = uri("file://${dir.absolutePath}") }
} else {
mavenLocal()
}
}
}
/* ---------------------- Java project deps ---------------------------- */
dependencies {
// Root project carries no direct compile deps; subprojects declare their own.
}
/* ---------------------- Reproducible jars ---------------------------- */
tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .jars
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
/* ----------------------------- Shadow -------------------------------- */
tasks.shadowJar {
archiveClassifier.set("") // Use empty string instead of null.
minimize()
}
tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only.
tasks.build { dependsOn(tasks.spotlessApply) } // Build depends on spotless and shadow.
/* --------------------------- Javac opts ------------------------------- */
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-parameters") // Enable reflection for java code.
options.isFork = true // Run javac in its own process.
options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages.
options.encoding = "UTF-8" // Use UTF-8 file encoding.
}
/* ----------------------------- Auto Formatting ------------------------ */
spotless {
java {
eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting.
leadingTabsToSpaces() // Convert leftover leading tabs to spaces.
removeUnusedImports() // Remove imports that aren't being called.
}
kotlinGradle {
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting.
target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format.
}
}
checkstyle {
toolVersion = "10.18.1" // Declare checkstyle version to use.
configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file.
isIgnoreFailures = true // Don't fail the build if checkstyle does not pass.
isShowViolations = true // Show the violations in any IDE with the checkstyle plugin.
}
tasks.named("compileJava") {
dependsOn("spotlessApply") // Run spotless before compiling with the JDK.
}
tasks.named("spotlessCheck") {
dependsOn("spotlessApply") // Run spotless before checking if spotless ran.
}
/* --------------------------- LuckPerms modules ------------------------ */
subprojects {
apply(plugin = "java-library")
apply(plugin = "eclipse")
group = "me.lucko.luckperms"
version = rootProject.version
repositories {
maven("https://libraries.minecraft.net") { content { includeModule("org.lwjgl", "lwjgl-freetype") } }
mavenCentral()
maven { url = uri("https://repo.lucko.me/") }
maven { url = uri("https://libraries.minecraft.net/") }
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let {
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
maven { url = uri("file://${dir.absolutePath}") }
} else {
mavenLocal()
}
}
}
tasks.withType<Test>().configureEach {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
useJUnitPlatform()
}
tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" }
tasks.withType<Jar>().configureEach { from(rootProject.file("LICENSE.txt")) }
}
/* :api */
project(":api") {
dependencies {
compileOnly("org.checkerframework:checker-qual:3.49.3")
compileOnly("org.jetbrains:annotations:26.0.2")
}
tasks.jar { manifest { attributes(mapOf("Automatic-Module-Name" to "net.luckperms.api")) } }
}
/* :common */
project(":common") {
apply(plugin = "jacoco")
val patchedDir = layout.buildDirectory.dir("patchedSrc/commonMain/java")
val patchCommonForJ17 =
tasks.register<Copy>("patchCommonForJ17") {
from("src/main/java")
into(patchedDir)
filteringCharset = "UTF-8"
filesMatching("**/*.java") {
filter { line ->
if (
line.contains(".reversed()") &&
!line.contains("Comparator") &&
!line.contains("comparing") &&
!line.contains("thenComparing") &&
!line.contains("INSTANCE") &&
!line.contains("normal()")
) {
line.replace(
".reversed()",
".stream().collect(java.util.stream.Collectors.collectingAndThen(" +
"java.util.stream.Collectors.toList(), l -> { java.util.Collections.reverse(l); return l; }))",
)
} else line
}
}
}
sourceSets.named("main") {
java.setSrcDirs(emptyList<Any>())
java.srcDir(patchedDir)
}
tasks.named<JavaCompile>("compileJava") { dependsOn(patchCommonForJ17) }
dependencies {
testImplementation("org.slf4j:slf4j-simple:1.7.36")
testImplementation("org.junit.jupiter:junit-jupiter:5.13.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("org.testcontainers:junit-jupiter:1.21.1")
testImplementation("org.mockito:mockito-core:5.18.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.18.0")
testImplementation("com.h2database:h2:2.1.214")
testImplementation("org.mongodb:mongodb-driver-legacy:5.5.0")
testImplementation("org.spongepowered:configurate-yaml:3.7.3")
testImplementation("org.spongepowered:configurate-hocon:3.7.3")
testImplementation("me.lucko.configurate:configurate-toml:3.7")
testImplementation("net.luckperms:rest-api-java-client:0.1")
api(project(":api"))
api("org.checkerframework:checker-qual:3.12.0")
compileOnly(project(":common:loader-utils"))
compileOnly("com.mojang:brigadier:1.0.18")
compileOnly("org.slf4j:slf4j-api:1.7.30")
compileOnly("org.apache.logging.log4j:log4j-api:2.14.0")
api("net.kyori:adventure-api:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "checker-qual")
exclude(module = "annotations")
}
api("net.kyori:adventure-text-serializer-gson:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
exclude(module = "gson")
}
api("net.kyori:adventure-text-serializer-legacy:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:adventure-text-serializer-plain:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:adventure-text-minimessage:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:event-api:3.0.0") {
exclude(module = "checker-qual")
exclude(module = "guava")
}
api("com.google.code.gson:gson:2.7")
api("com.google.guava:guava:19.0")
api("com.github.ben-manes.caffeine:caffeine:3.2.0")
api("com.squareup.okhttp3:okhttp:3.14.9")
api("com.squareup.okio:okio:1.17.6")
api("net.bytebuddy:byte-buddy:1.15.11")
api("org.spongepowered:configurate-core:3.7.3") { isTransitive = false }
api("org.spongepowered:configurate-yaml:3.7.3") { isTransitive = false }
api("org.spongepowered:configurate-gson:3.7.3") { isTransitive = false }
api("org.spongepowered:configurate-hocon:3.7.3") { isTransitive = false }
api("me.lucko.configurate:configurate-toml:3.7") { isTransitive = false }
compileOnly("com.zaxxer:HikariCP:6.3.0")
compileOnly("redis.clients:jedis:5.2.0")
compileOnly("io.nats:jnats:2.21.1")
compileOnly("com.rabbitmq:amqp-client:5.25.0")
compileOnly("org.mongodb:mongodb-driver-legacy:5.5.0")
compileOnly("org.postgresql:postgresql:42.7.6")
compileOnly("org.yaml:snakeyaml:1.33")
compileOnly("net.luckperms:rest-api-java-client:0.1")
}
}
/* :common:loader-utils */
project(":common:loader-utils") {
// nothing special
}
/* :bukkit */
project(":bukkit") {
apply(plugin = "com.gradleup.shadow")
dependencies {
implementation(project(":common"))
compileOnly(project(":common:loader-utils"))
compileOnly("com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT")
compileOnly("net.kyori:adventure-platform-bukkit:4.4.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
exclude(module = "adventure-nbt")
}
compileOnly("me.lucko:commodore:2.0")
compileOnly("net.milkbowl.vault:VaultAPI:1.7") { exclude(module = "bukkit") }
compileOnly("lilypad.client.connect:api:0.0.1-SNAPSHOT")
}
tasks.named<ShadowJar>("shadowJar") {
archiveFileName.set("luckperms-bukkit.jarinjar")
dependencies { include(dependency("me.lucko.luckperms:.*")) }
relocate("net.kyori.adventure", "me.lucko.luckperms.lib.adventure")
relocate("net.kyori.event", "me.lucko.luckperms.lib.eventbus")
relocate("com.github.benmanes.caffeine", "me.lucko.luckperms.lib.caffeine")
relocate("okio", "me.lucko.luckperms.lib.okio")
relocate("okhttp3", "me.lucko.luckperms.lib.okhttp3")
relocate("net.bytebuddy", "me.lucko.luckperms.lib.bytebuddy")
relocate("me.lucko.commodore", "me.lucko.luckperms.lib.commodore")
relocate("org.mariadb.jdbc", "me.lucko.luckperms.lib.mariadb")
relocate("com.mysql", "me.lucko.luckperms.lib.mysql")
relocate("org.postgresql", "me.lucko.luckperms.lib.postgresql")
relocate("com.zaxxer.hikari", "me.lucko.luckperms.lib.hikari")
relocate("com.mongodb", "me.lucko.luckperms.lib.mongodb")
relocate("org.bson", "me.lucko.luckperms.lib.bson")
relocate("redis.clients.jedis", "me.lucko.luckperms.lib.jedis")
relocate("io.nats.client", "me.lucko.luckperms.lib.nats")
relocate("com.rabbitmq", "me.lucko.luckperms.lib.rabbitmq")
relocate("org.apache.commons.pool2", "me.lucko.luckperms.lib.commonspool2")
relocate("ninja.leaping.configurate", "me.lucko.luckperms.lib.configurate")
}
}
/* :bukkit:loader */
project(":bukkit:loader") {
apply(plugin = "com.gradleup.shadow")
dependencies {
compileOnly("com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT")
implementation(project(":api"))
implementation(project(":common:loader-utils"))
}
tasks.processResources {
filesMatching("plugin.yml") { expand(mapOf("pluginVersion" to rootProject.version.toString())) }
}
tasks.named<ShadowJar>("shadowJar") {
archiveFileName.set("LuckPerms-${rootProject.version}.jar")
from(project(":bukkit").tasks.named<ShadowJar>("shadowJar").flatMap { it.archiveFile })
manifest { attributes(mapOf("paperweight-mappings-namespace" to "mojang")) }
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
}
}
/* Build root produces the server jar */
tasks.named("build") { dependsOn(project(":bukkit:loader").tasks.named("shadowJar")) }
-45
View File
@@ -1,45 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
dependencies {
implementation project(':bukkit')
implementation 'com.google.code.gson:gson:2.7'
implementation 'com.google.guava:guava:19.0'
}
shadowJar {
archiveFileName = 'luckperms-bukkitlegacy.jarinjar'
dependencies {
include(dependency('me.lucko.luckperms:.*'))
include(dependency('com.google.guava:guava:.*'))
include(dependency('com.google.code.gson:gson:.*'))
}
relocate 'com.google.common', 'me.lucko.luckperms.lib.guava'
relocate 'com.google.gson', 'me.lucko.luckperms.lib.gson'
relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure'
relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus'
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql'
relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari'
relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb'
relocate 'org.bson', 'me.lucko.luckperms.lib.bson'
relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis'
relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats'
relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq'
relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2'
relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate'
}
artifacts {
archives shadowJar
}
-32
View File
@@ -1,32 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven { url 'https://repo.papermc.io/repository/maven-public/' }
}
dependencies {
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
implementation project(':api')
implementation project(':common:loader-utils')
}
processResources {
filesMatching('plugin.yml') {
expand 'pluginVersion': project.ext.fullVersion
}
}
shadowJar {
archiveFileName = "LuckPerms-Bukkit-Legacy-${project.ext.fullVersion}.jar"
from {
project(':bukkit-legacy').tasks.shadowJar.archiveFile
}
}
artifacts {
archives shadowJar
}
-55
View File
@@ -1,55 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven { url 'https://repo.papermc.io/repository/maven-public/' }
}
dependencies {
implementation project(':common')
compileOnly project(':common:loader-utils')
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
compileOnly('net.kyori:adventure-platform-bukkit:4.3.3') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
}
compileOnly 'me.lucko:commodore:2.0'
compileOnly('net.milkbowl.vault:VaultAPI:1.7') {
exclude(module: 'bukkit')
}
compileOnly 'lilypad.client.connect:api:0.0.1-SNAPSHOT'
}
shadowJar {
archiveFileName = 'luckperms-bukkit.jarinjar'
dependencies {
include(dependency('me.lucko.luckperms:.*'))
}
relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure'
relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus'
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql'
relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari'
relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb'
relocate 'org.bson', 'me.lucko.luckperms.lib.bson'
relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis'
relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats'
relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq'
relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2'
relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate'
}
artifacts {
archives shadowJar
}
-36
View File
@@ -1,36 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven { url 'https://repo.papermc.io/repository/maven-public/' }
}
dependencies {
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
implementation project(':api')
implementation project(':common:loader-utils')
}
processResources {
filesMatching('plugin.yml') {
expand 'pluginVersion': project.ext.fullVersion
}
}
shadowJar {
archiveFileName = "LuckPerms-Bukkit-${project.ext.fullVersion}.jar"
from {
project(':bukkit').tasks.shadowJar.archiveFile
}
manifest {
attributes(["paperweight-mappings-namespace": "mojang"])
}
}
artifacts {
archives shadowJar
}
@@ -307,7 +307,7 @@ public class LPBukkitBootstrap implements LuckPermsBootstrap, LoaderBootstrap, B
try {
Class.forName("com.google.gson.JsonElement");
return false;
} catch (ClassNotFoundException e) {
} catch (Exception e) {
return true;
}
}
@@ -221,10 +221,12 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
* - https://hub.spigotmc.org/jira/browse/SPIGOT-5546
* - https://github.com/PaperMC/Paper/pull/3509
*/
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getLoader().getName(), "Vault");
if (getConfiguration().get(ConfigKeys.VAULT_ENABLED)) {
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getLoader().getName(), "Vault");
// Provide vault support
tryVaultHook(false);
// Provide vault support
tryVaultHook(false);
}
}
@Override
@@ -237,6 +239,10 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
return; // already hooked
}
if (!getConfiguration().get(ConfigKeys.VAULT_ENABLED)) {
return; // disabled in config
}
try {
if (force || this.bootstrap.getServer().getPluginManager().isPluginEnabled("Vault")) {
this.vaultHookManager = new VaultHookManager(this);
@@ -347,7 +353,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
} catch (Exception e) {
return false;
}
}
@@ -25,85 +25,46 @@
package me.lucko.luckperms.bukkit.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BukkitContextManager extends ContextManager<Player, Player> {
public class BukkitContextManager extends DetachedContextManager<Player, Player> {
public static final OptionKey<Boolean> OP_OPTION = OptionKey.of("op", Boolean.class);
// cache the creation of ContextsCache instances for online players with no expiry
private final LoadingMap<Player, QueryOptionsCache<Player>> onlineSubjectCaches = LoadingMap.of(key -> new QueryOptionsCache<>(key, this));
// cache the creation of ContextsCache instances for offline players with a 1m expiry
private final LoadingCache<Player, QueryOptionsCache<Player>> offlineSubjectCaches = CaffeineFactory.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build(key -> {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(key);
if (cache != null) {
return cache;
}
return new QueryOptionsCache<>(key, this);
});
public BukkitContextManager(LPBukkitPlugin plugin) {
super(plugin, Player.class, Player.class);
}
public void onPlayerQuit(Player player) {
this.onlineSubjectCaches.remove(player);
}
@Override
public UUID getUniqueId(Player player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<Player> getCacheFor(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
if (subject.isOnline()) {
return this.onlineSubjectCaches.get(subject);
} else {
return this.offlineSubjectCaches.get(subject);
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(Player subject) {
Objects.requireNonNull(subject, "subject");
LuckPermsPermissible permissible = PermissibleInjector.get(subject);
if (permissible != null) {
return permissible.getQueryOptionsSupplier();
}
return null;
}
@Override
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
cache = this.offlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
}
@Override
public QueryOptions formQueryOptions(Player subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
public void customizeQueryOptions(Player subject, QueryOptions.Builder builder) {
if (subject.isOp()) {
queryOptions.option(OP_OPTION, true);
builder.option(OP_OPTION, true);
}
return queryOptions.context(contextSet).build();
}
}
@@ -32,7 +32,7 @@ import me.lucko.luckperms.bukkit.calculator.OpProcessor;
import me.lucko.luckperms.bukkit.calculator.PermissionMapProcessor;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import net.luckperms.api.query.QueryOptions;
@@ -68,7 +68,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation.
*
* This class is **thread safe**. This means that when LuckPerms is installed on the server,
* is is safe to call Player#hasPermission asynchronously.
* it is safe to call Player#hasPermission asynchronously.
*/
public class LuckPermsPermissible extends PermissibleBase {
@@ -93,7 +93,7 @@ public class LuckPermsPermissible extends PermissibleBase {
private final LPBukkitPlugin plugin;
// caches context lookups for the player
private final QueryOptionsCache<Player> queryOptionsSupplier;
private final QueryOptionsSupplier queryOptionsSupplier;
// the players previous permissible. (the one they had before this one was injected)
private PermissibleBase oldPermissible = null;
@@ -110,7 +110,7 @@ public class LuckPermsPermissible extends PermissibleBase {
this.user = Objects.requireNonNull(user, "user");
this.player = Objects.requireNonNull(player, "player");
this.plugin = Objects.requireNonNull(plugin, "plugin");
this.queryOptionsSupplier = plugin.getContextManager().getCacheFor(player);
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
injectFakeAttachmentsList();
}
@@ -293,7 +293,7 @@ public class LuckPermsPermissible extends PermissibleBase {
// the query options cache when op status changes.
// (#invalidate is a fast call)
if (this.queryOptionsSupplier != null) { // this method is called by the super class constructor, before this class has fully initialised
this.queryOptionsSupplier.invalidate();
this.queryOptionsSupplier.invalidateCache();
}
// but we don't need to do anything else in this method, unlike the CB impl.
@@ -316,6 +316,10 @@ public class LuckPermsPermissible extends PermissibleBase {
return this.plugin;
}
public QueryOptionsSupplier getQueryOptionsSupplier() {
return this.queryOptionsSupplier;
}
PermissibleBase getOldPermissible() {
return this.oldPermissible;
}
@@ -409,9 +413,7 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override public PermissionAttachment remove(int index) { throw new UnsupportedOperationException(); }
@Override public int indexOf(Object o) { throw new UnsupportedOperationException(); }
@Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); }
@Override
public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override
public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
@Override public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
}
}
@@ -30,6 +30,7 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.List;
@@ -161,6 +162,19 @@ public final class PermissibleInjector {
}
}
public static @Nullable LuckPermsPermissible get(Player player) {
PermissibleBase permissibleBase;
try {
permissibleBase = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
} catch (IllegalAccessException e) {
return null;
}
if (permissibleBase instanceof LuckPermsPermissible) {
return (LuckPermsPermissible) permissibleBase;
}
return null;
}
public static void checkInjected(Player player, PluginLogger logger) {
PermissibleBase permissibleBase;
try {
@@ -253,9 +253,6 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1L);
}
@@ -36,13 +36,26 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.server.RemoteServerCommandEvent;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import java.util.Locale;
import java.util.regex.Pattern;
public class BukkitPlatformListener implements Listener {
private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(\\w+:)?(deop|op)( .*)?$", Pattern.CASE_INSENSITIVE);
private static final boolean PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED;
static {
boolean supported = false;
try {
PluginDescriptionFile.class.getMethod("getProvides");
supported = true;
} catch (NoSuchMethodException ignored) {
}
PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED = supported;
}
private final LPBukkitPlugin plugin;
public BukkitPlatformListener(LPBukkitPlugin plugin) {
@@ -81,7 +94,15 @@ public class BukkitPlatformListener implements Listener {
@EventHandler
public void onPluginEnable(PluginEnableEvent e) {
if (e.getPlugin().getName().equalsIgnoreCase("Vault")) {
if (!this.plugin.getConfiguration().get(ConfigKeys.VAULT_ENABLED)) {
return;
}
Plugin p = e.getPlugin();
boolean shouldHook = p.getName().equalsIgnoreCase("Vault") ||
(PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED && p.getDescription().getProvides().contains("Vault"));
if (shouldHook) {
this.plugin.tryVaultHook(true);
}
}
+59 -18
View File
@@ -41,7 +41,7 @@ server: global
# - To get around this, you can use a player's uuid directly in the command, or enable this option.
# - When this is set to 'true', the server facility is used. This may use a number of methods,
# including checking the servers local cache, or making a request to the Mojang API.
use-server-uuid-cache: false
use-server-uuid-cache: true
@@ -83,7 +83,7 @@ use-server-uuid-cache: false
#
# - A H2 database is the default option.
# - If you want to edit data manually in "traditional" storage files, we suggest using YAML.
storage-method: h2
storage-method: postgresql
# The following block defines the settings for remote database storage methods.
#
@@ -99,11 +99,11 @@ data:
# The name of the database to store LuckPerms data in.
# - This must be created already. Don't worry about this setting if you're using MongoDB.
database: minecraft
database: luckperms
# Credentials for the database.
username: root
password: ''
username: '^{POSTGRES_USER}'
password: '^{POSTGRES_PASSWORD}'
# These settings apply to the MySQL connection pool.
# - The default values will be suitable for the majority of users.
@@ -132,11 +132,11 @@ data:
# from being timed out by the database or network infrastructure, measured in milliseconds.
# - The value should be less than maximum-lifetime and greater than 30000 (30 seconds).
# - Setting the value to zero will disable the keepalive functionality.
keepalive-time: 0
keepalive-time: 120000
# This setting controls the maximum number of milliseconds that the plugin will wait for a
# connection from the pool, before timing out.
connection-timeout: 5000 # 5 seconds
connection-timeout: 10000 # 10 seconds
# This setting allows you to define extra properties for connections.
#
@@ -216,7 +216,7 @@ sync-minutes: -1
# - When using a file-based storage type, LuckPerms can monitor the data files for changes, and
# automatically update when changes are detected.
# - If you don't want this feature to be active, set this option to false.
watch-files: true
watch-files: false
# Define which messaging service should be used by the plugin.
#
@@ -258,16 +258,16 @@ push-log-entries: true
# - 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
broadcast-received-log-entries: false
# Settings for Redis.
# Settings for Keydb.
# Port 6379 is used by default; set address to "host:port" if differs
# Multiple Redis nodes can be specified in the same format as a string list under the name "addresses".
# Multiple Keydb nodes can be specified in the same format as a string list under the name "addresses".
redis:
enabled: false
enabled: true
address: localhost
username: ''
password: ''
username: 'default'
password: 'bookchin'
# Settings for Nats.
# Port 4222 is used by default; set address to "host:port" if differs
@@ -335,7 +335,7 @@ argument-based-command-permissions: false
# If the plugin should check whether senders are a member of a given group before they're able to
# edit the groups data or add/remove other users to/from it.
# Note: these limitations do not apply to the web editor!
require-sender-group-membership-to-modify: false
require-sender-group-membership-to-modify: true
# If the plugin should send log notifications to users whenever permissions are modified.
#
@@ -408,14 +408,14 @@ meta-formatting:
duplicates: first-only
start-spacer: ""
middle-spacer: " "
end-spacer: ""
end-spacer: " "
suffix:
format:
- "highest"
duplicates: first-only
start-spacer: ""
start-spacer: " "
middle-spacer: " "
end-spacer: ""
end-spacer: " "
@@ -627,8 +627,19 @@ commands-allow-op: true
# +----------------------------------------------------------------------------------------------+ #
# | Vault integration settings | #
# | | #
# | Disabled by default (vault-enabled: false). The remaining vault-* settings below only take | #
# | effect when vault-enabled is set to true. | #
# +----------------------------------------------------------------------------------------------+ #
# If the Vault API bridge should be registered.
#
# - When false (default), LuckPerms will not hook into Vault at all. Vault-dependent plugins will
# fall back to SuperPerms and will not see LuckPerms groups, prefixes, or meta.
# - When true, LuckPerms will register Vault permission and chat providers if the Vault plugin is
# installed. Required for any plugin that reads groups/prefix/suffix via the Vault API.
vault-enabled: false
# If Vault lookups for offline players on the main server thread should be enabled.
#
# LuckPerms has a "catch" for plugins attempting to perform unsafe offline player data lookups
@@ -719,3 +730,33 @@ register-command-list-data: true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.wiki/w/Target_selectors
resolve-command-selectors: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: true
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: true
console: false
-47
View File
@@ -1,47 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
dependencies {
implementation project(':common')
compileOnly project(':common:loader-utils')
compileOnly 'net.md-5:bungeecord-api:1.16-R0.4'
compileOnly('net.kyori:adventure-platform-bungeecord:4.3.3') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
}
compileOnly 'com.imaginarycode.minecraft:RedisBungee:0.4'
}
shadowJar {
archiveFileName = 'luckperms-bungee.jarinjar'
dependencies {
include(dependency('me.lucko.luckperms:.*'))
}
relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure'
relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus'
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql'
relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari'
relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb'
relocate 'org.bson', 'me.lucko.luckperms.lib.bson'
relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis'
relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats'
relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq'
relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2'
relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate'
}
artifacts {
archives shadowJar
}
-28
View File
@@ -1,28 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
dependencies {
compileOnly 'net.md-5:bungeecord-api:1.15-SNAPSHOT'
implementation project(':api')
implementation project(':common:loader-utils')
}
processResources {
filesMatching('plugin.yml') {
expand 'pluginVersion': project.ext.fullVersion
}
}
shadowJar {
archiveFileName = "LuckPerms-Bungee-${project.ext.fullVersion}.jar"
from {
project(':bungee').tasks.shadowJar.archiveFile
}
}
artifacts {
archives shadowJar
}
@@ -302,7 +302,7 @@ public class LPBungeeBootstrap implements LuckPermsBootstrap, LoaderBootstrap, B
try {
Class.forName("com.google.gson.internal.bind.TreeTypeAdapter");
return false;
} catch (ClassNotFoundException e) {
} catch (Exception e) {
return true;
}
}
@@ -25,25 +25,13 @@
package me.lucko.luckperms.bungee.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.InlineQueryOptionsSupplier;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryOptions;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedPlayer> {
private final LoadingCache<ProxiedPlayer, QueryOptions> contextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
public class BungeeContextManager extends SimpleContextManager<ProxiedPlayer, ProxiedPlayer> {
public BungeeContextManager(LPBungeePlugin plugin) {
super(plugin, ProxiedPlayer.class, ProxiedPlayer.class);
}
@@ -52,35 +40,4 @@ public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedP
public UUID getUniqueId(ProxiedPlayer player) {
return player.getUniqueId();
}
@Override
public QueryOptionsSupplier getCacheFor(ProxiedPlayer subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return new InlineQueryOptionsSupplier<>(subject, this.contextsCache);
}
// override getContext, getQueryOptions and invalidateCache to skip the QueryOptionsSupplier
@Override
public ImmutableContextSet getContext(ProxiedPlayer subject) {
return getQueryOptions(subject).context();
}
@Override
public QueryOptions getQueryOptions(ProxiedPlayer subject) {
return this.contextsCache.get(subject);
}
@Override
protected void invalidateCache(ProxiedPlayer subject) {
this.contextsCache.invalidate(subject);
}
@Override
public QueryOptions formQueryOptions(ProxiedPlayer subject, ImmutableContextSet contextSet) {
return formQueryOptions(contextSet);
}
}
+30
View File
@@ -611,3 +611,33 @@ disable-bulkupdate: false
#
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: false
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: false
console: false
-93
View File
@@ -1,93 +0,0 @@
plugins {
id("java-library")
id("jacoco")
}
test {
useJUnitPlatform {}
}
jacocoTestReport {
dependsOn test
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.1'
testImplementation 'org.mockito:mockito-core:5.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
testImplementation 'com.h2database:h2:2.1.214'
api project(':api')
api 'org.checkerframework:checker-qual:3.12.0'
compileOnly project(':common:loader-utils')
compileOnly 'com.mojang:brigadier:1.0.18'
compileOnly 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.apache.logging.log4j:log4j-api:2.14.0'
api('net.kyori:adventure-api:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'checker-qual')
exclude(module: 'annotations')
}
api('net.kyori:adventure-text-serializer-gson:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'gson')
}
api('net.kyori:adventure-text-serializer-legacy:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:adventure-text-serializer-plain:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api("net.kyori:adventure-text-minimessage:4.11.0") {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:event-api:3.0.0') {
exclude(module: 'checker-qual')
exclude(module: 'guava')
}
api 'com.google.code.gson:gson:2.7'
api 'com.google.guava:guava:19.0'
api 'com.github.ben-manes.caffeine:caffeine:2.9.0'
api 'com.squareup.okhttp3:okhttp:3.14.9'
api 'com.squareup.okio:okio:1.17.5'
api 'net.bytebuddy:byte-buddy:1.10.22'
api('org.spongepowered:configurate-core:3.7.2') {
transitive = false
}
api('org.spongepowered:configurate-yaml:3.7.2') {
transitive = false
}
api('org.spongepowered:configurate-gson:3.7.2') {
transitive = false
}
api('org.spongepowered:configurate-hocon:3.7.2') {
transitive = false
}
api('me.lucko.configurate:configurate-toml:3.7') {
transitive = false
}
compileOnly 'com.zaxxer:HikariCP:4.0.3'
compileOnly 'redis.clients:jedis:4.4.3'
compileOnly 'io.nats:jnats:2.16.4'
compileOnly 'com.rabbitmq:amqp-client:5.12.0'
compileOnly 'org.mongodb:mongodb-driver-legacy:4.5.0'
compileOnly 'org.postgresql:postgresql:42.6.0'
compileOnly 'org.yaml:snakeyaml:1.28'
}
-1
View File
@@ -1 +0,0 @@
// nothing special to do here (yet)!
@@ -1,108 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import net.luckperms.api.actionlog.Action;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.UUID;
public class Log {
private static final Log EMPTY = new Log(ImmutableList.of());
public static Builder builder() {
return new Builder();
}
public static Log empty() {
return EMPTY;
}
private final SortedSet<LoggedAction> content;
Log(List<LoggedAction> content) {
this.content = ImmutableSortedSet.copyOf(content);
}
public SortedSet<LoggedAction> getContent() {
return this.content;
}
public SortedSet<LoggedAction> getContent(UUID actor) {
return this.content.stream()
.filter(e -> e.getSource().getUniqueId().equals(actor))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getUserHistory(UUID uniqueId) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.USER)
.filter(e -> e.getTarget().getUniqueId().isPresent() && e.getTarget().getUniqueId().get().equals(uniqueId))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getGroupHistory(String name) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.GROUP)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getTrackHistory(String name) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.TRACK)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getSearch(String query) {
return this.content.stream()
.filter(e -> e.matchesSearch(query))
.collect(ImmutableCollectors.toSortedSet());
}
public static class Builder {
private final List<LoggedAction> content = new ArrayList<>();
public Builder add(LoggedAction e) {
this.content.add(e);
return this;
}
public Log build() {
if (this.content.isEmpty()) {
return EMPTY;
}
return new Log(this.content);
}
}
}
@@ -29,12 +29,14 @@ import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.commands.log.LogNotify;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.luckperms.api.event.log.LogBroadcastEvent;
import net.luckperms.api.event.log.LogNotifyEvent;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
public class LogDispatcher {
@@ -64,7 +66,12 @@ public class LogDispatcher {
return !this.plugin.getEventDispatcher().dispatchLogBroadcast(cancelled, entry, origin);
}
private void broadcast(LoggedAction entry, LogNotifyEvent.Origin origin, Sender sender) {
// broadcast the entry to online players
private void broadcast(LoggedAction entry, LogBroadcastEvent.Origin broadcastOrigin, LogNotifyEvent.Origin origin, Sender sender) {
if (!shouldBroadcast(entry, broadcastOrigin)) {
return;
}
this.plugin.getOnlineSenders()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
.filter(s -> {
@@ -74,41 +81,46 @@ public class LogDispatcher {
.forEach(s -> Message.LOG.send(s, entry));
}
public void dispatch(LoggedAction entry, Sender sender) {
// log the entry to storage
public CompletableFuture<Void> logToStorage(LoggedAction entry) {
if (!this.plugin.getEventDispatcher().dispatchLogPublish(false, entry)) {
this.plugin.getStorage().logAction(entry);
return this.plugin.getStorage().logAction(entry);
} else {
return CompletableFuture.completedFuture(null);
}
}
this.plugin.getMessagingService().ifPresent(service -> service.pushLog(entry));
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL, sender);
// log the entry to messaging
public CompletableFuture<Void> logToMessaging(LoggedAction entry) {
InternalMessagingService messagingService = this.plugin.getMessagingService().orElse(null);
if (messagingService != null) {
return messagingService.pushLog(entry);
} else {
return CompletableFuture.completedFuture(null);
}
}
// log the entry to storage and messaging, and broadcast it to online players
private CompletableFuture<Void> dispatch(LoggedAction entry, Sender sender, LogBroadcastEvent.Origin broadcastOrigin, LogNotifyEvent.Origin origin) {
CompletableFuture<Void> storageFuture = logToStorage(entry);
CompletableFuture<Void> messagingFuture = logToMessaging(entry);
broadcast(entry, broadcastOrigin, origin, sender);
return CompletableFuture.allOf(storageFuture, messagingFuture);
}
public CompletableFuture<Void> dispatch(LoggedAction entry, Sender sender) {
return dispatch(entry, sender, LogBroadcastEvent.Origin.LOCAL, LogNotifyEvent.Origin.LOCAL);
}
public CompletableFuture<Void> dispatchFromApi(LoggedAction entry) {
return dispatch(entry, null, LogBroadcastEvent.Origin.LOCAL_API, LogNotifyEvent.Origin.LOCAL_API);
}
public void broadcastFromApi(LoggedAction entry) {
this.plugin.getMessagingService().ifPresent(extendedMessagingService -> extendedMessagingService.pushLog(entry));
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL_API)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL_API, null);
}
broadcast(entry, LogBroadcastEvent.Origin.LOCAL_API, LogNotifyEvent.Origin.LOCAL_API, null);
}
public void dispatchFromApi(LoggedAction entry) {
if (!this.plugin.getEventDispatcher().dispatchLogPublish(false, entry)) {
try {
this.plugin.getStorage().logAction(entry).get();
} catch (Exception e) {
this.plugin.getLogger().warn("Error whilst storing action", e);
}
}
broadcastFromApi(entry);
}
public void dispatchFromRemote(LoggedAction entry) {
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.REMOTE)) {
broadcast(entry, LogNotifyEvent.Origin.REMOTE, null);
}
public void broadcastFromRemote(LoggedAction entry) {
broadcast(entry, LogBroadcastEvent.Origin.REMOTE, LogNotifyEvent.Origin.REMOTE, null);
}
}
@@ -23,54 +23,50 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.util;
package me.lucko.luckperms.common.actionlog;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.filter.PageParameters;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* A simple pagination utility
*
* @param <T> the element type
*/
public class Paginated<T> {
private final List<T> content;
public Paginated(Collection<T> content) {
this.content = ImmutableList.copyOf(content);
public class LogPage {
public static LogPage of(List<LoggedAction> content, @Nullable PageParameters params, int totalEntries) {
return new LogPage(content, params, totalEntries);
}
public List<T> getContent() {
private final List<LoggedAction> content;
private final @Nullable PageParameters params;
private final int totalEntries;
LogPage(List<LoggedAction> content, @Nullable PageParameters params, int totalEntries) {
this.content = ImmutableList.copyOf(content);
this.params = params;
this.totalEntries = totalEntries;
}
public List<LoggedAction> getContent() {
return this.content;
}
public int getMaxPages(int entriesPerPage) {
return (int) Math.ceil((double) this.content.size() / (double) entriesPerPage);
public List<Entry<LoggedAction>> getNumberedContent() {
int startIndex = this.params != null
? this.params.pageSize() * (this.params.pageNumber() - 1)
: 0;
List<Entry<LoggedAction>> numberedContent = new ArrayList<>();
for (int i = 0; i < this.content.size(); i++) {
int index = startIndex + i + 1;
numberedContent.add(new Entry<>(index, this.content.get(i)));
}
return numberedContent;
}
public List<Entry<T>> getPage(int pageNo, int pageSize) {
if (pageNo < 1) {
throw new IllegalArgumentException("pageNo cannot be less than 1: " + pageNo);
}
int first = (pageNo - 1) * pageSize;
if (this.content.size() <= first) {
throw new IllegalStateException("Content does not contain that many elements. (requested page: " + pageNo +
", page size: " + pageSize + ", page first index: " + first + ", content size: " + this.content.size() + ")");
}
int last = first + pageSize - 1;
List<Entry<T>> out = new ArrayList<>(pageSize);
for (int i = first; i <= last && i < this.content.size(); i++) {
out.add(new Entry<>(i + 1, this.content.get(i)));
}
return out;
public int getTotalEntries() {
return this.totalEntries;
}
public static final class Entry<T> {
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.actionlog;
import com.google.common.base.Strings;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.PermissionHolder;
@@ -44,10 +45,10 @@ import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* An implementation of {@link Action} and {@link Action.Builder},
@@ -123,15 +124,11 @@ public class LoggedAction implements Action {
return ActionComparator.INSTANCE.compare(this, other);
}
public boolean matchesSearch(String query) {
query = Objects.requireNonNull(query, "query").toLowerCase(Locale.ROOT);
return this.source.name.toLowerCase(Locale.ROOT).contains(query) ||
this.target.name.toLowerCase(Locale.ROOT).contains(query) ||
this.description.toLowerCase(Locale.ROOT).contains(query);
}
public void submit(LuckPermsPlugin plugin, Sender sender) {
plugin.getLogDispatcher().dispatch(this, sender);
CompletableFuture<Void> future = plugin.getLogDispatcher().dispatch(this, sender);
if (plugin.getConfiguration().get(ConfigKeys.LOG_SYNCHRONOUSLY_IN_COMMANDS)) {
future.join();
}
}
@Override
@@ -393,14 +390,14 @@ public class LoggedAction implements Action {
}
}
public static char getTypeCharacter(Target.Type type) {
public static String getTypeString(Target.Type type) {
switch (type) {
case USER:
return 'U';
return "U";
case GROUP:
return 'G';
return "G";
case TRACK:
return 'T';
return "T";
default:
throw new AssertionError();
}
@@ -0,0 +1,60 @@
/*
* 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.filter;
import me.lucko.luckperms.common.filter.FilterField;
import net.luckperms.api.actionlog.Action;
import java.util.UUID;
public final class ActionFields {
public static final FilterField<Action, UUID> SOURCE_UNIQUE_ID = FilterField.named(
"SOURCE_UNIQUE_ID",
action -> action.getSource().getUniqueId()
);
public static final FilterField<Action, String> SOURCE_NAME = FilterField.named(
"SOURCE_NAME",
action -> action.getSource().getName()
);
public static final FilterField<Action, Action.Target.Type> TARGET_TYPE = FilterField.named(
"TARGET_TYPE",
action -> action.getTarget().getType()
);
public static final FilterField<Action, UUID> TARGET_UNIQUE_ID = FilterField.named(
"TARGET_UNIQUE_ID",
action -> action.getTarget().getUniqueId().orElse(null)
);
public static final FilterField<Action, String> TARGET_NAME = FilterField.named(
"TARGET_NAME",
action -> action.getTarget().getName()
);
public static final FilterField<Action, String> DESCRIPTION = FilterField.named(
"DESCRIPTION",
action -> action.getDescription()
);
}
@@ -0,0 +1,70 @@
/*
* 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.filter;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.mongo.FilterMongoBuilder;
import net.luckperms.api.actionlog.Action;
import java.util.UUID;
public final class ActionFilterMongoBuilder extends FilterMongoBuilder<Action> {
public static final ActionFilterMongoBuilder INSTANCE = new ActionFilterMongoBuilder();
private ActionFilterMongoBuilder() {
}
@Override
public String mapFieldName(FilterField<Action, ?> field) {
if (field == ActionFields.SOURCE_UNIQUE_ID) {
return "source.uniqueId";
} else if (field == ActionFields.SOURCE_NAME) {
return "source.name";
} else if (field == ActionFields.TARGET_TYPE) {
return "target.type";
} else if (field == ActionFields.TARGET_UNIQUE_ID) {
return "target.uniqueId";
} else if (field == ActionFields.TARGET_NAME) {
return "target.name";
} else if (field == ActionFields.DESCRIPTION) {
return "description";
}
throw new AssertionError(field);
}
@Override
public Object mapConstraintValue(Object value) {
if (value instanceof String | value instanceof UUID) {
return value;
} else if (value instanceof Action.Target.Type) {
return ((Action.Target.Type) value).name();
} else {
throw new IllegalArgumentException("Don't know how to map value with type: " + value.getClass().getName());
}
}
}
@@ -0,0 +1,69 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog.filter;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.sql.FilterSqlBuilder;
import net.luckperms.api.actionlog.Action;
import java.util.UUID;
public class ActionFilterSqlBuilder extends FilterSqlBuilder<Action> {
@Override
public void visitFieldName(FilterField<Action, ?> field) {
if (field == ActionFields.SOURCE_UNIQUE_ID) {
this.builder.append("actor_uuid");
} else if (field == ActionFields.SOURCE_NAME) {
this.builder.append("actor_name");
} else if (field == ActionFields.TARGET_TYPE) {
this.builder.append("type");
} else if (field == ActionFields.TARGET_UNIQUE_ID) {
this.builder.append("acted_uuid");
} else if (field == ActionFields.TARGET_NAME) {
this.builder.append("acted_name");
} else if (field == ActionFields.DESCRIPTION) {
this.builder.append("action");
} else {
throw new AssertionError(field);
}
}
@Override
public void visitConstraintValue(Object value) {
if (value instanceof String) {
this.builder.variable(((String) value));
} else if (value instanceof UUID) {
this.builder.variable(value.toString());
} else if (value instanceof Action.Target.Type) {
this.builder.variable(LoggedAction.getTypeString((Action.Target.Type) value));
} else {
throw new IllegalArgumentException("Don't know how to write value with type: " + value.getClass().getName());
}
}
}
@@ -0,0 +1,110 @@
/*
* 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.filter;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.ConstraintFactory;
import me.lucko.luckperms.common.filter.FilterList;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.Action.Target;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.regex.Pattern;
public final class ActionFilters {
private ActionFilters() {}
// all actions
public static FilterList<Action> all() {
return FilterList.empty();
}
// all actions performed by a given source (actor)
public static FilterList<Action> source(UUID uniqueId) {
return FilterList.and(
ActionFields.SOURCE_UNIQUE_ID.isEqualTo(uniqueId, ConstraintFactory.UUIDS)
);
}
// all actions affecting a given user
public static FilterList<Action> user(UUID uniqueId) {
return FilterList.and(
ActionFields.TARGET_TYPE.isEqualTo(Target.Type.USER, TARGET_TYPE_CONSTRAINT_FACTORY),
ActionFields.TARGET_UNIQUE_ID.isEqualTo(uniqueId, ConstraintFactory.UUIDS)
);
}
// all actions affecting a given group
public static FilterList<Action> group(String name) {
return FilterList.and(
ActionFields.TARGET_TYPE.isEqualTo(Target.Type.GROUP, TARGET_TYPE_CONSTRAINT_FACTORY),
ActionFields.TARGET_NAME.isEqualTo(name, ConstraintFactory.STRINGS)
);
}
// all actions affecting a given track
public static FilterList<Action> track(String name) {
return FilterList.and(
ActionFields.TARGET_TYPE.isEqualTo(Target.Type.TRACK, TARGET_TYPE_CONSTRAINT_FACTORY),
ActionFields.TARGET_NAME.isEqualTo(name, ConstraintFactory.STRINGS)
);
}
// all actions matching the given search query
public static FilterList<Action> search(String query) {
return FilterList.or(
ActionFields.SOURCE_NAME.isSimilarTo("%" + query + "%", ConstraintFactory.STRINGS),
ActionFields.TARGET_NAME.isSimilarTo("%" + query + "%", ConstraintFactory.STRINGS),
ActionFields.DESCRIPTION.isSimilarTo("%" + query + "%", ConstraintFactory.STRINGS)
);
}
private static final ConstraintFactory<Target.Type> TARGET_TYPE_CONSTRAINT_FACTORY = new ConstraintFactory<Target.Type>() {
@Override
public Predicate<Target.Type> equal(Target.Type value) {
return value::equals;
}
@Override
public Predicate<Target.Type> notEqual(Target.Type value) {
return string -> !value.equals(string);
}
@Override
public Predicate<Target.Type> similar(Target.Type value) {
Pattern pattern = Comparison.compilePatternForLikeSyntax(value.toString());
return type -> pattern.matcher(type.toString()).matches();
}
@Override
public Predicate<Target.Type> notSimilar(Target.Type value) {
Pattern pattern = Comparison.compilePatternForLikeSyntax(value.toString());
return type -> !pattern.matcher(type.toString()).matches();
}
};
}
@@ -25,6 +25,7 @@
package me.lucko.luckperms.common.api;
import me.lucko.luckperms.common.api.implementation.ApiActionFilterFactory;
import me.lucko.luckperms.common.api.implementation.ApiActionLogger;
import me.lucko.luckperms.common.api.implementation.ApiContextManager;
import me.lucko.luckperms.common.api.implementation.ApiGroupManager;
@@ -47,6 +48,7 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.messaging.MessagingService;
import net.luckperms.api.messenger.MessengerProvider;
@@ -225,4 +227,8 @@ public class LuckPermsApiProvider implements LuckPerms {
return ApiNodeMatcherFactory.INSTANCE;
}
@Override
public @NonNull ActionFilterFactory getActionFilterFactory() {
return ApiActionFilterFactory.INSTANCE;
}
}
@@ -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.api.implementation;
import me.lucko.luckperms.common.filter.FilterList;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.filter.ActionFilter;
public class ApiActionFilter implements ActionFilter {
private final FilterList<Action> filter;
public ApiActionFilter(FilterList<Action> filter) {
this.filter = filter;
}
@Override
public boolean test(Action action) {
return this.filter.evaluate(action);
}
public FilterList<Action> getFilter() {
return this.filter;
}
}
@@ -0,0 +1,77 @@
/*
* 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.api.implementation;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import java.util.Objects;
import java.util.UUID;
public final class ApiActionFilterFactory implements ActionFilterFactory {
public static final ApiActionFilterFactory INSTANCE = new ApiActionFilterFactory();
private ApiActionFilterFactory() {
}
@Override
public ActionFilter any() {
return new ApiActionFilter(ActionFilters.all());
}
@Override
public ActionFilter source(UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uniqueId");
return new ApiActionFilter(ActionFilters.source(uniqueId));
}
@Override
public ActionFilter user(UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uniqueId");
return new ApiActionFilter(ActionFilters.user(uniqueId));
}
@Override
public ActionFilter group(String name) {
Objects.requireNonNull(name, "name");
return new ApiActionFilter(ActionFilters.group(name));
}
@Override
public ActionFilter track(String name) {
Objects.requireNonNull(name, "name");
return new ApiActionFilter(ActionFilters.track(name));
}
@Override
public ActionFilter search(String query) {
Objects.requireNonNull(query, "query");
return new ApiActionFilter(ActionFilters.search(query));
}
}
@@ -25,50 +25,63 @@
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.api.ApiUtils;
import com.google.common.collect.ImmutableSortedSet;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.ActionLog;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Objects;
import java.util.SortedSet;
import java.util.UUID;
@SuppressWarnings({"unchecked", "rawtypes"})
@Deprecated
public class ApiActionLog implements ActionLog {
private final Log handle;
private final SortedSet<Action> content;
public ApiActionLog(Log handle) {
this.handle = handle;
public ApiActionLog(List<LoggedAction> content) {
this.content = ImmutableSortedSet.copyOf(content);
}
@Override
public @NonNull SortedSet<Action> getContent() {
return (SortedSet) this.handle.getContent();
return this.content;
}
@Override
public @NonNull SortedSet<Action> getContent(@NonNull UUID actor) {
Objects.requireNonNull(actor, "actor");
return (SortedSet) this.handle.getContent(actor);
return this.content.stream()
.filter(e -> e.getSource().getUniqueId().equals(actor))
.collect(ImmutableCollectors.toSortedSet());
}
@Override
public @NonNull SortedSet<Action> getUserHistory(@NonNull UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uuid");
return (SortedSet) this.handle.getUserHistory(uniqueId);
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.USER)
.filter(e -> e.getTarget().getUniqueId().isPresent() && e.getTarget().getUniqueId().get().equals(uniqueId))
.collect(ImmutableCollectors.toSortedSet());
}
@Override
public @NonNull SortedSet<Action> getGroupHistory(@NonNull String name) {
Objects.requireNonNull(name, "name");
return (SortedSet) this.handle.getGroupHistory(ApiUtils.checkName(name));
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.GROUP)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
@Override
public @NonNull SortedSet<Action> getTrackHistory(@NonNull String name) {
Objects.requireNonNull(name, "name");
return (SortedSet) this.handle.getTrackHistory(ApiUtils.checkName(name));
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.TRACK)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
}
@@ -25,13 +25,22 @@
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.actionlog.LogDispatcher;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.ActionLog;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.util.Page;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class ApiActionLogger implements ActionLogger {
@@ -47,22 +56,66 @@ public class ApiActionLogger implements ActionLogger {
}
@Override
@Deprecated
public @NonNull CompletableFuture<ActionLog> getLog() {
return this.plugin.getStorage().getLog().thenApply(ApiActionLog::new);
return this.plugin.getStorage().getLogPage(ActionFilters.all(), null)
.thenApply(result -> new ApiActionLog(result.getContent()));
}
@Override
public @NonNull CompletableFuture<List<Action>> queryActions(@NonNull ActionFilter filter) {
return this.plugin.getStorage().getLogPage(getFilterList(filter), null).thenApply(ActionPage::new).thenApply(Page::entries);
}
@Override
public @NonNull CompletableFuture<Page<Action>> queryActions(@NonNull ActionFilter filter, int pageSize, int pageNumber) {
return this.plugin.getStorage().getLogPage(getFilterList(filter), new PageParameters(pageSize, pageNumber)).thenApply(ActionPage::new);
}
@Override
public @NonNull CompletableFuture<Void> submit(@NonNull Action entry) {
return CompletableFuture.runAsync(() -> this.plugin.getLogDispatcher().dispatchFromApi((LoggedAction) entry), this.plugin.getBootstrap().getScheduler().async());
return this.plugin.getLogDispatcher().dispatchFromApi((LoggedAction) entry);
}
@Override
public @NonNull CompletableFuture<Void> submitToStorage(@NonNull Action entry) {
return this.plugin.getStorage().logAction(entry);
return this.plugin.getLogDispatcher().logToStorage((LoggedAction) entry);
}
@Override
public @NonNull CompletableFuture<Void> broadcastAction(@NonNull Action entry) {
return CompletableFuture.runAsync(() -> this.plugin.getLogDispatcher().broadcastFromApi((LoggedAction) entry), this.plugin.getBootstrap().getScheduler().async());
LogDispatcher dispatcher = this.plugin.getLogDispatcher();
CompletableFuture<Void> messagingFuture = dispatcher.logToStorage(((LoggedAction) entry));
dispatcher.broadcastFromApi(((LoggedAction) entry));
return messagingFuture;
}
private static FilterList<Action> getFilterList(ActionFilter filter) {
Objects.requireNonNull(filter, "filter");
if (filter instanceof ApiActionFilter) {
return ((ApiActionFilter) filter).getFilter();
} else {
throw new IllegalArgumentException("Unknown filter type: " + filter.getClass());
}
}
private static final class ActionPage implements Page<Action> {
private final LogPage page;
private ActionPage(LogPage page) {
this.page = page;
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public @NonNull List<Action> entries() {
return (List) this.page.getContent();
}
@Override
public int overallSize() {
return this.page.getTotalEntries();
}
}
}
@@ -26,8 +26,8 @@
package me.lucko.luckperms.common.api.implementation;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.metastacking.MetaStackDefinition;
@@ -25,14 +25,13 @@
package me.lucko.luckperms.common.bulkupdate;
import me.lucko.luckperms.common.bulkupdate.action.Action;
import me.lucko.luckperms.common.bulkupdate.query.Query;
import me.lucko.luckperms.common.bulkupdate.action.BulkUpdateAction;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.model.HolderType;
import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -46,35 +45,30 @@ public final class BulkUpdate {
private final DataType dataType;
// the action to apply to the data which matches the constraints
private final Action action;
private final BulkUpdateAction action;
// a set of constraints which data must match to be acted upon
private final List<Query> queries;
// a set of filters which data must match to be acted upon
private final FilterList<Node> filters;
// update statistics of the operation (number of nodes, users and groups affected)
private final BulkUpdateStatistics statistics = new BulkUpdateStatistics();
private final boolean trackStatistics;
public BulkUpdate(DataType dataType, Action action, List<Query> queries, boolean trackStatistics) {
public BulkUpdate(DataType dataType, BulkUpdateAction action, FilterList<Node> filters, boolean trackStatistics) {
this.dataType = dataType;
this.action = action;
this.queries = queries;
this.filters = filters;
this.trackStatistics = trackStatistics;
}
/**
* Check to see if a Node instance satisfies the constrints of this query
* Check to see if a Node instance satisfies the constraints of this query
*
* @param node the node to check
* @return true if satisfied
*/
public boolean satisfiesConstraints(Node node) {
for (Query query : this.queries) {
if (!query.isSatisfiedBy(node)) {
return false;
}
}
return true;
public boolean satisfiesFilters(Node node) {
return this.filters.evaluate(node);
}
/**
@@ -84,7 +78,7 @@ public final class BulkUpdate {
* @return the transformed node, or null if the node should be deleted
*/
private Node apply(Node node) {
if (!satisfiesConstraints(node)) {
if (!satisfiesFilters(node)) {
return node; // make no change
}
@@ -129,63 +123,16 @@ public final class BulkUpdate {
return results;
}
/**
* Converts this {@link BulkUpdate} to SQL syntax
*
* @return this query in SQL form
*/
public PreparedStatementBuilder buildAsSql() {
// DELETE FROM {table} WHERE ...
// UPDATE {table} SET ... WHERE ...
PreparedStatementBuilder builder = new PreparedStatementBuilder();
// add the action
// (DELETE FROM or UPDATE)
this.action.appendSql(builder);
return appendConstraintsAsSql(builder);
}
/**
* Appends the constraints of this {@link BulkUpdate} to the provided statement builder in SQL syntax
*
* @param builder the statement builder to append the constraints to
* @return the same statement builder provided as input
*/
public PreparedStatementBuilder appendConstraintsAsSql(PreparedStatementBuilder builder) {
// if there are no constraints, just return without a WHERE clause
if (this.queries.isEmpty()) {
return builder;
}
// append constraints
builder.append(" WHERE");
for (int i = 0; i < this.queries.size(); i++) {
Query query = this.queries.get(i);
builder.append(" ");
if (i != 0) {
builder.append("AND ");
}
query.appendSql(builder);
}
return builder;
}
public DataType getDataType() {
return this.dataType;
}
public Action getAction() {
public BulkUpdateAction getAction() {
return this.action;
}
public List<Query> getQueries() {
return this.queries;
public FilterList<Node> getFilters() {
return this.filters;
}
public boolean isTrackingStatistics() {
@@ -204,12 +151,12 @@ public final class BulkUpdate {
return this.getDataType() == that.getDataType() &&
Objects.equals(this.getAction(), that.getAction()) &&
Objects.equals(this.getQueries(), that.getQueries());
Objects.equals(this.getFilters(), that.getFilters());
}
@Override
public int hashCode() {
return Objects.hash(getDataType(), getAction(), getQueries(), isTrackingStatistics());
return Objects.hash(getDataType(), getAction(), getFilters(), isTrackingStatistics());
}
@Override
@@ -217,7 +164,7 @@ public final class BulkUpdate {
return "BulkUpdate(" +
"dataType=" + this.getDataType() + ", " +
"action=" + this.getAction() + ", " +
"constraints=" + this.getQueries() + ", " +
"constraints=" + this.getFilters() + ", " +
"trackStatistics=" + this.isTrackingStatistics() + ")";
}
}
@@ -26,8 +26,12 @@
package me.lucko.luckperms.common.bulkupdate;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.bulkupdate.action.Action;
import me.lucko.luckperms.common.bulkupdate.query.Query;
import me.lucko.luckperms.common.bulkupdate.action.BulkUpdateAction;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.ConstraintFactory;
import me.lucko.luckperms.common.filter.Filter;
import me.lucko.luckperms.common.filter.FilterList;
import net.luckperms.api.node.Node;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -45,18 +49,18 @@ public class BulkUpdateBuilder {
private DataType dataType = DataType.ALL;
// the action to apply to the data which matches the constraints
private Action action = null;
private BulkUpdateAction action = null;
// should the operation count the number of affected nodes, users and groups
private boolean trackStatistics = false;
// a set of constraints which data must match to be acted upon
private final Set<Query> queries = new LinkedHashSet<>();
// a set of filters which data must match to be acted upon
private final Set<Filter<Node, String>> filters = new LinkedHashSet<>();
private BulkUpdateBuilder() {
}
public BulkUpdateBuilder action(Action action) {
public BulkUpdateBuilder action(BulkUpdateAction action) {
this.action = action;
return this;
}
@@ -71,8 +75,8 @@ public class BulkUpdateBuilder {
return this;
}
public BulkUpdateBuilder query(Query query) {
this.queries.add(query);
public BulkUpdateBuilder filter(BulkUpdateField field, Comparison comparison, String value) {
this.filters.add(new Filter<>(field, ConstraintFactory.STRINGS.build(comparison, value)));
return this;
}
@@ -81,7 +85,8 @@ public class BulkUpdateBuilder {
throw new IllegalStateException("no action specified");
}
return new BulkUpdate(this.dataType, this.action, ImmutableList.copyOf(this.queries), this.trackStatistics);
FilterList<Node> filters = new FilterList<>(FilterList.LogicalOperator.AND, ImmutableList.copyOf(this.filters));
return new BulkUpdate(this.dataType, this.action, filters, this.trackStatistics);
}
@Override
@@ -89,7 +94,7 @@ public class BulkUpdateBuilder {
return "BulkUpdateBuilder(" +
"dataType=" + this.dataType + ", " +
"action=" + this.action + ", " +
"constraints=" + this.queries + ", " +
"constraints=" + this.filters + ", " +
"trackStatistics=" + this.trackStatistics + ")";
}
}
@@ -23,35 +23,45 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.plugin.classpath;
package me.lucko.luckperms.common.bulkupdate;
import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
import me.lucko.luckperms.common.filter.FilterField;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.node.Node;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Locale;
public class ReflectionClassPathAppender implements ClassPathAppender {
private final URLClassLoaderAccess classLoaderAccess;
/**
* Represents a field being used in a bulk update
*/
public enum BulkUpdateField implements FilterField<Node, String> {
public ReflectionClassPathAppender(ClassLoader classLoader) throws IllegalStateException {
if (classLoader instanceof URLClassLoader) {
this.classLoaderAccess = URLClassLoaderAccess.create((URLClassLoader) classLoader);
} else {
throw new IllegalStateException("ClassLoader is not instance of URLClassLoader");
PERMISSION {
@Override
public String getValue(Node node) {
return node.getKey();
}
}
},
public ReflectionClassPathAppender(LuckPermsBootstrap bootstrap) throws IllegalStateException {
this(bootstrap.getClass().getClassLoader());
}
SERVER {
@Override
public String getValue(Node node) {
return node.getContexts().getAnyValue(DefaultContextKeys.SERVER_KEY).orElse("global");
}
},
@Override
public void addJarToClasspath(Path file) {
WORLD {
@Override
public String getValue(Node node) {
return node.getContexts().getAnyValue(DefaultContextKeys.WORLD_KEY).orElse("global");
}
};
public static BulkUpdateField of(String s) {
try {
this.classLoaderAccess.addURL(file.toUri().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
return valueOf(s.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
return null;
}
}
@@ -0,0 +1,76 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate;
import me.lucko.luckperms.common.bulkupdate.action.BulkUpdateAction;
import me.lucko.luckperms.common.bulkupdate.action.DeleteAction;
import me.lucko.luckperms.common.bulkupdate.action.UpdateAction;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.sql.FilterSqlBuilder;
import net.luckperms.api.node.Node;
public class BulkUpdateSqlBuilder extends FilterSqlBuilder<Node> {
public void visit(BulkUpdate update) {
visit(update.getAction());
visit(update.getFilters());
}
public void visit(BulkUpdateAction action) {
if (action instanceof UpdateAction) {
visit(((UpdateAction) action));
} else if (action instanceof DeleteAction) {
visit(((DeleteAction) action));
} else {
throw new UnsupportedOperationException(action.getClass().getName());
}
}
public void visit(UpdateAction action) {
this.builder.append("UPDATE {table} SET ");
visitFieldName(action.getField());
this.builder.append("=");
this.builder.variable(action.getNewValue());
}
public void visit(DeleteAction action) {
this.builder.append("DELETE FROM {table}");
}
@Override
public void visitFieldName(FilterField<Node, ?> field) {
if (field == BulkUpdateField.PERMISSION) {
this.builder.append("permission");
} else if (field == BulkUpdateField.SERVER) {
this.builder.append("server");
} else if (field == BulkUpdateField.WORLD) {
this.builder.append("world");
} else {
throw new AssertionError(field);
}
}
}
@@ -25,13 +25,12 @@
package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import net.luckperms.api.node.Node;
/**
* Represents an action to be applied to a given node.
*/
public interface Action {
public interface BulkUpdateAction {
/**
* Gets the name of this action
@@ -44,17 +43,8 @@ public interface Action {
* Applies this action to the given NodeModel, and returns the result.
*
* @param from the node to base changes from
* @return the new nodemodel instance, or null if the node should be deleted.
* @return the new node instance, or null if the node should be deleted.
*/
Node apply(Node from);
/**
* Gets this action in SQL form.
*
* Will include a placeholder for the table, as "{table}".
*
* @param builder the statement builder
*/
void appendSql(PreparedStatementBuilder builder);
}
@@ -25,10 +25,9 @@
package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import net.luckperms.api.node.Node;
public class DeleteAction implements Action {
public class DeleteAction implements BulkUpdateAction {
public static DeleteAction create() {
return new DeleteAction();
@@ -46,9 +45,4 @@ public class DeleteAction implements Action {
public Node apply(Node from) {
return null; // this action just deletes nodes, so return null
}
@Override
public void appendSql(PreparedStatementBuilder builder) {
builder.append("DELETE FROM {table}");
}
}
@@ -25,28 +25,27 @@
package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.bulkupdate.query.QueryField;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateField;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.node.Node;
public class UpdateAction implements Action {
public class UpdateAction implements BulkUpdateAction {
public static UpdateAction of(QueryField field, String value) {
public static UpdateAction of(BulkUpdateField field, String value) {
return new UpdateAction(field, value);
}
// the field we're updating
private final QueryField field;
private final BulkUpdateField field;
// the new value of the field
private final String value;
private final String newValue;
private UpdateAction(QueryField field, String value) {
private UpdateAction(BulkUpdateField field, String newValue) {
this.field = field;
this.value = value;
this.newValue = newValue;
}
@Override
@@ -54,11 +53,19 @@ public class UpdateAction implements Action {
return "update";
}
public BulkUpdateField getField() {
return this.field;
}
public String getNewValue() {
return this.newValue;
}
@Override
public Node apply(Node from) {
switch (this.field) {
case PERMISSION:
return NodeBuilders.determineMostApplicable(this.value)
return NodeBuilders.determineMostApplicable(this.newValue)
.value(from.getValue())
.expiry(from.getExpiry())
.context(from.getContexts())
@@ -66,8 +73,8 @@ public class UpdateAction implements Action {
case SERVER: {
MutableContextSet contexts = from.getContexts().mutableCopy();
contexts.removeAll(DefaultContextKeys.SERVER_KEY);
if (!this.value.equals("global")) {
contexts.add(DefaultContextKeys.SERVER_KEY, this.value);
if (!this.newValue.equals("global")) {
contexts.add(DefaultContextKeys.SERVER_KEY, this.newValue);
}
return from.toBuilder()
@@ -77,8 +84,8 @@ public class UpdateAction implements Action {
case WORLD: {
MutableContextSet contexts = from.getContexts().mutableCopy();
contexts.removeAll(DefaultContextKeys.WORLD_KEY);
if (!this.value.equals("global")) {
contexts.add(DefaultContextKeys.WORLD_KEY, this.value);
if (!this.newValue.equals("global")) {
contexts.add(DefaultContextKeys.WORLD_KEY, this.newValue);
}
return from.toBuilder()
@@ -89,10 +96,4 @@ public class UpdateAction implements Action {
throw new RuntimeException();
}
}
@Override
public void appendSql(PreparedStatementBuilder builder) {
builder.append("UPDATE {table} SET " + this.field.getSqlName() + "=");
builder.variable(this.value);
}
}
@@ -1,112 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate.comparison;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import java.util.regex.Pattern;
/**
* An enumeration of standard {@link Comparison}s.
*/
public enum StandardComparison implements Comparison {
EQUAL("==", "=") {
@Override
public CompiledExpression compile(String expression) {
return expression::equalsIgnoreCase;
}
},
NOT_EQUAL("!=", "!=") {
@Override
public CompiledExpression compile(String expression) {
return string -> !expression.equalsIgnoreCase(string);
}
},
SIMILAR("~~", "LIKE") {
@Override
public CompiledExpression compile(String expression) {
Pattern pattern = StandardComparison.compilePatternForLikeSyntax(expression);
return string -> pattern.matcher(string).matches();
}
},
NOT_SIMILAR("!~", "NOT LIKE") {
@Override
public CompiledExpression compile(String expression) {
Pattern pattern = StandardComparison.compilePatternForLikeSyntax(expression);
return string -> !pattern.matcher(string).matches();
}
};
public static final String WILDCARD = "%";
public static final String WILDCARD_ONE = "_";
private final String symbol;
private final String asSql;
StandardComparison(String symbol, String asSql) {
this.symbol = symbol;
this.asSql = asSql;
}
@Override
public String getSymbol() {
return this.symbol;
}
@Override
public void appendSql(PreparedStatementBuilder builder) {
builder.append(this.asSql);
}
@Override
public String toString() {
return this.symbol;
}
public static StandardComparison parseComparison(String s) {
for (StandardComparison t : values()) {
if (t.getSymbol().equals(s)) {
return t;
}
}
return null;
}
static Pattern compilePatternForLikeSyntax(String expression) {
expression = expression.replace(".", "\\.");
// convert from SQL LIKE syntax to regex
expression = expression.replace(WILDCARD_ONE, ".");
expression = expression.replace(WILDCARD, ".*");
return Pattern.compile(expression, Pattern.CASE_INSENSITIVE);
}
}
@@ -1,83 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate.query;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.bulkupdate.comparison.Constraint;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.node.Node;
/**
* Represents a query component
*/
public class Query {
public static Query of(QueryField field, Constraint constraint) {
return new Query(field, constraint);
}
// the field this query is comparing against
private final QueryField field;
// the constraint
private final Constraint constraint;
private Query(QueryField field, Constraint constraint) {
this.field = field;
this.constraint = constraint;
}
/**
* Returns if the given node satisfies this query
*
* @param node the node
* @return true if satisfied
*/
public boolean isSatisfiedBy(Node node) {
switch (this.field) {
case PERMISSION:
return this.constraint.eval(node.getKey());
case SERVER:
return this.constraint.eval(node.getContexts().getAnyValue(DefaultContextKeys.SERVER_KEY).orElse("global"));
case WORLD:
return this.constraint.eval(node.getContexts().getAnyValue(DefaultContextKeys.WORLD_KEY).orElse("global"));
default:
throw new RuntimeException();
}
}
public void appendSql(PreparedStatementBuilder builder) {
this.constraint.appendSql(builder, this.field.getSqlName());
}
public QueryField getField() {
return this.field;
}
public Constraint getConstraint() {
return this.constraint;
}
}
@@ -193,7 +193,7 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
}
public void cleanup() {
this.cache.values().removeIf(value -> ((UsageTracked) value).usedSince(TimeUnit.MINUTES.toMillis(2)));
this.cache.values().removeIf(value -> !((UsageTracked) value).usedInTheLast(2, TimeUnit.MINUTES));
}
@Override
@@ -25,14 +25,20 @@
package me.lucko.luckperms.common.cacheddata;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
public abstract class UsageTracked {
private long lastUsed = System.currentTimeMillis();
@VisibleForTesting
protected long lastUsed = System.currentTimeMillis();
public void recordUsage() {
this.lastUsed = System.currentTimeMillis();
}
public boolean usedSince(long duration) {
return this.lastUsed > System.currentTimeMillis() - duration;
public boolean usedInTheLast(long duration, TimeUnit unit) {
return this.lastUsed > System.currentTimeMillis() - unit.toMillis(duration);
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import com.google.common.collect.ImmutableList;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -220,8 +220,11 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return track != null && origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
return origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
}
@Override
@@ -250,8 +253,11 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return track != null && !track.containsGroup(origin.getName());
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName()));
}
@Override
@@ -305,7 +311,7 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return !this.groupName.equals(origin.getName());
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && this.groupName.equals(origin.getName()));
}
@Override
@@ -0,0 +1,57 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.Nullable;
public abstract class AbstractResult<T, N extends Node, S extends AbstractResult<T, N, S>> implements Result<T, N> {
/** The node that caused the result */
protected final N node;
/** A reference to another result that this one overrides */
protected S overriddenResult;
public AbstractResult(N node, S overriddenResult) {
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
public final @Nullable N node() {
return this.node;
}
public final @Nullable S overriddenResult() {
return this.overriddenResult;
}
public final void setOverriddenResult(S overriddenResult) {
this.overriddenResult = overriddenResult;
}
}
@@ -25,30 +25,23 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.WeightNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of an integer meta lookup
*
* @param <N> the node type
*/
public final class IntegerResult<N extends Node> implements Result<Integer, N> {
public final class IntegerResult<N extends Node> extends AbstractResult<Integer, N, IntegerResult<N>> {
/** The result */
private final int result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private IntegerResult<N> overriddenResult;
public IntegerResult(int result, N node, IntegerResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -73,19 +66,6 @@ public final class IntegerResult<N extends Node> implements Result<Integer, N> {
}
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable IntegerResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(IntegerResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public boolean isNull() {
return this == NULL_RESULT;
}
@@ -25,7 +25,6 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.ChatMetaNode;
import net.luckperms.api.node.types.MetaNode;
@@ -36,19 +35,14 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* @param <N> the node type
*/
public final class StringResult<N extends Node> implements Result<String, N> {
public final class StringResult<N extends Node> extends AbstractResult<String, N, StringResult<N>> {
/** The result, nullable */
private final String result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private StringResult<N> overriddenResult;
public StringResult(String result, N node, StringResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -56,19 +50,6 @@ public final class StringResult<N extends Node> implements Result<String, N> {
return this.result;
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable StringResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(StringResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public StringResult<N> copy() {
return new StringResult<>(this.result, this.node, this.overriddenResult);
}
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.cacheddata.result;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.util.Tristate;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -36,20 +35,16 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of a {@link PermissionCalculator} lookup.
*/
public final class TristateResult implements Result<Tristate, Node> {
public final class TristateResult extends AbstractResult<Tristate, Node, TristateResult> {
/** The result */
private final Tristate result;
/** The node that caused the result */
private final Node node;
/** The permission processor that provided the result */
private final Class<? extends PermissionProcessor> processorClass;
/** A reference to another result that this one overrides */
private TristateResult overriddenResult;
private TristateResult(Tristate result, Node node, Class<? extends PermissionProcessor> processorClass) {
super(node, null);
this.result = result;
this.node = node;
this.processorClass = processorClass;
}
@@ -58,11 +53,6 @@ public final class TristateResult implements Result<Tristate, Node> {
return this.result;
}
@Override
public @Nullable Node node() {
return this.node;
}
public @Nullable Class<? extends PermissionProcessor> processorClass() {
return this.processorClass;
}
@@ -79,14 +69,6 @@ public final class TristateResult implements Result<Tristate, Node> {
}
}
public @Nullable TristateResult overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(TristateResult overriddenResult) {
this.overriddenResult = overriddenResult;
}
@Override
public String toString() {
return "TristateResult(" +
@@ -79,6 +79,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -101,7 +102,7 @@ public class CommandManager {
.build()
);
private final AtomicBoolean executingCommand = new AtomicBoolean(false);
private final ExpiringSet<UUID> playerRateLimit = new ExpiringSet<>(500, TimeUnit.MILLISECONDS);
private final Set<UUID> playerRateLimit = ExpiringSet.newExpiringSet(500, TimeUnit.MILLISECONDS);
private final TabCompletions tabCompletions;
private final Map<String, Command<?>> mainCommands;
@@ -154,11 +155,20 @@ public class CommandManager {
public CompletableFuture<Void> executeCommand(Sender sender, String label, List<String> args) {
UUID uniqueId = sender.getUniqueId();
if (this.plugin.getConfiguration().get(ConfigKeys.COMMANDS_RATE_LIMIT) && !sender.isConsole() && !this.playerRateLimit.add(uniqueId)) {
if (this.plugin.getConfiguration().get(ConfigKeys.COMMANDS_RATE_LIMIT) && !sender.isConsole() && !Sender.CONSOLE_UUID.equals(uniqueId) && !this.playerRateLimit.add(uniqueId)) {
this.plugin.getLogger().warn("Player '" + uniqueId + "' is spamming LuckPerms commands. Ignoring further inputs.");
return CompletableFuture.completedFuture(null);
}
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
Message.COMMANDS_DISABLED.send(sender);
return CompletableFuture.completedFuture(null);
}
SchedulerAdapter scheduler = this.plugin.getBootstrap().getScheduler();
List<String> argsCopy = new ArrayList<>(args);
@@ -285,6 +295,14 @@ public class CommandManager {
}
public List<String> tabCompleteCommand(Sender sender, List<String> arguments) {
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
return Collections.emptyList();
}
applyConvenienceAliases(arguments, false);
final List<Command<?>> mains = this.mainCommands.values().stream()
@@ -31,6 +31,7 @@ import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -66,7 +67,7 @@ public abstract class ChildCommand<T> extends Command<T> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -35,6 +35,7 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -114,7 +115,7 @@ public abstract class GenericChildCommand {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -86,39 +86,44 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
return;
}
final String targetArgument = args.get(0);
I targetId = null;
if (this.type == Type.TAKES_ARGUMENT_FOR_TARGET) {
targetId = parseTarget(targetArgument, plugin, sender);
if (this.type == Type.TARGETED) {
final String targetArgument = args.get(0);
I targetId = parseTarget(targetArgument, plugin, sender);
if (targetId == null) {
return;
}
}
ReentrantLock lock = getLockForTarget(targetId);
lock.lock();
try {
T target = getTarget(targetId, plugin, sender);
if (target == null) {
return;
}
ReentrantLock lock = getLockForTarget(targetId);
lock.lock();
try {
sub.execute(plugin, sender, target, args.subList(this.type.minArgs, args.size()), label);
T target = getTarget(targetId, plugin, sender);
if (target == null) {
return;
}
try {
sub.execute(plugin, sender, target, args.subList(this.type.minArgs, args.size()), label);
} catch (CommandException e) {
e.handle(sender, label, sub);
}
cleanup(target, plugin);
} finally {
lock.unlock();
}
} else {
try {
sub.execute(plugin, sender, null, args.subList(this.type.minArgs, args.size()), label);
} catch (CommandException e) {
e.handle(sender, label, sub);
}
cleanup(target, plugin);
} finally {
lock.unlock();
}
}
@Override
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) {
switch (this.type) {
case TAKES_ARGUMENT_FOR_TARGET:
case TARGETED:
return TabCompleter.create()
.at(0, CompletionSupplier.startsWith(() -> getTargets(plugin).stream()))
.at(1, CompletionSupplier.startsWith(() -> getChildren().stream()
@@ -133,7 +138,7 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
.orElse(Collections.emptyList())
)
.complete(args);
case NO_TARGET_ARGUMENT:
case NOT_TARGETED:
return TabCompleter.create()
.at(0, CompletionSupplier.startsWith(() -> getChildren().stream()
.filter(s -> s.isAuthorized(sender))
@@ -178,21 +183,31 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
return getChildren().stream().anyMatch(sc -> sc.isAuthorized(sender));
}
protected abstract List<String> getTargets(LuckPermsPlugin plugin);
protected List<String> getTargets(LuckPermsPlugin plugin) {
throw new UnsupportedOperationException();
}
protected abstract I parseTarget(String target, LuckPermsPlugin plugin, Sender sender);
protected I parseTarget(String target, LuckPermsPlugin plugin, Sender sender) {
throw new UnsupportedOperationException();
}
protected abstract ReentrantLock getLockForTarget(I target);
protected ReentrantLock getLockForTarget(I target) {
throw new UnsupportedOperationException();
}
protected abstract T getTarget(I target, LuckPermsPlugin plugin, Sender sender);
protected T getTarget(I target, LuckPermsPlugin plugin, Sender sender) {
throw new UnsupportedOperationException();
}
protected abstract void cleanup(T t, LuckPermsPlugin plugin);
protected void cleanup(T t, LuckPermsPlugin plugin) {
throw new UnsupportedOperationException();
}
public enum Type {
// e.g. /lp log sub-command....
NO_TARGET_ARGUMENT(0),
NOT_TARGETED(0),
// e.g. /lp user <USER> sub-command....
TAKES_ARGUMENT_FOR_TARGET(1);
TARGETED(1);
private final int cmdIndex;
private final int minArgs;
@@ -33,6 +33,7 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -70,7 +71,7 @@ public abstract class SingleCommand extends Command<Void> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -32,143 +32,143 @@ import me.lucko.luckperms.common.sender.Sender;
*/
public enum CommandPermission {
SYNC("sync", Type.NONE),
INFO("info", Type.NONE),
EDITOR("editor", Type.NONE),
VERBOSE("verbose", Type.NONE),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE),
TREE("tree", Type.NONE),
SEARCH("search", Type.NONE),
IMPORT("import", Type.NONE),
EXPORT("export", Type.NONE),
RELOAD_CONFIG("reloadconfig", Type.NONE),
BULK_UPDATE("bulkupdate", Type.NONE),
APPLY_EDITS("applyedits", Type.NONE),
TRUST_EDITOR("trusteditor", Type.NONE),
TRANSLATIONS("translations", Type.NONE),
SYNC("sync", Type.NONE, true),
INFO("info", Type.NONE, true),
EDITOR("editor", Type.NONE, true),
VERBOSE("verbose", Type.NONE, true),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE, false),
TREE("tree", Type.NONE, true),
SEARCH("search", Type.NONE, true),
IMPORT("import", Type.NONE, false),
EXPORT("export", Type.NONE, true),
RELOAD_CONFIG("reloadconfig", Type.NONE, true),
BULK_UPDATE("bulkupdate", Type.NONE, false),
APPLY_EDITS("applyedits", Type.NONE, false),
TRUST_EDITOR("trusteditor", Type.NONE, false),
TRANSLATIONS("translations", Type.NONE, true),
CREATE_GROUP("creategroup", Type.NONE),
DELETE_GROUP("deletegroup", Type.NONE),
LIST_GROUPS("listgroups", Type.NONE),
CREATE_GROUP("creategroup", Type.NONE, false),
DELETE_GROUP("deletegroup", Type.NONE, false),
LIST_GROUPS("listgroups", Type.NONE, true),
CREATE_TRACK("createtrack", Type.NONE),
DELETE_TRACK("deletetrack", Type.NONE),
LIST_TRACKS("listtracks", Type.NONE),
CREATE_TRACK("createtrack", Type.NONE, false),
DELETE_TRACK("deletetrack", Type.NONE, false),
LIST_TRACKS("listtracks", Type.NONE, true),
USER_INFO("info", Type.USER),
USER_PERM_INFO("permission.info", Type.USER),
USER_PERM_SET("permission.set", Type.USER),
USER_PERM_UNSET("permission.unset", Type.USER),
USER_PERM_SET_TEMP("permission.settemp", Type.USER),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER),
USER_PERM_CHECK("permission.check", Type.USER),
USER_PERM_CLEAR("permission.clear", Type.USER),
USER_PARENT_INFO("parent.info", Type.USER),
USER_PARENT_SET("parent.set", Type.USER),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER),
USER_PARENT_ADD("parent.add", Type.USER),
USER_PARENT_REMOVE("parent.remove", Type.USER),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER),
USER_PARENT_CLEAR("parent.clear", Type.USER),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER),
USER_META_INFO("meta.info", Type.USER),
USER_META_SET("meta.set", Type.USER),
USER_META_UNSET("meta.unset", Type.USER),
USER_META_SET_TEMP("meta.settemp", Type.USER),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER),
USER_META_SET_PREFIX("meta.setprefix", Type.USER),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER),
USER_META_CLEAR("meta.clear", Type.USER),
USER_EDITOR("editor", Type.USER),
USER_SHOW_TRACKS("showtracks", Type.USER),
USER_PROMOTE("promote", Type.USER),
USER_DEMOTE("demote", Type.USER),
USER_CLEAR("clear", Type.USER),
USER_CLONE("clone", Type.USER),
USER_INFO("info", Type.USER, true),
USER_PERM_INFO("permission.info", Type.USER, true),
USER_PERM_SET("permission.set", Type.USER, false),
USER_PERM_UNSET("permission.unset", Type.USER, false),
USER_PERM_SET_TEMP("permission.settemp", Type.USER, false),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER, false),
USER_PERM_CHECK("permission.check", Type.USER, true),
USER_PERM_CLEAR("permission.clear", Type.USER, false),
USER_PARENT_INFO("parent.info", Type.USER, true),
USER_PARENT_SET("parent.set", Type.USER, false),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER, false),
USER_PARENT_ADD("parent.add", Type.USER, false),
USER_PARENT_REMOVE("parent.remove", Type.USER, false),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER, false),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER, false),
USER_PARENT_CLEAR("parent.clear", Type.USER, false),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER, false),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER, false),
USER_META_INFO("meta.info", Type.USER, true),
USER_META_SET("meta.set", Type.USER, false),
USER_META_UNSET("meta.unset", Type.USER, false),
USER_META_SET_TEMP("meta.settemp", Type.USER, false),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER, false),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER, false),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER, false),
USER_META_SET_PREFIX("meta.setprefix", Type.USER, false),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER, false),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER, false),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER, false),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER, false),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER, false),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER, false),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER, false),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER, false),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER, false),
USER_META_CLEAR("meta.clear", Type.USER, false),
USER_EDITOR("editor", Type.USER, true),
USER_SHOW_TRACKS("showtracks", Type.USER, true),
USER_PROMOTE("promote", Type.USER, false),
USER_DEMOTE("demote", Type.USER, false),
USER_CLEAR("clear", Type.USER, false),
USER_CLONE("clone", Type.USER, false),
GROUP_INFO("info", Type.GROUP),
GROUP_PERM_INFO("permission.info", Type.GROUP),
GROUP_PERM_SET("permission.set", Type.GROUP),
GROUP_PERM_UNSET("permission.unset", Type.GROUP),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP),
GROUP_PERM_CHECK("permission.check", Type.GROUP),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP),
GROUP_PARENT_INFO("parent.info", Type.GROUP),
GROUP_PARENT_SET("parent.set", Type.GROUP),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP),
GROUP_PARENT_ADD("parent.add", Type.GROUP),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP),
GROUP_META_INFO("meta.info", Type.GROUP),
GROUP_META_SET("meta.set", Type.GROUP),
GROUP_META_UNSET("meta.unset", Type.GROUP),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP),
GROUP_META_CLEAR("meta.clear", Type.GROUP),
GROUP_EDITOR("editor", Type.GROUP),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP),
GROUP_SET_WEIGHT("setweight", Type.GROUP),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP),
GROUP_CLEAR("clear", Type.GROUP),
GROUP_RENAME("rename", Type.GROUP),
GROUP_CLONE("clone", Type.GROUP),
GROUP_INFO("info", Type.GROUP, true),
GROUP_PERM_INFO("permission.info", Type.GROUP, true),
GROUP_PERM_SET("permission.set", Type.GROUP, false),
GROUP_PERM_UNSET("permission.unset", Type.GROUP, false),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP, false),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP, false),
GROUP_PERM_CHECK("permission.check", Type.GROUP, true),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP, false),
GROUP_PARENT_INFO("parent.info", Type.GROUP, true),
GROUP_PARENT_SET("parent.set", Type.GROUP, false),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP, false),
GROUP_PARENT_ADD("parent.add", Type.GROUP, false),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP, false),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP, false),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP, false),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP, false),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP, false),
GROUP_META_INFO("meta.info", Type.GROUP, true),
GROUP_META_SET("meta.set", Type.GROUP, false),
GROUP_META_UNSET("meta.unset", Type.GROUP, false),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP, false),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP, false),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP, false),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP, false),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP, false),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP, false),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP, false),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP, false),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP, false),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP, false),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP, false),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP, false),
GROUP_META_CLEAR("meta.clear", Type.GROUP, false),
GROUP_EDITOR("editor", Type.GROUP, true),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP, true),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP, true),
GROUP_SET_WEIGHT("setweight", Type.GROUP, false),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP, false),
GROUP_CLEAR("clear", Type.GROUP, false),
GROUP_RENAME("rename", Type.GROUP, false),
GROUP_CLONE("clone", Type.GROUP, false),
TRACK_INFO("info", Type.TRACK),
TRACK_EDITOR("editor", Type.TRACK),
TRACK_APPEND("append", Type.TRACK),
TRACK_INSERT("insert", Type.TRACK),
TRACK_REMOVE("remove", Type.TRACK),
TRACK_CLEAR("clear", Type.TRACK),
TRACK_RENAME("rename", Type.TRACK),
TRACK_CLONE("clone", Type.TRACK),
TRACK_INFO("info", Type.TRACK, true),
TRACK_EDITOR("editor", Type.TRACK, true),
TRACK_APPEND("append", Type.TRACK, false),
TRACK_INSERT("insert", Type.TRACK, false),
TRACK_REMOVE("remove", Type.TRACK, false),
TRACK_CLEAR("clear", Type.TRACK, false),
TRACK_RENAME("rename", Type.TRACK, false),
TRACK_CLONE("clone", Type.TRACK, false),
LOG_RECENT("recent", Type.LOG),
LOG_USER_HISTORY("userhistory", Type.LOG),
LOG_GROUP_HISTORY("grouphistory", Type.LOG),
LOG_TRACK_HISTORY("trackhistory", Type.LOG),
LOG_SEARCH("search", Type.LOG),
LOG_NOTIFY("notify", Type.LOG),
LOG_RECENT("recent", Type.LOG, true),
LOG_USER_HISTORY("userhistory", Type.LOG, true),
LOG_GROUP_HISTORY("grouphistory", Type.LOG, true),
LOG_TRACK_HISTORY("trackhistory", Type.LOG, true),
LOG_SEARCH("search", Type.LOG, true),
LOG_NOTIFY("notify", Type.LOG, true),
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE),
SPONGE_OPTION_INFO("option.info", Type.SPONGE),
SPONGE_OPTION_SET("option.set", Type.SPONGE),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE);
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE, true),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE, false),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE, false),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE, true),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE, false),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE, false),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE, false),
SPONGE_OPTION_INFO("option.info", Type.SPONGE, true),
SPONGE_OPTION_SET("option.set", Type.SPONGE, false),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE, false),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE, false);
public static final String ROOT = "luckperms.";
@@ -176,9 +176,11 @@ public enum CommandPermission {
private final String permission;
private final Type type;
private final boolean readOnly;
CommandPermission(String node, Type type) {
CommandPermission(String node, Type type, boolean readOnly) {
this.type = type;
this.readOnly = readOnly;
if (type == Type.NONE) {
this.node = node;
@@ -205,6 +207,10 @@ public enum CommandPermission {
return this.type;
}
public boolean isReadOnly() {
return this.readOnly;
}
public enum Type {
NONE(null),
@@ -28,12 +28,9 @@ package me.lucko.luckperms.common.commands.group;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateBuilder;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateField;
import me.lucko.luckperms.common.bulkupdate.DataType;
import me.lucko.luckperms.common.bulkupdate.action.DeleteAction;
import me.lucko.luckperms.common.bulkupdate.comparison.Constraint;
import me.lucko.luckperms.common.bulkupdate.comparison.StandardComparison;
import me.lucko.luckperms.common.bulkupdate.query.Query;
import me.lucko.luckperms.common.bulkupdate.query.QueryField;
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.ArgumentPermissions;
import me.lucko.luckperms.common.command.access.CommandPermission;
@@ -43,6 +40,7 @@ import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.tabcomplete.TabCompletions;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.model.Group;
@@ -115,7 +113,7 @@ public class DeleteGroup extends SingleCommand {
.trackStatistics(false)
.dataType(DataType.ALL)
.action(DeleteAction.create())
.query(Query.of(QueryField.PERMISSION, Constraint.of(StandardComparison.EQUAL, Inheritance.key(groupName))))
.filter(BulkUpdateField.PERMISSION, Comparison.EQUAL, Inheritance.key(groupName))
.build();
plugin.getStorage().applyBulkUpdate(operation).whenCompleteAsync((v, ex) -> {
if (ex != null) {
@@ -60,7 +60,7 @@ public class GroupParentCommand extends ParentCommand<Group, String> {
.build(key -> new ReentrantLock());
public GroupParentCommand() {
super(CommandSpec.GROUP, "Group", Type.TAKES_ARGUMENT_FOR_TARGET, ImmutableList.<Command<Group>>builder()
super(CommandSpec.GROUP, "Group", Type.TARGETED, ImmutableList.<Command<Group>>builder()
.add(new GroupInfo())
.add(new CommandPermission<>(HolderType.GROUP))
.add(new CommandParent<>(HolderType.GROUP))
@@ -28,11 +28,8 @@ package me.lucko.luckperms.common.commands.group;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateBuilder;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateField;
import me.lucko.luckperms.common.bulkupdate.action.UpdateAction;
import me.lucko.luckperms.common.bulkupdate.comparison.Constraint;
import me.lucko.luckperms.common.bulkupdate.comparison.StandardComparison;
import me.lucko.luckperms.common.bulkupdate.query.Query;
import me.lucko.luckperms.common.bulkupdate.query.QueryField;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.ArgumentPermissions;
import me.lucko.luckperms.common.command.access.CommandPermission;
@@ -41,6 +38,7 @@ import me.lucko.luckperms.common.command.tabcomplete.CompletionSupplier;
import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.command.utils.StorageAssistant;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.node.types.Inheritance;
@@ -112,8 +110,8 @@ public class GroupRename extends ChildCommand<Group> {
BulkUpdate operation = BulkUpdateBuilder.create()
.trackStatistics(false)
.dataType(me.lucko.luckperms.common.bulkupdate.DataType.ALL)
.action(UpdateAction.of(QueryField.PERMISSION, Inheritance.key(newGroupName)))
.query(Query.of(QueryField.PERMISSION, Constraint.of(StandardComparison.EQUAL, Inheritance.key(target.getName()))))
.action(UpdateAction.of(BulkUpdateField.PERMISSION, Inheritance.key(newGroupName)))
.filter(BulkUpdateField.PERMISSION, Comparison.EQUAL, Inheritance.key(target.getName()))
.build();
return plugin.getStorage().applyBulkUpdate(operation);
} else {
@@ -25,25 +25,26 @@
package me.lucko.luckperms.common.commands.log;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.tabcomplete.TabCompletions;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.misc.DataConstraints;
import me.lucko.luckperms.common.util.Paginated;
import me.lucko.luckperms.common.util.Predicates;
import java.util.List;
import java.util.Locale;
public class LogGroupHistory extends ChildCommand<Log> {
public class LogGroupHistory extends ChildCommand<Void> {
private static final int ENTRIES_PER_PAGE = 10;
public LogGroupHistory() {
@@ -51,44 +52,34 @@ public class LogGroupHistory extends ChildCommand<Log> {
}
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, Log log, ArgumentList args, String label) {
public void execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
String group = args.get(0).toLowerCase(Locale.ROOT);
if (!DataConstraints.GROUP_NAME_TEST.test(group)) {
Message.GROUP_INVALID_ENTRY.send(sender, group);
return;
}
Paginated<LoggedAction> content = new Paginated<>(log.getGroupHistory(group));
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, 1));
LogPage log = plugin.getStorage().getLogPage(ActionFilters.group(group), pageParams).join();
int page = args.getIntOrDefault(1, Integer.MIN_VALUE);
if (page != Integer.MIN_VALUE) {
showLog(page, sender, content);
} else {
showLog(content.getMaxPages(ENTRIES_PER_PAGE), sender, content);
}
}
int page = pageParams.pageNumber();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
private static void showLog(int page, Sender sender, Paginated<LoggedAction> log) {
int maxPage = log.getMaxPages(ENTRIES_PER_PAGE);
if (maxPage == 0) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}
if (page == Integer.MIN_VALUE) {
page = maxPage;
}
if (page < 1 || page > maxPage) {
Message.LOG_INVALID_PAGE_RANGE.send(sender, maxPage);
return;
}
List<Paginated.Entry<LoggedAction>> entries = log.getPage(page, ENTRIES_PER_PAGE);
List<LogPage.Entry<LoggedAction>> entries = log.getNumberedContent();
String name = entries.stream().findAny().get().value().getTarget().getName();
Message.LOG_HISTORY_GROUP_HEADER.send(sender, name, page, maxPage);
for (Paginated.Entry<LoggedAction> e : entries) {
for (LogPage.Entry<LoggedAction> e : entries) {
Message.LOG_ENTRY.send(sender, e.position(), e.value());
}
}
@@ -25,7 +25,6 @@
package me.lucko.luckperms.common.commands.log;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.spec.CommandSpec;
@@ -43,7 +42,7 @@ import net.luckperms.api.node.Node;
import java.util.Optional;
import java.util.UUID;
public class LogNotify extends ChildCommand<Log> {
public class LogNotify extends ChildCommand<Void> {
private static final String IGNORE_NODE = "luckperms.log.notify.ignoring";
public LogNotify() {
@@ -83,7 +82,7 @@ public class LogNotify extends ChildCommand<Log> {
}
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, Log log, ArgumentList args, String label) {
public void execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
if (sender.isConsole()) {
Message.LOG_NOTIFY_CONSOLE.send(sender);
return;
@@ -26,22 +26,13 @@
package me.lucko.luckperms.common.commands.log;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.command.abstraction.Command;
import me.lucko.luckperms.common.command.abstraction.ParentCommand;
import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class LogParentCommand extends ParentCommand<Log, Void> {
private final ReentrantLock lock = new ReentrantLock();
public class LogParentCommand extends ParentCommand<Void, Void> {
public LogParentCommand() {
super(CommandSpec.LOG, "Log", Type.NO_TARGET_ARGUMENT, ImmutableList.<Command<Log>>builder()
super(CommandSpec.LOG, "Log", Type.NOT_TARGETED, ImmutableList.<Command<Void>>builder()
.add(new LogRecent())
.add(new LogSearch())
.add(new LogNotify())
@@ -51,38 +42,4 @@ public class LogParentCommand extends ParentCommand<Log, Void> {
.build()
);
}
@Override
protected ReentrantLock getLockForTarget(Void target) {
return this.lock; // all commands target the same log, so we share a lock between all "targets"
}
@Override
protected Log getTarget(Void target, LuckPermsPlugin plugin, Sender sender) {
Log log = plugin.getStorage().getLog().join();
if (log == null) {
Message.LOG_LOAD_ERROR.send(sender);
}
return log;
}
@Override
protected void cleanup(Log log, LuckPermsPlugin plugin) {
}
@Override
protected List<String> getTargets(LuckPermsPlugin plugin) {
// should never be called if we specify Type.NO_TARGET_ARGUMENT in the constructor
throw new UnsupportedOperationException();
}
@Override
protected Void parseTarget(String target, LuckPermsPlugin plugin, Sender sender) {
// should never be called if we specify Type.NO_TARGET_ARGUMENT in the constructor
throw new UnsupportedOperationException();
}
}
@@ -25,22 +25,23 @@
package me.lucko.luckperms.common.commands.log;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Paginated;
import me.lucko.luckperms.common.util.Predicates;
import java.util.List;
import java.util.UUID;
public class LogRecent extends ChildCommand<Log> {
public class LogRecent extends ChildCommand<Void> {
private static final int ENTRIES_PER_PAGE = 10;
public LogRecent() {
@@ -48,39 +49,32 @@ public class LogRecent extends ChildCommand<Log> {
}
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, Log log, ArgumentList args, String label) {
if (args.isEmpty()) {
// No page or user
Paginated<LoggedAction> content = new Paginated<>(log.getContent());
showLog(content.getMaxPages(ENTRIES_PER_PAGE), false, sender, content);
return;
public void execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
int page = 1;
UUID uuid = null;
if (!args.isEmpty()) {
int pageNo = args.getIntOrDefault(0, Integer.MIN_VALUE);
if (pageNo != Integer.MIN_VALUE) {
page = pageNo;
} else {
uuid = args.getUserTarget(0, plugin, sender);
if (uuid == null) {
return;
}
pageNo = args.getIntOrDefault(1, Integer.MIN_VALUE);
if (pageNo != Integer.MIN_VALUE) {
page = pageNo;
}
}
}
int page = args.getIntOrDefault(0, Integer.MIN_VALUE);
if (page != Integer.MIN_VALUE) {
Paginated<LoggedAction> content = new Paginated<>(log.getContent());
showLog(page, false, sender, content);
return;
}
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, page);
LogPage log = plugin.getStorage().getLogPage(uuid == null ? ActionFilters.all() : ActionFilters.source(uuid), pageParams).join();
// User and possibly page
UUID uuid = args.getUserTarget(0, plugin, sender);
if (uuid == null) {
return;
}
Paginated<LoggedAction> content = new Paginated<>(log.getContent(uuid));
page = args.getIntOrDefault(1, Integer.MIN_VALUE);
if (page != Integer.MIN_VALUE) {
showLog(page, true, sender, content);
} else {
showLog(content.getMaxPages(ENTRIES_PER_PAGE), true, sender, content);
}
}
private static void showLog(int page, boolean specificUser, Sender sender, Paginated<LoggedAction> log) {
int maxPage = log.getMaxPages(ENTRIES_PER_PAGE);
if (maxPage == 0) {
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}
@@ -90,8 +84,8 @@ public class LogRecent extends ChildCommand<Log> {
return;
}
List<Paginated.Entry<LoggedAction>> entries = log.getPage(page, ENTRIES_PER_PAGE);
if (specificUser) {
List<LogPage.Entry<LoggedAction>> entries = log.getNumberedContent();
if (uuid != null) {
String name = entries.stream().findAny().get().value().getSource().getName();
if (name.contains("@")) {
name = name.split("@")[0];
@@ -101,8 +95,9 @@ public class LogRecent extends ChildCommand<Log> {
Message.LOG_RECENT_HEADER.send(sender, page, maxPage);
}
for (Paginated.Entry<LoggedAction> e : entries) {
for (LogPage.Entry<LoggedAction> e : entries) {
Message.LOG_ENTRY.send(sender, e.position(), e.value());
}
}
}
@@ -25,21 +25,22 @@
package me.lucko.luckperms.common.commands.log;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Paginated;
import me.lucko.luckperms.common.util.Predicates;
import java.util.List;
public class LogSearch extends ChildCommand<Log> {
public class LogSearch extends ChildCommand<Void> {
private static final int ENTRIES_PER_PAGE = 10;
public LogSearch() {
@@ -47,8 +48,8 @@ public class LogSearch extends ChildCommand<Log> {
}
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, Log log, ArgumentList args, String label) {
int page = Integer.MIN_VALUE;
public void execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
int page = 1;
if (args.size() > 1) {
try {
page = Integer.parseInt(args.get(args.size() - 1));
@@ -59,36 +60,26 @@ public class LogSearch extends ChildCommand<Log> {
}
final String query = String.join(" ", args);
Paginated<LoggedAction> content = new Paginated<>(log.getSearch(query));
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, page);
LogPage log = plugin.getStorage().getLogPage(ActionFilters.search(query), pageParams).join();
if (page != Integer.MIN_VALUE) {
showLog(page, query, sender, content);
} else {
showLog(content.getMaxPages(ENTRIES_PER_PAGE), query, sender, content);
}
}
private static void showLog(int page, String query, Sender sender, Paginated<LoggedAction> log) {
int maxPage = log.getMaxPages(ENTRIES_PER_PAGE);
if (maxPage == 0) {
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}
if (page == Integer.MIN_VALUE) {
page = maxPage;
}
if (page < 1 || page > maxPage) {
Message.LOG_INVALID_PAGE_RANGE.send(sender, maxPage);
return;
}
List<Paginated.Entry<LoggedAction>> entries = log.getPage(page, ENTRIES_PER_PAGE);
List<LogPage.Entry<LoggedAction>> entries = log.getNumberedContent();
Message.LOG_SEARCH_HEADER.send(sender, query, page, maxPage);
for (Paginated.Entry<LoggedAction> e : entries) {
for (LogPage.Entry<LoggedAction> e : entries) {
Message.LOG_ENTRY.send(sender, e.position(), e.value());
}
}
}
@@ -25,25 +25,26 @@
package me.lucko.luckperms.common.commands.log;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.tabcomplete.TabCompletions;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.misc.DataConstraints;
import me.lucko.luckperms.common.util.Paginated;
import me.lucko.luckperms.common.util.Predicates;
import java.util.List;
import java.util.Locale;
public class LogTrackHistory extends ChildCommand<Log> {
public class LogTrackHistory extends ChildCommand<Void> {
private static final int ENTRIES_PER_PAGE = 10;
public LogTrackHistory() {
@@ -51,44 +52,33 @@ public class LogTrackHistory extends ChildCommand<Log> {
}
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, Log log, ArgumentList args, String label) {
public void execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
String track = args.get(0).toLowerCase(Locale.ROOT);
if (!DataConstraints.TRACK_NAME_TEST.test(track)) {
Message.TRACK_INVALID_ENTRY.send(sender, track);
return;
}
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, 1));
LogPage log = plugin.getStorage().getLogPage(ActionFilters.track(track), pageParams).join();
Paginated<LoggedAction> content = new Paginated<>(log.getTrackHistory(track));
int page = pageParams.pageNumber();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
int page = args.getIntOrDefault(1, Integer.MIN_VALUE);
if (page != Integer.MIN_VALUE) {
showLog(page, sender, content);
} else {
showLog(content.getMaxPages(ENTRIES_PER_PAGE), sender, content);
}
}
private static void showLog(int page, Sender sender, Paginated<LoggedAction> log) {
int maxPage = log.getMaxPages(ENTRIES_PER_PAGE);
if (maxPage == 0) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}
if (page == Integer.MIN_VALUE) {
page = maxPage;
}
if (page < 1 || page > maxPage) {
Message.LOG_INVALID_PAGE_RANGE.send(sender, maxPage);
return;
}
List<Paginated.Entry<LoggedAction>> entries = log.getPage(page, ENTRIES_PER_PAGE);
List<LogPage.Entry<LoggedAction>> entries = log.getNumberedContent();
String name = entries.stream().findAny().get().value().getTarget().getName();
Message.LOG_HISTORY_TRACK_HEADER.send(sender, name, page, maxPage);
for (Paginated.Entry<LoggedAction> e : entries) {
for (LogPage.Entry<LoggedAction> e : entries) {
Message.LOG_ENTRY.send(sender, e.position(), e.value());
}
}
@@ -25,22 +25,23 @@
package me.lucko.luckperms.common.commands.log;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.command.abstraction.ChildCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Paginated;
import me.lucko.luckperms.common.util.Predicates;
import java.util.List;
import java.util.UUID;
public class LogUserHistory extends ChildCommand<Log> {
public class LogUserHistory extends ChildCommand<Void> {
private static final int ENTRIES_PER_PAGE = 10;
public LogUserHistory() {
@@ -48,25 +49,19 @@ public class LogUserHistory extends ChildCommand<Log> {
}
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, Log log, ArgumentList args, String label) {
public void execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
UUID uuid = args.getUserTarget(0, plugin, sender);
if (uuid == null) {
return;
}
Paginated<LoggedAction> content = new Paginated<>(log.getUserHistory(uuid));
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, 1));
LogPage log = plugin.getStorage().getLogPage(ActionFilters.user(uuid), pageParams).join();
int page = args.getIntOrDefault(1, Integer.MIN_VALUE);
if (page != Integer.MIN_VALUE) {
showLog(page, sender, content);
} else {
showLog(content.getMaxPages(ENTRIES_PER_PAGE), sender, content);
}
}
int page = pageParams.pageNumber();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
private static void showLog(int page, Sender sender, Paginated<LoggedAction> log) {
int maxPage = log.getMaxPages(ENTRIES_PER_PAGE);
if (maxPage == 0) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}
@@ -76,11 +71,11 @@ public class LogUserHistory extends ChildCommand<Log> {
return;
}
List<Paginated.Entry<LoggedAction>> entries = log.getPage(page, ENTRIES_PER_PAGE);
List<LogPage.Entry<LoggedAction>> entries = log.getNumberedContent();
String name = entries.stream().findAny().get().value().getTarget().getName();
Message.LOG_HISTORY_USER_HEADER.send(sender, name, page, maxPage);
for (Paginated.Entry<LoggedAction> e : entries) {
for (LogPage.Entry<LoggedAction> e : entries) {
Message.LOG_ENTRY.send(sender, e.position(), e.value());
}
}
@@ -28,15 +28,12 @@ package me.lucko.luckperms.common.commands.misc;
import com.github.benmanes.caffeine.cache.Cache;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateBuilder;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateField;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateSqlBuilder;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateStatistics;
import me.lucko.luckperms.common.bulkupdate.DataType;
import me.lucko.luckperms.common.bulkupdate.action.DeleteAction;
import me.lucko.luckperms.common.bulkupdate.action.UpdateAction;
import me.lucko.luckperms.common.bulkupdate.comparison.Comparison;
import me.lucko.luckperms.common.bulkupdate.comparison.Constraint;
import me.lucko.luckperms.common.bulkupdate.comparison.StandardComparison;
import me.lucko.luckperms.common.bulkupdate.query.Query;
import me.lucko.luckperms.common.bulkupdate.query.QueryField;
import me.lucko.luckperms.common.command.abstraction.CommandException;
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
@@ -44,6 +41,7 @@ import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.command.utils.ArgumentException;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
@@ -112,7 +110,7 @@ public class BulkUpdateCommand extends SingleCommand {
}
String field = args.remove(0);
QueryField queryField = QueryField.of(field);
BulkUpdateField queryField = BulkUpdateField.of(field);
if (queryField == null) {
throw new ArgumentException.DetailedUsage();
}
@@ -131,20 +129,20 @@ public class BulkUpdateCommand extends SingleCommand {
return;
}
QueryField field = QueryField.of(parts[0]);
BulkUpdateField field = BulkUpdateField.of(parts[0]);
if (field == null) {
Message.BULK_UPDATE_INVALID_CONSTRAINT.send(sender, constraint);
return;
}
Comparison comparison = StandardComparison.parseComparison(parts[1]);
Comparison comparison = Comparison.parse(parts[1]);
if (comparison == null) {
Message.BULK_UPDATE_INVALID_COMPARISON.send(sender, parts[1]);
return;
}
String expr = parts[2];
bulkUpdateBuilder.query(Query.of(field, Constraint.of(comparison, expr)));
bulkUpdateBuilder.filter(field, comparison, expr);
}
BulkUpdate bulkUpdate = bulkUpdateBuilder.build();
@@ -155,7 +153,11 @@ public class BulkUpdateCommand extends SingleCommand {
String id = String.format("%04d", ThreadLocalRandom.current().nextInt(10000));
this.pendingOperations.put(id, bulkUpdate);
Message.BULK_UPDATE_QUEUED.send(sender, bulkUpdate.buildAsSql().toReadableString().replace("{table}", bulkUpdate.getDataType().getName()));
BulkUpdateSqlBuilder sqlBuilder = new BulkUpdateSqlBuilder();
sqlBuilder.visit(bulkUpdate);
String readableSql = sqlBuilder.builder().toReadableString().replace("{table}", bulkUpdate.getDataType().getName());
Message.BULK_UPDATE_QUEUED.send(sender, readableSql);
Message.BULK_UPDATE_CONFIRM.send(sender, label, id);
}
}
@@ -26,9 +26,6 @@
package me.lucko.luckperms.common.commands.misc;
import com.google.common.collect.Maps;
import me.lucko.luckperms.common.bulkupdate.comparison.Comparison;
import me.lucko.luckperms.common.bulkupdate.comparison.Constraint;
import me.lucko.luckperms.common.bulkupdate.comparison.StandardComparison;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
@@ -36,6 +33,7 @@ import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.command.tabcomplete.TabCompleter;
import me.lucko.luckperms.common.command.tabcomplete.TabCompletions;
import me.lucko.luckperms.common.command.utils.ArgumentList;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.node.comparator.NodeEntryComparator;
@@ -62,13 +60,13 @@ public class SearchCommand extends SingleCommand {
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, ArgumentList args, String label) {
Comparison comparison = StandardComparison.parseComparison(args.get(0));
Comparison comparison = Comparison.parse(args.get(0));
if (comparison == null) {
comparison = StandardComparison.EQUAL;
comparison = Comparison.EQUAL;
args.add(0, "==");
}
ConstraintNodeMatcher<Node> matcher = StandardNodeMatchers.of(Constraint.of(comparison, args.get(1)));
ConstraintNodeMatcher<Node> matcher = StandardNodeMatchers.key(args.get(1), comparison);
int page = args.getIntOrDefault(2, 1);
Message.SEARCH_SEARCHING.send(sender, matcher.toString());
@@ -120,7 +118,7 @@ public class SearchCommand extends SingleCommand {
headerMessage.send(sender, page, pages.size(), results.size());
for (Map.Entry<String, NodeEntry<T, Node>> ent : mappedContent) {
Message.SEARCH_NODE_ENTRY.send(sender, comparison != StandardComparison.EQUAL, ent.getValue().getNode(), ent.getKey(), holderType, label, sender.getPlugin());
Message.SEARCH_NODE_ENTRY.send(sender, comparison != Comparison.EQUAL, ent.getValue().getNode(), ent.getKey(), holderType, label, sender.getPlugin());
}
}
}
@@ -53,7 +53,7 @@ public class TrackParentCommand extends ParentCommand<Track, String> {
.build(key -> new ReentrantLock());
public TrackParentCommand() {
super(CommandSpec.TRACK, "Track", Type.TAKES_ARGUMENT_FOR_TARGET, ImmutableList.<Command<Track>>builder()
super(CommandSpec.TRACK, "Track", Type.TARGETED, ImmutableList.<Command<Track>>builder()
.add(new TrackInfo())
.add(new TrackEditor())
.add(new TrackAppend())
@@ -62,7 +62,7 @@ public class UserParentCommand extends ParentCommand<User, UserIdentifier> {
.build(key -> new ReentrantLock());
public UserParentCommand() {
super(CommandSpec.USER, "User", Type.TAKES_ARGUMENT_FOR_TARGET, ImmutableList.<Command<User>>builder()
super(CommandSpec.USER, "User", Type.TARGETED, ImmutableList.<Command<User>>builder()
.add(new UserInfo())
.add(new CommandPermission<>(HolderType.USER))
.add(new CommandParent<>(HolderType.USER))
Loaded 100 of 329 files, more files were not shown because too many files have changed in this diff. Show more