Update the client commands list when permissions are changed (#2262)

This commit is contained in:
Luck 2020-05-10 22:18:01 +01:00
parent 5f0df1b167
commit 9f22bf2fbf
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
7 changed files with 158 additions and 30 deletions

View File

@ -38,6 +38,7 @@ import me.lucko.luckperms.bukkit.inject.server.InjectorSubscriptionMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsDefaultsMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsPermissionMap;
import me.lucko.luckperms.bukkit.inject.server.LuckPermsSubscriptionMap;
import me.lucko.luckperms.bukkit.listeners.BukkitCommandListUpdater;
import me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener;
import me.lucko.luckperms.bukkit.listeners.BukkitPlatformListener;
import me.lucko.luckperms.bukkit.messaging.BukkitMessagingFactory;
@ -273,6 +274,12 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
});
}
// register bukkit command list updater
if (getConfiguration().get(ConfigKeys.UPDATE_CLIENT_COMMAND_LIST) && BukkitCommandListUpdater.isSupported()) {
BukkitCommandListUpdater commandListUpdater = new BukkitCommandListUpdater(this);
getApiProvider().getEventBus().subscribe(UserDataRecalculateEvent.class, commandListUpdater::onUserDataRecalculate);
}
// Load any online users (in the case of a reload)
for (Player player : this.bootstrap.getServer().getOnlinePlayers()) {
this.bootstrap.getScheduler().executeAsync(() -> {

View File

@ -0,0 +1,99 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.bukkit.listeners;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.BufferedRequest;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.event.user.UserDataRecalculateEvent;
import org.bukkit.entity.Player;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Calls {@link Player#updateCommands()} when a players permissions change.
*/
public class BukkitCommandListUpdater {
public static boolean isSupported() {
try {
Player.class.getMethod("updateCommands");
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
private final LPBukkitPlugin plugin;
private final LoadingCache<UUID, SendBuffer> sendingBuffers = CaffeineFactory.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.build(SendBuffer::new);
public BukkitCommandListUpdater(LPBukkitPlugin plugin) {
this.plugin = plugin;
}
// Called when a user's data is recalculated.
public void onUserDataRecalculate(UserDataRecalculateEvent e) {
UUID uniqueId = e.getUser().getUniqueId();
if (!this.plugin.getBootstrap().isPlayerOnline(uniqueId)) {
return;
}
// Buffer the request to send a commands update.
this.sendingBuffers.get(uniqueId).request();
}
// Called when the buffer times out.
private void sendUpdate(UUID uniqueId) {
this.plugin.getBootstrap().getScheduler().sync().execute(() -> {
Player player = this.plugin.getBootstrap().getPlayer(uniqueId).orElse(null);
if (player != null) {
player.updateCommands();
}
});
}
private final class SendBuffer extends BufferedRequest<Void> {
private final UUID uniqueId;
SendBuffer(UUID uniqueId) {
super(500, TimeUnit.MILLISECONDS, BukkitCommandListUpdater.this.plugin.getBootstrap().getScheduler());
this.uniqueId = uniqueId;
}
@Override
protected Void perform() {
sendUpdate(this.uniqueId);
return null;
}
}
}

View File

@ -608,6 +608,9 @@ allow-invalid-usernames: false
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal: false
# If LuckPerms should update the list of commands sent to the client when permissions are changed.
update-client-command-list: true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors
resolve-command-selectors: false

View File

@ -75,7 +75,7 @@ public abstract class BufferedRequest<T> {
if (this.processor != null) {
try {
return this.processor.extendAndGetFuture();
} catch (IllegalStateException e) {
} catch (ProcessorAlreadyRanException e) {
// ignore
}
}
@ -101,8 +101,8 @@ public abstract class BufferedRequest<T> {
*/
protected abstract T perform();
private static class Processor<R> {
private Supplier<R> supplier;
private static final class Processor<R> {
private final Supplier<R> supplier;
private final long delay;
private final TimeUnit unit;
@ -110,11 +110,11 @@ public abstract class BufferedRequest<T> {
private final SchedulerAdapter schedulerAdapter;
private final Object[] mutex = new Object[0];
private CompletableFuture<R> future = new CompletableFuture<>();
private final CompletableFuture<R> future = new CompletableFuture<>();
private boolean usable = true;
private SchedulerTask scheduledTask;
private BoundTask boundTask = null;
private CompletionTask boundTask = null;
Processor(Supplier<R> supplier, long delay, TimeUnit unit, SchedulerAdapter schedulerAdapter) {
this.supplier = supplier;
@ -122,32 +122,36 @@ public abstract class BufferedRequest<T> {
this.unit = unit;
this.schedulerAdapter = schedulerAdapter;
rescheduleTask();
scheduleTask();
}
private void rescheduleTask() {
private void rescheduleTask() throws ProcessorAlreadyRanException {
synchronized (this.mutex) {
if (!this.usable) {
throw new IllegalStateException("Processor not usable");
throw new ProcessorAlreadyRanException();
}
if (this.scheduledTask != null) {
this.scheduledTask.cancel();
}
this.boundTask = new BoundTask();
this.scheduledTask = this.schedulerAdapter.asyncLater(this.boundTask, this.delay, this.unit);
scheduleTask();
}
}
private void scheduleTask() {
this.boundTask = new CompletionTask();
this.scheduledTask = this.schedulerAdapter.asyncLater(this.boundTask, this.delay, this.unit);
}
CompletableFuture<R> getFuture() {
return this.future;
}
CompletableFuture<R> extendAndGetFuture() {
CompletableFuture<R> extendAndGetFuture() throws ProcessorAlreadyRanException {
rescheduleTask();
return this.future;
}
private final class BoundTask implements Runnable {
private final class CompletionTask implements Runnable {
@Override
public void run() {
synchronized (Processor.this.mutex) {
@ -172,14 +176,11 @@ public abstract class BufferedRequest<T> {
new RuntimeException("Processor " + Processor.this.supplier + " threw an exception whilst computing a result", e).printStackTrace();
Processor.this.future.completeExceptionally(e);
}
// allow supplier and future to be GCed
Processor.this.supplier = null;
Processor.this.future = null;
Processor.this.scheduledTask = null;
Processor.this.boundTask = null;
}
}
}
private static final class ProcessorAlreadyRanException extends Exception {
}

View File

@ -90,6 +90,13 @@ public class MetaCache extends SimpleMetaCache implements CachedMetaData {
return new MonitoredMetaMap(super.getMeta(origin), origin);
}
@Override
public int getWeight(MetaCheckEvent.Origin origin) {
int value = super.getWeight(origin);
this.plugin.getVerboseHandler().offerMetaCheckEvent(origin, this.verboseCheckTarget, this.metadata.getQueryOptions(), "weight", String.valueOf(value));
return value;
}
public @Nullable String getPrimaryGroup(MetaCheckEvent.Origin origin) {
String value = super.getPrimaryGroup(origin);
this.plugin.getVerboseHandler().offerMetaCheckEvent(origin, this.verboseCheckTarget, this.metadata.getQueryOptions(), "primarygroup", String.valueOf(value));

View File

@ -134,6 +134,11 @@ public final class ConfigKeys {
*/
public static final ConfigKey<Boolean> CANCEL_FAILED_LOGINS = booleanKey("cancel-failed-logins", false);
/**
* If LuckPerms should update the list of commands sent to the client when permissions are changed.
*/
public static final ConfigKey<Boolean> UPDATE_CLIENT_COMMAND_LIST = enduringKey(booleanKey("update-client-command-list", true));
/**
* If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
*/

View File

@ -35,25 +35,31 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Abstract implementation of {@link SchedulerAdapter} using a {@link ScheduledExecutorService}.
*/
public abstract class AbstractJavaScheduler implements SchedulerAdapter {
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("luckperms-scheduler")
.build()
);
private final ScheduledThreadPoolExecutor scheduler;
private final ErrorReportingExecutor schedulerWorkerPool;
private final ForkJoinPool worker;
private final ErrorReportingExecutor schedulerWorkerPool = new ErrorReportingExecutor(Executors.newCachedThreadPool(new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("luckperms-scheduler-worker-%d")
.build()
));
private final ForkJoinPool worker = new ForkJoinPool(32, ForkJoinPool.defaultForkJoinWorkerThreadFactory, (t, e) -> e.printStackTrace(), false);
public AbstractJavaScheduler() {
this.scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("luckperms-scheduler")
.build()
);
this.scheduler.setRemoveOnCancelPolicy(true);
this.schedulerWorkerPool = new ErrorReportingExecutor(Executors.newCachedThreadPool(new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("luckperms-scheduler-worker-%d")
.build()
));
this.worker = new ForkJoinPool(32, ForkJoinPool.defaultForkJoinWorkerThreadFactory, (t, e) -> e.printStackTrace(), false);
}
@Override
public Executor async() {