Compare commits

..
5 Commits
242 changed files with 3629 additions and 8128 deletions

No files matched your search

-6
View File
@@ -1,6 +0,0 @@
#
# 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
@@ -1,3 +0,0 @@
# 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
@@ -0,0 +1,2 @@
github: lucko
patreon: luckdev
+63
View File
@@ -0,0 +1,63 @@
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
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 💬 ​ Ask a question
url: https://discord.gg/luckperms
about: We use Discord to provide support for the project. If you have a question, need help, or aren't sure if you've found a bug or not, please come and talk to us there!
+31
View File
@@ -0,0 +1,31 @@
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
@@ -0,0 +1,130 @@
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
@@ -0,0 +1,26 @@
### :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
@@ -0,0 +1,106 @@
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 manager for Minecraft servers. It allows server admins to control what
LuckPerms is a permissions plugin for Minecraft servers. It allows server admins to control what
features players can use by creating groups and assigning permissions.
</p>
<h4>Useful Links</h4>
@@ -79,8 +79,7 @@ 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" +
" d) the plugin in the stacktrace is incorrectly 'shading' the LuckPerms API into its jar\n";
" (call the #get method in onEnable, not the constructor!)\n";
NotLoadedException() {
super(MESSAGE);
@@ -63,7 +63,6 @@ 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,7 +67,6 @@ 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,7 +71,6 @@ 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,7 +88,6 @@ 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,7 +75,6 @@ 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,7 +76,6 @@ 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} (commonly one of the values in {@link DataTypeFilter}).
* {@code predicate}.
*
* @param predicate the predicate
* @return the data type filter function
+83
View File
@@ -0,0 +1,83 @@
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
@@ -1,348 +0,0 @@
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
@@ -0,0 +1,45 @@
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
@@ -0,0 +1,32 @@
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
@@ -0,0 +1,55 @@
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
@@ -0,0 +1,36 @@
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 (Exception e) {
} catch (ClassNotFoundException e) {
return true;
}
}
@@ -221,12 +221,10 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
* - https://hub.spigotmc.org/jira/browse/SPIGOT-5546
* - https://github.com/PaperMC/Paper/pull/3509
*/
if (getConfiguration().get(ConfigKeys.VAULT_ENABLED)) {
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getLoader().getName(), "Vault");
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getLoader().getName(), "Vault");
// Provide vault support
tryVaultHook(false);
}
// Provide vault support
tryVaultHook(false);
}
@Override
@@ -239,10 +237,6 @@ 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);
@@ -353,7 +347,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
try {
Class.forName(className);
return true;
} catch (Exception e) {
} catch (ClassNotFoundException e) {
return false;
}
}
@@ -25,46 +25,85 @@
package me.lucko.luckperms.bukkit.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
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 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 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 DetachedContextManager<Player, Player> {
public class BukkitContextManager extends ContextManager<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 @Nullable QueryOptionsSupplier getQueryOptionsSupplier(Player subject) {
Objects.requireNonNull(subject, "subject");
LuckPermsPermissible permissible = PermissibleInjector.get(subject);
if (permissible != null) {
return permissible.getQueryOptionsSupplier();
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);
}
return null;
}
@Override
public void customizeQueryOptions(Player subject, QueryOptions.Builder builder) {
if (subject.isOp()) {
builder.option(OP_OPTION, true);
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();
if (subject.isOp()) {
queryOptions.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.QueryOptionsSupplier;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
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,
* it is safe to call Player#hasPermission asynchronously.
* is 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 QueryOptionsSupplier queryOptionsSupplier;
private final QueryOptionsCache<Player> 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().createQueryOptionsSupplier(player);
this.queryOptionsSupplier = plugin.getContextManager().getCacheFor(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.invalidateCache();
this.queryOptionsSupplier.invalidate();
}
// but we don't need to do anything else in this method, unlike the CB impl.
@@ -316,10 +316,6 @@ public class LuckPermsPermissible extends PermissibleBase {
return this.plugin;
}
public QueryOptionsSupplier getQueryOptionsSupplier() {
return this.queryOptionsSupplier;
}
PermissibleBase getOldPermissible() {
return this.oldPermissible;
}
@@ -413,7 +409,9 @@ 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,7 +30,6 @@ 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;
@@ -162,19 +161,6 @@ 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,6 +253,9 @@ 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,26 +36,12 @@ 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.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) {
@@ -94,15 +80,7 @@ public class BukkitPlatformListener implements Listener {
@EventHandler
public void onPluginEnable(PluginEnableEvent e) {
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) {
if (e.getPlugin().getName().equalsIgnoreCase("Vault")) {
this.plugin.tryVaultHook(true);
}
}
+18 -59
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: true
use-server-uuid-cache: false
@@ -83,7 +83,7 @@ use-server-uuid-cache: true
#
# - A H2 database is the default option.
# - If you want to edit data manually in "traditional" storage files, we suggest using YAML.
storage-method: postgresql
storage-method: h2
# 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: luckperms
database: minecraft
# Credentials for the database.
username: '^{POSTGRES_USER}'
password: '^{POSTGRES_PASSWORD}'
username: root
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: 120000
keepalive-time: 0
# This setting controls the maximum number of milliseconds that the plugin will wait for a
# connection from the pool, before timing out.
connection-timeout: 10000 # 10 seconds
connection-timeout: 5000 # 5 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: false
watch-files: true
# 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: false
broadcast-received-log-entries: true
# Settings for Keydb.
# Settings for Redis.
# Port 6379 is used by default; set address to "host:port" if differs
# Multiple Keydb nodes can be specified in the same format as a string list under the name "addresses".
# Multiple Redis nodes can be specified in the same format as a string list under the name "addresses".
redis:
enabled: true
enabled: false
address: localhost
username: 'default'
password: 'bookchin'
username: ''
password: ''
# 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: true
require-sender-group-membership-to-modify: false
# 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,19 +627,8 @@ 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
@@ -730,33 +719,3 @@ 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
@@ -0,0 +1,47 @@
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
@@ -0,0 +1,28 @@
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 (Exception e) {
} catch (ClassNotFoundException e) {
return true;
}
}
@@ -25,13 +25,25 @@
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.SimpleContextManager;
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 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);
}
@@ -40,4 +52,35 @@ public class BungeeContextManager extends SimpleContextManager<ProxiedPlayer, Pr
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,33 +611,3 @@ 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
+102
View File
@@ -0,0 +1,102 @@
plugins {
id("java-library")
id("jacoco")
}
test {
useJUnitPlatform {
if (!project.hasProperty('dockerTests')) {
excludeTags 'docker'
}
}
}
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.testcontainers:junit-jupiter:1.19.8"
testImplementation 'org.mockito:mockito-core:5.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
testImplementation 'com.h2database:h2:2.1.214'
testImplementation 'org.mongodb:mongodb-driver-legacy:4.5.0'
testImplementation 'org.spongepowered:configurate-yaml:3.7.2'
testImplementation 'org.spongepowered:configurate-hocon:3.7.2'
testImplementation 'me.lucko.configurate:configurate-toml:3.7'
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
@@ -0,0 +1 @@
// nothing special to do here (yet)!
@@ -26,8 +26,8 @@
package me.lucko.luckperms.common.api.implementation;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.metastacking.MetaStackDefinition;
@@ -193,7 +193,7 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
}
public void cleanup() {
this.cache.values().removeIf(value -> !((UsageTracked) value).usedInTheLast(2, TimeUnit.MINUTES));
this.cache.values().removeIf(value -> ((UsageTracked) value).usedSince(TimeUnit.MINUTES.toMillis(2)));
}
@Override
@@ -25,20 +25,14 @@
package me.lucko.luckperms.common.cacheddata;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
public abstract class UsageTracked {
@VisibleForTesting
protected long lastUsed = System.currentTimeMillis();
private long lastUsed = System.currentTimeMillis();
public void recordUsage() {
this.lastUsed = System.currentTimeMillis();
}
public boolean usedInTheLast(long duration, TimeUnit unit) {
return this.lastUsed > System.currentTimeMillis() - unit.toMillis(duration);
public boolean usedSince(long duration) {
return this.lastUsed > System.currentTimeMillis() - duration;
}
}
@@ -1,57 +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.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,23 +25,30 @@
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> extends AbstractResult<Integer, N, IntegerResult<N>> {
public final class IntegerResult<N extends Node> implements Result<Integer, 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
@@ -66,6 +73,19 @@ public final class IntegerResult<N extends Node> extends AbstractResult<Integer,
}
}
@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,6 +25,7 @@
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;
@@ -35,14 +36,19 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* @param <N> the node type
*/
public final class StringResult<N extends Node> extends AbstractResult<String, N, StringResult<N>> {
public final class StringResult<N extends Node> implements Result<String, 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
@@ -50,6 +56,19 @@ public final class StringResult<N extends Node> extends AbstractResult<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,6 +27,7 @@ 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;
@@ -35,16 +36,20 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of a {@link PermissionCalculator} lookup.
*/
public final class TristateResult extends AbstractResult<Tristate, Node, TristateResult> {
public final class TristateResult implements Result<Tristate, Node> {
/** 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;
}
@@ -53,6 +58,11 @@ public final class TristateResult extends AbstractResult<Tristate, Node, Tristat
return this.result;
}
@Override
public @Nullable Node node() {
return this.node;
}
public @Nullable Class<? extends PermissionProcessor> processorClass() {
return this.processorClass;
}
@@ -69,6 +79,14 @@ public final class TristateResult extends AbstractResult<Tristate, Node, Tristat
}
}
public @Nullable TristateResult overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(TristateResult overriddenResult) {
this.overriddenResult = overriddenResult;
}
@Override
public String toString() {
return "TristateResult(" +
@@ -79,7 +79,6 @@ 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;
@@ -102,7 +101,7 @@ public class CommandManager {
.build()
);
private final AtomicBoolean executingCommand = new AtomicBoolean(false);
private final Set<UUID> playerRateLimit = ExpiringSet.newExpiringSet(500, TimeUnit.MILLISECONDS);
private final ExpiringSet<UUID> playerRateLimit = new ExpiringSet<>(500, TimeUnit.MILLISECONDS);
private final TabCompletions tabCompletions;
private final Map<String, Command<?>> mainCommands;
@@ -155,20 +154,11 @@ 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() && !Sender.CONSOLE_UUID.equals(uniqueId) && !this.playerRateLimit.add(uniqueId)) {
if (this.plugin.getConfiguration().get(ConfigKeys.COMMANDS_RATE_LIMIT) && !sender.isConsole() && !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);
@@ -295,14 +285,6 @@ 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,7 +31,6 @@ 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;
@@ -67,7 +66,7 @@ public abstract class ChildCommand<T> extends Command<T> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.append(Component.join(Component.space(), argUsages))
.build();
}
@@ -35,7 +35,6 @@ 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;
@@ -115,7 +114,7 @@ public abstract class GenericChildCommand {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.append(Component.join(Component.space(), argUsages))
.build();
}
@@ -33,7 +33,6 @@ 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;
@@ -71,7 +70,7 @@ public abstract class SingleCommand extends Command<Void> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.append(Component.join(Component.space(), argUsages))
.build();
}
@@ -32,143 +32,143 @@ import me.lucko.luckperms.common.sender.Sender;
*/
public enum CommandPermission {
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),
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),
CREATE_GROUP("creategroup", Type.NONE, false),
DELETE_GROUP("deletegroup", Type.NONE, false),
LIST_GROUPS("listgroups", Type.NONE, true),
CREATE_GROUP("creategroup", Type.NONE),
DELETE_GROUP("deletegroup", Type.NONE),
LIST_GROUPS("listgroups", Type.NONE),
CREATE_TRACK("createtrack", Type.NONE, false),
DELETE_TRACK("deletetrack", Type.NONE, false),
LIST_TRACKS("listtracks", Type.NONE, true),
CREATE_TRACK("createtrack", Type.NONE),
DELETE_TRACK("deletetrack", Type.NONE),
LIST_TRACKS("listtracks", Type.NONE),
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),
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),
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),
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),
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),
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),
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),
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),
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);
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);
public static final String ROOT = "luckperms.";
@@ -176,11 +176,9 @@ public enum CommandPermission {
private final String permission;
private final Type type;
private final boolean readOnly;
CommandPermission(String node, Type type, boolean readOnly) {
CommandPermission(String node, Type type) {
this.type = type;
this.readOnly = readOnly;
if (type == Type.NONE) {
this.node = node;
@@ -207,10 +205,6 @@ public enum CommandPermission {
return this.type;
}
public boolean isReadOnly() {
return this.readOnly;
}
public enum Type {
NONE(null),
@@ -28,14 +28,14 @@ package me.lucko.luckperms.common.config;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.type.SimpleMetaValueSelector;
import me.lucko.luckperms.common.config.generic.KeyedConfiguration;
import me.lucko.luckperms.common.config.generic.key.ConfigKey;
import me.lucko.luckperms.common.config.generic.key.SimpleConfigKey;
import me.lucko.luckperms.common.context.calculator.WorldNameRewriter;
import me.lucko.luckperms.common.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.model.PrimaryGroupHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.query.QueryOptionsBuilderImpl;
@@ -246,26 +246,6 @@ public final class ConfigKeys {
*/
public static final ConfigKey<Boolean> REQUIRE_SENDER_GROUP_MEMBERSHIP_TO_MODIFY = booleanKey("require-sender-group-membership-to-modify", false);
/**
* If the plugin is in read-only mode for players
*/
public static final ConfigKey<Boolean> READ_ONLY_MODE_PLAYERS = booleanKey("commands-read-only-mode.players", false);
/**
* If the plugin is in read-only mode for the server console
*/
public static final ConfigKey<Boolean> READ_ONLY_MODE_CONSOLE = booleanKey("commands-read-only-mode.console", false);
/**
* If LuckPerms commands are disabled for players
*/
public static final ConfigKey<Boolean> DISABLE_LUCKPERMS_COMMANDS_PLAYERS = booleanKey("disable-luckperms-commands.players", false);
/**
* If LuckPerms commands are disabled for the server console
*/
public static final ConfigKey<Boolean> DISABLE_LUCKPERMS_COMMANDS_CONSOLE = booleanKey("disable-luckperms-commands.console", false);
/**
* If wildcards are being applied
*/
@@ -390,16 +370,16 @@ public final class ConfigKeys {
/**
* Creates a new prefix MetaStack element based upon the configured values.
*/
public static final ConfigKey<MetaStackDefinition> PREFIX_FORMATTING_OPTIONS = key(c -> {
List<String> format = c.getStringList("meta-formatting.prefix.format", new ArrayList<>());
public static final ConfigKey<MetaStackDefinition> PREFIX_FORMATTING_OPTIONS = key(l -> {
List<String> format = l.getStringList("meta-formatting.prefix.format", new ArrayList<>());
if (format.isEmpty()) {
format.add("highest");
}
String startSpacer = c.getString("meta-formatting.prefix.start-spacer", "");
String middleSpacer = c.getString("meta-formatting.prefix.middle-spacer", " ");
String endSpacer = c.getString("meta-formatting.prefix.end-spacer", "");
String startSpacer = l.getString("meta-formatting.prefix.start-spacer", "");
String middleSpacer = l.getString("meta-formatting.prefix.middle-spacer", " ");
String endSpacer = l.getString("meta-formatting.prefix.end-spacer", "");
DuplicateRemovalFunction duplicateRemovalFunction;
switch (c.getString("meta-formatting.prefix.duplicates", "").toLowerCase(Locale.ROOT)) {
switch (l.getString("meta-formatting.prefix.duplicates", "").toLowerCase(Locale.ROOT)) {
case "first-only":
duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY;
break;
@@ -411,22 +391,22 @@ public final class ConfigKeys {
break;
}
return new SimpleMetaStackDefinition(StandardStackElements.parseList(c.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
});
/**
* Creates a new suffix MetaStack element based upon the configured values.
*/
public static final ConfigKey<MetaStackDefinition> SUFFIX_FORMATTING_OPTIONS = key(c -> {
List<String> format = c.getStringList("meta-formatting.suffix.format", new ArrayList<>());
public static final ConfigKey<MetaStackDefinition> SUFFIX_FORMATTING_OPTIONS = key(l -> {
List<String> format = l.getStringList("meta-formatting.suffix.format", new ArrayList<>());
if (format.isEmpty()) {
format.add("highest");
}
String startSpacer = c.getString("meta-formatting.suffix.start-spacer", "");
String middleSpacer = c.getString("meta-formatting.suffix.middle-spacer", " ");
String endSpacer = c.getString("meta-formatting.suffix.end-spacer", "");
String startSpacer = l.getString("meta-formatting.suffix.start-spacer", "");
String middleSpacer = l.getString("meta-formatting.suffix.middle-spacer", " ");
String endSpacer = l.getString("meta-formatting.suffix.end-spacer", "");
DuplicateRemovalFunction duplicateRemovalFunction;
switch (c.getString("meta-formatting.suffix.duplicates", "").toLowerCase(Locale.ROOT)) {
switch (l.getString("meta-formatting.suffix.duplicates", "").toLowerCase(Locale.ROOT)) {
case "first-only":
duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY;
break;
@@ -438,7 +418,7 @@ public final class ConfigKeys {
break;
}
return new SimpleMetaStackDefinition(StandardStackElements.parseList(c.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
});
/**
@@ -493,12 +473,6 @@ public final class ConfigKeys {
*/
public static final ConfigKey<Boolean> COMMANDS_RATE_LIMIT = booleanKey("commands-rate-limit", true);
/**
* If the Vault API bridge should be registered on platforms that support it (Bukkit).
* Disabled by default; the remaining vault-* keys below only take effect when this is true.
*/
public static final ConfigKey<Boolean> VAULT_ENABLED = booleanKey("vault-enabled", false);
/**
* If Vault lookups for offline players on the main server thread should be enabled
*/
@@ -611,16 +585,6 @@ public final class ConfigKeys {
return c.getString("data.mongodb-connection-uri", c.getString("data.mongodb_connection_URI", ""));
}));
/**
* The REST storage URL
*/
public static final ConfigKey<String> REST_STORAGE_URL = notReloadable(stringKey("data.rest-url", "http://localhost:8080/"));
/**
* The REST storage auth key
*/
public static final ConfigKey<String> REST_STORAGE_AUTH_KEY = notReloadable(stringKey("data.rest-auth-key", ""));
/**
* The name of the storage method being used
*/
@@ -1,90 +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.config.generic.adapter;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
public class FileSecretConfigAdapter extends StringBasedConfigurationAdapter {
private static final String PREFIX = "luckperms_";
private final LuckPermsPlugin plugin;
private final Path directory;
public FileSecretConfigAdapter(LuckPermsPlugin plugin, String directory) {
this.plugin = plugin;
this.directory = directory == null ? null : Paths.get(directory);
}
public FileSecretConfigAdapter(LuckPermsPlugin plugin) {
this(plugin, System.getenv("LUCKPERMS_FILE_SECRET_DIRECTORY"));
}
@Override
protected @Nullable String resolveValue(String path) {
if (this.directory == null) {
return null; // not configured
}
// e.g.
// 'server' -> luckperms_server
// 'data.table_prefix' -> luckperms_data_table_prefix
String key = PREFIX + path.toLowerCase(Locale.ROOT)
.replace('-', '_')
.replace('.', '_');
Path resolvedFile = this.directory.resolve(key);
String value;
try {
value = new String(Files.readAllBytes(resolvedFile), StandardCharsets.UTF_8);
} catch (IOException e) {
return null;
}
String printableValue = ConfigKeys.shouldCensorValue(path) ? "*****" : value;
this.plugin.getLogger().info(String.format("Resolved configuration value from file secret: %s = %s", key, printableValue));
return value;
}
@Override
public LuckPermsPlugin getPlugin() {
return this.plugin;
}
@Override
public void reload() {
// no-op
}
}
@@ -46,10 +46,9 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* Base implementation of {@link ContextManager}.
* Base implementation of {@link ContextManager} which caches content lookups.
*
* @param <S> the subject type
* @param <P> the player type
* @param <S> the calculator type
*/
public abstract class ContextManager<S, P extends S> {
@@ -78,10 +77,14 @@ public abstract class ContextManager<S, P extends S> {
public abstract UUID getUniqueId(P player);
public abstract QueryOptions getQueryOptions(S subject);
public abstract QueryOptionsSupplier getCacheFor(S subject);
public QueryOptions getQueryOptions(S subject) {
return getCacheFor(subject).getQueryOptions();
}
public ImmutableContextSet getContext(S subject) {
return getQueryOptions(subject).context();
return getCacheFor(subject).getContextSet();
}
public QueryOptions getStaticQueryOptions() {
@@ -92,13 +95,11 @@ public abstract class ContextManager<S, P extends S> {
return getStaticQueryOptions().context();
}
public void customizeStaticQueryOptions(QueryOptions.Builder builder) {
// overridden
public QueryOptions formQueryOptions(ImmutableContextSet contextSet) {
return this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(contextSet).build();
}
public void customizeQueryOptions(S subject, QueryOptions.Builder builder) {
// overridden
}
public abstract QueryOptions formQueryOptions(S subject, ImmutableContextSet contextSet);
public void signalContextUpdate(S subject) {
if (subject == null) {
@@ -112,7 +113,7 @@ public abstract class ContextManager<S, P extends S> {
this.plugin.getEventDispatcher().dispatchContextUpdate(subject);
}
public abstract void invalidateCache(S subject);
protected abstract void invalidateCache(S subject);
public void registerCalculator(ContextCalculator<? super S> calculator) {
String calculatorClass = calculator.getClass().getName();
@@ -156,9 +157,7 @@ public abstract class ContextManager<S, P extends S> {
callContextCalculator(calculator, subject, consumer);
}
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeQueryOptions(subject, builder);
return builder.build();
return formQueryOptions(subject, accumulator.build());
}
private QueryOptions calculateStatic() {
@@ -169,9 +168,7 @@ public abstract class ContextManager<S, P extends S> {
callStaticContextCalculator(calculator, consumer);
}
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeStaticQueryOptions(builder);
return builder.build();
return formQueryOptions(accumulator.build());
}
public ImmutableContextSet getPotentialContexts() {
@@ -1,73 +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.context.manager;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.concurrent.TimeUnit;
/**
* Implementation of {@link ContextManager} which utilises 'detached' supplier caches stored alongside the subject instances.
*/
public abstract class DetachedContextManager<S, P extends S> extends ContextManager<S, P> {
private final LoadingCache<S, QueryOptions> fallbackContextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
protected DetachedContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
super(plugin, subjectClass, playerClass);
}
@Override
public QueryOptions getQueryOptions(S subject) {
QueryOptionsSupplier supplier = getQueryOptionsSupplier(subject);
if (supplier != null) {
return supplier.getQueryOptions();
}
return this.fallbackContextsCache.get(subject);
}
@Override
public void invalidateCache(S subject) {
QueryOptionsSupplier queryOptionsSupplier = getQueryOptionsSupplier(subject);
if (queryOptionsSupplier != null) {
queryOptionsSupplier.invalidateCache();
}
this.fallbackContextsCache.invalidate(subject);
}
public QueryOptionsSupplier createQueryOptionsSupplier(S subject) {
return new QueryOptionsCache<>(subject, this);
}
public abstract @Nullable QueryOptionsSupplier getQueryOptionsSupplier(S subject);
}
@@ -23,23 +23,22 @@
* SOFTWARE.
*/
package me.lucko.luckperms.neoforge;
package me.lucko.luckperms.common.context.manager;
import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler;
import com.github.benmanes.caffeine.cache.LoadingCache;
import net.luckperms.api.query.QueryOptions;
import java.util.concurrent.Executor;
public final class InlineQueryOptionsSupplier<T> implements QueryOptionsSupplier {
private final T key;
private final LoadingCache<T, QueryOptions> cache;
public class NeoForgeSchedulerAdapter extends AbstractJavaScheduler {
private final Executor sync;
public NeoForgeSchedulerAdapter(LPNeoForgeBootstrap bootstrap) {
super(bootstrap);
this.sync = r -> bootstrap.getServer().orElseThrow(() -> new IllegalStateException("Server not ready")).executeBlocking(r);
public InlineQueryOptionsSupplier(T key, LoadingCache<T, QueryOptions> cache) {
this.key = key;
this.cache = cache;
}
@Override
public Executor sync() {
return this.sync;
public QueryOptions getQueryOptions() {
return this.cache.get(this.key);
}
}
@@ -37,11 +37,11 @@ import java.util.concurrent.TimeUnit;
*
* @param <T> the player type
*/
final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
public final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
private final T subject;
private final ContextManager<T, ?> contextManager;
QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
public QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
super(50L, TimeUnit.MILLISECONDS); // expire roughly every tick
this.subject = subject;
this.contextManager = contextManager;
@@ -61,9 +61,4 @@ final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements
public ImmutableContextSet getContextSet() {
return get().context();
}
@Override
public void invalidateCache() {
invalidate();
}
}
@@ -39,6 +39,4 @@ public interface QueryOptionsSupplier {
return getQueryOptions().context();
}
void invalidateCache();
}
@@ -44,14 +44,14 @@ public enum Dependency {
ASM(
"org.ow2.asm",
"asm",
"9.8",
"h26raoPa7K1cpn65/KuwY8l7WuuM8fynqYns3hdSIFE="
"9.1",
"zaTeRV+rSP8Ly3xItGOUR9TehZp6/DCglKmG8JNr66I="
),
ASM_COMMONS(
"org.ow2.asm",
"asm-commons",
"9.8",
"MwGhwctMWfzFKSZI2sHXxa7UwPBn376IhzuM3+d0BPQ="
"9.1",
"r8sm3B/BLAxKma2mcJCN2C4Y38SIyvXuklRplrRwwAw="
),
JAR_RELOCATOR(
"me.lucko",
@@ -62,28 +62,28 @@ public enum Dependency {
ADVENTURE(
"me{}lucko",
"adventure-api",
"4.21.1",
"kQJlZ0gUxdTRRkskT43qiy2kpt9s654LvB0nqoCP6YE=",
"4.13.0",
"gcmYlY1KDrzbnE9nB8rA44oBZKPSMvv9kNRefoEuud0=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM(
"me{}lucko",
"adventure-platform-api",
"4.21.1",
"4.13.0",
"Kk8IkEMVa9ITBfC3yocpcXQiZ9CwN9VxeWjKUD8I0n0=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM_BUKKIT(
"me{}lucko",
"adventure-platform-bukkit",
"4.21.1",
"NffwBnfT/Mc6VpsmTcaXPvckv9T4vPJD83adt0C8vao=",
"4.13.3",
"ZXw7pf+a6tjGdB2WO1rB/KQ+ETLAH8kQFSu3El7jopY=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM_BUNGEECORD(
"me{}lucko",
"adventure-platform-bungeecord",
"4.21.1",
"4.13.3",
"7hnndD6dO6LZoRbtOBdn6OFK0y/T2PqNLHxCg6zaQlo=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
@@ -97,15 +97,15 @@ public enum Dependency {
CAFFEINE(
"com{}github{}ben-manes{}caffeine",
"caffeine",
"3.2.0",
"7EEd/fDAPyUhhkjOiYYWMLcWgOWFippyeOusjlXKs9c=",
"2.9.0",
"VFMotEO3XLbTHfRKfL3m36GlN72E/dzRFH9B5BJiX2o=",
Relocation.of("caffeine", "com{}github{}benmanes{}caffeine")
),
OKIO(
"com{}squareup{}" + RelocationHelper.OKIO_STRING,
RelocationHelper.OKIO_STRING,
"1.17.6",
"joiwVVI8yAYT37hE1Zh0DhCtpi9L2YMEzdFAxYVMw7Y=",
"1.17.5",
"Gaf/SNhtPPRJf38lD78pX0MME6Uo3Vt7ID+CGAK4hq0=",
Relocation.of(RelocationHelper.OKIO_STRING, RelocationHelper.OKIO_STRING)
),
OKHTTP(
@@ -119,8 +119,8 @@ public enum Dependency {
BYTEBUDDY(
"net{}bytebuddy",
"byte-buddy",
"1.15.11",
"+giZiq4ee9roO94HEsUOhETXHA4MGWuyJHrejUrQ65A=",
"1.10.22",
"+TGtxDkxd6+lJExHJXqDlV4n/gR8QJN4xu2gkPsHSoQ=",
Relocation.of("bytebuddy", "net{}bytebuddy")
),
COMMODORE(
@@ -140,22 +140,22 @@ public enum Dependency {
MARIADB_DRIVER(
"org{}mariadb{}jdbc",
"mariadb-java-client",
"3.5.2",
"8vPDwaO9rKad0dThzYrtB1JC/HKuQUY924LjZ7OI9q0=",
"3.1.3",
"ESl+5lYkJsScgTh8hgFTy8ExxMPQQkktT20tl6s6HKU=",
Relocation.of("mariadb", "org{}mariadb{}jdbc")
),
MYSQL_DRIVER(
"com{}mysql",
"mysql-connector-j",
"9.3.0",
"bI5mkrUhN22JvFYYwWzer4xhhUMp9PolZ37Qh3bFu3Y=",
"mysql",
"mysql-connector-java",
"8.0.23",
"/31bQCr9OcEnh0cVBaM6MEEDsjjsG3pE6JNtMynadTU=",
Relocation.of("mysql", "com{}mysql")
),
POSTGRESQL_DRIVER(
"org{}postgresql",
"postgresql",
"42.7.6",
"8qHMA1LdXlxvZdut/ye+4Awy5DLGrQMNB0R/ilmDxCo=",
"42.6.0",
"uBfGekDJQkn9WdTmhuMyftDT0/rkJrINoPHnVlLPxGE=",
Relocation.of("postgresql", "org{}postgresql")
),
H2_DRIVER_LEGACY(
@@ -179,124 +179,124 @@ public enum Dependency {
SQLITE_DRIVER(
"org.xerial",
"sqlite-jdbc",
"3.49.1.0",
"XIYJ0so0HeuMb3F3iXS1ukmVx9MtfHyJ2TkqPnLDkpE="
"3.28.0",
"k3hOVtv1RiXgbJks+D9w6cG93Vxq0dPwEwjIex2WG2A="
// we don't apply relocations to sqlite - it gets loaded via
// an isolated classloader
),
HIKARI(
"com{}zaxxer",
"HikariCP",
"6.3.0",
"B8Y0QFmvMKE1FEIJx8i9ZmuIIxJEIuyFmGTSCdSrfKE=",
"4.0.3",
"fAJK7/HBBjV210RTUT+d5kR9jmJNF/jifzCi6XaIxsk=",
Relocation.of("hikari", "com{}zaxxer{}hikari")
),
SLF4J_SIMPLE(
"org.slf4j",
"slf4j-simple",
"1.7.36",
"Lzm+2UPWJN+o9BAtBXEoOhCHC2qjbxl6ilBvFHAQwQ8="
"1.7.30",
"i5J5y/9rn4hZTvrjzwIDm2mVAw7sAj7UOSh0jEFnD+4="
),
SLF4J_API(
"org.slf4j",
"slf4j-api",
"1.7.36",
"0+9XXj5JeWeNwBvx3M5RAhSTtNEft/G+itmCh3wWocA="
"1.7.30",
"zboHlk0btAoHYUhcax6ML4/Z6x0ZxTkorA1/lRAQXFc="
),
MONGODB_DRIVER_CORE(
"org.mongodb",
"mongodb-driver-core",
"5.5.0",
"69tQuKep52lbYvX2YM+J6GGlYkNySXkMBDuk6BqtsJE=",
"4.5.0",
"awqoW0ImUcrCTA2d1rDCjDLEjLMCrOjKWIcC7E+zLGA=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
MONGODB_DRIVER_LEGACY(
"org.mongodb",
"mongodb-driver-legacy",
"5.5.0",
"yo/0wEdLw0/Md1xqgEd/iqiKV+t0AqAcdnS1TNAaygM=",
"4.5.0",
"77KZGIr3KZmzBpN69rGOLXmnlJIBCXRl/U4gEIdlFhY=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
MONGODB_DRIVER_SYNC(
"org.mongodb",
"mongodb-driver-sync",
"5.5.0",
"nFECiREXgMc5Ikamvmnzaxumhz75NKG+ajIhAW/ioPI=",
"4.5.0",
"q9XDSGJjlo/Ek6jHoCbqWnaK/dghB8y9aDM0hCLiSvk=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
MONGODB_DRIVER_BSON(
"org.mongodb",
"bson",
"5.5.0",
"hQx5w0v/DuQvASpnGXkLuWxkhXhewDTTAmrifPWbBJQ=",
"4.5.0",
"6CFyEzxbdeiBEXdDBmcgqWs5dvicgFkBLU3MlQUIqRA=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
JEDIS(
"redis.clients",
"jedis",
"5.2.0",
"3U+9osED8xmrSVrbK8GQYTmEB0bP1MZrJ3ENGvmDgtQ=",
"4.4.3",
"wwwoCDPCywcfoNwpvwP95kXYusXSTtXhuVrB31sxE0k=",
Relocation.of("jedis", "redis{}clients{}jedis"),
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
),
NATS(
"io.nats",
"jnats",
"2.21.1",
"QHUHUCnCCy/oRSwoqhy0245SrvD4lwCfc+ZVmemHXLg=",
"2.16.4",
"/WZgFi9iJToRGIiGoShlwE5aHwstOuNGZyr4UaBRilM=",
Relocation.of("nats", "io{}nats{}client")
),
RABBITMQ(
"com{}rabbitmq",
"amqp-client",
"5.25.0",
"WqlvAFCEE56xB32UtV3GQo7KfafizFPqtEp3M5H4qo8=",
"5.12.0",
"CxliwVWAnPKi5BwxCu1S1SGzx5fbhTk5JCKdBS27P2c=",
Relocation.of("rabbitmq", "com{}rabbitmq")
),
COMMONS_POOL_2(
"org.apache.commons",
"commons-pool2",
"2.12.1",
"UnPIvIwNyiIRF1wNJ++9cijvrplomqwAGo4e+Ohy6e8=",
"2.9.0",
"vJGbQmv6+zHsxF1mUqnxN0YkZdhJ+zhz142Qw/jTWwE=",
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
),
CONFIGURATE_CORE(
"org{}spongepowered",
"configurate-core",
"3.7.3",
"06R3WDViB84WtSkHTudV8TSPxF1eQyCyfab8L7Pvo2M=",
"3.7.2",
"XF2LzWLkSV0wyQRDt33I+gDlf3t2WzxH1h8JCZZgPp4=",
Relocation.of("configurate", "ninja{}leaping{}configurate")
),
CONFIGURATE_GSON(
"org{}spongepowered",
"configurate-gson",
"3.7.3",
"QM+bGrgrzfwT9nvIvTHtR2TUEpun+RwlXIO/a9BU0Mc=",
"3.7.2",
"9S/mp3Ig9De7NNd6+2kX+L4R90bHnAosSNVbFjrl7sM=",
Relocation.of("configurate", "ninja{}leaping{}configurate")
),
CONFIGURATE_YAML(
"org{}spongepowered",
"configurate-yaml",
"3.7.3",
"a04vRkLhigIqiG/gdVvK7c1YiBQJ7k1q/kBNsS9OVDs=",
"3.7.2",
"OBfYn4nSMGZfVf2DoZhZq+G9TF1mODX/C5OOz/mkPmc=",
Relocation.of("configurate", "ninja{}leaping{}configurate")
),
SNAKEYAML(
"org.yaml",
"snakeyaml",
"1.33",
"Ef9Fl4jwoteB9WpKhtfmkgLOus0Cc9UmnErp8C8/2PA=",
"1.28",
"NURqFCFDXUXkxqwN47U3hSfVzCRGwHGD4kRHcwzh//o=",
Relocation.of("yaml", "org{}yaml{}snakeyaml")
),
CONFIGURATE_HOCON(
"org{}spongepowered",
"configurate-hocon",
"3.7.3",
"e/UDpbIrWdJNB6yMFXtrOnnNn3nptmSv/J8n46uQPNs=",
"3.7.2",
"GOORZlK1FKLzdIm7dKyyXtBdvk7Z89HARAd2H6NiWSY=",
Relocation.of("configurate", "ninja{}leaping{}configurate"),
Relocation.of("hocon", "com{}typesafe{}config")
),
@@ -143,7 +143,7 @@ public class DependencyRegistry {
try {
Class.forName(className);
return true;
} catch (Exception e) {
} catch (ClassNotFoundException e) {
return false;
}
}
@@ -138,7 +138,7 @@ public enum DependencyRepository {
}
/**
* Downloads the {@code dependency} to the {@code file}, ensuring the
* Downloads the the {@code dependency} to the {@code file}, ensuring the
* downloaded bytes match the checksum.
*
* @param dependency the dependency to download
@@ -231,11 +231,11 @@ public final class EventDispatcher {
}
public void dispatchNodeChanges(PermissionHolder target, DataType dataType, Difference<Node> changes) {
if (changes.isEmpty()) {
if (!this.eventBus.shouldPost(NodeAddEvent.class) && !this.eventBus.shouldPost(NodeRemoveEvent.class)) {
return;
}
if (!this.eventBus.shouldPost(NodeAddEvent.class) && !this.eventBus.shouldPost(NodeRemoveEvent.class)) {
if (changes.isEmpty()) {
return;
}
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.extension;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.classpath.URLClassLoaderAccess;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.extension.Extension;
@@ -39,6 +40,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@@ -113,6 +115,7 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
}
String className;
boolean useParentClassLoader = false;
try (JarFile jar = new JarFile(path.toFile())) {
JarEntry extensionJarEntry = jar.getJarEntry("extension.json");
if (extensionJarEntry == null) {
@@ -125,14 +128,8 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
JsonObject parsed = GsonProvider.parser().parse(reader).getAsJsonObject();
className = parsed.get("class").getAsString();
// check for deprecated flags
if (parsed.has("useParentClassLoader")) {
boolean useParentClassLoader = parsed.get("useParentClassLoader").getAsBoolean();
if (useParentClassLoader) {
this.plugin.getLogger().warn("Extension '" + className + "' specifies the 'useParentClassLoader' extension flag, which has been deprecated/removed. " +
"The extension will be loaded using the classloader of the LuckPerms plugin, and not the classloader of the platform, which may break functionality.");
}
useParentClassLoader = parsed.get("useParentClassLoader").getAsBoolean();
}
}
}
@@ -142,7 +139,15 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
throw new IllegalArgumentException("class is null");
}
this.plugin.getBootstrap().getClassPathAppender().addJarToClasspath(path);
if (useParentClassLoader && isJarInJar()) {
try {
addJarToParentClasspath(path);
} catch (Throwable e) {
throw new RuntimeException("Exception whilst classloading extension", e);
}
} else {
this.plugin.getBootstrap().getClassPathAppender().addJarToClasspath(path);
}
Class<? extends Extension> extensionClass;
try {
@@ -186,6 +191,21 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
return this.extensions.stream().map(e -> e.instance).collect(Collectors.toSet());
}
private static boolean isJarInJar() {
String thisClassLoaderName = SimpleExtensionManager.class.getClassLoader().getClass().getName();
return thisClassLoaderName.equals("me.lucko.luckperms.common.loader.JarInJarClassLoader");
}
@Deprecated
private static void addJarToParentClasspath(Path path) throws Exception {
ClassLoader parentClassLoader = SimpleExtensionManager.class.getClassLoader().getParent();
if (!(parentClassLoader instanceof URLClassLoader)) {
throw new RuntimeException("useParentClassLoader is true but parent is not a URLClassLoader");
}
URLClassLoaderAccess.create(((URLClassLoader) parentClassLoader)).addURL(path.toUri().toURL());
}
private static final class LoadedExtension {
private final Extension instance;
private final Path path;
@@ -179,13 +179,6 @@ public interface Message {
.color(GRAY)
);
Args0 COMMANDS_DISABLED = () -> prefixed(translatable()
// "&3LuckPerms commands are disabled."
.key("luckperms.commandsystem.commands-disabled")
.color(DARK_AQUA)
.append(FULL_STOP)
);
Args2<String, String> FIRST_TIME_SETUP = (label, username) -> joinNewline(
// "&3It seems that no permissions have been setup yet!"
// "&3Before you can use any of the LuckPerms commands in-game, you need to use the console to give yourself access."
@@ -54,14 +54,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class LuckPermsMessagingService extends AsyncInterface implements InternalMessagingService, IncomingMessageConsumer {
private final LuckPermsPlugin plugin;
private final Set<UUID> receivedMessages;
private final ExpiringSet<UUID> receivedMessages;
private final PushUpdateBuffer updateBuffer;
private final MessengerProvider messengerProvider;
@@ -75,7 +74,7 @@ public class LuckPermsMessagingService extends AsyncInterface implements Interna
this.messenger = messengerProvider.obtain(this);
Objects.requireNonNull(this.messenger, "messenger");
this.receivedMessages = ExpiringSet.newExpiringSet(5, TimeUnit.MINUTES);
this.receivedMessages = new ExpiringSet<>(5, TimeUnit.MINUTES);
this.updateBuffer = new PushUpdateBuffer(plugin);
}
@@ -86,6 +86,6 @@ public class SqlMessenger extends AbstractSqlMessenger {
@Override
protected String getTableName() {
return this.sqlStorage.getStatementProcessor().process("{prefix}messenger");
return this.sqlStorage.getStatementProcessor().apply("{prefix}messenger");
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.metastack;
package me.lucko.luckperms.common.metastacking;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.metastack;
package me.lucko.luckperms.common.metastacking;
import com.google.common.collect.ImmutableList;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.metastack;
package me.lucko.luckperms.common.metastacking;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -220,11 +220,8 @@ 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 origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
return track != null && origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
}
@Override
@@ -253,11 +250,8 @@ 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 !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName()));
return track != null && !track.containsGroup(origin.getName());
}
@Override
@@ -311,7 +305,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 !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && this.groupName.equals(origin.getName()));
return !this.groupName.equals(origin.getName());
}
@Override
@@ -431,8 +431,8 @@ public abstract class PermissionHolder {
Difference<Node> result = getData(dataType).removeIf(Node::hasExpired);
if (!result.isEmpty()) {
invalidateCache();
this.plugin.getEventDispatcher().dispatchNodeChanges(this, dataType, result);
}
this.plugin.getEventDispatcher().dispatchNodeChanges(this, dataType, result);
return !result.isEmpty();
}
@@ -30,7 +30,6 @@ import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.ExpiringSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@@ -42,16 +41,16 @@ public class UserHousekeeper implements Runnable {
private final UserManager<?> userManager;
// contains the uuids of users who have recently logged in / out
private final Set<UUID> recentlyUsed;
private final ExpiringSet<UUID> recentlyUsed;
// contains the uuids of users who have recently been retrieved from the API
private final Set<UUID> recentlyUsedApi;
private final ExpiringSet<UUID> recentlyUsedApi;
public UserHousekeeper(LuckPermsPlugin plugin, UserManager<?> userManager, TimeoutSettings timeoutSettings) {
this.plugin = plugin;
this.userManager = userManager;
this.recentlyUsed = ExpiringSet.newExpiringSet(timeoutSettings.duration, timeoutSettings.unit);
this.recentlyUsedApi = ExpiringSet.newExpiringSet(5, TimeUnit.MINUTES);
this.recentlyUsed = new ExpiringSet<>(timeoutSettings.duration, timeoutSettings.unit);
this.recentlyUsedApi = new ExpiringSet<>(5, TimeUnit.MINUTES);
}
// called when a player attempts a connection or logs out
@@ -72,7 +72,7 @@ public final class StandardNodeMatchers {
return new TypeEquals<>(type);
}
public static class Generic extends ConstraintNodeMatcher<Node> {
private static class Generic extends ConstraintNodeMatcher<Node> {
Generic(Comparison comparison, String value) {
super(comparison, value);
}
@@ -83,7 +83,7 @@ public final class StandardNodeMatchers {
}
}
public static final class NodeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private static final class NodeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private final T node;
private final NodeEqualityPredicate equalityPredicate;
@@ -93,14 +93,6 @@ public final class StandardNodeMatchers {
this.equalityPredicate = equalityPredicate;
}
public T getNode() {
return this.node;
}
public NodeEqualityPredicate getEqualityPredicate() {
return this.equalityPredicate;
}
@SuppressWarnings("unchecked")
@Override
public @Nullable T filterConstraintMatch(@NonNull Node node) {
@@ -111,16 +103,9 @@ public final class StandardNodeMatchers {
}
}
public static final class MetaKeyEquals extends ConstraintNodeMatcher<MetaNode> {
private final String metaKey;
private static final class MetaKeyEquals extends ConstraintNodeMatcher<MetaNode> {
MetaKeyEquals(String metaKey) {
super(Comparison.SIMILAR, Meta.key(metaKey, Comparison.WILDCARD));
this.metaKey = metaKey;
}
public String getMetaKey() {
return this.metaKey;
}
@Override
@@ -129,7 +114,7 @@ public final class StandardNodeMatchers {
}
}
public static final class TypeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private static final class TypeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private final NodeType<? extends T> type;
TypeEquals(NodeType<? extends T> type) {
@@ -137,10 +122,6 @@ public final class StandardNodeMatchers {
this.type = type;
}
public NodeType<? extends T> getType() {
return this.type;
}
@Override
public @Nullable T filterConstraintMatch(@NonNull Node node) {
return this.type.tryCast(node).orElse(null);
@@ -210,17 +210,12 @@ public enum ShorthandParser {
* Implements an iterator over a given range of ints.
*/
private abstract static class RangeIterator implements Iterator<String> {
private static final int MAX_RANGE = 250;
private final int max;
private int next;
RangeIterator(int a, int b) {
this.max = Math.max(a, b);
this.next = Math.min(a, b);
if ((this.max - this.next) > MAX_RANGE) {
throw new IllegalArgumentException("Range too large");
}
}
protected abstract String toString(int i);
@@ -33,7 +33,6 @@ import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.EnvironmentVariableConfigAdapter;
import me.lucko.luckperms.common.config.generic.adapter.FileSecretConfigAdapter;
import me.lucko.luckperms.common.config.generic.adapter.MultiConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.SystemPropertyConfigAdapter;
import me.lucko.luckperms.common.context.calculator.ConfigurationContextCalculator;
@@ -62,7 +61,6 @@ import me.lucko.luckperms.common.storage.misc.DataConstraints;
import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.SyncTask;
import me.lucko.luckperms.common.treeview.AsyncPermissionRegistry;
import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.webeditor.socket.WebEditorSocket;
@@ -94,10 +92,10 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// init during load
private DependencyManager dependencyManager;
private TranslationManager translationManager;
private AsyncPermissionRegistry permissionRegistry;
private VerboseHandler verboseHandler;
// init during enable
private VerboseHandler verboseHandler;
private PermissionRegistry permissionRegistry;
private LogDispatcher logDispatcher;
private LuckPermsConfiguration configuration;
private OkHttpClient httpClient;
@@ -130,18 +128,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.translationManager.reload();
// load some utilities early
this.permissionRegistry = new AsyncPermissionRegistry(getBootstrap().getScheduler());
this.verboseHandler = new VerboseHandler(getBootstrap().getScheduler());
// load configuration
getLogger().info("Loading configuration...");
ConfigurationAdapter configFileAdapter = provideConfigurationAdapter();
this.configuration = new LuckPermsConfiguration(this, new MultiConfigurationAdapter(this,
new FileSecretConfigAdapter(this),
new SystemPropertyConfigAdapter(this),
new EnvironmentVariableConfigAdapter(this),
configFileAdapter
));
this.permissionRegistry = new PermissionRegistry(getBootstrap().getScheduler());
}
public final void enable() {
@@ -151,9 +138,19 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// send the startup banner
Message.STARTUP_BANNER.send(getConsoleSender(), getBootstrap());
// setup log dispatcher instance early
// load some utilities early
this.verboseHandler = new VerboseHandler(getBootstrap().getScheduler());
this.logDispatcher = new LogDispatcher(this);
// load configuration
getLogger().info("Loading configuration...");
ConfigurationAdapter configFileAdapter = provideConfigurationAdapter();
this.configuration = new LuckPermsConfiguration(this, new MultiConfigurationAdapter(this,
new SystemPropertyConfigAdapter(this),
new EnvironmentVariableConfigAdapter(this),
configFileAdapter
));
// setup a bytebin instance
this.httpClient = new OkHttpClient.Builder()
.callTimeout(15, TimeUnit.SECONDS)
@@ -208,11 +205,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.syncTaskBuffer = new SyncTask.Buffer(this);
// register commands
if (skipCommandRegistration()) {
getLogger().warn("LuckPerms commands are disabled in the configuration for both console and players. Skipping command registration.");
} else {
registerCommands();
}
registerCommands();
// load internal managers
getLogger().info("Loading internal permission managers...");
@@ -392,11 +385,6 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
return configFile;
}
protected boolean skipCommandRegistration() {
return getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE) &&
getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
}
@Override
public PluginLogger getLogger() {
return getBootstrap().getPluginLogger();
@@ -23,24 +23,36 @@
* SOFTWARE.
*/
package me.lucko.luckperms.neoforge;
package me.lucko.luckperms.common.plugin.classpath;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurateConfigAdapter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.nio.file.Path;
public class NeoForgeConfigAdapter extends ConfigurateConfigAdapter {
public NeoForgeConfigAdapter(LuckPermsPlugin plugin, Path path) {
super(plugin, path);
public class ReflectionClassPathAppender implements ClassPathAppender {
private final URLClassLoaderAccess classLoaderAccess;
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");
}
}
public ReflectionClassPathAppender(LuckPermsBootstrap bootstrap) throws IllegalStateException {
this(bootstrap.getClass().getClassLoader());
}
@Override
protected ConfigurationLoader<? extends ConfigurationNode> createLoader(Path path) {
return HoconConfigurationLoader.builder().setPath(path).build();
public void addJarToClasspath(Path file) {
try {
this.classLoaderAccess.addURL(file.toUri().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,190 @@
/*
* 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.plugin.classpath;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
/**
* Provides access to {@link URLClassLoader}#addURL.
*/
public abstract class URLClassLoaderAccess {
/**
* Creates a {@link URLClassLoaderAccess} for the given class loader.
*
* @param classLoader the class loader
* @return the access object
*/
public static URLClassLoaderAccess create(URLClassLoader classLoader) {
if (Reflection.isSupported()) {
return new Reflection(classLoader);
} else if (Unsafe.isSupported()) {
return new Unsafe(classLoader);
} else {
return Noop.INSTANCE;
}
}
private final URLClassLoader classLoader;
protected URLClassLoaderAccess(URLClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Adds the given URL to the class loader.
*
* @param url the URL to add
*/
public abstract void addURL(@NonNull URL url);
private static void throwError(Throwable cause) throws UnsupportedOperationException {
throw new UnsupportedOperationException("LuckPerms is unable to inject into the plugin URLClassLoader.\n" +
"You may be able to fix this problem by adding the following command-line argument " +
"directly after the 'java' command in your start script: \n'--add-opens java.base/java.lang=ALL-UNNAMED'", cause);
}
/**
* Accesses using reflection, not supported on Java 9+.
*/
private static class Reflection extends URLClassLoaderAccess {
private static final Method ADD_URL_METHOD;
static {
Method addUrlMethod;
try {
addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
} catch (Exception e) {
addUrlMethod = null;
}
ADD_URL_METHOD = addUrlMethod;
}
private static boolean isSupported() {
return ADD_URL_METHOD != null;
}
Reflection(URLClassLoader classLoader) {
super(classLoader);
}
@Override
public void addURL(@NonNull URL url) {
try {
ADD_URL_METHOD.invoke(super.classLoader, url);
} catch (ReflectiveOperationException e) {
URLClassLoaderAccess.throwError(e);
}
}
}
/**
* Accesses using sun.misc.Unsafe, supported on Java 9+.
*
* @author Vaishnav Anil (https://github.com/slimjar/slimjar)
*/
private static class Unsafe extends URLClassLoaderAccess {
private static final sun.misc.Unsafe UNSAFE;
static {
sun.misc.Unsafe unsafe;
try {
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (sun.misc.Unsafe) unsafeField.get(null);
} catch (Throwable t) {
unsafe = null;
}
UNSAFE = unsafe;
}
private static boolean isSupported() {
return UNSAFE != null;
}
private final Collection<URL> unopenedURLs;
private final Collection<URL> pathURLs;
@SuppressWarnings("unchecked")
Unsafe(URLClassLoader classLoader) {
super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
} catch (Throwable e) {
unopenedURLs = null;
pathURLs = null;
}
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset);
}
@Override
public void addURL(@NonNull URL url) {
if (this.unopenedURLs == null || this.pathURLs == null) {
URLClassLoaderAccess.throwError(new NullPointerException("unopenedURLs or pathURLs"));
}
synchronized (this.unopenedURLs) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
}
}
private static class Noop extends URLClassLoaderAccess {
private static final Noop INSTANCE = new Noop();
private Noop() {
super(null);
}
@Override
public void addURL(@NonNull URL url) {
URLClassLoaderAccess.throwError(null);
}
}
}
@@ -54,7 +54,7 @@ final class FlagUtils {
private static byte toByte0(Set<Flag> settings) {
byte b = 0;
for (Flag setting : settings) {
b |= (byte) (1 << setting.ordinal());
b |= 1 << setting.ordinal();
}
return b;
}
@@ -56,7 +56,7 @@ public class QueryOptionsImpl implements QueryOptions {
// computed based on state above
private final int hashCode;
private Set<Flag> flagsSet = null;
private final ContextSatisfyMode overrideContextSatisfyMode;
private final ContextSatisfyMode contextSatisfyMode;
QueryOptionsImpl(QueryMode mode, @Nullable ImmutableContextSet context, byte flags, @Nullable Map<OptionKey<?>, Object> options) {
this.mode = mode;
@@ -65,7 +65,7 @@ public class QueryOptionsImpl implements QueryOptions {
this.options = options == null ? null : ImmutableMap.copyOf(options);
this.hashCode = calculateHashCode();
this.overrideContextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY);
this.contextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY);
}
@Override
@@ -119,7 +119,7 @@ public class QueryOptionsImpl implements QueryOptions {
public boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode) {
switch (this.mode) {
case CONTEXTUAL:
return contextSet.isSatisfiedBy(this.context, this.overrideContextSatisfyMode == null ? defaultContextSatisfyMode : this.overrideContextSatisfyMode);
return contextSet.isSatisfiedBy(this.context, this.contextSatisfyMode == null ? defaultContextSatisfyMode : this.contextSatisfyMode);
case NON_CONTEXTUAL:
return true;
default:
@@ -26,11 +26,8 @@
package me.lucko.luckperms.common.sender;
import com.google.common.collect.Iterables;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.luckperms.api.util.Tristate;
@@ -100,19 +97,6 @@ public final class AbstractSender<T> implements Sender {
return isConsole() || this.factory.hasPermission(this.sender, permission);
}
@Override
public boolean hasPermission(CommandPermission permission) {
boolean readOnlyMode = isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_PLAYERS);
if (readOnlyMode && !permission.isReadOnly()) {
return false;
}
return hasPermission(permission.getPermission());
}
@Override
public void performCommand(String commandLine) {
this.factory.performCommand(this.sender, commandLine);
@@ -171,7 +155,7 @@ public final class AbstractSender<T> implements Sender {
case 1:
return input.get(0);
default:
return Component.join(JoinConfiguration.separator(Component.empty()), input);
return Component.join(Component.empty(), input);
}
});
}
@@ -25,8 +25,6 @@
package me.lucko.luckperms.common.sender;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.util.Tristate;
@@ -49,16 +47,6 @@ public abstract class DummyConsoleSender implements Sender {
return true;
}
@Override
public boolean hasPermission(CommandPermission permission) {
boolean readOnlyMode = this.platform.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_CONSOLE);
if (readOnlyMode && !permission.isReadOnly()) {
return false;
}
return true;
}
@Override
public void performCommand(String commandLine) {
@@ -132,7 +132,9 @@ public interface Sender {
* @param permission the permission to check for
* @return true if the sender has the permission
*/
boolean hasPermission(CommandPermission permission);
default boolean hasPermission(CommandPermission permission) {
return hasPermission(permission.getPermission());
}
/**
* Makes the sender perform a command.
@@ -37,7 +37,6 @@ import me.lucko.luckperms.common.storage.implementation.file.loader.JsonLoader;
import me.lucko.luckperms.common.storage.implementation.file.loader.TomlLoader;
import me.lucko.luckperms.common.storage.implementation.file.loader.YamlLoader;
import me.lucko.luckperms.common.storage.implementation.mongodb.MongoStorage;
import me.lucko.luckperms.common.storage.implementation.rest.RestStorage;
import me.lucko.luckperms.common.storage.implementation.split.SplitStorage;
import me.lucko.luckperms.common.storage.implementation.split.SplitStorageType;
import me.lucko.luckperms.common.storage.implementation.sql.SqlStorage;
@@ -130,12 +129,6 @@ public class StorageFactory {
this.plugin.getConfiguration().get(ConfigKeys.MONGODB_COLLECTION_PREFIX),
this.plugin.getConfiguration().get(ConfigKeys.MONGODB_CONNECTION_URI)
);
case REST:
return new RestStorage(
this.plugin,
this.plugin.getConfiguration().get(ConfigKeys.REST_STORAGE_URL),
this.plugin.getConfiguration().get(ConfigKeys.REST_STORAGE_AUTH_KEY)
);
case YAML:
return new SeparatedConfigurateStorage(this.plugin, "YAML", new YamlLoader(), ".yml", "yaml-storage");
case JSON:
@@ -51,9 +51,6 @@ public enum StorageType {
SQLITE("SQLite", "sqlite"),
H2("H2", "h2"),
// REST
REST("REST", "rest"),
// Custom
CUSTOM("Custom", "custom");
@@ -36,7 +36,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -98,7 +97,7 @@ public class FileWatcher extends AbstractFileWatcher {
private final Path path;
/** A set of files which have been modified recently */
private final Set<String> recentlyModifiedFiles = ExpiringSet.newExpiringSet(4, TimeUnit.SECONDS);
private final ExpiringSet<String> recentlyModifiedFiles = new ExpiringSet<>(4, TimeUnit.SECONDS);
/** The listener callback functions */
private final List<Consumer<Path>> callbacks = new CopyOnWriteArrayList<>();
@@ -25,7 +25,6 @@
package me.lucko.luckperms.common.storage.implementation.mongodb;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
@@ -587,8 +586,7 @@ public class MongoStorage implements StorageImplementation {
return new Document("_id", track.getName()).append("groups", track.getGroups());
}
@VisibleForTesting
static Document nodeToDoc(Node node) {
private static Document nodeToDoc(Node node) {
Document document = new Document()
.append("key", node.getKey())
.append("value", node.getValue());
@@ -605,8 +603,7 @@ public class MongoStorage implements StorageImplementation {
return document;
}
@VisibleForTesting
static Node nodeFromDoc(Document document) {
private static Node nodeFromDoc(Document document) {
String key = document.containsKey("permission") ? document.getString("permission") : document.getString("key");
if (key == null || key.isEmpty()) {
@@ -625,7 +622,7 @@ public class MongoStorage implements StorageImplementation {
}
if (document.containsKey("expiry")) {
builder.expiry(((Number) document.get("expiry")).longValue());
builder.expiry((long) document.get("expiry"));
}
if (document.containsKey("context") && document.get("context") instanceof List) {
@@ -1,664 +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.storage.implementation.rest;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFields;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.Constraint;
import me.lucko.luckperms.common.filter.Filter;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher;
import me.lucko.luckperms.common.node.matcher.StandardNodeMatchers;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageMetadata;
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
import me.lucko.luckperms.common.storage.misc.NodeEntry;
import me.lucko.luckperms.common.storage.misc.PlayerSaveResultImpl;
import me.lucko.luckperms.common.util.Difference;
import me.lucko.luckperms.common.util.Iterators;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.model.PlayerSaveResult;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeBuilder;
import net.luckperms.api.node.NodeType;
import net.luckperms.rest.LuckPermsRestClient;
import net.luckperms.rest.model.ActionPage;
import net.luckperms.rest.model.Context;
import net.luckperms.rest.model.CreateGroupRequest;
import net.luckperms.rest.model.CreateTrackRequest;
import net.luckperms.rest.model.CreateUserRequest;
import net.luckperms.rest.model.GroupSearchResult;
import net.luckperms.rest.model.Health;
import net.luckperms.rest.model.UpdateTrackRequest;
import net.luckperms.rest.model.UpdateUserRequest;
import net.luckperms.rest.model.UserLookupResult;
import net.luckperms.rest.model.UserSearchResult;
import org.checkerframework.checker.nullness.qual.Nullable;
import retrofit2.Response;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class RestStorage implements StorageImplementation {
private final LuckPermsPlugin plugin;
private final LuckPermsRestClient client;
public RestStorage(LuckPermsPlugin plugin, String baseUrl, String apiKey) {
this.plugin = plugin;
this.client = LuckPermsRestClient.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.build();
}
@Override
public LuckPermsPlugin getPlugin() {
return this.plugin;
}
@Override
public String getImplementationName() {
return "REST";
}
@Override
public void init() throws IOException {
Health health = this.client.misc().health().execute().body();
if (health == null || !health.healthy()) {
this.plugin.getLogger().warn("REST storage endpoint is unhealthy");
}
}
@Override
public void shutdown() {
this.client.close();
}
@Override
public StorageMetadata getMeta() {
StorageMetadata metadata = new StorageMetadata();
boolean success = true;
long start = System.currentTimeMillis();
try {
Health health = this.client.misc().health().execute().body();
if (health == null || !health.healthy()) {
success = false;
}
} catch (IOException e) {
success = false;
}
if (success) {
int duration = (int) (System.currentTimeMillis() - start);
metadata.ping(duration);
}
metadata.connected(success);
return metadata;
}
@Override
public void logAction(Action entry) throws IOException {
this.client.actions().submit(convertAction(entry)).execute();
}
@Override
public LogPage getLogPage(FilterList<Action> filters, @Nullable PageParameters page) throws Exception {
Response<ActionPage> resp = null;
if (filters.isEmpty()) {
// ActionFilters.all()
resp = page != null
? this.client.actions().query(page.pageSize(), page.pageNumber()).execute()
: this.client.actions().query().execute();
} else if (filters.size() == 1) {
// ActionFilters.source(uniqueId)
Filter<Action, ?> filter = filters.get(0);
if (filter.field() == ActionFields.SOURCE_UNIQUE_ID && filter.constraint().comparison() == Comparison.EQUAL) {
UUID uniqueId = (UUID) filter.constraint().value();
resp = page != null
? this.client.actions().querySource(uniqueId, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().querySource(uniqueId).execute();
}
} else if (filters.operator() == FilterList.LogicalOperator.AND && filters.size() == 2) {
Filter<Action, ?> filterA = filters.get(0);
Filter<Action, ?> filterB = filters.get(1);
if (filterA.field() == ActionFields.TARGET_TYPE && filterA.constraint().comparison() == Comparison.EQUAL) {
Action.Target.Type type = (Action.Target.Type) filterA.constraint().value();
if (type == Action.Target.Type.USER) {
// ActionFilters.user(uniqueId)
if (filterB.field() == ActionFields.TARGET_UNIQUE_ID && filterB.constraint().comparison() == Comparison.EQUAL) {
UUID uniqueId = (UUID) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetUser(uniqueId, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetUser(uniqueId).execute();
}
} else if (type == Action.Target.Type.GROUP) {
// ActionFilters.group(name)
if (filterB.field() == ActionFields.TARGET_NAME && filterB.constraint().comparison() == Comparison.EQUAL) {
String name = (String) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetGroup(name, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetGroup(name).execute();
}
} else if (type == Action.Target.Type.TRACK) {
// ActionFilters.track(name)
if (filterB.field() == ActionFields.TARGET_NAME && filterB.constraint().comparison() == Comparison.EQUAL) {
String name = (String) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetTrack(name, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetTrack(name).execute();
}
}
}
} else if (filters.operator() == FilterList.LogicalOperator.OR && filters.size() == 3) {
// ActionFilters.search(query)
ImmutableList<FilterField<Action, String>> searchFields = ImmutableList.of(ActionFields.SOURCE_NAME, ActionFields.TARGET_NAME, ActionFields.DESCRIPTION);
if (filters.stream().allMatch(filter -> filter.constraint().comparison() == Comparison.SIMILAR && searchFields.contains(filter.field()))) {
String query = (String) filters.get(0).constraint().value();
if (!query.startsWith("%") || !query.endsWith("%")) {
throw new IllegalArgumentException("Unsupported search query: " + query);
}
String searchTerm = query.substring(1, query.length() - 1);
resp = page != null
? this.client.actions().querySearch(searchTerm, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().querySearch(searchTerm).execute();
}
}
if (resp == null) {
throw new UnsupportedOperationException("Unsupported filter: " + filters);
}
ActionPage body = Objects.requireNonNull(resp.body(), "resp.body()");
return LogPage.of(
body.entries().stream().map(RestStorage::convertAction).collect(Collectors.toList()),
page,
body.overallSize()
);
}
@Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public User loadUser(UUID uniqueId, String username) throws Exception {
net.luckperms.rest.model.User remoteUser = this.client.users().get(uniqueId).execute().body();
if (remoteUser == null) {
throw new IllegalStateException("Client did not return a user for " + uniqueId);
}
User user = this.plugin.getUserManager().getOrMake(uniqueId, username);
user.setUsername(remoteUser.username(), true);
user.loadNodesFromStorage(remoteUser.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return user;
}
@Override
public Map<UUID, User> loadUsers(Set<UUID> uniqueIds) throws Exception {
return uniqueIds.parallelStream()
.map(uniqueId -> {
try {
return loadUser(uniqueId, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toMap(User::getUniqueId, Function.identity()));
}
@Override
public void saveUser(User user) throws Exception {
Difference<Node> changes = user.normalData().exportChanges(results -> {
if (this.plugin.getUserManager().isNonDefaultUser(user)) {
return true;
}
// if the only change is adding the default node, we don't need to export
if (results.getChanges().size() == 1) {
Difference.Change<Node> onlyChange = results.getChanges().iterator().next();
return !(onlyChange.type() == Difference.ChangeType.ADD && this.plugin.getUserManager().isDefaultNode(onlyChange.value()));
}
return true;
});
if (changes == null) {
return;
}
String username = user.getUsername().orElse(null);
if (username != null) {
this.client.users().update(user.getUniqueId(), new UpdateUserRequest(username)).execute();
}
Set<Node> added = changes.getAdded();
Set<Node> removed = changes.getRemoved();
if (!removed.isEmpty()) {
this.client.users().nodesDelete(user.getUniqueId(), removed.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
if (!added.isEmpty()) {
this.client.users().nodesAdd(user.getUniqueId(), added.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
}
@Override
public Set<UUID> getUniqueUsers() throws Exception {
return this.client.users().list().execute().body();
}
@Override
public <N extends Node> List<NodeEntry<UUID, N>> searchUserNodes(ConstraintNodeMatcher<N> matcher) throws Exception {
List<UserSearchResult> results;
if (matcher instanceof StandardNodeMatchers.TypeEquals) {
NodeType<? extends N> type = ((StandardNodeMatchers.TypeEquals<N>) matcher).getType();
results = this.client.users().searchNodesByType(convertNodeType(type)).execute().body();
} else {
Constraint<String> constraint = matcher.getConstraint();
Comparison comparison = constraint.comparison();
String value = constraint.value();
if (comparison == Comparison.EQUAL) {
results = this.client.users().searchNodesByKey(value).execute().body();
} else if (comparison == Comparison.SIMILAR) {
long wildcards = value.chars().filter(ch -> ch == '%').count();
if (wildcards == 0) {
results = this.client.users().searchNodesByKey(value).execute().body();
} else if (wildcards == 1 && value.endsWith("%")) {
results = this.client.users().searchNodesByKeyStartsWith(value.substring(0, value.length() - 1)).execute().body();
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
}
if (results == null) {
throw new IllegalStateException("Client returned null results");
}
List<NodeEntry<UUID, N>> held = new ArrayList<>();
for (UserSearchResult result : results) {
UUID uniqueId = result.uniqueId();
for (net.luckperms.rest.model.Node node : result.results()) {
N match = matcher.filterConstraintMatch(convertNode(node));
if (match != null) {
held.add(NodeEntry.of(uniqueId, match));
}
}
}
return held;
}
@Override
public Group createAndLoadGroup(String name) throws Exception {
net.luckperms.rest.model.Group remoteGroup = this.client.groups().create(new CreateGroupRequest(name)).execute().body();
if (remoteGroup == null) {
remoteGroup = this.client.groups().get(name).execute().body();
if (remoteGroup == null) {
throw new IllegalStateException("Unable to create group: " + name);
}
}
Group group = this.plugin.getGroupManager().getOrMake(name);
group.loadNodesFromStorage(remoteGroup.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return group;
}
@Override
public Optional<Group> loadGroup(String name) throws Exception {
net.luckperms.rest.model.Group remoteGroup = this.client.groups().get(name).execute().body();
if (remoteGroup == null) {
return Optional.empty();
}
Group group = this.plugin.getGroupManager().getOrMake(name);
group.loadNodesFromStorage(remoteGroup.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return Optional.of(group);
}
@Override
public void loadAllGroups() throws Exception {
Set<String> groups = this.client.groups().list().execute().body();
if (groups == null) {
throw new IllegalStateException("Client returned a null list of groups");
}
if (!Iterators.tryIterate(groups, this::loadGroup)) {
throw new RuntimeException("Exception occurred whilst loading a group");
}
this.plugin.getGroupManager().retainAll(groups);
}
@Override
public void saveGroup(Group group) throws Exception {
Difference<Node> changes = group.normalData().exportChanges(c -> true);
Set<Node> added = changes.getAdded();
Set<Node> removed = changes.getRemoved();
if (!removed.isEmpty()) {
this.client.groups().nodesDelete(group.getName(), removed.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
if (!added.isEmpty()) {
this.client.groups().nodesAdd(group.getName(), added.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
}
@Override
public void deleteGroup(Group group) throws Exception {
this.client.groups().delete(group.getName()).execute();
}
@Override
public <N extends Node> List<NodeEntry<String, N>> searchGroupNodes(ConstraintNodeMatcher<N> matcher) throws Exception {
List<GroupSearchResult> results;
if (matcher instanceof StandardNodeMatchers.TypeEquals) {
NodeType<? extends N> type = ((StandardNodeMatchers.TypeEquals<N>) matcher).getType();
results = this.client.groups().searchNodesByType(convertNodeType(type)).execute().body();
} else {
Constraint<String> constraint = matcher.getConstraint();
Comparison comparison = constraint.comparison();
String value = constraint.value();
if (comparison == Comparison.EQUAL) {
results = this.client.groups().searchNodesByKey(value).execute().body();
} else if (comparison == Comparison.SIMILAR) {
long wildcards = value.chars().filter(ch -> ch == '%').count();
if (wildcards == 0) {
results = this.client.groups().searchNodesByKey(value).execute().body();
} else if (wildcards == 1 && value.endsWith("%")) {
results = this.client.groups().searchNodesByKeyStartsWith(value.substring(0, value.length() - 1)).execute().body();
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
}
if (results == null) {
throw new IllegalStateException("Client returned null results");
}
List<NodeEntry<String, N>> held = new ArrayList<>();
for (GroupSearchResult result : results) {
String name = result.name();
for (net.luckperms.rest.model.Node node : result.results()) {
N match = matcher.filterConstraintMatch(convertNode(node));
if (match != null) {
held.add(NodeEntry.of(name, match));
}
}
}
return held;
}
@Override
public Track createAndLoadTrack(String name) throws Exception {
net.luckperms.rest.model.Track remoteTrack = this.client.tracks().create(new CreateTrackRequest(name)).execute().body();
if (remoteTrack == null) {
remoteTrack = this.client.tracks().get(name).execute().body();
if (remoteTrack == null) {
throw new IllegalStateException("Unable to create track: " + name);
}
}
Track track = this.plugin.getTrackManager().getOrMake(name);
track.setGroups(remoteTrack.groups());
return track;
}
@Override
public Optional<Track> loadTrack(String name) throws Exception {
net.luckperms.rest.model.Track remoteTrack = this.client.tracks().get(name).execute().body();
if (remoteTrack == null) {
return Optional.empty();
}
Track track = this.plugin.getTrackManager().getOrMake(name);
track.setGroups(remoteTrack.groups());
return Optional.of(track);
}
@Override
public void loadAllTracks() throws Exception {
Set<String> tracks = this.client.tracks().list().execute().body();
if (tracks == null) {
throw new IllegalStateException("Client returned a null list of tracks");
}
if (!Iterators.tryIterate(tracks, this::loadTrack)) {
throw new RuntimeException("Exception occurred whilst loading a track");
}
this.plugin.getTrackManager().retainAll(tracks);
}
@Override
public void saveTrack(Track track) throws Exception {
this.client.tracks().update(track.getName(), new UpdateTrackRequest(track.getGroups())).execute();
}
@Override
public void deleteTrack(Track track) throws Exception {
this.client.tracks().delete(track.getName()).execute();
}
@Override
public PlayerSaveResult savePlayerData(UUID uniqueId, String username) throws Exception {
net.luckperms.rest.model.PlayerSaveResult remoteResult = this.client.users().create(new CreateUserRequest(uniqueId, username)).execute().body();
Set<PlayerSaveResult.Outcome> outcomes = remoteResult.outcomes().stream().map(outcome -> {
switch (outcome) {
case CLEAN_INSERT:
return PlayerSaveResult.Outcome.CLEAN_INSERT;
case NO_CHANGE:
return PlayerSaveResult.Outcome.NO_CHANGE;
case USERNAME_UPDATED:
return PlayerSaveResult.Outcome.USERNAME_UPDATED;
case OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME:
return PlayerSaveResult.Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME;
default:
throw new AssertionError(outcome);
}
}).collect(Collectors.toSet());
if (outcomes.isEmpty()) {
throw new IllegalStateException("No outcomes returned");
}
PlayerSaveResultImpl result;
if (outcomes.contains(PlayerSaveResult.Outcome.CLEAN_INSERT)) {
result = PlayerSaveResultImpl.cleanInsert();
} else if (outcomes.contains(PlayerSaveResult.Outcome.NO_CHANGE)) {
result = PlayerSaveResultImpl.noChange();
} else if (outcomes.contains(PlayerSaveResult.Outcome.USERNAME_UPDATED)) {
result = PlayerSaveResultImpl.usernameUpdated(remoteResult.previousUsername());
} else {
throw new IllegalStateException("No base outcome returned");
}
if (outcomes.contains(PlayerSaveResult.Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME)) {
result = result.withOtherUuidsPresent(remoteResult.otherUniqueIds());
}
return result;
}
@Override
public void deletePlayerData(UUID uniqueId) throws Exception {
this.client.users().delete(uniqueId, true).execute();
}
@Override
public @Nullable UUID getPlayerUniqueId(String username) throws Exception {
UserLookupResult result = this.client.users().lookup(username).execute().body();
return result == null ? null : result.uniqueId();
}
@Override
public @Nullable String getPlayerName(UUID uniqueId) throws Exception {
UserLookupResult result = this.client.users().lookup(uniqueId).execute().body();
return result == null ? null : result.username();
}
private static net.luckperms.rest.model.Action convertAction(Action action) {
return new net.luckperms.rest.model.Action(
action.getTimestamp().getEpochSecond(),
new net.luckperms.rest.model.Action.Source(
action.getSource().getUniqueId(),
action.getSource().getName()
),
new net.luckperms.rest.model.Action.Target(
action.getTarget().getUniqueId().orElse(null),
action.getTarget().getName(),
convertActionTargetType(action.getTarget().getType())
),
action.getDescription()
);
}
private static net.luckperms.rest.model.Action.Target.Type convertActionTargetType(Action.Target.Type type) {
switch (type) {
case USER:
return net.luckperms.rest.model.Action.Target.Type.USER;
case GROUP:
return net.luckperms.rest.model.Action.Target.Type.GROUP;
case TRACK:
return net.luckperms.rest.model.Action.Target.Type.TRACK;
default:
throw new AssertionError(type);
}
}
private static LoggedAction convertAction(net.luckperms.rest.model.Action action) {
return LoggedAction.build()
.timestamp(Instant.ofEpochSecond(action.timestamp()))
.source(action.source().uniqueId())
.sourceName(action.source().name())
.target(action.target().uniqueId())
.targetName(action.target().name())
.targetType(convertActionTargetType(action.target().type()))
.description(action.description())
.build();
}
private static Action.Target.Type convertActionTargetType(net.luckperms.rest.model.Action.Target.Type type) {
switch (type) {
case USER:
return Action.Target.Type.USER;
case GROUP:
return Action.Target.Type.GROUP;
case TRACK:
return Action.Target.Type.TRACK;
default:
throw new AssertionError(type);
}
}
private static Node convertNode(net.luckperms.rest.model.Node node) {
NodeBuilder<?, ?> builder = NodeBuilders.determineMostApplicable(node.key());
if (node.value() != null) {
builder.value(node.value());
}
if (node.context() != null) {
builder.context(convertContexts(node.context()));
}
if (node.expiry() != null) {
builder.expiry(node.expiry());
}
return builder.build();
}
private static net.luckperms.rest.model.Node convertNode(Node node) {
return new net.luckperms.rest.model.Node(
node.getKey(),
node.getValue(),
convertContexts(node.getContexts()),
node.getExpiry() == null ? null : node.getExpiry().getEpochSecond()
);
}
public static ContextSet convertContexts(Set<Context> contexts) {
ImmutableContextSetImpl.BuilderImpl builder = new ImmutableContextSetImpl.BuilderImpl();
for (Context context : contexts) {
builder.add(context.key(), context.value());
}
return builder.build();
}
public static Set<Context> convertContexts(ContextSet contexts) {
return StreamSupport.stream(contexts.spliterator(), false)
.map(c -> new Context(c.getKey(), c.getValue()))
.collect(Collectors.toSet());
}
public static net.luckperms.rest.model.NodeType convertNodeType(NodeType<?> type) {
if (type == NodeType.REGEX_PERMISSION) return net.luckperms.rest.model.NodeType.REGEX_PERMISSION;
if (type == NodeType.INHERITANCE) return net.luckperms.rest.model.NodeType.INHERITANCE;
if (type == NodeType.PREFIX) return net.luckperms.rest.model.NodeType.PREFIX;
if (type == NodeType.SUFFIX) return net.luckperms.rest.model.NodeType.SUFFIX;
if (type == NodeType.META) return net.luckperms.rest.model.NodeType.META;
if (type == NodeType.WEIGHT) return net.luckperms.rest.model.NodeType.WEIGHT;
if (type == NodeType.DISPLAY_NAME) return net.luckperms.rest.model.NodeType.DISPLAY_NAME;
throw new IllegalArgumentException("Invalid type: " + type.name());
}
}
@@ -32,24 +32,10 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class SchemaReader {
private SchemaReader() {}
private static final Pattern CREATE_TABLE_PATTERN = Pattern.compile("^CREATE TABLE [`\"']([^`\"']+)[`\"'].*");
private static final Pattern CREATE_INDEX_PATTERN = Pattern.compile("^CREATE INDEX.* ON [`\"']([^`\"']+)[`\"'].*");
/**
* Parses a schema file to a list of SQL statements
*
* @param is the input stream to read from
* @return a list of statements
* @throws IOException if an error occurs whilst reading the file
*/
public static List<String> getStatements(InputStream is) throws IOException {
List<String> queries = new LinkedList<>();
@@ -67,7 +53,7 @@ public final class SchemaReader {
if (line.endsWith(";")) {
sb.deleteCharAt(sb.length() - 1);
String result = sb.toString().trim().replaceAll(" +", " ");
String result = sb.toString().trim();
if (!result.isEmpty()) {
queries.add(result);
}
@@ -80,29 +66,4 @@ public final class SchemaReader {
return queries;
}
public static String tableFromStatement(String statement) {
Matcher table = CREATE_TABLE_PATTERN.matcher(statement);
if (table.matches()) {
return table.group(1).toLowerCase(Locale.ROOT);
}
Matcher index = CREATE_INDEX_PATTERN.matcher(statement);
if (index.matches()) {
return index.group(1).toLowerCase(Locale.ROOT);
}
throw new IllegalArgumentException("Unknown statement type: " + statement);
}
/**
* Filters which statements should be executed based on the current list of tables in the database
*
* @param statements the statements to filter
* @param currentTables the current tables in the database
* @return the filtered list of statements
*/
public static List<String> filterStatements(List<String> statements, List<String> currentTables) {
return statements.stream()
.filter(statement -> !currentTables.contains(tableFromStatement(statement)))
.collect(Collectors.toList());
}
}
@@ -80,6 +80,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
public class SqlStorage implements StorageImplementation {
@@ -136,7 +137,7 @@ public class SqlStorage implements StorageImplementation {
private final LuckPermsPlugin plugin;
private final ConnectionFactory connectionFactory;
private final StatementProcessor statementProcessor;
private final Function<String, String> statementProcessor;
public SqlStorage(LuckPermsPlugin plugin, ConnectionFactory connectionFactory, String tablePrefix) {
this.plugin = plugin;
@@ -158,7 +159,7 @@ public class SqlStorage implements StorageImplementation {
return this.connectionFactory;
}
public StatementProcessor getStatementProcessor() {
public Function<String, String> getStatementProcessor() {
return this.statementProcessor;
}
@@ -166,32 +167,30 @@ public class SqlStorage implements StorageImplementation {
public void init() throws Exception {
this.connectionFactory.init(this.plugin);
List<String> tables;
boolean tableExists;
try (Connection c = this.connectionFactory.getConnection()) {
tables = listTables(c);
tableExists = tableExists(c, this.statementProcessor.apply("{prefix}user_permissions"));
}
if (!tableExists) {
applySchema();
}
applySchema(tables);
}
private void applySchema(List<String> existingTables) throws IOException, SQLException {
String schemaFileName = "me/lucko/luckperms/schema/" + this.connectionFactory.getImplementationName().toLowerCase(Locale.ROOT) + ".sql";
private void applySchema() throws IOException, SQLException {
List<String> statements;
String schemaFileName = "me/lucko/luckperms/schema/" + this.connectionFactory.getImplementationName().toLowerCase(Locale.ROOT) + ".sql";
try (InputStream is = this.plugin.getBootstrap().getResourceStream(schemaFileName)) {
if (is == null) {
throw new IOException("Couldn't locate schema file for " + this.connectionFactory.getImplementationName());
}
statements = SchemaReader.getStatements(is).stream()
.map(this.statementProcessor::process)
.map(this.statementProcessor)
.collect(Collectors.toList());
}
statements = SchemaReader.filterStatements(statements, existingTables);
if (statements.isEmpty()) {
return;
}
try (Connection connection = this.connectionFactory.getConnection()) {
boolean utf8mb4Unsupported = false;
@@ -241,7 +240,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void logAction(Action entry) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(ACTION_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(ACTION_INSERT))) {
writeAction(entry, ps);
ps.execute();
}
@@ -289,7 +288,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
if (bulkUpdate.getDataType().isIncludingUsers()) {
StatementProcessor tableReplacement = s -> s.replace("{table}", "{prefix}user_permissions");
Function<String, String> tableReplacement = s -> s.replace("{table}", "{prefix}user_permissions");
BulkUpdateSqlBuilder sqlBuilder = new BulkUpdateSqlBuilder();
sqlBuilder.visit(bulkUpdate);
@@ -320,7 +319,7 @@ public class SqlStorage implements StorageImplementation {
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
StatementProcessor tableReplacement = s -> s.replace("{table}", "{prefix}group_permissions");
Function<String, String> tableReplacement = s -> s.replace("{table}", "{prefix}group_permissions");
BulkUpdateSqlBuilder sqlBuilder = new BulkUpdateSqlBuilder();
sqlBuilder.visit(bulkUpdate);
@@ -449,7 +448,7 @@ public class SqlStorage implements StorageImplementation {
public Set<UUID> getUniqueUsers() throws SQLException {
Set<UUID> uuids = new HashSet<>();
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_SELECT_DISTINCT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT_DISTINCT))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
UUID uuid = Uuids.fromString(rs.getString("uuid"));
@@ -497,7 +496,7 @@ public class SqlStorage implements StorageImplementation {
public Group createAndLoadGroup(String name) throws SQLException {
String query = GROUP_INSERT.getOrDefault(this.connectionFactory.getImplementationName(), GROUP_INSERT_DEFAULT);
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(query))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(query))) {
ps.setString(1, name);
ps.execute();
}
@@ -560,7 +559,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
deleteGroupPermissions(c, group.getName());
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_DELETE))) {
ps.setString(1, group.getName());
ps.execute();
}
@@ -663,7 +662,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void deleteTrack(Track track) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_DELETE))) {
ps.setString(1, track.getName());
ps.execute();
}
@@ -680,7 +679,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
SqlPlayerData existingPlayerData = selectPlayerData(c, uniqueId);
if (existingPlayerData == null) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_INSERT))) {
ps.setString(1, uniqueId.toString());
ps.setString(2, username);
ps.setString(3, GroupManager.DEFAULT_GROUP_NAME);
@@ -689,7 +688,7 @@ public class SqlStorage implements StorageImplementation {
} else {
oldUsername = existingPlayerData.username;
if (!username.equals(oldUsername)) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
ps.execute();
@@ -702,7 +701,7 @@ public class SqlStorage implements StorageImplementation {
Set<UUID> conflicting = new HashSet<>();
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_ALL_UUIDS_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_ALL_UUIDS_BY_USERNAME))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
@@ -716,7 +715,7 @@ public class SqlStorage implements StorageImplementation {
if (!conflicting.isEmpty()) {
// remove the mappings for conflicting uuids
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
ps.execute();
@@ -731,7 +730,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void deletePlayerData(UUID uniqueId) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_DELETE))) {
ps.setString(1, uniqueId.toString());
ps.execute();
}
@@ -742,7 +741,7 @@ public class SqlStorage implements StorageImplementation {
public UUID getPlayerUniqueId(String username) throws SQLException {
username = username.toLowerCase(Locale.ROOT);
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_UUID_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_UUID_BY_USERNAME))) {
ps.setString(1, username);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -757,7 +756,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public String getPlayerName(UUID uniqueId) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_USERNAME_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_USERNAME_BY_UUID))) {
ps.setString(1, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -873,7 +872,7 @@ public class SqlStorage implements StorageImplementation {
}
if (!deleteRows.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(deleteSpecificQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteSpecificQuery))) {
for (Long id : deleteRows) {
ps.setLong(1, id);
ps.addBatch();
@@ -882,7 +881,7 @@ public class SqlStorage implements StorageImplementation {
}
}
if (!deleteNodes.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(deleteQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteQuery))) {
for (Node node : deleteNodes) {
ps.setString(1, holder);
writeNode(node, ps);
@@ -894,7 +893,7 @@ public class SqlStorage implements StorageImplementation {
}
if (!add.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(insertQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(insertQuery))) {
for (Node node : add) {
ps.setString(1, holder);
writeNode(node, ps);
@@ -907,7 +906,7 @@ public class SqlStorage implements StorageImplementation {
private List<Node> selectUserPermissions(Connection c, UUID user) throws SQLException {
List<Node> nodes = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -922,7 +921,7 @@ public class SqlStorage implements StorageImplementation {
}
private SqlPlayerData selectPlayerData(Connection c, UUID user) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_BY_UUID))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -983,15 +982,15 @@ public class SqlStorage implements StorageImplementation {
// we don't want to use preparedstatements because the parameter length is variable
// safe to do string concat/replacement because the UUID.toString value isn't injectable
return this.statementProcessor.process(baseQuery) + param;
return this.statementProcessor.apply(baseQuery) + param;
}
private void deleteUser(Connection c, UUID user) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_DELETE))) {
ps.setString(1, user.toString());
ps.execute();
}
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, GroupManager.DEFAULT_GROUP_NAME);
ps.setString(2, user.toString());
ps.execute();
@@ -1000,7 +999,7 @@ public class SqlStorage implements StorageImplementation {
private void insertPlayerData(Connection c, UUID user, SqlPlayerData data) throws SQLException {
boolean hasPrimaryGroupSaved;
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
hasPrimaryGroupSaved = rs.next();
@@ -1009,14 +1008,14 @@ public class SqlStorage implements StorageImplementation {
if (hasPrimaryGroupSaved) {
// update
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, data.primaryGroup);
ps.setString(2, user.toString());
ps.execute();
}
} else {
// insert
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_INSERT))) {
ps.setString(1, user.toString());
ps.setString(2, data.username);
ps.setString(3, data.primaryGroup);
@@ -1027,7 +1026,7 @@ public class SqlStorage implements StorageImplementation {
private Set<String> selectGroups(Connection c) throws SQLException {
Set<String> groups = new HashSet<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
groups.add(rs.getString("name").toLowerCase(Locale.ROOT));
@@ -1039,7 +1038,7 @@ public class SqlStorage implements StorageImplementation {
private List<Node> selectGroupPermissions(Connection c, String group) throws SQLException {
List<Node> nodes = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT))) {
ps.setString(1, group);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -1054,7 +1053,7 @@ public class SqlStorage implements StorageImplementation {
}
private void selectAllGroupPermissions(Map<String, Collection<Node>> nodes, Connection c) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String holder = rs.getString("name");
@@ -1071,7 +1070,7 @@ public class SqlStorage implements StorageImplementation {
}
private void deleteGroupPermissions(Connection c, String group) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_DELETE))) {
ps.setString(1, group);
ps.execute();
}
@@ -1079,7 +1078,7 @@ public class SqlStorage implements StorageImplementation {
private List<String> selectTrack(Connection c, String name) throws SQLException {
String groups;
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT))) {
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -1094,7 +1093,7 @@ public class SqlStorage implements StorageImplementation {
private void insertTrack(Connection c, String name, List<String> groups) throws SQLException {
String json = GsonProvider.normal().toJson(groups);
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_INSERT))) {
ps.setString(1, name);
ps.setString(2, json);
ps.execute();
@@ -1103,7 +1102,7 @@ public class SqlStorage implements StorageImplementation {
private void updateTrack(Connection c, String name, List<String> groups) throws SQLException {
String json = GsonProvider.normal().toJson(groups);
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_UPDATE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_UPDATE))) {
ps.setString(1, json);
ps.setString(2, name);
ps.execute();
@@ -1112,7 +1111,7 @@ public class SqlStorage implements StorageImplementation {
private Set<String> selectTracks(Connection c) throws SQLException {
Set<String> tracks = new HashSet<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
tracks.add(rs.getString("name").toLowerCase(Locale.ROOT));
@@ -1122,14 +1121,15 @@ public class SqlStorage implements StorageImplementation {
return tracks;
}
private static List<String> listTables(Connection connection) throws SQLException {
List<String> tables = new ArrayList<>();
private static boolean tableExists(Connection connection, String table) throws SQLException {
try (ResultSet rs = connection.getMetaData().getTables(connection.getCatalog(), null, "%", null)) {
while (rs.next()) {
tables.add(rs.getString(3).toLowerCase(Locale.ROOT));
if (rs.getString(3).equalsIgnoreCase(table)) {
return true;
}
}
return false;
}
return tables;
}
private static final class SqlPlayerData {
@@ -25,13 +25,12 @@
package me.lucko.luckperms.common.storage.implementation.sql.builder;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class PreparedStatementBuilder {
private final StringBuilder sb = new StringBuilder();
@@ -57,10 +56,10 @@ public class PreparedStatementBuilder {
return this;
}
public PreparedStatement build(Connection connection, StatementProcessor processor) throws SQLException {
public PreparedStatement build(Connection connection, Function<String, String> mapping) throws SQLException {
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(processor.process(this.sb.toString()));
statement = connection.prepareStatement(mapping.apply(this.sb.toString()));
for (int i = 0; i < this.variables.size(); i++) {
String var = this.variables.get(i);
statement.setString(i + 1, var);
@@ -27,10 +27,10 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageMetadata;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Function;
public interface ConnectionFactory {
@@ -42,7 +42,7 @@ public interface ConnectionFactory {
StorageMetadata getMeta();
StatementProcessor getStatementProcessor();
Function<String, String> getStatementProcessor();
Connection getConnection() throws SQLException;
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -39,14 +38,9 @@ import java.sql.Statement;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Properties;
import java.util.function.Function;
public class H2ConnectionFactory extends FlatfileConnectionFactory {
public static final StatementProcessor STATEMENT_PROCESSOR = s -> s
.replace('\'', '`')
.replace("LIKE", "ILIKE")
.replace("value", "`value`")
.replace("``value``", "`value`");
private Constructor<?> connectionConstructor;
public H2ConnectionFactory(Path file) {
@@ -97,8 +91,11 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory {
}
@Override
public StatementProcessor getStatementProcessor() {
return STATEMENT_PROCESSOR;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`')
.replace("LIKE", "ILIKE")
.replace("value", "`value`")
.replace("``value``", "`value`");
}
/**
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.lang.reflect.Constructor;
import java.nio.file.Path;
@@ -35,6 +34,7 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Properties;
import java.util.function.Function;
public class SqliteConnectionFactory extends FlatfileConnectionFactory {
private Constructor<?> connectionConstructor;
@@ -74,7 +74,7 @@ public class SqliteConnectionFactory extends FlatfileConnectionFactory {
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`');
}
}
@@ -25,9 +25,10 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.function.Function;
public class MariaDbConnectionFactory extends DriverBasedHikariConnectionFactory {
public MariaDbConnectionFactory(StorageCredentials configuration) {
super(configuration);
@@ -54,7 +55,7 @@ public class MariaDbConnectionFactory extends DriverBasedHikariConnectionFactory
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`'); // use backticks for quotes
}
}
@@ -25,10 +25,10 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.Map;
import java.util.function.Function;
public class MySqlConnectionFactory extends DriverBasedHikariConnectionFactory {
public MySqlConnectionFactory(StorageCredentials configuration) {
@@ -80,7 +80,7 @@ public class MySqlConnectionFactory extends DriverBasedHikariConnectionFactory {
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`'); // use backticks for quotes
}
}
@@ -25,10 +25,10 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.Map;
import java.util.function.Function;
public class PostgresConnectionFactory extends DriverBasedHikariConnectionFactory {
public PostgresConnectionFactory(StorageCredentials configuration) {
@@ -65,7 +65,7 @@ public class PostgresConnectionFactory extends DriverBasedHikariConnectionFactor
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_DOUBLE_QUOTES;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '"');
}
}
@@ -1,70 +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.treeview;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
public class AsyncPermissionRegistry extends PermissionRegistry implements AutoCloseable {
/** A queue of permission strings to be added to the tree */
private final Queue<String> queue;
/** The tick task */
private final SchedulerTask task;
public AsyncPermissionRegistry(SchedulerAdapter scheduler) {
this.queue = new ConcurrentLinkedQueue<>();
this.task = scheduler.asyncRepeating(this::tick, 1, TimeUnit.SECONDS);
}
@Override
public void offer(String permission) {
if (permission == null) {
throw new NullPointerException("permission");
}
this.queue.offer(permission);
}
private void tick() {
for (String e; (e = this.queue.poll()) != null; ) {
try {
doInsert(e);
} catch (Exception ex) {
// ignore
}
}
}
@Override
public void close() {
this.task.cancel();
}
}
@@ -26,20 +26,35 @@
package me.lucko.luckperms.common.treeview;
import com.google.common.base.Splitter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
/**
* Stores a collection of all permissions known to the platform.
*/
public class PermissionRegistry {
public class PermissionRegistry implements AutoCloseable {
private static final Splitter DOT_SPLIT = Splitter.on('.').omitEmptyStrings();
/** The root node in the tree */
private final TreeNode rootNode = new TreeNode();
private final TreeNode rootNode;
/** A queue of permission strings to be added to the tree */
private final Queue<String> queue;
/** The tick task */
private final SchedulerTask task;
public PermissionRegistry(SchedulerAdapter scheduler) {
this.rootNode = new TreeNode();
this.queue = new ConcurrentLinkedQueue<>();
this.task = scheduler.asyncRepeating(this::tick, 1, TimeUnit.SECONDS);
}
public TreeNode getRootNode() {
return this.rootNode;
@@ -51,33 +66,33 @@ public class PermissionRegistry {
.collect(ImmutableCollectors.toList());
}
/**
* Offer a permission to the registry (to be potentially inserted asynchronously).
*
* @param permission the permission
*/
public void offer(String permission) {
insert(permission);
}
/**
* Insert a permission into the registry.
*
* @param permission the permission
*/
public void insert(String permission) {
if (permission == null) {
throw new NullPointerException("permission");
}
this.queue.offer(permission);
}
try {
doInsert(permission);
} catch (Exception ex) {
// ignore
private void tick() {
for (String e; (e = this.queue.poll()) != null; ) {
insert(e);
}
}
protected void doInsert(String permission) {
@Override
public void close() {
this.task.cancel();
}
public void insert(String permission) {
try {
doInsert(permission);
} catch (Exception ex) {
ex.printStackTrace();
}
}
private void doInsert(String permission) {
permission = permission.toLowerCase(Locale.ROOT);
// split the permission up into parts
@@ -29,7 +29,7 @@ import me.lucko.luckperms.common.locale.TranslationManager;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@@ -72,7 +72,7 @@ public class DurationFormatter {
* @return the formatted string
*/
public String formatString(Duration duration) {
return PlainTextComponentSerializer.plainText().serialize(TranslationManager.render(format(duration)));
return PlainComponentSerializer.plain().serialize(TranslationManager.render(format(duration)));
}
/**
@@ -25,21 +25,36 @@
package me.lucko.luckperms.common.util;
import java.util.Collections;
import java.util.Set;
import com.github.benmanes.caffeine.cache.Cache;
import java.util.concurrent.TimeUnit;
public final class ExpiringSet {
private ExpiringSet() {}
/**
* A simple expiring set implementation using Caffeine caches
*
* @param <E> element type
*/
public class ExpiringSet<E> {
private final Cache<E, Long> cache;
private final long lifetime;
/**
* An expiring set using Caffeine caches
*
* @param <E> the element type
* @return a new expiring set
*/
public static <E> Set<E> newExpiringSet(long duration, TimeUnit unit) {
return Collections.newSetFromMap(CaffeineFactory.newBuilder().expireAfterWrite(duration, unit).<E, Boolean>build().asMap());
public ExpiringSet(long duration, TimeUnit unit) {
this.cache = CaffeineFactory.newBuilder().expireAfterWrite(duration, unit).build();
this.lifetime = unit.toMillis(duration);
}
public boolean add(E item) {
boolean present = contains(item);
this.cache.put(item, System.currentTimeMillis() + this.lifetime);
return !present;
}
public boolean contains(E item) {
Long timeout = this.cache.getIfPresent(item);
return timeout != null && timeout > System.currentTimeMillis();
}
public void remove(E item) {
this.cache.invalidate(item);
}
}
@@ -43,7 +43,6 @@ import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import me.lucko.luckperms.common.verbose.event.VerboseEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.event.HoverEvent;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
@@ -223,7 +222,7 @@ public class VerboseListener {
}
// send the message
HoverEvent<Component> hoverEvent = HoverEvent.showText(Component.join(JoinConfiguration.newlines(), hover));
HoverEvent<Component> hoverEvent = HoverEvent.showText(Component.join(Component.newline(), hover));
this.notifiedSender.sendMessage(component.hoverEvent(hoverEvent));
}
Loaded 100 of 242 files, more files were not shown because too many files have changed in this diff. Show more