1
0
mirror of https://github.com/lucko/LuckPerms.git synced 2025-08-23 06:32:49 +02:00

Add API methods and command tests for new ActionLog system

This commit is contained in:
Luck
2024-06-13 20:31:36 +01:00
parent 73abf64aa3
commit 5ecbc473e9
51 changed files with 1347 additions and 300 deletions

View File

@@ -26,6 +26,7 @@
package net.luckperms.api;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.event.EventBus;
@@ -267,4 +268,14 @@ public interface LuckPerms {
@Internal
@NonNull NodeMatcherFactory getNodeMatcherFactory();
/**
* Gets the {@link ActionFilterFactory}.
*
* @return the action filter factory
* @since 5.5
*/
@Internal
@NonNull
ActionFilterFactory getActionFilterFactory();
}

View File

@@ -28,6 +28,7 @@ package net.luckperms.api.actionlog;
import net.luckperms.api.LuckPermsProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import java.time.Instant;
import java.util.Optional;
@@ -35,7 +36,10 @@ import java.util.UUID;
/**
* Represents a logged action.
*
* <p>API users should not implement this interface directly.</p>
*/
@NonExtendable
public interface Action extends Comparable<Action> {
/**
@@ -81,6 +85,7 @@ public interface Action extends Comparable<Action> {
/**
* Represents the source of an action.
*/
@NonExtendable
interface Source {
/**
@@ -102,6 +107,7 @@ public interface Action extends Comparable<Action> {
/**
* Represents the target of an action.
*/
@NonExtendable
interface Target {
/**
@@ -126,7 +132,7 @@ public interface Action extends Comparable<Action> {
@NonNull Type getType();
/**
* Represents the type of a {@link Target}.
* Represents the type of {@link Target}.
*/
enum Type {
USER, GROUP, TRACK

View File

@@ -25,6 +25,7 @@
package net.luckperms.api.actionlog;
import net.luckperms.api.actionlog.filter.ActionFilter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Unmodifiable;
@@ -40,7 +41,11 @@ import java.util.UUID;
* You can add to the log using the {@link ActionLogger}, and then request an updated copy.</p>
*
* <p>All methods are thread safe, and return immutable and thread safe collections.</p>
*
* @deprecated Use {@link ActionLogger#queryActions(ActionFilter)} or
* {@link ActionLogger#queryActions(ActionFilter, int, int)} instead.
*/
@Deprecated
public interface ActionLog {
/**

View File

@@ -25,9 +25,11 @@
package net.luckperms.api.actionlog;
import net.luckperms.api.messaging.MessagingService;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.util.Page;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -46,43 +48,78 @@ public interface ActionLogger {
* Gets a {@link ActionLog} instance from the plugin storage.
*
* @return a log instance
* @deprecated Use {@link #queryActions(ActionFilter)} or {@link #queryActions(ActionFilter, int, int)} instead. These methods
* are more efficient (they don't load the full action log into memory) and allow for pagination.
*/
@Deprecated
@NonNull CompletableFuture<ActionLog> getLog();
/**
* Submits a log entry to the plugin to be handled.
* Gets all actions from the action log matching the given {@code filter}.
*
* <p>This method submits the log to the storage provider and broadcasts
* it.</p>
* <p>If the filter is {@code null}, all actions will be returned.</p>
*
* <p>It is therefore roughly equivalent to calling
* {@link #submitToStorage(Action)} and {@link #broadcastAction(Action)},
* however, using this method is preferred to making the calls individually.</p>
* <p>Unlike {@link #queryActions(ActionFilter, int, int)}, this method does not implement any pagination and will return
* all entries at once.</p>
*
* <p>If you want to submit a log entry but don't know which method to pick,
* @param filter the filter, optional
* @return the actions
* @since 5.5
*/
@NonNull CompletableFuture<List<Action>> queryActions(@NonNull ActionFilter filter);
/**
* Gets a page of actions from the action log matching the given {@code filter}.
*
* <p>If the filter is {@code null}, all actions will be returned.</p>
*
* @param filter the filter, optional
* @param pageSize the size of the page
* @param pageNumber the page number
* @return the page of actions
* @since 5.5
*/
@NonNull CompletableFuture<Page<Action>> queryActions(@NonNull ActionFilter filter, int pageSize, int pageNumber);
/**
* Submits a logged action to LuckPerms.
*
* <p>This method submits the action to the storage provider to be persisted in the action log.
* It also broadcasts it to administrator players on the current instance and to admins on other
* connected servers if a messaging service is configured.</p>
*
* <p>It is roughly equivalent to calling
* {@link #submitToStorage(Action)} followed by {@link #broadcastAction(Action)},
* however using this method is preferred to making the calls individually.</p>
*
* <p>If you want to submit an action log entry but don't know which method to pick,
* use this one.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is submitted
*/
@NonNull CompletableFuture<Void> submit(@NonNull Action entry);
/**
* Submits a log entry to the plugins storage handler.
* Submits a logged action to LuckPerms and persists it in the storage backend.
*
* <p>This method does not broadcast the action or send it through the messaging service.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is submitted
*/
@NonNull CompletableFuture<Void> submitToStorage(@NonNull Action entry);
/**
* Submits a log entry to the plugins log broadcasting handler.
* Submits a logged action to LuckPerms and broadcasts it to administrators.
*
* <p>If enabled, this method will also dispatch the log entry via the
* plugins {@link MessagingService}.</p>
* <p>The broadcast is made to administrator players on the current instance
* and to admins on other connected servers if a messaging service is configured.</p>
*
* <p>This method does not save the action to the plugin storage backend.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is broadcasted
*/
@NonNull CompletableFuture<Void> broadcastAction(@NonNull Action entry);

View File

@@ -0,0 +1,114 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.actionlog.filter;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.actionlog.Action;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import java.util.UUID;
import java.util.function.Predicate;
/**
* A predicate filter which matches certain {@link Action}s.
*
* <p>API users should not implement this interface directly.</p>
*
* @since 5.5
*/
@NonExtendable
public interface ActionFilter extends Predicate<Action> {
/**
* Gets an {@link ActionFilter} which matches any action.
*
* @return the matcher
*/
static ActionFilter any() {
return LuckPermsProvider.get().getActionFilterFactory().any();
}
/**
* Gets an {@link ActionFilter} which matches actions with a specific source user.
*
* @param uniqueId the source user unique id
* @return the matcher
*/
static ActionFilter source(UUID uniqueId) {
return LuckPermsProvider.get().getActionFilterFactory().source(uniqueId);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific user.
*
* @param uniqueId the target user unique id
* @return the matcher
*/
static ActionFilter user(UUID uniqueId) {
return LuckPermsProvider.get().getActionFilterFactory().user(uniqueId);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific group.
*
* @param name the target group name
* @return the matcher
*/
static ActionFilter group(String name) {
return LuckPermsProvider.get().getActionFilterFactory().group(name);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific track.
*
* @param name the target track name
* @return the matcher
*/
static ActionFilter track(String name) {
return LuckPermsProvider.get().getActionFilterFactory().track(name);
}
/**
* Gets an {@link ActionFilter} which matches actions which contain a specific search query in the source name,
* target name or description.
*
* @param query the search query
* @return the matcher
*/
static ActionFilter search(String query) {
return LuckPermsProvider.get().getActionFilterFactory().search(query);
}
/**
* Tests to see if the given {@link Action} matches the filter.
*
* @param action the action to test
* @return true if the action matched
*/
@Override
boolean test(Action action);
}

View File

@@ -0,0 +1,88 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.actionlog.filter;
import org.jetbrains.annotations.ApiStatus.Internal;
import java.util.UUID;
/**
* A factory which creates {@link ActionFilter}s.
*
* @since 5.5
*/
@Internal
public interface ActionFilterFactory {
/**
* Gets an {@link ActionFilter} which matches any action.
*
* @return the matcher
*/
ActionFilter any();
/**
* Gets an {@link ActionFilter} which matches actions with a specific source user.
*
* @param uniqueId the source user unique id
* @return the matcher
*/
ActionFilter source(UUID uniqueId);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific user.
*
* @param uniqueId the target user unique id
* @return the matcher
*/
ActionFilter user(UUID uniqueId);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific group.
*
* @param name the target group name
* @return the matcher
*/
ActionFilter group(String name);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific track.
*
* @param name the target track name
* @return the matcher
*/
ActionFilter track(String name);
/**
* Gets an {@link ActionFilter} which matches actions which contain a specific search query in the source name,
* target name or description.
*
* @param query the search query
* @return the matcher
*/
ActionFilter search(String query);
}

View File

@@ -0,0 +1,29 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* {@link net.luckperms.api.actionlog.Action} filters.
*/
package net.luckperms.api.actionlog.filter;

View File

@@ -0,0 +1,53 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.util;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
/**
* Represents a page of entries.
*
* @since 5.5
*/
public interface Page<T> {
/**
* Gets the entries on this page.
*
* @return the entries
*/
@NonNull List<T> entries();
/**
* Gets the total/overall number of entries (not just the number of entries on this page).
*
* @return the total number of entries
*/
int overallSize();
}

View File

@@ -29,12 +29,14 @@ import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.commands.log.LogNotify;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.luckperms.api.event.log.LogBroadcastEvent;
import net.luckperms.api.event.log.LogNotifyEvent;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
public class LogDispatcher {
@@ -64,7 +66,12 @@ public class LogDispatcher {
return !this.plugin.getEventDispatcher().dispatchLogBroadcast(cancelled, entry, origin);
}
private void broadcast(LoggedAction entry, LogNotifyEvent.Origin origin, Sender sender) {
// broadcast the entry to online players
private void broadcast(LoggedAction entry, LogBroadcastEvent.Origin broadcastOrigin, LogNotifyEvent.Origin origin, Sender sender) {
if (!shouldBroadcast(entry, broadcastOrigin)) {
return;
}
this.plugin.getOnlineSenders()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
.filter(s -> {
@@ -74,41 +81,46 @@ public class LogDispatcher {
.forEach(s -> Message.LOG.send(s, entry));
}
public void dispatch(LoggedAction entry, Sender sender) {
// log the entry to storage
public CompletableFuture<Void> logToStorage(LoggedAction entry) {
if (!this.plugin.getEventDispatcher().dispatchLogPublish(false, entry)) {
this.plugin.getStorage().logAction(entry);
return this.plugin.getStorage().logAction(entry);
} else {
return CompletableFuture.completedFuture(null);
}
}
this.plugin.getMessagingService().ifPresent(service -> service.pushLog(entry));
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL, sender);
// log the entry to messaging
public CompletableFuture<Void> logToMessaging(LoggedAction entry) {
InternalMessagingService messagingService = this.plugin.getMessagingService().orElse(null);
if (messagingService != null) {
return messagingService.pushLog(entry);
} else {
return CompletableFuture.completedFuture(null);
}
}
// log the entry to storage and messaging, and broadcast it to online players
private CompletableFuture<Void> dispatch(LoggedAction entry, Sender sender, LogBroadcastEvent.Origin broadcastOrigin, LogNotifyEvent.Origin origin) {
CompletableFuture<Void> storageFuture = logToStorage(entry);
CompletableFuture<Void> messagingFuture = logToMessaging(entry);
broadcast(entry, broadcastOrigin, origin, sender);
return CompletableFuture.allOf(storageFuture, messagingFuture);
}
public CompletableFuture<Void> dispatch(LoggedAction entry, Sender sender) {
return dispatch(entry, sender, LogBroadcastEvent.Origin.LOCAL, LogNotifyEvent.Origin.LOCAL);
}
public CompletableFuture<Void> dispatchFromApi(LoggedAction entry) {
return dispatch(entry, null, LogBroadcastEvent.Origin.LOCAL_API, LogNotifyEvent.Origin.LOCAL_API);
}
public void broadcastFromApi(LoggedAction entry) {
this.plugin.getMessagingService().ifPresent(extendedMessagingService -> extendedMessagingService.pushLog(entry));
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL_API)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL_API, null);
}
broadcast(entry, LogBroadcastEvent.Origin.LOCAL_API, LogNotifyEvent.Origin.LOCAL_API, null);
}
public void dispatchFromApi(LoggedAction entry) {
if (!this.plugin.getEventDispatcher().dispatchLogPublish(false, entry)) {
try {
this.plugin.getStorage().logAction(entry).get();
} catch (Exception e) {
this.plugin.getLogger().warn("Error whilst storing action", e);
}
}
broadcastFromApi(entry);
}
public void dispatchFromRemote(LoggedAction entry) {
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.REMOTE)) {
broadcast(entry, LogNotifyEvent.Origin.REMOTE, null);
}
public void broadcastFromRemote(LoggedAction entry) {
broadcast(entry, LogBroadcastEvent.Origin.REMOTE, LogNotifyEvent.Origin.REMOTE, null);
}
}

View File

@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.actionlog;
import com.google.common.base.Strings;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.PermissionHolder;
@@ -44,10 +45,10 @@ import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* An implementation of {@link Action} and {@link Action.Builder},
@@ -123,15 +124,11 @@ public class LoggedAction implements Action {
return ActionComparator.INSTANCE.compare(this, other);
}
public boolean matchesSearch(String query) {
query = Objects.requireNonNull(query, "query").toLowerCase(Locale.ROOT);
return this.source.name.toLowerCase(Locale.ROOT).contains(query) ||
this.target.name.toLowerCase(Locale.ROOT).contains(query) ||
this.description.toLowerCase(Locale.ROOT).contains(query);
}
public void submit(LuckPermsPlugin plugin, Sender sender) {
plugin.getLogDispatcher().dispatch(this, sender);
CompletableFuture<Void> future = plugin.getLogDispatcher().dispatch(this, sender);
if (plugin.getConfiguration().get(ConfigKeys.LOG_SYNCHRONOUSLY_IN_COMMANDS)) {
future.join();
}
}
@Override

View File

@@ -25,6 +25,7 @@
package me.lucko.luckperms.common.api;
import me.lucko.luckperms.common.api.implementation.ApiActionFilterFactory;
import me.lucko.luckperms.common.api.implementation.ApiActionLogger;
import me.lucko.luckperms.common.api.implementation.ApiContextManager;
import me.lucko.luckperms.common.api.implementation.ApiGroupManager;
@@ -47,6 +48,7 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.messaging.MessagingService;
import net.luckperms.api.messenger.MessengerProvider;
@@ -225,4 +227,8 @@ public class LuckPermsApiProvider implements LuckPerms {
return ApiNodeMatcherFactory.INSTANCE;
}
@Override
public @NonNull ActionFilterFactory getActionFilterFactory() {
return ApiActionFilterFactory.INSTANCE;
}
}

View File

@@ -0,0 +1,47 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.filter.FilterList;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.filter.ActionFilter;
public class ApiActionFilter implements ActionFilter {
private final FilterList<Action> filter;
public ApiActionFilter(FilterList<Action> filter) {
this.filter = filter;
}
@Override
public boolean test(Action action) {
return this.filter.evaluate(action);
}
public FilterList<Action> getFilter() {
return this.filter;
}
}

View File

@@ -0,0 +1,77 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import java.util.Objects;
import java.util.UUID;
public final class ApiActionFilterFactory implements ActionFilterFactory {
public static final ApiActionFilterFactory INSTANCE = new ApiActionFilterFactory();
private ApiActionFilterFactory() {
}
@Override
public ActionFilter any() {
return new ApiActionFilter(ActionFilters.all());
}
@Override
public ActionFilter source(UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uniqueId");
return new ApiActionFilter(ActionFilters.source(uniqueId));
}
@Override
public ActionFilter user(UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uniqueId");
return new ApiActionFilter(ActionFilters.user(uniqueId));
}
@Override
public ActionFilter group(String name) {
Objects.requireNonNull(name, "name");
return new ApiActionFilter(ActionFilters.group(name));
}
@Override
public ActionFilter track(String name) {
Objects.requireNonNull(name, "name");
return new ApiActionFilter(ActionFilters.track(name));
}
@Override
public ActionFilter search(String query) {
Objects.requireNonNull(query, "query");
return new ApiActionFilter(ActionFilters.search(query));
}
}

View File

@@ -37,6 +37,7 @@ import java.util.Objects;
import java.util.SortedSet;
import java.util.UUID;
@Deprecated
public class ApiActionLog implements ActionLog {
private final SortedSet<Action> content;

View File

@@ -25,14 +25,22 @@
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.actionlog.LogDispatcher;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.ActionLog;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.util.Page;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class ApiActionLogger implements ActionLogger {
@@ -48,23 +56,66 @@ public class ApiActionLogger implements ActionLogger {
}
@Override
@Deprecated
public @NonNull CompletableFuture<ActionLog> getLog() {
return this.plugin.getStorage().getLogPage(ActionFilters.all(), null)
.thenApply(result -> new ApiActionLog(result.getContent()));
}
@Override
public @NonNull CompletableFuture<List<Action>> queryActions(@NonNull ActionFilter filter) {
return this.plugin.getStorage().getLogPage(getFilterList(filter), null).thenApply(ActionPage::new).thenApply(Page::entries);
}
@Override
public @NonNull CompletableFuture<Page<Action>> queryActions(@NonNull ActionFilter filter, int pageSize, int pageNumber) {
return this.plugin.getStorage().getLogPage(getFilterList(filter), new PageParameters(pageSize, pageNumber)).thenApply(ActionPage::new);
}
@Override
public @NonNull CompletableFuture<Void> submit(@NonNull Action entry) {
return CompletableFuture.runAsync(() -> this.plugin.getLogDispatcher().dispatchFromApi((LoggedAction) entry), this.plugin.getBootstrap().getScheduler().async());
return this.plugin.getLogDispatcher().dispatchFromApi((LoggedAction) entry);
}
@Override
public @NonNull CompletableFuture<Void> submitToStorage(@NonNull Action entry) {
return this.plugin.getStorage().logAction(entry);
return this.plugin.getLogDispatcher().logToStorage((LoggedAction) entry);
}
@Override
public @NonNull CompletableFuture<Void> broadcastAction(@NonNull Action entry) {
return CompletableFuture.runAsync(() -> this.plugin.getLogDispatcher().broadcastFromApi((LoggedAction) entry), this.plugin.getBootstrap().getScheduler().async());
LogDispatcher dispatcher = this.plugin.getLogDispatcher();
CompletableFuture<Void> messagingFuture = dispatcher.logToStorage(((LoggedAction) entry));
dispatcher.broadcastFromApi(((LoggedAction) entry));
return messagingFuture;
}
private static FilterList<Action> getFilterList(ActionFilter filter) {
Objects.requireNonNull(filter, "filter");
if (filter instanceof ApiActionFilter) {
return ((ApiActionFilter) filter).getFilter();
} else {
throw new IllegalArgumentException("Unknown filter type: " + filter.getClass());
}
}
private static final class ActionPage implements Page<Action> {
private final LogPage page;
private ActionPage(LogPage page) {
this.page = page;
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public @NonNull List<Action> entries() {
return (List) this.page.getContent();
}
@Override
public int overallSize() {
return this.page.getTotalEntries();
}
}
}

View File

@@ -1,3 +1,28 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate;
import me.lucko.luckperms.common.filter.FilterField;

View File

@@ -28,14 +28,10 @@ package me.lucko.luckperms.common.bulkupdate;
import me.lucko.luckperms.common.bulkupdate.action.BulkUpdateAction;
import me.lucko.luckperms.common.bulkupdate.action.DeleteAction;
import me.lucko.luckperms.common.bulkupdate.action.UpdateAction;
import me.lucko.luckperms.common.filter.Filter;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.filter.sql.FilterSqlBuilder;
import net.luckperms.api.node.Node;
import java.util.List;
public class BulkUpdateSqlBuilder extends FilterSqlBuilder<Node> {
public void visit(BulkUpdate update) {

View File

@@ -59,13 +59,13 @@ public class LogGroupHistory extends ChildCommand<Void> {
return;
}
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, Integer.MIN_VALUE));
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, 1));
LogPage log = plugin.getStorage().getLogPage(ActionFilters.group(group), pageParams).join();
int page = pageParams.pageNumber();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
if (log.getContent().isEmpty()) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}

View File

@@ -74,7 +74,7 @@ public class LogRecent extends ChildCommand<Void> {
LogPage log = plugin.getStorage().getLogPage(uuid == null ? ActionFilters.all() : ActionFilters.source(uuid), pageParams).join();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
if (log.getContent().isEmpty()) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}

View File

@@ -49,7 +49,7 @@ public class LogSearch extends ChildCommand<Void> {
@Override
public void execute(LuckPermsPlugin plugin, Sender sender, Void ignored, ArgumentList args, String label) {
int page = Integer.MIN_VALUE;
int page = 1;
if (args.size() > 1) {
try {
page = Integer.parseInt(args.get(args.size() - 1));
@@ -64,7 +64,7 @@ public class LogSearch extends ChildCommand<Void> {
LogPage log = plugin.getStorage().getLogPage(ActionFilters.search(query), pageParams).join();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
if (log.getContent().isEmpty()) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}

View File

@@ -58,13 +58,13 @@ public class LogTrackHistory extends ChildCommand<Void> {
Message.TRACK_INVALID_ENTRY.send(sender, track);
return;
}
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, Integer.MIN_VALUE));
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, 1));
LogPage log = plugin.getStorage().getLogPage(ActionFilters.track(track), pageParams).join();
int page = pageParams.pageNumber();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
if (log.getContent().isEmpty()) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}

View File

@@ -55,13 +55,13 @@ public class LogUserHistory extends ChildCommand<Void> {
return;
}
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, Integer.MIN_VALUE));
PageParameters pageParams = new PageParameters(ENTRIES_PER_PAGE, args.getIntOrDefault(1, 1));
LogPage log = plugin.getStorage().getLogPage(ActionFilters.user(uuid), pageParams).join();
int page = pageParams.pageNumber();
int maxPage = pageParams.getMaxPage(log.getTotalEntries());
if (log.getContent().isEmpty()) {
if (log.getTotalEntries() == 0) {
Message.LOG_NO_ENTRIES.send(sender);
return;
}

View File

@@ -443,6 +443,11 @@ public final class ConfigKeys {
.collect(ImmutableCollectors.toList());
});
/**
* If log should be posted synchronously to storage/messaging in commands
*/
public static final ConfigKey<Boolean> LOG_SYNCHRONOUSLY_IN_COMMANDS = booleanKey("log-synchronously-in-commands", false);
/**
* If LuckPerms should automatically install translation bundles and periodically update them.
*/

View File

@@ -1,3 +1,28 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.filter;
import java.util.UUID;

View File

@@ -1,3 +1,28 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.filter;
import com.google.common.collect.ForwardingList;

View File

@@ -1,3 +1,28 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.filter.sql;
import me.lucko.luckperms.common.filter.Comparison;

View File

@@ -31,6 +31,8 @@ import net.luckperms.api.actionlog.Action;
import net.luckperms.api.messenger.Messenger;
import net.luckperms.api.messenger.MessengerProvider;
import java.util.concurrent.CompletableFuture;
public interface InternalMessagingService {
/**
@@ -60,21 +62,21 @@ public interface InternalMessagingService {
* Uses the messaging service to inform other servers about a general
* change.
*/
void pushUpdate();
CompletableFuture<Void> pushUpdate();
/**
* Pushes an update for a specific user.
*
* @param user the user
*/
void pushUserUpdate(User user);
CompletableFuture<Void> pushUserUpdate(User user);
/**
* Pushes a log entry to connected servers.
*
* @param logEntry the log entry
*/
void pushLog(Action logEntry);
CompletableFuture<Void> pushLog(Action logEntry);
/**
* Pushes a custom payload to connected servers.
@@ -82,6 +84,6 @@ public interface InternalMessagingService {
* @param channelId the channel id
* @param payload the payload
*/
void pushCustomPayload(String channelId, String payload);
CompletableFuture<Void> pushCustomPayload(String channelId, String payload);
}

View File

@@ -36,6 +36,7 @@ import me.lucko.luckperms.common.messaging.message.UpdateMessageImpl;
import me.lucko.luckperms.common.messaging.message.UserUpdateMessageImpl;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.AsyncInterface;
import me.lucko.luckperms.common.util.ExpiringSet;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.util.gson.JObject;
@@ -54,9 +55,10 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class LuckPermsMessagingService implements InternalMessagingService, IncomingMessageConsumer {
public class LuckPermsMessagingService extends AsyncInterface implements InternalMessagingService, IncomingMessageConsumer {
private final LuckPermsPlugin plugin;
private final ExpiringSet<UUID> receivedMessages;
private final PushUpdateBuffer updateBuffer;
@@ -65,6 +67,7 @@ public class LuckPermsMessagingService implements InternalMessagingService, Inco
private final Messenger messenger;
public LuckPermsMessagingService(LuckPermsPlugin plugin, MessengerProvider messengerProvider) {
super(plugin);
this.plugin = plugin;
this.messengerProvider = messengerProvider;
@@ -107,8 +110,8 @@ public class LuckPermsMessagingService implements InternalMessagingService, Inco
}
@Override
public void pushUpdate() {
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
public CompletableFuture<Void> pushUpdate() {
return future(() -> {
UUID requestId = generatePingId();
this.plugin.getLogger().info("[Messaging] Sending ping with id: " + requestId);
this.messenger.sendOutgoingMessage(new UpdateMessageImpl(requestId));
@@ -116,8 +119,8 @@ public class LuckPermsMessagingService implements InternalMessagingService, Inco
}
@Override
public void pushUserUpdate(User user) {
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
public CompletableFuture<Void> pushUserUpdate(User user) {
return future(() -> {
UUID requestId = generatePingId();
this.plugin.getLogger().info("[Messaging] Sending user ping for '" + user.getPlainDisplayName() + "' with id: " + requestId);
this.messenger.sendOutgoingMessage(new UserUpdateMessageImpl(requestId, user.getUniqueId()));
@@ -125,8 +128,8 @@ public class LuckPermsMessagingService implements InternalMessagingService, Inco
}
@Override
public void pushLog(Action logEntry) {
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
public CompletableFuture<Void> pushLog(Action logEntry) {
return future(() -> {
UUID requestId = generatePingId();
if (this.plugin.getEventDispatcher().dispatchLogNetworkPublish(!this.plugin.getConfiguration().get(ConfigKeys.PUSH_LOG_ENTRIES), requestId, logEntry)) {
@@ -139,8 +142,8 @@ public class LuckPermsMessagingService implements InternalMessagingService, Inco
}
@Override
public void pushCustomPayload(String channelId, String payload) {
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
public CompletableFuture<Void> pushCustomPayload(String channelId, String payload) {
return future(() -> {
UUID requestId = generatePingId();
this.messenger.sendOutgoingMessage(new CustomMessageImpl(requestId, channelId, payload));
});
@@ -283,7 +286,7 @@ public class LuckPermsMessagingService implements InternalMessagingService, Inco
ActionLogMessage msg = (ActionLogMessage) message;
this.plugin.getEventDispatcher().dispatchLogReceive(msg.getId(), msg.getAction());
this.plugin.getLogDispatcher().dispatchFromRemote((LoggedAction) msg.getAction());
this.plugin.getLogDispatcher().broadcastFromRemote((LoggedAction) msg.getAction());
} else if (message instanceof CustomMessage) {
CustomMessage msg = (CustomMessage) message;

View File

@@ -78,7 +78,7 @@ public final class AbstractSender<T> implements Sender {
@Override
public void sendMessage(Component message) {
if (isConsole()) {
if (this.factory.shouldSplitNewlines(this.sender)) {
for (Component line : splitNewlines(message)) {
this.factory.sendMessage(this.sender, line);
}
@@ -89,12 +89,12 @@ public final class AbstractSender<T> implements Sender {
@Override
public Tristate getPermissionValue(String permission) {
return (isConsole() && this.factory.consoleHasAllPermissions()) ? Tristate.TRUE : this.factory.getPermissionValue(this.sender, permission);
return isConsole() ? Tristate.TRUE : this.factory.getPermissionValue(this.sender, permission);
}
@Override
public boolean hasPermission(String permission) {
return (isConsole() && this.factory.consoleHasAllPermissions()) || this.factory.hasPermission(this.sender, permission);
return isConsole() || this.factory.hasPermission(this.sender, permission);
}
@Override

View File

@@ -63,8 +63,8 @@ public abstract class SenderFactory<P extends LuckPermsPlugin, T> implements Aut
protected abstract boolean isConsole(T sender);
protected boolean consoleHasAllPermissions() {
return true;
protected boolean shouldSplitNewlines(T sender) {
return isConsole(sender);
}
public final Sender wrap(T sender) {

View File

@@ -38,7 +38,7 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
import me.lucko.luckperms.common.storage.implementation.split.SplitStorage;
import me.lucko.luckperms.common.storage.misc.NodeEntry;
import me.lucko.luckperms.common.util.Throwing;
import me.lucko.luckperms.common.util.AsyncInterface;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.event.cause.DeletionCause;
@@ -54,18 +54,17 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
/**
* Provides a {@link CompletableFuture} based API for interacting with a {@link StorageImplementation}.
*/
public class Storage {
public class Storage extends AsyncInterface {
private final LuckPermsPlugin plugin;
private final StorageImplementation implementation;
public Storage(LuckPermsPlugin plugin, StorageImplementation implementation) {
super(plugin);
this.plugin = plugin;
this.implementation = implementation;
}
@@ -82,32 +81,6 @@ public class Storage {
}
}
private <T> CompletableFuture<T> future(Callable<T> supplier) {
return CompletableFuture.supplyAsync(() -> {
try {
return supplier.call();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
}, this.plugin.getBootstrap().getScheduler().async());
}
private CompletableFuture<Void> future(Throwing.Runnable runnable) {
return CompletableFuture.runAsync(() -> {
try {
runnable.run();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
}, this.plugin.getBootstrap().getScheduler().async());
}
public String getName() {
return this.implementation.getImplementationName();
}

View File

@@ -137,7 +137,7 @@ public class FileActionLogger {
}
}
private Stream<LoggedAction> getRawLog() throws IOException {
private Stream<LoggedAction> loadLog(FilterList<Action> filters) throws IOException {
// if there is log content waiting to be written, flush immediately before trying to read
if (this.saveBuffer.isEnqueued()) {
this.saveBuffer.requestDirectly();
@@ -153,7 +153,10 @@ public class FileActionLogger {
while ((line = reader.readLine()) != null) {
try {
JsonElement parsed = GsonProvider.parser().parse(line);
builder.add(ActionJsonSerializer.deserialize(parsed));
LoggedAction action = ActionJsonSerializer.deserialize(parsed);
if (filters.evaluate(action)) {
builder.add(action);
}
} catch (Exception e) {
e.printStackTrace();
}
@@ -163,10 +166,10 @@ public class FileActionLogger {
}
public LogPage getLogPage(FilterList<Action> filters, @Nullable PageParameters page) throws IOException {
List<LoggedAction> filtered = getRawLog()
.filter(filters::evaluate)
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
List<LoggedAction> filtered = loadLog(filters)
.sorted(Comparator.comparing(LoggedAction::getTimestamp))
.collect(Collectors.toList())
.reversed();
int size = filtered.size();
List<LoggedAction> paginated = page != null ? page.paginate(filtered) : filtered;

View File

@@ -186,7 +186,7 @@ public class MongoStorage implements StorageImplementation {
long count = c.countDocuments(filter);
List<LoggedAction> content = new ArrayList<>();
try (MongoCursor<Document> cursor = ConstraintMongoBuilder.page(page, c.find(filter).sort(Sorts.descending("timestamp"))).iterator()) {
try (MongoCursor<Document> cursor = ConstraintMongoBuilder.page(page, c.find(filter).sort(Sorts.descending("timestamp", "_id"))).iterator()) {
while (cursor.hasNext()) {
content.add(actionFromDoc(cursor.next()));
}

View File

@@ -268,7 +268,7 @@ public class SqlStorage implements StorageImplementation {
ActionFilterSqlBuilder sqlBuilder = new ActionFilterSqlBuilder();
sqlBuilder.builder().append(ACTION_SELECT_ALL);
sqlBuilder.visit(filter);
sqlBuilder.builder().append(" ORDER BY time DESC");
sqlBuilder.builder().append(" ORDER BY time DESC, id DESC");
sqlBuilder.visit(page);
try (PreparedStatement ps = sqlBuilder.builder().build(c, this.statementProcessor)) {

View File

@@ -0,0 +1,71 @@
/*
* 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.util;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
/**
* Base class for an interface which can perform operations asynchronously and return {@link CompletableFuture}s
*/
public abstract class AsyncInterface {
private final LuckPermsPlugin plugin;
protected AsyncInterface(LuckPermsPlugin plugin) {
this.plugin = plugin;
}
protected <T> CompletableFuture<T> future(Callable<T> supplier) {
return CompletableFuture.supplyAsync(() -> {
try {
return supplier.call();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
}, this.plugin.getBootstrap().getScheduler().async());
}
protected CompletableFuture<Void> future(Throwing.Runnable runnable) {
return CompletableFuture.runAsync(() -> {
try {
runnable.run();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
}, this.plugin.getBootstrap().getScheduler().async());
}
}

View File

@@ -54,18 +54,18 @@ public class ActionFilterMongoTest {
),
Arguments.of(
ActionFilters.user(UUID.fromString("725d585e-4ff1-4f18-acca-6ac538364080")),
// {"$and": [{"target.type": "U"}, {"target.uniqueId": {"$binary": {"base64": "cl1YXk/xTxisymrFODZAgA==", "subType": "04"}}}]}
"{\"$and\": [{\"target.type\": \"U\"}, {\"target.uniqueId\": {\"$binary\": {\"base64\": \"cl1YXk/xTxisymrFODZAgA==\", \"subType\": \"04\"}}}]}"
// {"$and": [{"target.type": "USER"}, {"target.uniqueId": {"$binary": {"base64": "cl1YXk/xTxisymrFODZAgA==", "subType": "04"}}}]}
"{\"$and\": [{\"target.type\": \"USER\"}, {\"target.uniqueId\": {\"$binary\": {\"base64\": \"cl1YXk/xTxisymrFODZAgA==\", \"subType\": \"04\"}}}]}"
),
Arguments.of(
ActionFilters.group("test"),
// {"$and": [{"target.type": "G"}, {"target.name": "test"}]}
"{\"$and\": [{\"target.type\": \"G\"}, {\"target.name\": \"test\"}]}"
// {"$and": [{"target.type": "GROUP"}, {"target.name": "test"}]}
"{\"$and\": [{\"target.type\": \"GROUP\"}, {\"target.name\": \"test\"}]}"
),
Arguments.of(
ActionFilters.track("test"),
// {"$and": [{"target.type": "T"}, {"target.name": "test"}]}
"{\"$and\": [{\"target.type\": \"T\"}, {\"target.name\": \"test\"}]}"
// {"$and": [{"target.type": "TRACK"}, {"target.name": "test"}]}
"{\"$and\": [{\"target.type\": \"TRACK\"}, {\"target.name\": \"test\"}]}"
),
Arguments.of(
ActionFilters.search("test"),

View File

@@ -152,6 +152,7 @@ public abstract class AbstractStorageTest {
.targetType(Action.Target.Type.GROUP)
.targetName(i % 2 == 0 ? "test_group" : "dummy")
.description("group test " + i)
.timestamp(baseTime)
.build());
}
@@ -162,6 +163,7 @@ public abstract class AbstractStorageTest {
.targetType(Action.Target.Type.TRACK)
.targetName(i % 2 == 0 ? "test_track" : "dummy")
.description("track test " + i)
.timestamp(baseTime)
.build());
}
@@ -210,9 +212,17 @@ public abstract class AbstractStorageTest {
page = this.storage.getLogPage(ActionFilters.group("test_group"), new PageParameters(10, 1));
assertEquals(5, page.getContent().size());
assertEquals(
ImmutableList.of("group test 8", "group test 6", "group test 4", "group test 2", "group test 0"),
page.getContent().stream().map(LoggedAction::getDescription).collect(Collectors.toList())
);
page = this.storage.getLogPage(ActionFilters.track("test_track"), new PageParameters(10, 1));
assertEquals(5, page.getContent().size());
assertEquals(
ImmutableList.of("track test 8", "track test 6", "track test 4", "track test 2", "track test 0"),
page.getContent().stream().map(LoggedAction::getDescription).collect(Collectors.toList())
);
page = this.storage.getLogPage(ActionFilters.search("hello"), new PageParameters(500, 1));
assertEquals(300, page.getContent().size());

View File

@@ -1,3 +1,28 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;

View File

@@ -1,3 +1,28 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;

View File

@@ -33,8 +33,16 @@ import java.util.concurrent.CompletableFuture;
*/
public interface CommandExecutor {
CompletableFuture<Void> execute(String command);
CompletableFuture<Void> execute(StandaloneSender player, String command);
List<String> tabComplete(String command);
List<String> tabComplete(StandaloneSender player, String command);
default CompletableFuture<Void> execute(String command) {
return execute(StandaloneUser.INSTANCE, command);
}
default List<String> tabComplete(String command) {
return tabComplete(StandaloneUser.INSTANCE, command);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.standalone.app.integration;
import net.kyori.adventure.text.Component;
import net.luckperms.api.util.Tristate;
import java.util.Locale;
import java.util.UUID;
public interface StandaloneSender {
String getName();
UUID getUniqueId();
void sendMessage(Component component);
Tristate getPermissionValue(String permission);
boolean hasPermission(String permission);
boolean isConsole();
Locale getLocale();
}

View File

@@ -28,57 +28,57 @@ package me.lucko.luckperms.standalone.app.integration;
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
import net.luckperms.api.util.Tristate;
import java.util.Set;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
/**
* Dummy/singleton player class used by the standalone plugin.
*
* <p>In various places (ContextManager, SenderFactory, ..) the platform "player" type is used
* as a generic parameter. This class acts as this type for the standalone plugin.</p>
* The sender instance used for the console / users executing commands
* on a standalone instance of LuckPerms
*/
public class SingletonPlayer {
public class StandaloneUser implements StandaloneSender {
/** Empty UUID used by the singleton player. */
private static final UUID UUID = new UUID(0, 0);
/** A message sink that prints the component to stdout */
private static final Consumer<Component> PRINT_TO_STDOUT = component -> LuckPermsApplication.LOGGER.info(ANSIComponentSerializer.ansi().serialize(component));
public static final StandaloneUser INSTANCE = new StandaloneUser();
/** Singleton instance */
public static final SingletonPlayer INSTANCE = new SingletonPlayer();
/** A set of message sinks that messages are delivered to */
private final Set<Consumer<Component>> messageSinks;
private SingletonPlayer() {
this.messageSinks = new CopyOnWriteArraySet<>();
this.messageSinks.add(PRINT_TO_STDOUT);
private StandaloneUser() {
}
@Override
public String getName() {
return "StandaloneUser";
}
@Override
public UUID getUniqueId() {
return UUID;
}
@Override
public void sendMessage(Component component) {
for (Consumer<Component> sink : this.messageSinks) {
sink.accept(component);
}
LuckPermsApplication.LOGGER.info(ANSIComponentSerializer.ansi().serialize(component));
}
public void addMessageSink(Consumer<Component> sink) {
this.messageSinks.add(sink);
@Override
public Tristate getPermissionValue(String permission) {
return Tristate.TRUE;
}
public void removeMessageSink(Consumer<Component> sink) {
this.messageSinks.remove(sink);
@Override
public boolean hasPermission(String permission) {
return true;
}
@Override
public boolean isConsole() {
return true;
}
@Override
public Locale getLocale() {
return Locale.getDefault();
}
}

View File

@@ -39,7 +39,7 @@ import me.lucko.luckperms.common.plugin.AbstractLuckPermsPlugin;
import me.lucko.luckperms.common.plugin.util.AbstractConnectionListener;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import me.lucko.luckperms.standalone.app.integration.StandaloneUser;
import me.lucko.luckperms.standalone.stub.StandaloneContextManager;
import me.lucko.luckperms.standalone.stub.StandaloneDummyConnectionListener;
import me.lucko.luckperms.standalone.stub.StandaloneEventBus;
@@ -162,7 +162,7 @@ public class LPStandalonePlugin extends AbstractLuckPermsPlugin {
@Override
public Sender getConsoleSender() {
return getSenderFactory().wrap(SingletonPlayer.INSTANCE);
return getSenderFactory().wrap(StandaloneUser.INSTANCE);
}
public StandaloneSenderFactory getSenderFactory() {

View File

@@ -29,7 +29,7 @@ import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.command.utils.ArgumentTokenizer;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import me.lucko.luckperms.standalone.app.integration.StandaloneSender;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -43,15 +43,15 @@ public class StandaloneCommandManager extends CommandManager implements CommandE
}
@Override
public CompletableFuture<Void> execute(String command) {
Sender wrapped = this.plugin.getSenderFactory().wrap(SingletonPlayer.INSTANCE);
public CompletableFuture<Void> execute(StandaloneSender player, String command) {
Sender wrapped = this.plugin.getSenderFactory().wrap(player);
List<String> arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(command);
return executeCommand(wrapped, "lp", arguments);
}
@Override
public List<String> tabComplete(String command) {
Sender wrapped = this.plugin.getSenderFactory().wrap(SingletonPlayer.INSTANCE);
public List<String> tabComplete(StandaloneSender player, String command) {
Sender wrapped = this.plugin.getSenderFactory().wrap(player);
List<String> arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(command);
return tabCompleteCommand(wrapped, arguments);
}

View File

@@ -27,53 +27,56 @@ package me.lucko.luckperms.standalone;
import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import me.lucko.luckperms.standalone.app.integration.StandaloneSender;
import net.kyori.adventure.text.Component;
import net.luckperms.api.util.Tristate;
import java.util.Locale;
import java.util.UUID;
public class StandaloneSenderFactory extends SenderFactory<LPStandalonePlugin, SingletonPlayer> {
public class StandaloneSenderFactory extends SenderFactory<LPStandalonePlugin, StandaloneSender> {
public StandaloneSenderFactory(LPStandalonePlugin plugin) {
super(plugin);
}
@Override
protected String getName(SingletonPlayer sender) {
protected String getName(StandaloneSender sender) {
return sender.getName();
}
@Override
protected UUID getUniqueId(SingletonPlayer sender) {
protected UUID getUniqueId(StandaloneSender sender) {
return sender.getUniqueId();
}
@Override
protected void sendMessage(SingletonPlayer sender, Component message) {
Component rendered = TranslationManager.render(message, Locale.getDefault());
protected void sendMessage(StandaloneSender sender, Component message) {
Component rendered = TranslationManager.render(message, sender.getLocale());
sender.sendMessage(rendered);
}
@Override
protected Tristate getPermissionValue(SingletonPlayer sender, String node) {
return Tristate.TRUE;
protected Tristate getPermissionValue(StandaloneSender sender, String node) {
return sender.getPermissionValue(node);
}
@Override
protected boolean hasPermission(SingletonPlayer sender, String node) {
protected boolean hasPermission(StandaloneSender sender, String node) {
return sender.hasPermission(node);
}
@Override
protected void performCommand(StandaloneSender sender, String command) {
}
@Override
protected boolean isConsole(StandaloneSender sender) {
return sender.isConsole();
}
@Override
protected boolean shouldSplitNewlines(StandaloneSender sender) {
return true;
}
@Override
protected void performCommand(SingletonPlayer sender, String command) {
}
@Override
protected boolean isConsole(SingletonPlayer sender) {
return true;
}
}

View File

@@ -29,39 +29,45 @@ 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.standalone.LPStandalonePlugin;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import me.lucko.luckperms.standalone.app.integration.StandaloneSender;
import me.lucko.luckperms.standalone.app.integration.StandaloneUser;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryOptions;
import java.util.UUID;
public class StandaloneContextManager extends ContextManager<SingletonPlayer, SingletonPlayer> {
private final QueryOptionsCache<SingletonPlayer> singletonCache = new QueryOptionsCache<>(SingletonPlayer.INSTANCE, this);
public class StandaloneContextManager extends ContextManager<StandaloneSender, StandaloneSender> {
private final QueryOptionsCache<StandaloneSender> singletonCache = new QueryOptionsCache<>(StandaloneUser.INSTANCE, this);
public StandaloneContextManager(LPStandalonePlugin plugin) {
super(plugin, SingletonPlayer.class, SingletonPlayer.class);
super(plugin, StandaloneSender.class, StandaloneSender.class);
}
@Override
public UUID getUniqueId(SingletonPlayer player) {
public UUID getUniqueId(StandaloneSender player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<SingletonPlayer> getCacheFor(SingletonPlayer subject) {
public QueryOptionsCache<StandaloneSender> getCacheFor(StandaloneSender subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return this.singletonCache;
if (subject == StandaloneUser.INSTANCE) {
return this.singletonCache;
}
// just return a new one every time - not optimal but this case should only be hit using unit tests anyway
return new QueryOptionsCache<>(subject, this);
}
@Override
protected void invalidateCache(SingletonPlayer subject) {
protected void invalidateCache(StandaloneSender subject) {
this.singletonCache.invalidate();
}
@Override
public QueryOptions formQueryOptions(SingletonPlayer subject, ImmutableContextSet contextSet) {
public QueryOptions formQueryOptions(StandaloneSender subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
return queryOptions.context(contextSet).build();
}

View File

@@ -29,9 +29,9 @@ import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import me.lucko.luckperms.standalone.utils.CommandTester;
import me.lucko.luckperms.standalone.utils.TestPluginProvider;
import me.lucko.luckperms.standalone.utils.TestSender;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.luckperms.api.event.log.LogNotifyEvent;
import org.junit.jupiter.api.Test;
@@ -54,6 +54,7 @@ public class CommandsIntegrationTest {
private static final Map<String, String> CONFIG = ImmutableMap.<String, String>builder()
.put("log-notify", "false")
.put("commands-rate-limit", "false")
.build();
@Test
@@ -61,7 +62,7 @@ public class CommandsIntegrationTest {
TestPluginProvider.use(tempDir, CONFIG, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions("luckperms.creategroup")
.whenRunCommand("creategroup test")
.thenExpect("[LP] test was successfully created.")
@@ -97,9 +98,9 @@ public class CommandsIntegrationTest {
"""
)
.givenHasAllPermissions()
.givenHasPermissions("luckperms.group.meta.set")
.whenRunCommand("group test meta set hello world")
.clearMessageBuffer()
.thenExpect("[LP] Set meta key 'hello' to 'world' for test in context global.")
.givenHasPermissions("luckperms.group.setweight")
.whenRunCommand("group test setweight 10")
@@ -182,7 +183,7 @@ public class CommandsIntegrationTest {
TestPluginProvider.use(tempDir, CONFIG, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(plugin, executor)
new CommandTester(executor)
.whenRunCommand("creategroup test")
.clearMessageBuffer()
@@ -262,7 +263,7 @@ public class CommandsIntegrationTest {
TestPluginProvider.use(tempDir, CONFIG, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(plugin, executor)
new CommandTester(executor)
.whenRunCommand("creategroup test")
.whenRunCommand("creategroup test2")
.whenRunCommand("creategroup test3")
@@ -321,7 +322,7 @@ public class CommandsIntegrationTest {
TestPluginProvider.use(tempDir, CONFIG, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(plugin, executor)
new CommandTester(executor)
.whenRunCommand("creategroup test")
.clearMessageBuffer()
@@ -445,7 +446,7 @@ public class CommandsIntegrationTest {
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
plugin.getStorage().savePlayerData(UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"), "Notch").join();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions("luckperms.user.info")
.whenRunCommand("user Luck info")
.thenExpect("""
@@ -508,7 +509,7 @@ public class CommandsIntegrationTest {
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions("luckperms.user.permission.set")
.whenRunCommand("user Luck permission set test.node true")
.thenExpect("[LP] Set test.node to true for luck in context global.")
@@ -587,7 +588,7 @@ public class CommandsIntegrationTest {
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(plugin, executor)
new CommandTester(executor)
.whenRunCommand("creategroup test2")
.whenRunCommand("creategroup test3")
.clearMessageBuffer()
@@ -651,7 +652,7 @@ public class CommandsIntegrationTest {
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions("luckperms.user.meta.info")
.whenRunCommand("user Luck meta info")
.thenExpect("""
@@ -773,7 +774,7 @@ public class CommandsIntegrationTest {
TestPluginProvider.use(tempDir, CONFIG, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions("luckperms.createtrack")
.whenRunCommand("createtrack test1")
.thenExpect("[LP] test1 was successfully created.")
@@ -884,7 +885,7 @@ public class CommandsIntegrationTest {
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(plugin, executor)
new CommandTester(executor)
.whenRunCommand("createtrack staff")
.whenRunCommand("createtrack premium")
@@ -1023,7 +1024,7 @@ public class CommandsIntegrationTest {
plugin.getStorage().savePlayerData(UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"), "Luck").join();
new CommandTester(plugin, executor)
new CommandTester(executor)
.whenRunCommand("creategroup test")
.whenRunCommand("user Luck permission set hello.world true server=survival")
.whenRunCommand("group test permission set hello.world true world=nether")
@@ -1045,12 +1046,12 @@ public class CommandsIntegrationTest {
.givenHasPermissions("luckperms.search")
.whenRunCommand("search ~~ group.%")
.thenExpect("""
[LP] Searching for users and groups with permissions ~~ group.%...
[LP] Found 2 entries from 2 users and 0 groups.
[LP] Showing user entries: (page 1 of 1 - 2 entries)
> luck - (group.test) - true
> luck - (group.default) - true
"""
[LP] Searching for users and groups with permissions ~~ group.%...
[LP] Found 2 entries from 2 users and 0 groups.
[LP] Showing user entries: (page 1 of 1 - 2 entries)
> luck - (group.test) - true
> luck - (group.default) - true
"""
);
});
}
@@ -1068,14 +1069,16 @@ public class CommandsIntegrationTest {
CountDownLatch completed = new CountDownLatch(1);
SingletonPlayer.INSTANCE.addMessageSink(component -> {
TestSender console = new TestSender();
console.setConsole(true);
console.addMessageSink(component -> {
String plain = PlainTextComponentSerializer.plainText().serialize(component);
if (plain.contains("Bulk update completed successfully")) {
completed.countDown();
}
});
new CommandTester(plugin, executor)
new CommandTester(executor, console)
.whenRunCommand("creategroup moderator")
.whenRunCommand("creategroup admin")
.whenRunCommand("user Luck parent add moderator server=survival")
@@ -1084,7 +1087,6 @@ public class CommandsIntegrationTest {
.whenRunCommand("group moderator rename mod")
.clearMessageBuffer()
.givenHasPermissions("luckperms.bulkupdate")
.whenRunCommand("bulkupdate all update permission group.mod \"permission == group.moderator\"")
.thenExpectStartsWith("[LP] Running bulk update.");
@@ -1113,6 +1115,151 @@ public class CommandsIntegrationTest {
),
notchUser.normalData().asSet()
);
// test normal players are unable to use
new CommandTester(executor)
.givenHasPermissions("luckperms.bulkupdate")
.whenRunCommand("bulkupdate all update permission group.mod \"permission == group.moderator\"")
.thenExpect("[LP] The bulk update command can only be used from the console.");
});
}
@Test
public void testLogCommands(@TempDir Path tempDir) {
Map<String, String> config = new HashMap<>(CONFIG);
config.put("log-notify", "true");
config.put("log-synchronously-in-commands", "true");
TestPluginProvider.use(tempDir, config, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
UUID luckUniqueId = UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d");
plugin.getStorage().savePlayerData(luckUniqueId, "Luck").join();
plugin.getStorage().savePlayerData(UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"), "Notch").join();
TestSender testSender = new TestSender();
testSender.setUniqueId(luckUniqueId);
testSender.setName("Luck");
new CommandTester(executor, testSender)
.whenRunCommand("group default permission set hello");
new CommandTester(executor)
.whenRunCommand("creategroup moderator")
.whenRunCommand("creategroup admin")
.whenRunCommand("createtrack staff")
.whenRunCommand("user Luck parent add moderator server=survival")
.whenRunCommand("user Notch parent add moderator")
.whenRunCommand("group admin parent add moderator")
.whenRunCommand("group moderator rename mod")
.whenRunCommand("user Luck permission set test.1")
.whenRunCommand("user Luck permission set test.2 false")
.whenRunCommand("user Luck permission set test.3 server=survival")
.whenRunCommand("user Luck permission settemp test.4 true 1h")
.whenRunCommand("user Luck permission settemp test.5 false 2d")
.clearMessageBuffer()
.givenHasPermissions("luckperms.log.userhistory")
.whenRunCommand("log userhistory Luck")
.thenExpectReplacing("\\ds ago", "1m ago", """
[LP] Showing history for user luck (page 1 of 1)
[LP] #1 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission settemp test.5 false 2d
[LP] #2 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission settemp test.4 true 1h
[LP] #3 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission set test.3 true server=survival
[LP] #4 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission set test.2 false
[LP] #5 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission set test.1 true
[LP] #6 (1m ago) (StandaloneUser) [U] (luck)
[LP] > parent add moderator server=survival
"""
)
.givenHasPermissions("luckperms.log.grouphistory")
.whenRunCommand("log grouphistory admin")
.thenExpectReplacing("\\ds ago", "1m ago", """
[LP] Showing history for group admin (page 1 of 1)
[LP] #1 (1m ago) (StandaloneUser) [G] (admin)
[LP] > parent add moderator
[LP] #2 (1m ago) (StandaloneUser) [G] (admin)
[LP] > create
"""
)
.givenHasPermissions("luckperms.log.trackhistory")
.whenRunCommand("log trackhistory staff")
.thenExpectReplacing("\\ds ago", "1m ago", """
[LP] Showing history for track staff (page 1 of 1)
[LP] #1 (1m ago) (StandaloneUser) [T] (staff)
[LP] > create
"""
)
.givenHasPermissions("luckperms.log.recent")
.whenRunCommand("log recent")
.thenExpectReplacing("\\ds ago", "1m ago", """
[LP] Showing recent actions (page 1 of 2)
[LP] #1 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission settemp test.5 false 2d
[LP] #2 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission settemp test.4 true 1h
[LP] #3 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission set test.3 true server=survival
[LP] #4 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission set test.2 false
[LP] #5 (1m ago) (StandaloneUser) [U] (luck)
[LP] > permission set test.1 true
[LP] #6 (1m ago) (StandaloneUser) [G] (moderator)
[LP] > rename mod
[LP] #7 (1m ago) (StandaloneUser) [G] (admin)
[LP] > parent add moderator
[LP] #8 (1m ago) (StandaloneUser) [U] (notch)
[LP] > parent add moderator
[LP] #9 (1m ago) (StandaloneUser) [U] (luck)
[LP] > parent add moderator server=survival
[LP] #10 (1m ago) (StandaloneUser) [T] (staff)
[LP] > create
"""
)
.givenHasPermissions("luckperms.log.recent")
.whenRunCommand("log recent 2")
.thenExpectReplacing("\\ds ago", "1m ago", """
[LP] Showing recent actions (page 2 of 2)
[LP] #11 (1m ago) (StandaloneUser) [G] (admin)
[LP] > create
[LP] #12 (1m ago) (StandaloneUser) [G] (moderator)
[LP] > create
[LP] #13 (1m ago) (Luck) [G] (default)
[LP] > permission set hello true
"""
)
.givenHasPermissions("luckperms.log.recent")
.whenRunCommand("log recent 3")
.thenExpect("[LP] Invalid page number. Please enter a value between 1 and 2.")
.givenHasPermissions("luckperms.log.search")
.whenRunCommand("log search hello")
.thenExpectReplacing("\\ds ago", "1m ago", """
[LP] Showing recent actions for query hello (page 1 of 1)
[LP] #1 (1m ago) (Luck) [G] (default)
[LP] > permission set hello true
"""
)
.givenHasPermissions("luckperms.log.recent")
.whenRunCommand("log recent Luck")
.thenExpectReplacing("\\ds ago", "1m ago", """
[LP] Showing recent actions by Luck (page 1 of 1)
[LP] #1 (1m ago) (Luck) [G] (default)
[LP] > permission set hello true
"""
);
});
}
@@ -1121,7 +1268,7 @@ public class CommandsIntegrationTest {
TestPluginProvider.use(tempDir, CONFIG, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions("luckperms.user.info")
.whenRunCommand("user unknown info")
.thenExpect("[LP] A user for unknown could not be found.")
@@ -1154,7 +1301,7 @@ public class CommandsIntegrationTest {
CommandExecutor executor = app.getCommandExecutor();
String version = "v" + bootstrap.getVersion();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions(/* empty */)
.whenRunCommand("")
@@ -1188,7 +1335,10 @@ public class CommandsIntegrationTest {
// by default, notifications are not sent to the user who initiated the event - override that
app.getApi().getEventBus().subscribe(LogNotifyEvent.class, e -> e.setCancelled(false));
new CommandTester(plugin, executor)
TestSender sender = new TestSender();
plugin.addOnlineSender(sender);
new CommandTester(executor, sender)
.givenHasPermissions("luckperms.group.permission.set", "luckperms.log.notify")
.whenRunCommand("group default permission set hello.world true server=test")
.thenExpect("""
@@ -1208,7 +1358,7 @@ public class CommandsIntegrationTest {
TestPluginProvider.use(tempDir, config, (app, bootstrap, plugin) -> {
CommandExecutor executor = app.getCommandExecutor();
new CommandTester(plugin, executor)
new CommandTester(executor)
.givenHasPermissions("luckperms.group.permission.set")
.whenRunCommand("group default permission set hello.world true server=test")
.thenExpect("[LP] You do not have permission to use this command!")

View File

@@ -36,7 +36,7 @@ import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.webeditor.WebEditorRequest;
import me.lucko.luckperms.common.webeditor.WebEditorSession;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import me.lucko.luckperms.standalone.app.integration.StandaloneUser;
import me.lucko.luckperms.standalone.utils.TestPluginProvider;
import net.luckperms.api.model.data.DataType;
import okhttp3.OkHttpClient;
@@ -115,7 +115,7 @@ public class WebEditorIntegrationTest {
assertFalse(holders.isEmpty());
// create a new editor session
Sender sender = plugin.getSenderFactory().wrap(SingletonPlayer.INSTANCE);
Sender sender = plugin.getSenderFactory().wrap(StandaloneUser.INSTANCE);
WebEditorSession session = WebEditorSession.create(holders, Collections.emptyList(), sender, "lp", plugin);
String bytebinKey = session.open();
String bytesocksKey = session.getSocket().getSocket().channelId();

View File

@@ -26,14 +26,12 @@
package me.lucko.luckperms.standalone.utils;
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import me.lucko.luckperms.standalone.utils.TestPluginBootstrap.TestPlugin;
import me.lucko.luckperms.standalone.utils.TestPluginBootstrap.TestSenderFactory;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.luckperms.api.util.Tristate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.intellij.lang.annotations.RegExp;
import java.util.ArrayList;
import java.util.Collections;
@@ -57,12 +55,12 @@ public final class CommandTester implements Consumer<Component>, Function<String
private static final Logger LOGGER = LogManager.getLogger(CommandTester.class);
/** The test plugin */
private final TestPlugin plugin;
/** The LuckPerms command executor */
private final CommandExecutor executor;
/** The test player */
private final TestSender sender;
/** The current map of permissions held by the fake executor */
private Map<String, Tristate> permissions = null;
@@ -72,9 +70,16 @@ public final class CommandTester implements Consumer<Component>, Function<String
/** A buffer of messages received by the test tool */
private final List<Component> messageBuffer = Collections.synchronizedList(new ArrayList<>());
public CommandTester(TestPlugin plugin, CommandExecutor executor) {
this.plugin = plugin;
public CommandTester(CommandExecutor executor, TestSender sender) {
this.executor = executor;
this.sender = sender;
this.sender.setPermissionChecker(this);
this.sender.addMessageSink(this);
}
public CommandTester(CommandExecutor executor) {
this(executor, new TestSender());
}
/**
@@ -138,15 +143,7 @@ public final class CommandTester implements Consumer<Component>, Function<String
*/
public CommandTester whenRunCommand(String command) {
LOGGER.info("Executing test command: " + command);
TestSenderFactory senderFactory = this.plugin.getSenderFactory();
senderFactory.setPermissionChecker(this);
SingletonPlayer.INSTANCE.addMessageSink(this);
this.executor.execute(command).join();
SingletonPlayer.INSTANCE.removeMessageSink(this);
senderFactory.resetPermissionChecker();
this.executor.execute(this.sender, command).join();
return this;
}
@@ -184,6 +181,23 @@ public final class CommandTester implements Consumer<Component>, Function<String
return this.clearMessageBuffer();
}
/**
* Asserts that the current contents of the message buffer matches the given input string.
*
* @param expected the expected contents
* @return this
*/
public CommandTester thenExpectReplacing(@RegExp String regex, String replacement, String expected) {
String actual = this.renderBuffer().replaceAll(regex, replacement);
assertEquals(expected.trim(), actual.trim());
if (this.permissions != null) {
assertEquals(this.checkedPermissions, this.permissions.keySet());
}
return this.clearMessageBuffer();
}
/**
* Clears the message buffer.
*

View File

@@ -27,21 +27,19 @@ package me.lucko.luckperms.standalone.utils;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.locale.TranslationManager;
import me.lucko.luckperms.common.plugin.classpath.ClassPathAppender;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.StorageType;
import me.lucko.luckperms.standalone.LPStandaloneBootstrap;
import me.lucko.luckperms.standalone.LPStandalonePlugin;
import me.lucko.luckperms.standalone.StandaloneSenderFactory;
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
import me.lucko.luckperms.standalone.app.integration.SingletonPlayer;
import net.kyori.adventure.text.Component;
import net.luckperms.api.util.Tristate;
import me.lucko.luckperms.standalone.app.integration.StandaloneSender;
import me.lucko.luckperms.standalone.app.integration.StandaloneUser;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Set;
import java.util.function.Function;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Stream;
/**
* An extension standalone bootstrap for testing.
@@ -50,7 +48,7 @@ import java.util.function.Function;
* <p>
* <ul>
* <li>Dependency loading system is replaced with a no-op stub that delegates to the test classloader</li>
* <li>Sender factory is extended and allows for permission checks to be intercepted</li>
* <li>Ability to register additional sender instances as being online</li>
* </ul>
* </p>
*/
@@ -81,7 +79,7 @@ public final class TestPluginBootstrap extends LPStandaloneBootstrap {
}
public static final class TestPlugin extends LPStandalonePlugin {
private TestSenderFactory senderFactory;
private final Set<StandaloneSender> onlineSenders = new CopyOnWriteArraySet<>();
TestPlugin(LPStandaloneBootstrap bootstrap) {
super(bootstrap);
@@ -93,13 +91,15 @@ public final class TestPluginBootstrap extends LPStandaloneBootstrap {
}
@Override
protected void setupSenderFactory() {
this.senderFactory = new TestSenderFactory(this);
public Stream<Sender> getOnlineSenders() {
return Stream.concat(
Stream.of(StandaloneUser.INSTANCE),
this.onlineSenders.stream()
).map(player -> getSenderFactory().wrap(player));
}
@Override
public TestSenderFactory getSenderFactory() {
return this.senderFactory;
public void addOnlineSender(StandaloneSender player) {
this.onlineSenders.add(player);
}
}
@@ -125,46 +125,4 @@ public final class TestPluginBootstrap extends LPStandaloneBootstrap {
}
}
public static final class TestSenderFactory extends StandaloneSenderFactory {
private Function<String, Tristate> permissionChecker;
public TestSenderFactory(LPStandalonePlugin plugin) {
super(plugin);
}
public void setPermissionChecker(Function<String, Tristate> permissionChecker) {
this.permissionChecker = permissionChecker;
}
public void resetPermissionChecker() {
this.permissionChecker = null;
}
@Override
protected boolean consoleHasAllPermissions() {
return false;
}
@Override
protected void sendMessage(SingletonPlayer sender, Component message) {
Component rendered = TranslationManager.render(message, Locale.ENGLISH);
sender.sendMessage(rendered);
}
@Override
protected Tristate getPermissionValue(SingletonPlayer sender, String node) {
return this.permissionChecker == null
? super.getPermissionValue(sender, node)
: this.permissionChecker.apply(node);
}
@Override
protected boolean hasPermission(SingletonPlayer sender, String node) {
return this.permissionChecker == null
? super.hasPermission(sender, node)
: this.permissionChecker.apply(node).asBoolean();
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.standalone.utils;
import me.lucko.luckperms.standalone.app.integration.StandaloneSender;
import me.lucko.luckperms.standalone.app.integration.StandaloneUser;
import net.kyori.adventure.text.Component;
import net.luckperms.api.util.Tristate;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
import java.util.function.Function;
public class TestSender implements StandaloneSender {
private final Set<Consumer<Component>> messageSinks;
private String name = "StandaloneUser";
private UUID uniqueId = UUID.randomUUID();
private boolean isConsole = false;
private Function<String, Tristate> permissionChecker;
public TestSender() {
this.messageSinks = new CopyOnWriteArraySet<>();
this.messageSinks.add(StandaloneUser.INSTANCE::sendMessage);
}
@Override
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public UUID getUniqueId() {
return this.uniqueId;
}
public void setUniqueId(UUID uuid) {
this.uniqueId = uuid;
}
@Override
public void sendMessage(Component component) {
for (Consumer<Component> sink : this.messageSinks) {
sink.accept(component);
}
}
@Override
public Tristate getPermissionValue(String permission) {
return this.permissionChecker == null
? Tristate.TRUE
: this.permissionChecker.apply(permission);
}
@Override
public boolean hasPermission(String permission) {
return getPermissionValue(permission).asBoolean();
}
@Override
public boolean isConsole() {
return this.isConsole;
}
public void setConsole(boolean console) {
this.isConsole = console;
}
@Override
public Locale getLocale() {
return Locale.ENGLISH;
}
public void setPermissionChecker(Function<String, Tristate> permissionChecker) {
this.permissionChecker = permissionChecker;
}
public void addMessageSink(Consumer<Component> sink) {
this.messageSinks.add(sink);
}
}