diff --git a/api/src/main/java/net/luckperms/api/LuckPerms.java b/api/src/main/java/net/luckperms/api/LuckPerms.java index 8e99fe610..6c851c45c 100644 --- a/api/src/main/java/net/luckperms/api/LuckPerms.java +++ b/api/src/main/java/net/luckperms/api/LuckPerms.java @@ -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(); + } diff --git a/api/src/main/java/net/luckperms/api/actionlog/Action.java b/api/src/main/java/net/luckperms/api/actionlog/Action.java index 82d3f2a5a..53260674c 100644 --- a/api/src/main/java/net/luckperms/api/actionlog/Action.java +++ b/api/src/main/java/net/luckperms/api/actionlog/Action.java @@ -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. + * + *

API users should not implement this interface directly.

*/ +@NonExtendable public interface Action extends Comparable { /** @@ -81,6 +85,7 @@ public interface Action extends Comparable { /** * Represents the source of an action. */ + @NonExtendable interface Source { /** @@ -102,6 +107,7 @@ public interface Action extends Comparable { /** * Represents the target of an action. */ + @NonExtendable interface Target { /** @@ -126,7 +132,7 @@ public interface Action extends Comparable { @NonNull Type getType(); /** - * Represents the type of a {@link Target}. + * Represents the type of {@link Target}. */ enum Type { USER, GROUP, TRACK diff --git a/api/src/main/java/net/luckperms/api/actionlog/ActionLog.java b/api/src/main/java/net/luckperms/api/actionlog/ActionLog.java index bba6eb457..131ab3a5c 100644 --- a/api/src/main/java/net/luckperms/api/actionlog/ActionLog.java +++ b/api/src/main/java/net/luckperms/api/actionlog/ActionLog.java @@ -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.

* *

All methods are thread safe, and return immutable and thread safe collections.

+ * + * @deprecated Use {@link ActionLogger#queryActions(ActionFilter)} or + * {@link ActionLogger#queryActions(ActionFilter, int, int)} instead. */ +@Deprecated public interface ActionLog { /** diff --git a/api/src/main/java/net/luckperms/api/actionlog/ActionLogger.java b/api/src/main/java/net/luckperms/api/actionlog/ActionLogger.java index 0f4bf7878..8b8eb6dca 100644 --- a/api/src/main/java/net/luckperms/api/actionlog/ActionLogger.java +++ b/api/src/main/java/net/luckperms/api/actionlog/ActionLogger.java @@ -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 getLog(); /** - * Submits a log entry to the plugin to be handled. + * Gets all actions from the action log matching the given {@code filter}. * - *

This method submits the log to the storage provider and broadcasts - * it.

+ *

If the filter is {@code null}, all actions will be returned.

* - *

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.

+ *

Unlike {@link #queryActions(ActionFilter, int, int)}, this method does not implement any pagination and will return + * all entries at once.

* - *

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> queryActions(@NonNull ActionFilter filter); + + /** + * Gets a page of actions from the action log matching the given {@code filter}. + * + *

If the filter is {@code null}, all actions will be returned.

+ * + * @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> queryActions(@NonNull ActionFilter filter, int pageSize, int pageNumber); + + /** + * Submits a logged action to LuckPerms. + * + *

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.

+ * + *

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.

+ * + *

If you want to submit an action log entry but don't know which method to pick, * use this one.

* * @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 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. + * + *

This method does not broadcast the action or send it through the messaging service.

* * @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 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. * - *

If enabled, this method will also dispatch the log entry via the - * plugins {@link MessagingService}.

+ *

The broadcast is made to administrator players on the current instance + * and to admins on other connected servers if a messaging service is configured.

+ * + *

This method does not save the action to the plugin storage backend.

* * @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 broadcastAction(@NonNull Action entry); diff --git a/api/src/main/java/net/luckperms/api/actionlog/filter/ActionFilter.java b/api/src/main/java/net/luckperms/api/actionlog/filter/ActionFilter.java new file mode 100644 index 000000000..6614cbb34 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/actionlog/filter/ActionFilter.java @@ -0,0 +1,114 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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. + * + *

API users should not implement this interface directly.

+ * + * @since 5.5 + */ +@NonExtendable +public interface ActionFilter extends Predicate { + + /** + * 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); + +} diff --git a/api/src/main/java/net/luckperms/api/actionlog/filter/ActionFilterFactory.java b/api/src/main/java/net/luckperms/api/actionlog/filter/ActionFilterFactory.java new file mode 100644 index 000000000..31e699ac5 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/actionlog/filter/ActionFilterFactory.java @@ -0,0 +1,88 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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); + +} diff --git a/api/src/main/java/net/luckperms/api/actionlog/filter/package-info.java b/api/src/main/java/net/luckperms/api/actionlog/filter/package-info.java new file mode 100644 index 000000000..2eebf3be8 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/actionlog/filter/package-info.java @@ -0,0 +1,29 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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; \ No newline at end of file diff --git a/api/src/main/java/net/luckperms/api/util/Page.java b/api/src/main/java/net/luckperms/api/util/Page.java new file mode 100644 index 000000000..3ecf589c4 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/util/Page.java @@ -0,0 +1,53 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 { + + /** + * Gets the entries on this page. + * + * @return the entries + */ + @NonNull List 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(); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/actionlog/LogDispatcher.java b/common/src/main/java/me/lucko/luckperms/common/actionlog/LogDispatcher.java index c4ffd5b45..befa7ab2a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/actionlog/LogDispatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/actionlog/LogDispatcher.java @@ -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 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 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 dispatch(LoggedAction entry, Sender sender, LogBroadcastEvent.Origin broadcastOrigin, LogNotifyEvent.Origin origin) { + CompletableFuture storageFuture = logToStorage(entry); + CompletableFuture messagingFuture = logToMessaging(entry); + broadcast(entry, broadcastOrigin, origin, sender); + return CompletableFuture.allOf(storageFuture, messagingFuture); + } + + public CompletableFuture dispatch(LoggedAction entry, Sender sender) { + return dispatch(entry, sender, LogBroadcastEvent.Origin.LOCAL, LogNotifyEvent.Origin.LOCAL); + } + + public CompletableFuture 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); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java b/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java index 0d04c0eac..00ce2c64d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java +++ b/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java @@ -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 future = plugin.getLogDispatcher().dispatch(this, sender); + if (plugin.getConfiguration().get(ConfigKeys.LOG_SYNCHRONOUSLY_IN_COMMANDS)) { + future.join(); + } } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java b/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java index 7da93b90e..a68ef671a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java @@ -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; + } } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionFilter.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionFilter.java new file mode 100644 index 000000000..65a4be9ea --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionFilter.java @@ -0,0 +1,47 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 filter; + + public ApiActionFilter(FilterList filter) { + this.filter = filter; + } + + @Override + public boolean test(Action action) { + return this.filter.evaluate(action); + } + + public FilterList getFilter() { + return this.filter; + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionFilterFactory.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionFilterFactory.java new file mode 100644 index 000000000..5bd9a60c6 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionFilterFactory.java @@ -0,0 +1,77 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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)); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLog.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLog.java index 40f1c0e6a..ef8e7df79 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLog.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLog.java @@ -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 content; diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLogger.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLogger.java index 67d219282..c571d5e04 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLogger.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiActionLogger.java @@ -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 getLog() { return this.plugin.getStorage().getLogPage(ActionFilters.all(), null) .thenApply(result -> new ApiActionLog(result.getContent())); } + @Override + public @NonNull CompletableFuture> queryActions(@NonNull ActionFilter filter) { + return this.plugin.getStorage().getLogPage(getFilterList(filter), null).thenApply(ActionPage::new).thenApply(Page::entries); + } + + @Override + public @NonNull CompletableFuture> 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 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 submitToStorage(@NonNull Action entry) { - return this.plugin.getStorage().logAction(entry); + return this.plugin.getLogDispatcher().logToStorage((LoggedAction) entry); } @Override public @NonNull CompletableFuture broadcastAction(@NonNull Action entry) { - return CompletableFuture.runAsync(() -> this.plugin.getLogDispatcher().broadcastFromApi((LoggedAction) entry), this.plugin.getBootstrap().getScheduler().async()); + LogDispatcher dispatcher = this.plugin.getLogDispatcher(); + + CompletableFuture messagingFuture = dispatcher.logToStorage(((LoggedAction) entry)); + dispatcher.broadcastFromApi(((LoggedAction) entry)); + return messagingFuture; + } + + private static FilterList 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 { + private final LogPage page; + + private ActionPage(LogPage page) { + this.page = page; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public @NonNull List entries() { + return (List) this.page.getContent(); + } + + @Override + public int overallSize() { + return this.page.getTotalEntries(); + } } } diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateField.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateField.java index eda70cb2a..03d674e03 100644 --- a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateField.java +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateField.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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; diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateSqlBuilder.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateSqlBuilder.java index d1b5da243..63b25c200 100644 --- a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateSqlBuilder.java +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateSqlBuilder.java @@ -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 { public void visit(BulkUpdate update) { diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogGroupHistory.java b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogGroupHistory.java index 334cf7816..9d8d56e14 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogGroupHistory.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogGroupHistory.java @@ -59,13 +59,13 @@ public class LogGroupHistory extends ChildCommand { 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java index a06fd6f54..248c7acc1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogRecent.java @@ -74,7 +74,7 @@ public class LogRecent extends ChildCommand { 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogSearch.java b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogSearch.java index 3e5d89c1b..5af98e275 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogSearch.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogSearch.java @@ -49,7 +49,7 @@ public class LogSearch extends ChildCommand { @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 { 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogTrackHistory.java b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogTrackHistory.java index 2176fd45d..f88b2a94f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogTrackHistory.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogTrackHistory.java @@ -58,13 +58,13 @@ public class LogTrackHistory extends ChildCommand { 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java index d8a0fca05..f12fbe560 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/log/LogUserHistory.java @@ -55,13 +55,13 @@ public class LogUserHistory extends ChildCommand { 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java index 120d9fd2a..495f3296f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java +++ b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java @@ -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 LOG_SYNCHRONOUSLY_IN_COMMANDS = booleanKey("log-synchronously-in-commands", false); + /** * If LuckPerms should automatically install translation bundles and periodically update them. */ diff --git a/common/src/main/java/me/lucko/luckperms/common/filter/ConstraintFactory.java b/common/src/main/java/me/lucko/luckperms/common/filter/ConstraintFactory.java index a3b763cfb..a7897384a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/filter/ConstraintFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/filter/ConstraintFactory.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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; diff --git a/common/src/main/java/me/lucko/luckperms/common/filter/FilterList.java b/common/src/main/java/me/lucko/luckperms/common/filter/FilterList.java index eca53064d..c70d4d32c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/filter/FilterList.java +++ b/common/src/main/java/me/lucko/luckperms/common/filter/FilterList.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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; diff --git a/common/src/main/java/me/lucko/luckperms/common/filter/sql/ConstraintSqlBuilder.java b/common/src/main/java/me/lucko/luckperms/common/filter/sql/ConstraintSqlBuilder.java index c5f776aaa..04e5d7fc5 100644 --- a/common/src/main/java/me/lucko/luckperms/common/filter/sql/ConstraintSqlBuilder.java +++ b/common/src/main/java/me/lucko/luckperms/common/filter/sql/ConstraintSqlBuilder.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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; diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/InternalMessagingService.java b/common/src/main/java/me/lucko/luckperms/common/messaging/InternalMessagingService.java index 26b26ec62..a54a2cc14 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/InternalMessagingService.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/InternalMessagingService.java @@ -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 pushUpdate(); /** * Pushes an update for a specific user. * * @param user the user */ - void pushUserUpdate(User user); + CompletableFuture pushUserUpdate(User user); /** * Pushes a log entry to connected servers. * * @param logEntry the log entry */ - void pushLog(Action logEntry); + CompletableFuture 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 pushCustomPayload(String channelId, String payload); } diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java b/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java index e828d032b..508fb8906 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java @@ -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 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 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 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 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 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; diff --git a/common/src/main/java/me/lucko/luckperms/common/sender/AbstractSender.java b/common/src/main/java/me/lucko/luckperms/common/sender/AbstractSender.java index 9d12ca53d..97b072493 100644 --- a/common/src/main/java/me/lucko/luckperms/common/sender/AbstractSender.java +++ b/common/src/main/java/me/lucko/luckperms/common/sender/AbstractSender.java @@ -78,7 +78,7 @@ public final class AbstractSender 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 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 diff --git a/common/src/main/java/me/lucko/luckperms/common/sender/SenderFactory.java b/common/src/main/java/me/lucko/luckperms/common/sender/SenderFactory.java index 0ec94ac84..5c020afdd 100644 --- a/common/src/main/java/me/lucko/luckperms/common/sender/SenderFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/sender/SenderFactory.java @@ -63,8 +63,8 @@ public abstract class SenderFactory

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) { diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java b/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java index 6adb20531..0f89eecb7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java @@ -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 CompletableFuture future(Callable 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 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(); } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java index 1b9105c91..e657e7bbc 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileActionLogger.java @@ -137,7 +137,7 @@ public class FileActionLogger { } } - private Stream getRawLog() throws IOException { + private Stream loadLog(FilterList 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 filters, @Nullable PageParameters page) throws IOException { - List filtered = getRawLog() - .filter(filters::evaluate) - .sorted(Comparator.reverseOrder()) - .collect(Collectors.toList()); + List filtered = loadLog(filters) + .sorted(Comparator.comparing(LoggedAction::getTimestamp)) + .collect(Collectors.toList()) + .reversed(); int size = filtered.size(); List paginated = page != null ? page.paginate(filtered) : filtered; diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java index 68cf79165..922e096bb 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/mongodb/MongoStorage.java @@ -186,7 +186,7 @@ public class MongoStorage implements StorageImplementation { long count = c.countDocuments(filter); List content = new ArrayList<>(); - try (MongoCursor cursor = ConstraintMongoBuilder.page(page, c.find(filter).sort(Sorts.descending("timestamp"))).iterator()) { + try (MongoCursor cursor = ConstraintMongoBuilder.page(page, c.find(filter).sort(Sorts.descending("timestamp", "_id"))).iterator()) { while (cursor.hasNext()) { content.add(actionFromDoc(cursor.next())); } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java index 8924fb327..321b814ea 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java @@ -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)) { diff --git a/common/src/main/java/me/lucko/luckperms/common/util/AsyncInterface.java b/common/src/main/java/me/lucko/luckperms/common/util/AsyncInterface.java new file mode 100644 index 000000000..d1996226e --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/util/AsyncInterface.java @@ -0,0 +1,71 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 CompletableFuture future(Callable 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 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()); + } + +} diff --git a/common/src/test/java/me/lucko/luckperms/common/actionlog/ActionFilterMongoTest.java b/common/src/test/java/me/lucko/luckperms/common/actionlog/ActionFilterMongoTest.java index 24f3d3fa4..f3920b67d 100644 --- a/common/src/test/java/me/lucko/luckperms/common/actionlog/ActionFilterMongoTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/actionlog/ActionFilterMongoTest.java @@ -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"), diff --git a/common/src/test/java/me/lucko/luckperms/common/storage/AbstractStorageTest.java b/common/src/test/java/me/lucko/luckperms/common/storage/AbstractStorageTest.java index b20e5f255..cf5025a6d 100644 --- a/common/src/test/java/me/lucko/luckperms/common/storage/AbstractStorageTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/storage/AbstractStorageTest.java @@ -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()); diff --git a/common/src/test/java/me/lucko/luckperms/common/storage/ConfigurateStorageTest.java b/common/src/test/java/me/lucko/luckperms/common/storage/ConfigurateStorageTest.java index 45038a4ba..22d1db444 100644 --- a/common/src/test/java/me/lucko/luckperms/common/storage/ConfigurateStorageTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/storage/ConfigurateStorageTest.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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; diff --git a/common/src/test/java/me/lucko/luckperms/common/storage/MongoStorageTest.java b/common/src/test/java/me/lucko/luckperms/common/storage/MongoStorageTest.java index 668632884..cc28698da 100644 --- a/common/src/test/java/me/lucko/luckperms/common/storage/MongoStorageTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/storage/MongoStorageTest.java @@ -1,3 +1,28 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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; diff --git a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/CommandExecutor.java b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/CommandExecutor.java index 8cdec14c9..c5d92a1e5 100644 --- a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/CommandExecutor.java +++ b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/CommandExecutor.java @@ -33,8 +33,16 @@ import java.util.concurrent.CompletableFuture; */ public interface CommandExecutor { - CompletableFuture execute(String command); + CompletableFuture execute(StandaloneSender player, String command); - List tabComplete(String command); + List tabComplete(StandaloneSender player, String command); + + default CompletableFuture execute(String command) { + return execute(StandaloneUser.INSTANCE, command); + } + + default List tabComplete(String command) { + return tabComplete(StandaloneUser.INSTANCE, command); + } } diff --git a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/StandaloneSender.java b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/StandaloneSender.java new file mode 100644 index 000000000..eea1ad817 --- /dev/null +++ b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/StandaloneSender.java @@ -0,0 +1,48 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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(); +} diff --git a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/SingletonPlayer.java b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/StandaloneUser.java similarity index 57% rename from standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/SingletonPlayer.java rename to standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/StandaloneUser.java index 4a750d098..4d98b009f 100644 --- a/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/SingletonPlayer.java +++ b/standalone/app/src/main/java/me/lucko/luckperms/standalone/app/integration/StandaloneUser.java @@ -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. - * - *

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.

+ * 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 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> 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 sink : this.messageSinks) { - sink.accept(component); - } + LuckPermsApplication.LOGGER.info(ANSIComponentSerializer.ansi().serialize(component)); } - public void addMessageSink(Consumer sink) { - this.messageSinks.add(sink); + @Override + public Tristate getPermissionValue(String permission) { + return Tristate.TRUE; } - public void removeMessageSink(Consumer 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(); } } diff --git a/standalone/src/main/java/me/lucko/luckperms/standalone/LPStandalonePlugin.java b/standalone/src/main/java/me/lucko/luckperms/standalone/LPStandalonePlugin.java index 609b785e3..3970faa0b 100644 --- a/standalone/src/main/java/me/lucko/luckperms/standalone/LPStandalonePlugin.java +++ b/standalone/src/main/java/me/lucko/luckperms/standalone/LPStandalonePlugin.java @@ -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() { diff --git a/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneCommandManager.java b/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneCommandManager.java index 4e2c2d0a5..e1b67bce1 100644 --- a/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneCommandManager.java +++ b/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneCommandManager.java @@ -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 execute(String command) { - Sender wrapped = this.plugin.getSenderFactory().wrap(SingletonPlayer.INSTANCE); + public CompletableFuture execute(StandaloneSender player, String command) { + Sender wrapped = this.plugin.getSenderFactory().wrap(player); List arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(command); return executeCommand(wrapped, "lp", arguments); } @Override - public List tabComplete(String command) { - Sender wrapped = this.plugin.getSenderFactory().wrap(SingletonPlayer.INSTANCE); + public List tabComplete(StandaloneSender player, String command) { + Sender wrapped = this.plugin.getSenderFactory().wrap(player); List arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(command); return tabCompleteCommand(wrapped, arguments); } diff --git a/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneSenderFactory.java b/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneSenderFactory.java index ca21170a1..eec85c5b5 100644 --- a/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneSenderFactory.java +++ b/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneSenderFactory.java @@ -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 { +public class StandaloneSenderFactory extends SenderFactory { 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; - } - } diff --git a/standalone/src/main/java/me/lucko/luckperms/standalone/stub/StandaloneContextManager.java b/standalone/src/main/java/me/lucko/luckperms/standalone/stub/StandaloneContextManager.java index 43cfde390..35d48d7bd 100644 --- a/standalone/src/main/java/me/lucko/luckperms/standalone/stub/StandaloneContextManager.java +++ b/standalone/src/main/java/me/lucko/luckperms/standalone/stub/StandaloneContextManager.java @@ -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 { - private final QueryOptionsCache singletonCache = new QueryOptionsCache<>(SingletonPlayer.INSTANCE, this); +public class StandaloneContextManager extends ContextManager { + private final QueryOptionsCache 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 getCacheFor(SingletonPlayer subject) { + public QueryOptionsCache 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(); } diff --git a/standalone/src/test/java/me/lucko/luckperms/standalone/CommandsIntegrationTest.java b/standalone/src/test/java/me/lucko/luckperms/standalone/CommandsIntegrationTest.java index 485a3ccbe..362825e84 100644 --- a/standalone/src/test/java/me/lucko/luckperms/standalone/CommandsIntegrationTest.java +++ b/standalone/src/test/java/me/lucko/luckperms/standalone/CommandsIntegrationTest.java @@ -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 CONFIG = ImmutableMap.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 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!") diff --git a/standalone/src/test/java/me/lucko/luckperms/standalone/WebEditorIntegrationTest.java b/standalone/src/test/java/me/lucko/luckperms/standalone/WebEditorIntegrationTest.java index 28b43f3b2..1d88138ff 100644 --- a/standalone/src/test/java/me/lucko/luckperms/standalone/WebEditorIntegrationTest.java +++ b/standalone/src/test/java/me/lucko/luckperms/standalone/WebEditorIntegrationTest.java @@ -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(); diff --git a/standalone/src/test/java/me/lucko/luckperms/standalone/utils/CommandTester.java b/standalone/src/test/java/me/lucko/luckperms/standalone/utils/CommandTester.java index 97d85a0fc..3b9d29f79 100644 --- a/standalone/src/test/java/me/lucko/luckperms/standalone/utils/CommandTester.java +++ b/standalone/src/test/java/me/lucko/luckperms/standalone/utils/CommandTester.java @@ -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, Function permissions = null; @@ -72,9 +70,16 @@ public final class CommandTester implements Consumer, Function 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, Function, Function *
    *
  • Dependency loading system is replaced with a no-op stub that delegates to the test classloader
  • - *
  • Sender factory is extended and allows for permission checks to be intercepted
  • + *
  • Ability to register additional sender instances as being online
  • *
*

*/ @@ -81,7 +79,7 @@ public final class TestPluginBootstrap extends LPStandaloneBootstrap { } public static final class TestPlugin extends LPStandalonePlugin { - private TestSenderFactory senderFactory; + private final Set 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 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 permissionChecker; - - public TestSenderFactory(LPStandalonePlugin plugin) { - super(plugin); - } - - public void setPermissionChecker(Function 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(); - } - } } diff --git a/standalone/src/test/java/me/lucko/luckperms/standalone/utils/TestSender.java b/standalone/src/test/java/me/lucko/luckperms/standalone/utils/TestSender.java new file mode 100644 index 000000000..e1723dea5 --- /dev/null +++ b/standalone/src/test/java/me/lucko/luckperms/standalone/utils/TestSender.java @@ -0,0 +1,113 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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> messageSinks; + + private String name = "StandaloneUser"; + private UUID uniqueId = UUID.randomUUID(); + private boolean isConsole = false; + + private Function 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 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 permissionChecker) { + this.permissionChecker = permissionChecker; + } + + public void addMessageSink(Consumer sink) { + this.messageSinks.add(sink); + } +}