From 53fb46ee8540565d9652c7e7ed3a2d32e1f0891b Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 23 Nov 2020 23:54:02 +0000 Subject: [PATCH 01/38] Expose uuid/username lookups and validity checks as events in the API --- .../lookup/UniqueIdDetermineTypeEvent.java | 86 ++++++++++++++ .../player/lookup/UniqueIdLookupEvent.java | 61 ++++++++++ .../player/lookup/UsernameLookupEvent.java | 61 ++++++++++ .../lookup/UsernameValidityCheckEvent.java | 76 ++++++++++++ .../luckperms/api/event/type/ResultEvent.java | 59 ++++++++++ .../vault/LuckPermsVaultPermission.java | 14 +-- .../commands/group/GroupListMembers.java | 17 +-- .../common/commands/misc/SearchCommand.java | 17 +-- .../common/commands/user/UserInfo.java | 3 +- .../commands/user/UserParentCommand.java | 45 +++----- .../common/event/EventDispatcher.java | 49 ++++++++ .../luckperms/common/locale/Message.java | 4 +- .../plugin/AbstractLuckPermsPlugin.java | 49 ++++++++ .../common/plugin/LuckPermsPlugin.java | 25 ++++ .../luckperms/common/util/UniqueIdType.java | 109 ++++++++++++++++++ 15 files changed, 601 insertions(+), 74 deletions(-) create mode 100644 api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdDetermineTypeEvent.java create mode 100644 api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdLookupEvent.java create mode 100644 api/src/main/java/net/luckperms/api/event/player/lookup/UsernameLookupEvent.java create mode 100644 api/src/main/java/net/luckperms/api/event/player/lookup/UsernameValidityCheckEvent.java create mode 100644 api/src/main/java/net/luckperms/api/event/type/ResultEvent.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/util/UniqueIdType.java diff --git a/api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdDetermineTypeEvent.java b/api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdDetermineTypeEvent.java new file mode 100644 index 000000000..78bec2de9 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdDetermineTypeEvent.java @@ -0,0 +1,86 @@ +/* + * 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.event.player.lookup; + +import net.luckperms.api.event.LuckPermsEvent; +import net.luckperms.api.event.type.ResultEvent; +import net.luckperms.api.event.util.Param; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; +import java.util.UUID; + +/** + * Called when the platform needs to determine the type of a player's {@link UUID unique id}. + * + * @since 5.3 + */ +public interface UniqueIdDetermineTypeEvent extends LuckPermsEvent, ResultEvent { + + /** + * The players UUID has been obtained by authenticating with the Mojang session servers. + * + *

Usually indicated by the UUID being {@link UUID#version() version} 4.

+ */ + String TYPE_AUTHENTICATED = "authenticated"; + + /** + * The players UUID has not been obtained through authentication, and instead is likely based + * on the username they connected with. + * + *

Usually indicated by the UUID being {@link UUID#version() version} 3.

+ */ + String TYPE_UNAUTHENTICATED = "unauthenticated"; + + /** + * Gets the {@link UUID unique id} being queried. + * + * @return the unique id + */ + @Param(0) + @NonNull UUID getUniqueId(); + + /** + * Gets the current result unique id type. + * + * @return the type + */ + default @NonNull String getType() { + return result().get(); + } + + /** + * Sets the result unique id type. + * + * @param type the type + */ + default void setType(@NonNull String type) { + Objects.requireNonNull(type, "type"); + result().set(type); + } + +} diff --git a/api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdLookupEvent.java b/api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdLookupEvent.java new file mode 100644 index 000000000..e351926f3 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/event/player/lookup/UniqueIdLookupEvent.java @@ -0,0 +1,61 @@ +/* + * 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.event.player.lookup; + +import net.luckperms.api.event.LuckPermsEvent; +import net.luckperms.api.event.type.ResultEvent; +import net.luckperms.api.event.util.Param; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.UUID; + +/** + * Called when the platform needs a unique id for a given username. + * + * @since 5.3 + */ +public interface UniqueIdLookupEvent extends LuckPermsEvent, ResultEvent { + + /** + * Gets the username being looked up. + * + * @return the username + */ + @Param(0) + @NonNull String getUsername(); + + /** + * Sets the result unique id. + * + * @param uniqueId the unique id + */ + default void setUniqueId(@Nullable UUID uniqueId) { + result().set(uniqueId); + } + +} diff --git a/api/src/main/java/net/luckperms/api/event/player/lookup/UsernameLookupEvent.java b/api/src/main/java/net/luckperms/api/event/player/lookup/UsernameLookupEvent.java new file mode 100644 index 000000000..4650f080f --- /dev/null +++ b/api/src/main/java/net/luckperms/api/event/player/lookup/UsernameLookupEvent.java @@ -0,0 +1,61 @@ +/* + * 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.event.player.lookup; + +import net.luckperms.api.event.LuckPermsEvent; +import net.luckperms.api.event.type.ResultEvent; +import net.luckperms.api.event.util.Param; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.UUID; + +/** + * Called when the platform needs a username for a given unique id. + * + * @since 5.3 + */ +public interface UsernameLookupEvent extends LuckPermsEvent, ResultEvent { + + /** + * Gets the {@link UUID unique id} being looked up. + * + * @return the unique id + */ + @Param(0) + @NonNull UUID getUniqueId(); + + /** + * Sets the result username. + * + * @param username the username + */ + default void setUsername(@Nullable String username) { + result().set(username); + } + +} diff --git a/api/src/main/java/net/luckperms/api/event/player/lookup/UsernameValidityCheckEvent.java b/api/src/main/java/net/luckperms/api/event/player/lookup/UsernameValidityCheckEvent.java new file mode 100644 index 000000000..94b7444d7 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/event/player/lookup/UsernameValidityCheckEvent.java @@ -0,0 +1,76 @@ +/* + * 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.event.player.lookup; + +import net.luckperms.api.event.LuckPermsEvent; +import net.luckperms.api.event.util.Param; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Called when the validity of a username is being tested. + * + * @since 5.3 + */ +public interface UsernameValidityCheckEvent extends LuckPermsEvent { + + /** + * Gets the username being tested. + * + * @return the username + */ + @Param(0) + @NonNull String getUsername(); + + /** + * Gets the current validity state for the username. + * + * @return the validity state + */ + @Param(1) + @NonNull AtomicBoolean validityState(); + + /** + * Gets if the username is currently considered to be valid. + * + * @return if the username is valid + */ + default boolean isValid() { + return validityState().get(); + } + + /** + * Sets if the username should be considered valid or not. + * + * @param valid whether the username is valid + */ + default void setValid(boolean valid) { + validityState().set(valid); + } + +} diff --git a/api/src/main/java/net/luckperms/api/event/type/ResultEvent.java b/api/src/main/java/net/luckperms/api/event/type/ResultEvent.java new file mode 100644 index 000000000..685702a6a --- /dev/null +++ b/api/src/main/java/net/luckperms/api/event/type/ResultEvent.java @@ -0,0 +1,59 @@ +/* + * 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.event.type; + +import net.luckperms.api.event.util.Param; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Represents an event that has a result. + * + * @param the type of the result + * @since 5.3 + */ +public interface ResultEvent { + + /** + * Gets an {@link AtomicReference} containing the result. + * + * @return the result + */ + @Param(-1) + @NonNull AtomicReference result(); + + /** + * Gets if a result has been set for the event. + * + * @return if there is a result + */ + default boolean hasResult() { + return result().get() != null; + } + +} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java index c2dc804f1..4cb5e78b5 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java @@ -42,6 +42,7 @@ import me.lucko.luckperms.common.node.factory.NodeBuilders; import me.lucko.luckperms.common.node.types.Inheritance; import me.lucko.luckperms.common.query.QueryOptionsImpl; +import me.lucko.luckperms.common.util.UniqueIdType; import me.lucko.luckperms.common.util.Uuids; import me.lucko.luckperms.common.verbose.event.MetaCheckEvent; import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent; @@ -124,10 +125,7 @@ public UUID lookupUuid(String player) { } // lookup a username from the database - uuid = this.plugin.getStorage().getPlayerUniqueId(player.toLowerCase()).join(); - if (uuid == null) { - uuid = this.plugin.getBootstrap().lookupUniqueId(player).orElse(null); - } + uuid = this.plugin.lookupUniqueId(player).orElse(null); // unable to find a user, throw an exception if (uuid == null) { @@ -146,10 +144,8 @@ public PermissionHolder lookupUser(UUID uuid) { return user; } - // if the uuid is version 2, assume it is an NPC - // see: https://github.com/lucko/LuckPerms/issues/1470 - // and https://github.com/lucko/LuckPerms/issues/1470#issuecomment-475403162 - if (uuid.version() == 2) { + // is it an npc? + if (UniqueIdType.determineType(uuid, this.plugin).getType().equals("npc")) { String npcGroupName = this.plugin.getConfiguration().get(ConfigKeys.VAULT_NPC_GROUP); Group npcGroup = this.plugin.getGroupManager().getIfLoaded(npcGroupName); if (npcGroup == null) { @@ -380,7 +376,7 @@ QueryOptions getQueryOptions(@Nullable UUID uuid, @Nullable String world) { boolean op = false; if (player != null) { op = player.isOp(); - } else if (uuid != null && uuid.version() == 2) { // npc + } else if (uuid != null && UniqueIdType.determineType(uuid, this.plugin).getType().equals("npc")) { op = this.plugin.getConfiguration().get(ConfigKeys.VAULT_NPC_OP_STATUS); } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java index 7cd44a210..10c41fee3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java @@ -34,7 +34,6 @@ import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.HolderType; @@ -104,21 +103,7 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Group target Message.SEARCH_RESULT.send(sender, users + groups, users, groups); if (!matchedUsers.isEmpty()) { - Map uuidLookups = LoadingMap.of(u -> { - String s = plugin.getStorage().getPlayerName(u).join(); - if (s != null && !s.isEmpty() && !s.equals("null")) { - return s; - } - - if (plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { - s = plugin.getBootstrap().lookupUsername(u).orElse(null); - if (s != null) { - return s; - } - } - - return u.toString(); - }); + Map uuidLookups = LoadingMap.of(u -> plugin.lookupUsername(u).orElseGet(u::toString)); sendResult(sender, matchedUsers, uuidLookups::get, Message.SEARCH_SHOWING_USERS, HolderType.USER, label, page); } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java index 0f9d17cb9..a7df7c41a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/SearchCommand.java @@ -38,7 +38,6 @@ import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; import me.lucko.luckperms.common.command.tabcomplete.TabCompletions; import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.HolderType; import me.lucko.luckperms.common.node.comparator.NodeEntryComparator; @@ -86,21 +85,7 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, ArgumentList Message.SEARCH_RESULT.send(sender, users + groups, users, groups); if (!matchedUsers.isEmpty()) { - Map uuidLookups = LoadingMap.of(u -> { - String s = plugin.getStorage().getPlayerName(u).join(); - if (s != null && !s.isEmpty() && !s.equals("null")) { - return s; - } - - if (plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { - s = plugin.getBootstrap().lookupUsername(u).orElse(null); - if (s != null) { - return s; - } - } - - return u.toString(); - }); + Map uuidLookups = LoadingMap.of(u -> plugin.lookupUsername(u).orElseGet(u::toString)); sendResult(sender, matchedUsers, uuidLookups::get, Message.SEARCH_SHOWING_USERS, HolderType.USER, label, page, comparison); } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java index 8f9803989..bfc45cbae 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserInfo.java @@ -37,6 +37,7 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.util.Predicates; +import me.lucko.luckperms.common.util.UniqueIdType; import me.lucko.luckperms.common.verbose.event.MetaCheckEvent; import net.luckperms.api.context.ContextSet; @@ -63,7 +64,7 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, User target, Message.USER_INFO_GENERAL.send(sender, target.getUsername().orElse("Unknown"), target.getUniqueId().toString(), - target.getUniqueId().version() == 4, + UniqueIdType.determineType(target.getUniqueId(), plugin).describe(), plugin.getBootstrap().isPlayerOnline(target.getUniqueId()) ); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserParentCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserParentCommand.java index 80c1969bf..64d2ce346 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserParentCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserParentCommand.java @@ -37,14 +37,12 @@ import me.lucko.luckperms.common.commands.generic.other.HolderShowTracks; import me.lucko.luckperms.common.commands.generic.parent.CommandParent; import me.lucko.luckperms.common.commands.generic.permission.CommandPermission; -import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.HolderType; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.UserIdentifier; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.storage.misc.DataConstraints; import me.lucko.luckperms.common.util.CaffeineFactory; import me.lucko.luckperms.common.util.Uuids; @@ -81,36 +79,23 @@ public UserParentCommand() { } public static UUID parseTargetUniqueId(String target, LuckPermsPlugin plugin, Sender sender) { - UUID uniqueId = Uuids.parse(target); - if (uniqueId == null) { - if (!plugin.getConfiguration().get(ConfigKeys.ALLOW_INVALID_USERNAMES)) { - if (!DataConstraints.PLAYER_USERNAME_TEST.test(target)) { - Message.USER_INVALID_ENTRY.send(sender, target); - return null; - } - } else { - if (!DataConstraints.PLAYER_USERNAME_TEST_LENIENT.test(target)) { - Message.USER_INVALID_ENTRY.send(sender, target); - return null; - } - } - - uniqueId = plugin.getStorage().getPlayerUniqueId(target.toLowerCase()).join(); - if (uniqueId == null) { - if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { - Message.USER_NOT_FOUND.send(sender, target); - return null; - } - - uniqueId = plugin.getBootstrap().lookupUniqueId(target).orElse(null); - if (uniqueId == null) { - Message.USER_NOT_FOUND.send(sender, target); - return null; - } - } + UUID parsed = Uuids.parse(target); + if (parsed != null) { + return parsed; + } + + if (!plugin.testUsernameValidity(target)) { + Message.USER_INVALID_ENTRY.send(sender, target); + return null; + } + + UUID lookup = plugin.lookupUniqueId(target).orElse(null); + if (lookup == null) { + Message.USER_NOT_FOUND.send(sender, target); + return null; } - return uniqueId; + return lookup; } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java index 735e40c5e..e1130e25d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java @@ -64,6 +64,10 @@ import net.luckperms.api.event.node.NodeRemoveEvent; import net.luckperms.api.event.player.PlayerDataSaveEvent; import net.luckperms.api.event.player.PlayerLoginProcessEvent; +import net.luckperms.api.event.player.lookup.UniqueIdDetermineTypeEvent; +import net.luckperms.api.event.player.lookup.UniqueIdLookupEvent; +import net.luckperms.api.event.player.lookup.UsernameLookupEvent; +import net.luckperms.api.event.player.lookup.UsernameValidityCheckEvent; import net.luckperms.api.event.source.Source; import net.luckperms.api.event.sync.ConfigReloadEvent; import net.luckperms.api.event.sync.PostSyncEvent; @@ -95,6 +99,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; public final class EventDispatcher { @@ -313,6 +318,46 @@ public void dispatchPlayerDataSave(UUID uniqueId, String username, PlayerSaveRes post(PlayerDataSaveEvent.class, () -> generate(PlayerDataSaveEvent.class, uniqueId, username, result)); } + public String dispatchUniqueIdDetermineType(UUID uniqueId, String initialType) { + if (!shouldPost(UniqueIdDetermineTypeEvent.class)) { + return initialType; + } + + AtomicReference result = new AtomicReference<>(initialType); + post(generate(UniqueIdDetermineTypeEvent.class, result, uniqueId)); + return result.get(); + } + + public UUID dispatchUniqueIdLookup(String username, UUID initial) { + if (!shouldPost(UniqueIdLookupEvent.class)) { + return initial; + } + + AtomicReference result = new AtomicReference<>(initial); + post(generate(UniqueIdLookupEvent.class, result, username)); + return result.get(); + } + + public String dispatchUsernameLookup(UUID uniqueId, String initial) { + if (!shouldPost(UsernameLookupEvent.class)) { + return initial; + } + + AtomicReference result = new AtomicReference<>(initial); + post(generate(UsernameLookupEvent.class, result, uniqueId)); + return result.get(); + } + + public boolean dispatchUsernameValidityCheck(String username, boolean initialState) { + if (!shouldPost(UsernameValidityCheckEvent.class)) { + return initialState; + } + + AtomicBoolean result = new AtomicBoolean(initialState); + post(generate(UsernameValidityCheckEvent.class, username, result)); + return result.get(); + } + public void dispatchUserLoad(User user) { post(UserLoadEvent.class, () -> generate(UserLoadEvent.class, user.getApiProxy())); } @@ -361,6 +406,10 @@ public static List> getKnownEventTypes() { NodeRemoveEvent.class, PlayerDataSaveEvent.class, PlayerLoginProcessEvent.class, + UniqueIdDetermineTypeEvent.class, + UniqueIdLookupEvent.class, + UsernameLookupEvent.class, + UsernameValidityCheckEvent.class, ConfigReloadEvent.class, PostSyncEvent.class, PreNetworkSyncEvent.class, diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java index 662bc428d..c2027ee6c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java @@ -2757,7 +2757,7 @@ static TextComponent prefixed(ComponentLike component) { .append(FULL_STOP) ); - Args4 USER_INFO_GENERAL = (username, uuid, mojang, online) -> join(newline(), + Args4 USER_INFO_GENERAL = (username, uuid, uuidType, online) -> join(newline(), // "&b&l> &bUser Info: &f{}" // "&f- &3UUID: &f{}" // "&f &7(type: {}&7)" @@ -2781,7 +2781,7 @@ static TextComponent prefixed(ComponentLike component) { .append(OPEN_BRACKET) .append(translatable("luckperms.command.user.info.uuid-type-key")) .append(text(": ")) - .append(mojang ? translatable("luckperms.command.user.info.uuid-type.mojang", DARK_GREEN) : translatable("luckperms.command.user.info.uuid-type.not-mojang", DARK_GRAY)) + .append(uuidType) .append(CLOSE_BRACKET)), prefixed(text() .color(DARK_AQUA) diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java index 8cb03ab1c..28ee1850d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java @@ -51,6 +51,7 @@ import me.lucko.luckperms.common.storage.StorageFactory; import me.lucko.luckperms.common.storage.StorageType; import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher; +import me.lucko.luckperms.common.storage.misc.DataConstraints; import me.lucko.luckperms.common.tasks.SyncTask; import me.lucko.luckperms.common.treeview.PermissionRegistry; import me.lucko.luckperms.common.verbose.VerboseHandler; @@ -66,6 +67,7 @@ import java.util.EnumSet; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin { @@ -291,6 +293,53 @@ public void setMessagingService(InternalMessagingService messagingService) { } } + @Override + public Optional lookupUniqueId(String username) { + // get a result from the DB cache + UUID uniqueId = getStorage().getPlayerUniqueId(username.toLowerCase()).join(); + + // fire the event + uniqueId = getEventDispatcher().dispatchUniqueIdLookup(username, uniqueId); + + // try the servers cache + if (uniqueId == null && getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { + uniqueId = getBootstrap().lookupUniqueId(username).orElse(null); + } + + return Optional.ofNullable(uniqueId); + } + + @Override + public Optional lookupUsername(UUID uniqueId) { + // get a result from the DB cache + String username = getStorage().getPlayerName(uniqueId).join(); + + // fire the event + username = getEventDispatcher().dispatchUsernameLookup(uniqueId, username); + + // try the servers cache + if (username == null && getConfiguration().get(ConfigKeys.USE_SERVER_UUID_CACHE)) { + username = getBootstrap().lookupUsername(uniqueId).orElse(null); + } + + return Optional.ofNullable(username); + } + + @Override + public boolean testUsernameValidity(String username) { + // if the username doesn't even pass the lenient test, don't bother going any further + // it's either empty, or too long to fit in the sql column + if (!DataConstraints.PLAYER_USERNAME_TEST_LENIENT.test(username)) { + return false; + } + + // if invalid usernames are allowed in the config, set valid to true, otherwise, use the more strict test + boolean valid = getConfiguration().get(ConfigKeys.ALLOW_INVALID_USERNAMES) || DataConstraints.PLAYER_USERNAME_TEST.test(username); + + // fire the event & return + return getEventDispatcher().dispatchUsernameValidityCheck(username, valid); + } + @Override public DependencyManager getDependencyManager() { return this.dependencyManager; diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java index 718261baa..d002d2ece 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java @@ -61,6 +61,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Stream; /** @@ -255,6 +256,30 @@ public interface LuckPermsPlugin { */ Optional getQueryOptionsForUser(User user); + /** + * Lookup a uuid from a username. + * + * @param username the username to lookup + * @return an optional uuid, if found + */ + Optional lookupUniqueId(String username); + + /** + * Lookup a username from a uuid. + * + * @param uniqueId the uuid to lookup + * @return an optional username, if found + */ + Optional lookupUsername(UUID uniqueId); + + /** + * Tests whether the given username is valid. + * + * @param username the username + * @return true if valid + */ + boolean testUsernameValidity(String username); + /** * Gets a list of online Senders on the platform * diff --git a/common/src/main/java/me/lucko/luckperms/common/util/UniqueIdType.java b/common/src/main/java/me/lucko/luckperms/common/util/UniqueIdType.java new file mode 100644 index 000000000..b3dbfdd89 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/util/UniqueIdType.java @@ -0,0 +1,109 @@ +/* + * 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 net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.luckperms.api.event.player.lookup.UniqueIdDetermineTypeEvent; + +import java.util.UUID; + +/** + * Encapsulates the type of a players unique id. + */ +public final class UniqueIdType { + + public static final UniqueIdType AUTHENTICATED = new UniqueIdType( + UniqueIdDetermineTypeEvent.TYPE_AUTHENTICATED, + Component.translatable("luckperms.command.user.info.uuid-type.mojang", NamedTextColor.DARK_GREEN) + ); + + public static final UniqueIdType UNAUTHENTICATED = new UniqueIdType( + UniqueIdDetermineTypeEvent.TYPE_UNAUTHENTICATED, + Component.translatable("luckperms.command.user.info.uuid-type.not-mojang", NamedTextColor.DARK_GRAY) + ); + + private static final String TYPE_NPC = "npc"; + public static final UniqueIdType NPC = new UniqueIdType(TYPE_NPC); + + public static UniqueIdType determineType(UUID uniqueId, LuckPermsPlugin plugin) { + // determine initial type based on the uuid version + String type; + switch (uniqueId.version()) { + case 4: + type = UniqueIdDetermineTypeEvent.TYPE_AUTHENTICATED; + break; + case 3: + type = UniqueIdDetermineTypeEvent.TYPE_UNAUTHENTICATED; + break; + case 2: + // if the uuid is version 2, assume it is an NPC + // see: https://github.com/lucko/LuckPerms/issues/1470 + // and https://github.com/lucko/LuckPerms/issues/1470#issuecomment-475403162 + type = TYPE_NPC; + break; + default: + type = "unknown"; + break; + } + + // call the event + type = plugin.getEventDispatcher().dispatchUniqueIdDetermineType(uniqueId, type); + + switch (type) { + case UniqueIdDetermineTypeEvent.TYPE_AUTHENTICATED: + return AUTHENTICATED; + case UniqueIdDetermineTypeEvent.TYPE_UNAUTHENTICATED: + return UNAUTHENTICATED; + case TYPE_NPC: + return NPC; + default: + return new UniqueIdType(type); + } + } + + private final String type; + private final Component component; + + private UniqueIdType(String type) { + this(type, Component.text(type, NamedTextColor.GOLD)); + } + + private UniqueIdType(String type, Component component) { + this.type = type; + this.component = component; + } + + public String getType() { + return this.type; + } + + public Component describe() { + return this.component; + } +} From 8a38c5f4b72cf610c49bff5a3fef5f8752921c6b Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 26 Nov 2020 15:52:39 +0000 Subject: [PATCH 02/38] Properly open URLClassLoader module on Java 9+ to allow reflection and avoid access warning (#952) --- .../classloader/ReflectionClassLoader.java | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java index c5b5ab41a..e68cc43d7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java @@ -25,9 +25,6 @@ package me.lucko.luckperms.common.dependencies.classloader; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; - import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap; import java.lang.reflect.InvocationTargetException; @@ -38,10 +35,28 @@ import java.nio.file.Path; public class ReflectionClassLoader implements PluginClassLoader { - private final URLClassLoader classLoader; - @SuppressWarnings("Guava") // we can't use java.util.Function because old Guava versions are used at runtime - private final Supplier addUrlMethod; + private static final Method ADD_URL_METHOD; + + static { + // If on Java 9+, open the URLClassLoader module to this module + // so we can access its API via reflection without producing a warning. + try { + openUrlClassLoaderModule(); + } catch (Throwable e) { + // ignore exception - will throw on Java 8 since the Module classes don't exist + } + + // Get the protected 'addURL' method on URLClassLoader and set it to accessible. + try { + ADD_URL_METHOD = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + ADD_URL_METHOD.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private final URLClassLoader classLoader; public ReflectionClassLoader(LuckPermsBootstrap bootstrap) throws IllegalStateException { ClassLoader classLoader = bootstrap.getClass().getClassLoader(); @@ -50,42 +65,35 @@ public ReflectionClassLoader(LuckPermsBootstrap bootstrap) throws IllegalStateEx } else { throw new IllegalStateException("ClassLoader is not instance of URLClassLoader"); } - - this.addUrlMethod = Suppliers.memoize(() -> { - if (isJava9OrNewer()) { - bootstrap.getPluginLogger().info("It is safe to ignore any warning printed following this message " + - "starting with 'WARNING: An illegal reflective access operation has occurred, Illegal reflective " + - "access by " + getClass().getName() + "'. This is intended, and will not have any impact on the " + - "operation of LuckPerms."); - } - - try { - Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - addUrlMethod.setAccessible(true); - return addUrlMethod; - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - }); } @Override public void addJarToClasspath(Path file) { try { - this.addUrlMethod.get().invoke(this.classLoader, file.toUri().toURL()); + ADD_URL_METHOD.invoke(this.classLoader, file.toUri().toURL()); } catch (IllegalAccessException | InvocationTargetException | MalformedURLException e) { throw new RuntimeException(e); } } @SuppressWarnings("JavaReflectionMemberAccess") - private static boolean isJava9OrNewer() { - try { - // method was added in the Java 9 release - Runtime.class.getMethod("version"); - return true; - } catch (NoSuchMethodException e) { - return false; - } + private static void openUrlClassLoaderModule() throws Exception { + // This is effectively calling: + // + // URLClassLoader.class.getModule().addOpens( + // URLClassLoader.class.getPackageName(), + // ReflectionClassLoader.class.getModule() + // ); + // + // We use reflection since we build against Java 8. + + Class moduleClass = Class.forName("java.lang.Module"); + Method getModuleMethod = Class.class.getMethod("getModule"); + Method addOpensMethod = moduleClass.getMethod("addOpens", String.class, moduleClass); + + Object urlClassLoaderModule = getModuleMethod.invoke(URLClassLoader.class); + Object thisModule = getModuleMethod.invoke(ReflectionClassLoader.class); + + addOpensMethod.invoke(urlClassLoaderModule, URLClassLoader.class.getPackage().getName(), thisModule); } } From fdd3b117f1307063ea50d762c8690fcfb0900ab6 Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 26 Nov 2020 17:24:37 +0000 Subject: [PATCH 03/38] Reduce translation cache refresh slightly to 23 hours --- .../me/lucko/luckperms/common/locale/TranslationRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java b/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java index b6eb18dbb..3f320ca85 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java @@ -64,7 +64,7 @@ public class TranslationRepository { private static final String TRANSLATIONS_INFO_ENDPOINT = "https://metadata.luckperms.net/data/translations"; private static final String TRANSLATIONS_DOWNLOAD_ENDPOINT = "https://metadata.luckperms.net/translation/"; private static final long MAX_BUNDLE_SIZE = 1048576L; // 1mb - private static final long CACHE_MAX_AGE = TimeUnit.DAYS.toMillis(1); + private static final long CACHE_MAX_AGE = TimeUnit.HOURS.toMillis(23); private final LuckPermsPlugin plugin; From 727c2f92c37fd985d3253090583e991f64abbef3 Mon Sep 17 00:00:00 2001 From: Sven Arends <11473832+svenar-nl@users.noreply.github.com> Date: Thu, 26 Nov 2020 18:52:31 +0100 Subject: [PATCH 04/38] PowerRanks migration (#2718) --- bukkit/build.gradle | 3 + .../bukkit/migration/MigrationPowerRanks.java | 148 ++++++++++++++++++ .../migration/MigrationParentCommand.java | 1 + 3 files changed, 152 insertions(+) create mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 702537d6d..243ce791c 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -28,6 +28,9 @@ dependencies { } compileOnly 'com.github.gustav9797:PowerfulPermsAPI:4.5.2' compileOnly 'org.anjocaido:GroupManager:1.4' + compileOnly('nl.svenar:powerranks:1.9.2') { + exclude(module: 'nametagedit') + } compileOnly 'de.bananaco:bpermissions-api:2.12' compileOnly('com.platymuus:bukkit-permissions:2.5') { exclude(module: 'bukkit') diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java new file mode 100644 index 000000000..13a22b45d --- /dev/null +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java @@ -0,0 +1,148 @@ +/* + * 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.bukkit.migration; + +import me.lucko.luckperms.common.command.CommandResult; +import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.CommandPermission; +import me.lucko.luckperms.common.command.spec.CommandSpec; +import me.lucko.luckperms.common.command.utils.ArgumentList; +import me.lucko.luckperms.common.commands.migration.MigrationUtils; +import me.lucko.luckperms.common.locale.Message; +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.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.common.util.Predicates; +import me.lucko.luckperms.common.util.ProgressLogger; + +import net.luckperms.api.context.DefaultContextKeys; +import net.luckperms.api.event.cause.CreationCause; +import net.luckperms.api.model.data.DataType; + +import nl.svenar.PowerRanks.Cache.CachedPlayers; +import nl.svenar.PowerRanks.Cache.PowerConfigurationSection; +import nl.svenar.PowerRanks.Data.Users; +import nl.svenar.PowerRanks.PowerRanks; +import nl.svenar.PowerRanks.api.PowerRanksAPI; + +import org.bukkit.Bukkit; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +public class MigrationPowerRanks extends ChildCommand { + public MigrationPowerRanks() { + super(CommandSpec.MIGRATION_COMMAND, "powerranks", CommandPermission.MIGRATION, Predicates.alwaysFalse()); + } + + @Override + public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { + ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PowerRanks"); + log.addListener(plugin.getConsoleSender()); + log.addListener(sender); + + log.log("Starting."); + + if (!Bukkit.getPluginManager().isPluginEnabled("PowerRanks")) { + log.logError("Plugin not loaded."); + return CommandResult.STATE_ERROR; + } + + PowerRanks pr = (PowerRanks) Bukkit.getServer().getPluginManager().getPlugin("PowerRanks"); + PowerRanksAPI prApi = (pr).loadAPI(); + Users prUsers = new Users(pr); + + // Migrate all groups + log.log("Starting groups migration."); + Set ranks = prApi.getRanks(); + AtomicInteger groupCount = new AtomicInteger(0); + for (String rank : ranks) { + Group group = plugin.getStorage().createAndLoadGroup(rank, CreationCause.INTERNAL).join(); + + for (String node : prApi.getPermissions(rank)) { + if (node.isEmpty()) continue; + group.setNode(DataType.NORMAL, MigrationUtils.parseNode(node, true).build(), true); + } + + for (String parent : prApi.getInheritances(rank)) { + if (parent.isEmpty()) continue; + group.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(parent)).build(), true); + } + + plugin.getStorage().saveGroup(group); + log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); + } + log.log("Migrated " + groupCount.get() + " groups"); + + // Migrate all users + log.log("Starting user migration."); + Set playerUuids = prUsers.getCachedPlayers(); + AtomicInteger userCount = new AtomicInteger(0); + for (String uuidString : playerUuids) { + UUID uuid = BukkitUuids.lookupUuid(log, uuidString); + if (uuid == null) { + continue; + } + + User user = plugin.getStorage().loadUser(uuid, null).join(); + + user.setNode(DataType.NORMAL, Inheritance.builder(CachedPlayers.getString("players." + uuidString + ".rank")).build(), true); + + final PowerConfigurationSection subGroups = CachedPlayers.getConfigurationSection("players." + uuidString + ".subranks"); + if (subGroups != null) { + for (String subGroup : subGroups.getKeys(false)) { + Inheritance.Builder builder = Inheritance.builder(subGroup); + for (String worldName : CachedPlayers.getStringList("players." + uuidString + ".subranks." + subGroup + ".worlds")) { + if (!worldName.equalsIgnoreCase("all")) { + builder.withContext(DefaultContextKeys.WORLD_KEY, worldName); + } + } + user.setNode(DataType.NORMAL, builder.build(), true); + } + } + + for (String node : CachedPlayers.getStringList("players." + uuidString + ".permissions")) { + if (node.isEmpty()) continue; + user.setNode(DataType.NORMAL, MigrationUtils.parseNode(node, true).build(), true); + } + + user.getPrimaryGroup().setStoredValue(CachedPlayers.getString("players." + uuidString + ".rank")); + + plugin.getUserManager().getHouseKeeper().cleanup(user.getUniqueId()); + plugin.getStorage().saveUser(user); + log.logAllProgress("Migrated {} users so far.", userCount.incrementAndGet()); + } + + log.log("Migrated " + userCount.get() + " users."); + log.log("Success! Migration complete."); + log.log("Don't forget to remove the PowerRanks jar from your plugins folder & restart the server. " + + "LuckPerms may not take over as the server permission handler until this is done."); + return CommandResult.SUCCESS; + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java index d168274ee..9c1aea846 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java @@ -54,6 +54,7 @@ public class MigrationParentCommand extends ParentCommand { .put("org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService", "me.lucko.luckperms.bukkit.migration.MigrationZPermissions") .put("de.bananaco.bpermissions.api.WorldManager", "me.lucko.luckperms.bukkit.migration.MigrationBPermissions") .put("com.platymuus.bukkit.permissions.PermissionsPlugin", "me.lucko.luckperms.bukkit.migration.MigrationPermissionsBukkit") + .put("nl.svenar.PowerRanks.PowerRanks", "me.lucko.luckperms.bukkit.migration.MigrationPowerRanks") // bungee .put("net.alpenblock.bungeeperms.BungeePerms", "me.lucko.luckperms.bungee.migration.MigrationBungeePerms") .build(); From e183f520d60f3e379c892c24f7286a50b8019633 Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 1 Dec 2020 23:31:12 +0000 Subject: [PATCH 05/38] Cleanup EventDispatcher and some other minor tidying --- .../api/implementation/ApiPlatform.java | 3 +- .../common/event/EventDispatcher.java | 209 ++++++++---------- .../luckperms/common/query/FlagUtils.java | 6 +- .../luckperms/common/storage/Storage.java | 48 ++-- .../common/tasks/ExpireTemporaryTask.java | 8 +- .../common/treeview/PermissionRegistry.java | 6 +- 6 files changed, 127 insertions(+), 153 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlatform.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlatform.java index 558ff703b..d19884c76 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlatform.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlatform.java @@ -52,7 +52,8 @@ public ApiPlatform(LuckPermsPlugin plugin) { @Override public @NonNull String getApiVersion() { - return "5.2"; + String[] version = this.plugin.getBootstrap().getVersion().split("\\."); + return version[0] + '.' + version[1]; } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java index e1130e25d..a275b6c52 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java @@ -81,6 +81,7 @@ import net.luckperms.api.event.track.mutate.TrackClearEvent; import net.luckperms.api.event.track.mutate.TrackRemoveGroupEvent; import net.luckperms.api.event.type.Cancellable; +import net.luckperms.api.event.type.ResultEvent; import net.luckperms.api.event.user.UserCacheLoadEvent; import net.luckperms.api.event.user.UserDataRecalculateEvent; import net.luckperms.api.event.user.UserFirstLoginEvent; @@ -100,7 +101,6 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; public final class EventDispatcher { private final AbstractEventBus eventBus; @@ -113,26 +113,57 @@ public AbstractEventBus getEventBus() { return this.eventBus; } - private boolean shouldPost(Class eventClass) { - return this.eventBus.shouldPost(eventClass); + private void postAsync(Class eventClass, Object... params) { + // check against common mistakes - events with any sort of result shouldn't be posted async + if (Cancellable.class.isAssignableFrom(eventClass)) { + throw new RuntimeException("Cancellable event cannot be posted async (" + eventClass + ")"); + } + if (ResultEvent.class.isAssignableFrom(eventClass)) { + throw new RuntimeException("ResultEvent event cannot be posted async (" + eventClass + ")"); + } + + // if there aren't any handlers registered for the event, don't bother trying to post it + if (!this.eventBus.shouldPost(eventClass)) { + return; + } + + // async: generate an event class and post it + this.eventBus.getPlugin().getBootstrap().getScheduler().executeAsync(() -> { + T event = generate(eventClass, params); + this.eventBus.post(event); + }); } - private void post(LuckPermsEvent event) { + private void postSync(Class eventClass, Object... params) { + // if there aren't any handlers registered for our event, don't bother trying to post it + if (!this.eventBus.shouldPost(eventClass)) { + return; + } + + // generate an event class and post it + T event = generate(eventClass, params); this.eventBus.post(event); } - private void post(Class eventClass, Supplier supplier) { - if (Cancellable.class.isAssignableFrom(eventClass)) { - throw new RuntimeException("Cancellable event cannot be posted async (" + eventClass + ")"); + private boolean postCancellable(Class eventClass, Object... params) { + // extract the initial state from the first parameter + boolean initialState = (boolean) params[0]; + + // if there aren't any handlers registered for the event, just return the initial state + if (!this.eventBus.shouldPost(eventClass)) { + return initialState; } - this.eventBus.getPlugin().getBootstrap().getScheduler().executeAsync(() -> { - if (!shouldPost(eventClass)) { - return; - } - T event = supplier.get(); - post(event); - }); + // otherwise: + // - initialise an AtomicBoolean for the result with the initial state + // - replace the boolean with the AtomicBoolean in the params array + // - post the event + AtomicBoolean cancel = new AtomicBoolean(initialState); + params[0] = cancel; + postSync(eventClass, params); + + // return the final status + return cancel.get(); } @SuppressWarnings("unchecked") @@ -145,235 +176,171 @@ private T generate(Class eventClass, Object... par } public void dispatchContextUpdate(Object subject) { - if (!shouldPost(ContextUpdateEvent.class)) { - return; - } - - post(generate(ContextUpdateEvent.class, subject)); + postSync(ContextUpdateEvent.class, subject); } public void dispatchExtensionLoad(Extension extension) { - post(ExtensionLoadEvent.class, () -> generate(ExtensionLoadEvent.class, extension)); + postAsync(ExtensionLoadEvent.class, extension); } public void dispatchGroupCacheLoad(Group group, GroupCachedDataManager data) { - post(GroupCacheLoadEvent.class, () -> generate(GroupCacheLoadEvent.class, group.getApiProxy(), data)); + postAsync(GroupCacheLoadEvent.class, group.getApiProxy(), data); } public void dispatchGroupCreate(Group group, CreationCause cause) { - post(GroupCreateEvent.class, () -> generate(GroupCreateEvent.class, group.getApiProxy(), cause)); + postAsync(GroupCreateEvent.class, group.getApiProxy(), cause); } public void dispatchGroupDelete(Group group, DeletionCause cause) { - post(GroupDeleteEvent.class, () -> generate(GroupDeleteEvent.class, group.getName(), ImmutableSet.copyOf(group.normalData().asSet()), cause)); + postAsync(GroupDeleteEvent.class, group.getName(), ImmutableSet.copyOf(group.normalData().asSet()), cause); } public void dispatchGroupLoadAll() { - post(GroupLoadAllEvent.class, () -> generate(GroupLoadAllEvent.class)); + postAsync(GroupLoadAllEvent.class); } public void dispatchGroupLoad(Group group) { - post(GroupLoadEvent.class, () -> generate(GroupLoadEvent.class, group.getApiProxy())); + postAsync(GroupLoadEvent.class, group.getApiProxy()); } public boolean dispatchLogBroadcast(boolean initialState, Action entry, LogBroadcastEvent.Origin origin) { - if (!shouldPost(LogBroadcastEvent.class)) { - return initialState; - } - - AtomicBoolean cancel = new AtomicBoolean(initialState); - post(generate(LogBroadcastEvent.class, cancel, entry, origin)); - return cancel.get(); + return postCancellable(LogBroadcastEvent.class, initialState, entry, origin); } public boolean dispatchLogPublish(boolean initialState, Action entry) { - if (!shouldPost(LogPublishEvent.class)) { - return initialState; - } - - AtomicBoolean cancel = new AtomicBoolean(initialState); - post(generate(LogPublishEvent.class, cancel, entry)); - return cancel.get(); + return postCancellable(LogPublishEvent.class, initialState, entry); } public boolean dispatchLogNetworkPublish(boolean initialState, UUID id, Action entry) { - if (!shouldPost(LogNetworkPublishEvent.class)) { - return initialState; - } - - AtomicBoolean cancel = new AtomicBoolean(initialState); - post(generate(LogNetworkPublishEvent.class, cancel, id, entry)); - return cancel.get(); + return postCancellable(LogNetworkPublishEvent.class, initialState, id, entry); } public boolean dispatchLogNotify(boolean initialState, Action entry, LogNotifyEvent.Origin origin, Sender sender) { - if (!shouldPost(LogNotifyEvent.class)) { - return initialState; - } - - AtomicBoolean cancel = new AtomicBoolean(initialState); - post(generate(LogNotifyEvent.class, cancel, entry, origin, new SenderPlatformEntity(sender))); - return cancel.get(); + return postCancellable(LogNotifyEvent.class, initialState, entry, origin, new SenderPlatformEntity(sender)); } public void dispatchLogReceive(UUID id, Action entry) { - post(LogReceiveEvent.class, () -> generate(LogReceiveEvent.class, id, entry)); + postAsync(LogReceiveEvent.class, id, entry); } public void dispatchNodeAdd(Node node, PermissionHolder target, DataType dataType, Collection before, Collection after) { - post(NodeAddEvent.class, () -> generate(NodeAddEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after), node)); + postAsync(NodeAddEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after), node); } public void dispatchNodeClear(PermissionHolder target, DataType dataType, Collection before, Collection after) { - post(NodeClearEvent.class, () -> generate(NodeClearEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after))); + postAsync(NodeClearEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after)); } public void dispatchNodeRemove(Node node, PermissionHolder target, DataType dataType, Collection before, Collection after) { - post(NodeRemoveEvent.class, () -> generate(NodeRemoveEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after), node)); + postAsync(NodeRemoveEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after), node); } public void dispatchConfigReload() { - post(ConfigReloadEvent.class, () -> generate(ConfigReloadEvent.class)); + postAsync(ConfigReloadEvent.class); } public void dispatchPostSync() { - post(PostSyncEvent.class, () -> generate(PostSyncEvent.class)); + postAsync(PostSyncEvent.class); } public boolean dispatchNetworkPreSync(boolean initialState, UUID id) { - if (!shouldPost(PreNetworkSyncEvent.class)) { - return initialState; - } - - AtomicBoolean cancel = new AtomicBoolean(initialState); - post(generate(PreNetworkSyncEvent.class, cancel, id)); - return cancel.get(); + return postCancellable(PreNetworkSyncEvent.class, initialState, id); } public boolean dispatchPreSync(boolean initialState) { - if (!shouldPost(PreSyncEvent.class)) { - return initialState; - } - - AtomicBoolean cancel = new AtomicBoolean(initialState); - post(generate(PreSyncEvent.class, cancel)); - return cancel.get(); + return postCancellable(PreSyncEvent.class, initialState); } public void dispatchTrackCreate(Track track, CreationCause cause) { - post(TrackCreateEvent.class, () -> generate(TrackCreateEvent.class, track.getApiProxy(), cause)); + postAsync(TrackCreateEvent.class, track.getApiProxy(), cause); } public void dispatchTrackDelete(Track track, DeletionCause cause) { - post(TrackDeleteEvent.class, () -> generate(TrackDeleteEvent.class, track.getName(), ImmutableList.copyOf(track.getGroups()), cause)); + postAsync(TrackDeleteEvent.class, track.getName(), ImmutableList.copyOf(track.getGroups()), cause); } public void dispatchTrackLoadAll() { - post(TrackLoadAllEvent.class, () -> generate(TrackLoadAllEvent.class)); + postAsync(TrackLoadAllEvent.class); } public void dispatchTrackLoad(Track track) { - post(TrackLoadEvent.class, () -> generate(TrackLoadEvent.class, track.getApiProxy())); + postAsync(TrackLoadEvent.class, track.getApiProxy()); } public void dispatchTrackAddGroup(Track track, String group, List before, List after) { - post(TrackAddGroupEvent.class, () -> generate(TrackAddGroupEvent.class, track.getApiProxy(), ImmutableList.copyOf(before), ImmutableList.copyOf(after), group)); + postAsync(TrackAddGroupEvent.class, track.getApiProxy(), ImmutableList.copyOf(before), ImmutableList.copyOf(after), group); } public void dispatchTrackClear(Track track, List before) { - post(TrackClearEvent.class, () -> generate(TrackClearEvent.class, track.getApiProxy(), ImmutableList.copyOf(before), ImmutableList.of())); + postAsync(TrackClearEvent.class, track.getApiProxy(), ImmutableList.copyOf(before), ImmutableList.of()); } public void dispatchTrackRemoveGroup(Track track, String group, List before, List after) { - post(TrackRemoveGroupEvent.class, () -> generate(TrackRemoveGroupEvent.class, track.getApiProxy(), ImmutableList.copyOf(before), ImmutableList.copyOf(after), group)); + postAsync(TrackRemoveGroupEvent.class, track.getApiProxy(), ImmutableList.copyOf(before), ImmutableList.copyOf(after), group); } public void dispatchUserCacheLoad(User user, UserCachedDataManager data) { - post(UserCacheLoadEvent.class, () -> generate(UserCacheLoadEvent.class, user.getApiProxy(), data)); + postAsync(UserCacheLoadEvent.class, user.getApiProxy(), data); } public void dispatchDataRecalculate(PermissionHolder holder) { if (holder.getType() == HolderType.USER) { User user = (User) holder; - post(UserDataRecalculateEvent.class, () -> generate(UserDataRecalculateEvent.class, user.getApiProxy(), user.getCachedData())); + postAsync(UserDataRecalculateEvent.class, user.getApiProxy(), user.getCachedData()); } else { Group group = (Group) holder; - post(GroupDataRecalculateEvent.class, () -> generate(GroupDataRecalculateEvent.class, group.getApiProxy(), group.getCachedData())); + postAsync(GroupDataRecalculateEvent.class, group.getApiProxy(), group.getCachedData()); } } public void dispatchUserFirstLogin(UUID uniqueId, String username) { - post(UserFirstLoginEvent.class, () -> generate(UserFirstLoginEvent.class, uniqueId, username)); + postAsync(UserFirstLoginEvent.class, uniqueId, username); } public void dispatchPlayerLoginProcess(UUID uniqueId, String username, User user) { - if (!shouldPost(PlayerLoginProcessEvent.class)) { - return; - } - - post(generate(PlayerLoginProcessEvent.class, uniqueId, username, user.getApiProxy())); + postSync(PlayerLoginProcessEvent.class, uniqueId, username, user.getApiProxy()); } public void dispatchPlayerDataSave(UUID uniqueId, String username, PlayerSaveResult result) { - post(PlayerDataSaveEvent.class, () -> generate(PlayerDataSaveEvent.class, uniqueId, username, result)); + postAsync(PlayerDataSaveEvent.class, uniqueId, username, result); } public String dispatchUniqueIdDetermineType(UUID uniqueId, String initialType) { - if (!shouldPost(UniqueIdDetermineTypeEvent.class)) { - return initialType; - } - AtomicReference result = new AtomicReference<>(initialType); - post(generate(UniqueIdDetermineTypeEvent.class, result, uniqueId)); + postSync(UniqueIdDetermineTypeEvent.class, result, uniqueId); return result.get(); } public UUID dispatchUniqueIdLookup(String username, UUID initial) { - if (!shouldPost(UniqueIdLookupEvent.class)) { - return initial; - } - AtomicReference result = new AtomicReference<>(initial); - post(generate(UniqueIdLookupEvent.class, result, username)); + postSync(UniqueIdLookupEvent.class, result, username); return result.get(); } public String dispatchUsernameLookup(UUID uniqueId, String initial) { - if (!shouldPost(UsernameLookupEvent.class)) { - return initial; - } - AtomicReference result = new AtomicReference<>(initial); - post(generate(UsernameLookupEvent.class, result, uniqueId)); + postSync(UsernameLookupEvent.class, result, uniqueId); return result.get(); } public boolean dispatchUsernameValidityCheck(String username, boolean initialState) { - if (!shouldPost(UsernameValidityCheckEvent.class)) { - return initialState; - } - AtomicBoolean result = new AtomicBoolean(initialState); - post(generate(UsernameValidityCheckEvent.class, username, result)); + postSync(UsernameValidityCheckEvent.class, username, result); return result.get(); } public void dispatchUserLoad(User user) { - post(UserLoadEvent.class, () -> generate(UserLoadEvent.class, user.getApiProxy())); + postAsync(UserLoadEvent.class, user.getApiProxy()); } - public void dispatchUserDemote(User user, Track track, String from, String to, @Nullable Sender source) { - post(UserDemoteEvent.class, () -> { - Source s = source == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderPlatformEntity(source)); - return generate(UserDemoteEvent.class, s, track.getApiProxy(), user.getApiProxy(), Optional.ofNullable(from), Optional.ofNullable(to)); - }); + public void dispatchUserDemote(User user, Track track, String from, String to, @Nullable Sender sender) { + Source source = sender == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderPlatformEntity(sender)); + postAsync(UserDemoteEvent.class, source, track.getApiProxy(), user.getApiProxy(), Optional.ofNullable(from), Optional.ofNullable(to)); } - public void dispatchUserPromote(User user, Track track, String from, String to, @Nullable Sender source) { - post(UserPromoteEvent.class, () -> { - Source s = source == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderPlatformEntity(source)); - return generate(UserPromoteEvent.class, s, track.getApiProxy(), user.getApiProxy(), Optional.ofNullable(from), Optional.ofNullable(to)); - }); + public void dispatchUserPromote(User user, Track track, String from, String to, @Nullable Sender sender) { + Source source = sender == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderPlatformEntity(sender)); + postAsync(UserPromoteEvent.class, source, track.getApiProxy(), user.getApiProxy(), Optional.ofNullable(from), Optional.ofNullable(to)); } private static ApiPermissionHolder proxy(PermissionHolder holder) { diff --git a/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java b/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java index 653885f9e..a2e73a661 100644 --- a/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java +++ b/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java @@ -35,7 +35,7 @@ private FlagUtils() {} private static final EnumSet DEFAULT_FLAGS_SET = EnumSet.allOf(Flag.class); private static final int DEFAULT_FLAGS_SIZE = DEFAULT_FLAGS_SET.size(); - static final byte DEFAULT_FLAGS = encodeAsByte(DEFAULT_FLAGS_SET); + static final byte DEFAULT_FLAGS = toByte0(DEFAULT_FLAGS_SET); /* bitwise utility methods */ @@ -48,10 +48,10 @@ static byte toByte(Set settings) { if (settings.size() == DEFAULT_FLAGS_SIZE) { return DEFAULT_FLAGS; } - return encodeAsByte(settings); + return toByte0(settings); } - private static byte encodeAsByte(Set settings) { + private static byte toByte0(Set settings) { byte b = 0; for (Flag setting : settings) { b |= (1 << setting.ordinal()); 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 c3b3a1c92..9cfacbb89 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 @@ -81,7 +81,7 @@ public Collection getImplementations() { } } - private CompletableFuture makeFuture(Callable supplier) { + private CompletableFuture future(Callable supplier) { return CompletableFuture.supplyAsync(() -> { try { return supplier.call(); @@ -94,7 +94,7 @@ private CompletableFuture makeFuture(Callable supplier) { }, this.plugin.getBootstrap().getScheduler().async()); } - private CompletableFuture makeFuture(Throwing.Runnable runnable) { + private CompletableFuture future(Throwing.Runnable runnable) { return CompletableFuture.runAsync(() -> { try { runnable.run(); @@ -132,19 +132,19 @@ public Map getMeta() { } public CompletableFuture logAction(Action entry) { - return makeFuture(() -> this.implementation.logAction(entry)); + return future(() -> this.implementation.logAction(entry)); } public CompletableFuture getLog() { - return makeFuture(this.implementation::getLog); + return future(this.implementation::getLog); } public CompletableFuture applyBulkUpdate(BulkUpdate bulkUpdate) { - return makeFuture(() -> this.implementation.applyBulkUpdate(bulkUpdate)); + return future(() -> this.implementation.applyBulkUpdate(bulkUpdate)); } public CompletableFuture loadUser(UUID uniqueId, String username) { - return makeFuture(() -> { + return future(() -> { User user = this.implementation.loadUser(uniqueId, username); if (user != null) { this.plugin.getEventDispatcher().dispatchUserLoad(user); @@ -154,15 +154,15 @@ public CompletableFuture loadUser(UUID uniqueId, String username) { } public CompletableFuture saveUser(User user) { - return makeFuture(() -> this.implementation.saveUser(user)); + return future(() -> this.implementation.saveUser(user)); } public CompletableFuture> getUniqueUsers() { - return makeFuture(this.implementation::getUniqueUsers); + return future(this.implementation::getUniqueUsers); } public CompletableFuture>> searchUserNodes(ConstraintNodeMatcher constraint) { - return makeFuture(() -> { + return future(() -> { List> result = this.implementation.searchUserNodes(constraint); result.removeIf(entry -> entry.getNode().hasExpired()); return ImmutableList.copyOf(result); @@ -170,7 +170,7 @@ public CompletableFuture>> searchUserNo } public CompletableFuture createAndLoadGroup(String name, CreationCause cause) { - return makeFuture(() -> { + return future(() -> { Group group = this.implementation.createAndLoadGroup(name.toLowerCase()); if (group != null) { this.plugin.getEventDispatcher().dispatchGroupCreate(group, cause); @@ -180,7 +180,7 @@ public CompletableFuture createAndLoadGroup(String name, CreationCause ca } public CompletableFuture> loadGroup(String name) { - return makeFuture(() -> { + return future(() -> { Optional group = this.implementation.loadGroup(name.toLowerCase()); if (group.isPresent()) { this.plugin.getEventDispatcher().dispatchGroupLoad(group.get()); @@ -190,25 +190,25 @@ public CompletableFuture> loadGroup(String name) { } public CompletableFuture loadAllGroups() { - return makeFuture(() -> { + return future(() -> { this.implementation.loadAllGroups(); this.plugin.getEventDispatcher().dispatchGroupLoadAll(); }); } public CompletableFuture saveGroup(Group group) { - return makeFuture(() -> this.implementation.saveGroup(group)); + return future(() -> this.implementation.saveGroup(group)); } public CompletableFuture deleteGroup(Group group, DeletionCause cause) { - return makeFuture(() -> { + return future(() -> { this.implementation.deleteGroup(group); this.plugin.getEventDispatcher().dispatchGroupDelete(group, cause); }); } public CompletableFuture>> searchGroupNodes(ConstraintNodeMatcher constraint) { - return makeFuture(() -> { + return future(() -> { List> result = this.implementation.searchGroupNodes(constraint); result.removeIf(entry -> entry.getNode().hasExpired()); return ImmutableList.copyOf(result); @@ -216,7 +216,7 @@ public CompletableFuture>> searchGrou } public CompletableFuture createAndLoadTrack(String name, CreationCause cause) { - return makeFuture(() -> { + return future(() -> { Track track = this.implementation.createAndLoadTrack(name.toLowerCase()); if (track != null) { this.plugin.getEventDispatcher().dispatchTrackCreate(track, cause); @@ -226,7 +226,7 @@ public CompletableFuture createAndLoadTrack(String name, CreationCause ca } public CompletableFuture> loadTrack(String name) { - return makeFuture(() -> { + return future(() -> { Optional track = this.implementation.loadTrack(name.toLowerCase()); if (track.isPresent()) { this.plugin.getEventDispatcher().dispatchTrackLoad(track.get()); @@ -236,25 +236,25 @@ public CompletableFuture> loadTrack(String name) { } public CompletableFuture loadAllTracks() { - return makeFuture(() -> { + return future(() -> { this.implementation.loadAllTracks(); this.plugin.getEventDispatcher().dispatchTrackLoadAll(); }); } public CompletableFuture saveTrack(Track track) { - return makeFuture(() -> this.implementation.saveTrack(track)); + return future(() -> this.implementation.saveTrack(track)); } public CompletableFuture deleteTrack(Track track, DeletionCause cause) { - return makeFuture(() -> { + return future(() -> { this.implementation.deleteTrack(track); this.plugin.getEventDispatcher().dispatchTrackDelete(track, cause); }); } public CompletableFuture savePlayerData(UUID uniqueId, String username) { - return makeFuture(() -> { + return future(() -> { PlayerSaveResult result = this.implementation.savePlayerData(uniqueId, username); if (result != null) { this.plugin.getEventDispatcher().dispatchPlayerDataSave(uniqueId, username, result); @@ -264,14 +264,14 @@ public CompletableFuture savePlayerData(UUID uniqueId, String } public CompletableFuture deletePlayerData(UUID uniqueId) { - return makeFuture(() -> this.implementation.deletePlayerData(uniqueId)); + return future(() -> this.implementation.deletePlayerData(uniqueId)); } public CompletableFuture getPlayerUniqueId(String username) { - return makeFuture(() -> this.implementation.getPlayerUniqueId(username)); + return future(() -> this.implementation.getPlayerUniqueId(username)); } public CompletableFuture getPlayerName(UUID uniqueId) { - return makeFuture(() -> this.implementation.getPlayerName(uniqueId)); + return future(() -> this.implementation.getPlayerName(uniqueId)); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java b/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java index 25dc358ae..628f26783 100644 --- a/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java +++ b/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java @@ -30,6 +30,8 @@ import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import java.util.concurrent.locks.Lock; + public class ExpireTemporaryTask implements Runnable { private final LuckPermsPlugin plugin; @@ -67,15 +69,17 @@ public void run() { // return true if the holder's io lock is currently held, false otherwise private static boolean shouldSkip(PermissionHolder holder) { + Lock lock = holder.getIoLock(); + // if the holder is currently being manipulated by the storage impl, // don't attempt to audit temporary permissions - if (!holder.getIoLock().tryLock()) { + if (!lock.tryLock()) { // if #tryLock returns false, it means it's held by something else return true; } // immediately release the lock & return false - holder.getIoLock().unlock(); + lock.unlock(); return false; } } \ No newline at end of file diff --git a/common/src/main/java/me/lucko/luckperms/common/treeview/PermissionRegistry.java b/common/src/main/java/me/lucko/luckperms/common/treeview/PermissionRegistry.java index 8315993c3..c2248d93d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/treeview/PermissionRegistry.java +++ b/common/src/main/java/me/lucko/luckperms/common/treeview/PermissionRegistry.java @@ -61,7 +61,9 @@ public TreeNode getRootNode() { } public List rootAsList() { - return this.rootNode.makeImmutableCopy().getNodeEndings().stream().map(Map.Entry::getValue).collect(ImmutableCollectors.toList()); + return this.rootNode.makeImmutableCopy().getNodeEndings().stream() + .map(Map.Entry::getValue) + .collect(ImmutableCollectors.toList()); } public void offer(String permission) { @@ -92,7 +94,7 @@ public void insert(String permission) { private void doInsert(String permission) { // split the permission up into parts - List parts = DOT_SPLIT.splitToList(permission); + Iterable parts = DOT_SPLIT.split(permission); // insert the permission into the node structure TreeNode current = this.rootNode; From 7d7d40b08778a8ac886038944b85f53dc02cc032 Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 4 Dec 2020 14:12:36 +0000 Subject: [PATCH 06/38] Add some missing arg-based permission checks --- .../luckperms/common/commands/group/DeleteGroup.java | 6 ++++++ .../luckperms/common/commands/group/GroupRename.java | 6 ++++++ .../luckperms/common/commands/track/DeleteTrack.java | 6 ++++++ .../luckperms/common/commands/track/TrackAppend.java | 6 ++++++ .../luckperms/common/commands/track/TrackClear.java | 6 ++++++ .../luckperms/common/commands/track/TrackClone.java | 11 +++++++++++ .../luckperms/common/commands/track/TrackInfo.java | 6 ++++++ .../luckperms/common/commands/track/TrackInsert.java | 6 ++++++ .../luckperms/common/commands/track/TrackRemove.java | 6 ++++++ .../luckperms/common/commands/track/TrackRename.java | 6 ++++++ 10 files changed, 65 insertions(+) diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/group/DeleteGroup.java b/common/src/main/java/me/lucko/luckperms/common/commands/group/DeleteGroup.java index d67e7c4d7..8add3b13b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/group/DeleteGroup.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/group/DeleteGroup.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.SingleCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; @@ -70,6 +71,11 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, ArgumentList return CommandResult.LOADING_ERROR; } + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), group)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + try { plugin.getStorage().deleteGroup(group, DeletionCause.COMMAND).get(); } catch (Exception e) { diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupRename.java b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupRename.java index 5ca5c23b6..fafb31584 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupRename.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupRename.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; @@ -51,6 +52,11 @@ public GroupRename() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Group target, ArgumentList args, String label) { + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + String newGroupName = args.get(0).toLowerCase(); if (!DataConstraints.GROUP_NAME_TEST.test(newGroupName)) { Message.GROUP_INVALID_ENTRY.send(sender, newGroupName); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/DeleteTrack.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/DeleteTrack.java index 8a0f7a116..6882536f3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/DeleteTrack.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/DeleteTrack.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.SingleCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; @@ -64,6 +65,11 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, ArgumentList return CommandResult.LOADING_ERROR; } + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), track)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + try { plugin.getStorage().deleteTrack(track, DeletionCause.COMMAND).get(); } catch (Exception e) { diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackAppend.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackAppend.java index e4312f02e..2f2b1253e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackAppend.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackAppend.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; @@ -53,6 +54,11 @@ public TrackAppend() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + String groupName = args.get(0).toLowerCase(); if (!DataConstraints.GROUP_NAME_TEST.test(groupName)) { sendDetailedUsage(sender, label); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClear.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClear.java index 03a4b4a7d..2252ab807 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClear.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClear.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; @@ -45,6 +46,11 @@ public TrackClear() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + target.clearGroups(); Message.TRACK_CLEAR.send(sender, target.getName()); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClone.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClone.java index 1a5aa4abb..0f4888bfb 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClone.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackClone.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; @@ -49,6 +50,11 @@ public TrackClone() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + String newTrackName = args.get(0).toLowerCase(); if (!DataConstraints.TRACK_NAME_TEST.test(newTrackName)) { Message.TRACK_INVALID_ENTRY.send(sender, newTrackName); @@ -61,6 +67,11 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target return CommandResult.LOADING_ERROR; } + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), newTrack)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + newTrack.setGroups(target.getGroups()); Message.CLONE_SUCCESS.send(sender, Component.text(target.getName()), Component.text(newTrack.getName())); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInfo.java index 530cf1486..a2cd9bbcf 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInfo.java @@ -27,6 +27,7 @@ import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; @@ -43,6 +44,11 @@ public TrackInfo() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + Message.TRACK_INFO.send(sender, target.getName(), Message.formatTrackPath(target.getGroups())); return CommandResult.SUCCESS; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInsert.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInsert.java index d8269fd1a..dbb8b6dcb 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInsert.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackInsert.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; @@ -53,6 +54,11 @@ public TrackInsert() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + String groupName = args.get(0).toLowerCase(); if (!DataConstraints.GROUP_NAME_TEST.test(groupName)) { sendDetailedUsage(sender, label); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRemove.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRemove.java index 9ef661488..534bb584d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRemove.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRemove.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; @@ -52,6 +53,11 @@ public TrackRemove() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + String groupName = args.get(0).toLowerCase(); if (!DataConstraints.GROUP_NAME_TEST.test(groupName)) { sendDetailedUsage(sender, label); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRename.java b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRename.java index 8533d8733..e157ad7b1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRename.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/track/TrackRename.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.command.CommandResult; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; import me.lucko.luckperms.common.command.utils.ArgumentList; @@ -50,6 +51,11 @@ public TrackRename() { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Track target, ArgumentList args, String label) { + if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), target)) { + Message.COMMAND_NO_PERMISSION.send(sender); + return CommandResult.NO_PERMISSION; + } + String newTrackName = args.get(0).toLowerCase(); if (!DataConstraints.TRACK_NAME_TEST.test(newTrackName)) { Message.TRACK_INVALID_ENTRY.send(sender, newTrackName); From 5bfbf26e2f42c6c59ede0fe421c168a2462a996b Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 4 Dec 2020 14:17:35 +0000 Subject: [PATCH 07/38] Fix ArrayIndexOutOfBoundsException from null environment in BukkitPlayerCalculator --- .../luckperms/bukkit/context/BukkitPlayerCalculator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/context/BukkitPlayerCalculator.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/context/BukkitPlayerCalculator.java index 7abf68fb9..34bbab78b 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/context/BukkitPlayerCalculator.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/context/BukkitPlayerCalculator.java @@ -82,7 +82,10 @@ public void calculate(@NonNull Player subject, @NonNull ContextConsumer consumer World world = subject.getWorld(); if (world != null) { - consumer.accept(DefaultContextKeys.DIMENSION_TYPE_KEY, DIMENSION_TYPE_NAMER.name(world.getEnvironment())); + Environment environment = world.getEnvironment(); + if (environment != null) { + consumer.accept(DefaultContextKeys.DIMENSION_TYPE_KEY, DIMENSION_TYPE_NAMER.name(environment)); + } this.plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).rewriteAndSubmit(world.getName(), consumer); } } From 645efb0e5b729f7de4ef16ac1082deef57df6a05 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 5 Dec 2020 14:27:32 +0000 Subject: [PATCH 08/38] Cleanup applying bulkupdates to Node objects directly --- .../common/bulkupdate/BulkUpdate.java | 61 +++++++++++++--- .../bulkupdate/BulkUpdateStatistics.java | 31 +++++++-- .../file/AbstractConfigurateStorage.java | 32 ++------- .../file/SeparatedConfigurateStorage.java | 6 +- .../implementation/mongodb/MongoStorage.java | 69 +++++++------------ .../implementation/sql/SqlStorage.java | 8 +-- 6 files changed, 113 insertions(+), 94 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdate.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdate.java index 4a81db134..82bf1bae9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdate.java +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdate.java @@ -27,11 +27,16 @@ import me.lucko.luckperms.common.bulkupdate.action.Action; import me.lucko.luckperms.common.bulkupdate.query.Query; +import me.lucko.luckperms.common.model.HolderType; import net.luckperms.api.node.Node; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Represents a query to be applied to a set of data. @@ -75,17 +80,55 @@ public boolean satisfiesConstraints(Node node) { } /** - * Applies this query to the given NodeModel, and returns the result. + * Applies this query to the given node, and returns the result. * - * @param from the node to base changes from - * @return the new nodemodel instance, or null if the node should be deleted. + * @param node the node to apply changes to + * @return the transformed node, or null if the node should be deleted */ - public Node apply(Node from) { - if (!satisfiesConstraints(from)) { - return from; // make no change + private Node apply(Node node) { + if (!satisfiesConstraints(node)) { + return node; // make no change + } + + Node result = this.action.apply(node); + + if (this.trackStatistics && result != node) { + this.statistics.incrementAffectedNodes(); + } + + return result; + } + + /** + * Applies this query to the given set of nodes, and returns the result. + * + * @param nodes the input nodes + * @param holderType the holder type the nodes are from + * @return the transformed nodes, or null if no change was made + */ + public @Nullable Set apply(Set nodes, HolderType holderType) { + Set results = new HashSet<>(); + boolean change = false; + + for (Node node : nodes) { + Node result = apply(node); + if (result != node) { + change = true; + } + if (result != null) { + results.add(result); + } + } + + if (!change) { + return null; + } + + if (this.trackStatistics) { + this.statistics.incrementAffected(holderType); } - return this.action.apply(from); + return results; } /** @@ -148,11 +191,11 @@ public List getQueries() { } public boolean isTrackingStatistics() { - return trackStatistics; + return this.trackStatistics; } public BulkUpdateStatistics getStatistics() { - return statistics; + return this.statistics; } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateStatistics.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateStatistics.java index a327ec9e7..f1920b188 100644 --- a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateStatistics.java +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateStatistics.java @@ -25,6 +25,8 @@ package me.lucko.luckperms.common.bulkupdate; +import me.lucko.luckperms.common.model.HolderType; + /** * Keeps track of the number of nodes, users and groups that were affected in a BulkUpdate operation. */ @@ -56,26 +58,43 @@ public int getAffectedGroups() { } public void incrementAffectedNodes() { - this.affectedNodes++; + incrementAffectedNodes(1); } public void incrementAffectedUsers() { - this.affectedUsers++; + incrementAffectedUsers(1); } public void incrementAffectedGroups() { - this.affectedGroups++; + incrementAffectedGroups(1); + } + + public void incrementAffected(HolderType type) { + incrementAffected(type, 1); } - public void incrementAffectedNodesBy(int delta) { + public void incrementAffectedNodes(int delta) { this.affectedNodes += delta; } - public void incrementAffectedUsersBy(int delta) { + public void incrementAffectedUsers(int delta) { this.affectedUsers += delta; } - public void incrementAffectedGroupsBy(int delta) { + public void incrementAffectedGroups(int delta) { this.affectedGroups += delta; } + + public void incrementAffected(HolderType type, int delta) { + switch (type) { + case USER: + incrementAffectedUsers(delta); + break; + case GROUP: + incrementAffectedGroups(delta); + break; + default: + throw new AssertionError(); + } + } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java index 51479498f..57016ec72 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java @@ -31,7 +31,6 @@ import me.lucko.luckperms.common.actionlog.Log; import me.lucko.luckperms.common.bulkupdate.BulkUpdate; -import me.lucko.luckperms.common.bulkupdate.BulkUpdateStatistics; import me.lucko.luckperms.common.context.ContextSetConfigurateSerializer; import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; import me.lucko.luckperms.common.model.Group; @@ -77,12 +76,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Function; -import java.util.stream.Collectors; /** * Abstract implementation using configurate {@link ConfigurationNode}s to serialize and deserialize @@ -181,35 +178,16 @@ public Log getLog() throws IOException { return this.actionLogger.getLog(); } - protected ConfigurationNode processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode node, HolderType holderType) { - BulkUpdateStatistics stats = bulkUpdate.getStatistics(); - + protected boolean processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode node, HolderType holderType) { Set nodes = readNodes(node); - Set results = nodes.stream() - .map(bulkUpdate::apply) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - if (bulkUpdate.isTrackingStatistics() && !results.isEmpty()) { - stats.incrementAffectedNodesBy(results.size()); - - switch (holderType) { - case USER: - stats.incrementAffectedUsers(); - break; - - case GROUP: - stats.incrementAffectedGroups(); - break; - } - } + Set results = bulkUpdate.apply(nodes, holderType); - if (nodes.equals(results)) { - return null; + if (results == null) { + return false; } writeNodes(node, results); - return node; + return true; } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java index 6e9ee323c..7832f7ccf 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java @@ -213,8 +213,7 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { try { registerFileAction(StorageLocation.USER, file); ConfigurationNode object = readFile(file); - ConfigurationNode results = processBulkUpdate(bulkUpdate, object, HolderType.USER); - if (results != null) { + if (processBulkUpdate(bulkUpdate, object, HolderType.USER)) { saveFile(file, object); } } catch (Exception e) { @@ -230,8 +229,7 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { try { registerFileAction(StorageLocation.GROUP, file); ConfigurationNode object = readFile(file); - ConfigurationNode results = processBulkUpdate(bulkUpdate, object, HolderType.GROUP); - if (results != null) { + if (processBulkUpdate(bulkUpdate, object, HolderType.GROUP)) { saveFile(file, object); } } catch (Exception e) { 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 c0f0c20c6..51ad65bbd 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 @@ -40,10 +40,10 @@ import me.lucko.luckperms.common.actionlog.Log; import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.bulkupdate.BulkUpdate; -import me.lucko.luckperms.common.bulkupdate.BulkUpdateStatistics; import me.lucko.luckperms.common.context.contextset.MutableContextSetImpl; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.Group; +import me.lucko.luckperms.common.model.HolderType; import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.manager.group.GroupManager; @@ -76,7 +76,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -251,33 +250,15 @@ public Log getLog() { @Override public void applyBulkUpdate(BulkUpdate bulkUpdate) { - BulkUpdateStatistics stats = bulkUpdate.getStatistics(); - if (bulkUpdate.getDataType().isIncludingUsers()) { MongoCollection c = this.database.getCollection(this.prefix + "users"); try (MongoCursor cursor = c.find().iterator()) { while (cursor.hasNext()) { Document d = cursor.next(); - UUID uuid = getDocumentId(d); - Set nodes = new HashSet<>(nodesFromDoc(d)); - Set results = nodes.stream() - .map(bulkUpdate::apply) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - if (bulkUpdate.isTrackingStatistics() && !results.isEmpty()) { - stats.incrementAffectedUsers(); - stats.incrementAffectedNodesBy(results.size()); - } - - if (!nodes.equals(results)) { - List newNodes = results.stream() - .map(MongoStorage::nodeToDoc) - .collect(Collectors.toList()); - - d.append("permissions", newNodes).remove("perms"); - c.replaceOne(new Document("_id", uuid), d); + Document results = processBulkUpdate(d, bulkUpdate, HolderType.USER); + if (results != null) { + c.replaceOne(new Document("_id", uuid), results); } } } @@ -288,32 +269,32 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) { try (MongoCursor cursor = c.find().iterator()) { while (cursor.hasNext()) { Document d = cursor.next(); - String holder = d.getString("_id"); - Set nodes = new HashSet<>(nodesFromDoc(d)); - Set results = nodes.stream() - .map(bulkUpdate::apply) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - if (bulkUpdate.isTrackingStatistics() && !results.isEmpty()) { - stats.incrementAffectedGroups(); - stats.incrementAffectedNodesBy(results.size()); - } - - if (!nodes.equals(results)) { - List newNodes = results.stream() - .map(MongoStorage::nodeToDoc) - .collect(Collectors.toList()); - - d.append("permissions", newNodes).remove("perms"); - c.replaceOne(new Document("_id", holder), d); + Document results = processBulkUpdate(d, bulkUpdate, HolderType.GROUP); + if (results != null) { + c.replaceOne(new Document("_id", holder), results); } } } } } + private Document processBulkUpdate(Document document, BulkUpdate bulkUpdate, HolderType holderType) { + Set nodes = new HashSet<>(nodesFromDoc(document)); + Set results = bulkUpdate.apply(nodes, holderType); + + if (results == null) { + return null; + } + + List newNodes = results.stream() + .map(MongoStorage::nodeToDoc) + .collect(Collectors.toList()); + + document.append("permissions", newNodes).remove("perms"); + return document; + } + @Override public User loadUser(UUID uniqueId, String username) { User user = this.plugin.getUserManager().getOrMake(uniqueId, username); @@ -664,8 +645,8 @@ public String getPlayerName(UUID uniqueId) { return null; } - private static UUID getDocumentId(Document doc) { - Object id = doc.get("_id"); + private static UUID getDocumentId(Document document) { + Object id = document.get("_id"); if (id instanceof UUID) { return (UUID) id; } else if (id instanceof String) { 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 075e48108..0110cf72f 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 @@ -271,10 +271,10 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws SQLException { uuids.add(Uuids.fromString(rs.getString("uuid"))); } uuids.remove(null); - stats.incrementAffectedUsersBy(uuids.size()); + stats.incrementAffectedUsers(uuids.size()); } } - stats.incrementAffectedNodesBy(ps.executeUpdate()); + stats.incrementAffectedNodes(ps.executeUpdate()); } else { ps.execute(); } @@ -298,10 +298,10 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws SQLException { groups.add(rs.getString("name")); } groups.remove(null); - stats.incrementAffectedGroupsBy(groups.size()); + stats.incrementAffectedGroups(groups.size()); } } - stats.incrementAffectedNodesBy(ps.executeUpdate()); + stats.incrementAffectedNodes(ps.executeUpdate()); } else { ps.execute(); } From 798a5390248516954bfd50a40b1e6569f7ecc6ed Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 5 Dec 2020 14:28:54 +0000 Subject: [PATCH 09/38] Fix saving to flatfile storage when sections become empty (#2755) --- .../file/AbstractConfigurateStorage.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java index 57016ec72..f1e352a95 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java @@ -650,6 +650,13 @@ private void appendNode(ConfigurationNode base, String key, ConfigurationNode at private void writeNodes(ConfigurationNode to, Collection nodes) { ConfigurationNode permissionsSection = SimpleConfigurationNode.root(); + + // ensure for CombinedConfigurateStorage that there's at least *something* + // to save to the file even if it's just an empty list. + if (this instanceof CombinedConfigurateStorage) { + permissionsSection.setValue(Collections.emptyList()); + } + ConfigurationNode parentsSection = SimpleConfigurationNode.root(); ConfigurationNode prefixesSection = SimpleConfigurationNode.root(); ConfigurationNode suffixesSection = SimpleConfigurationNode.root(); @@ -713,24 +720,33 @@ private void writeNodes(ConfigurationNode to, Collection nodes) { } if (permissionsSection.hasListChildren() || this instanceof CombinedConfigurateStorage) { - // ensure for CombinedConfigurateStorage that there's at least *something* to save to the file - // even if it's just an empty list. - if (!permissionsSection.hasListChildren()) { - permissionsSection.setValue(Collections.emptyList()); - } to.getNode("permissions").setValue(permissionsSection); + } else { + to.removeChild("permissions"); } + if (parentsSection.hasListChildren()) { to.getNode("parents").setValue(parentsSection); + } else { + to.removeChild("parents"); } + if (prefixesSection.hasListChildren()) { to.getNode("prefixes").setValue(prefixesSection); + } else { + to.removeChild("prefixes"); } + if (suffixesSection.hasListChildren()) { to.getNode("suffixes").setValue(suffixesSection); + } else { + to.removeChild("suffixes"); } + if (metaSection.hasListChildren()) { to.getNode("meta").setValue(metaSection); + } else { + to.removeChild("meta"); } } } From 5fbb80f16a70698a7611c7e4ed5036fb4fbf96f6 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 9 Dec 2020 10:38:22 +0000 Subject: [PATCH 10/38] Ensure meta keys are always lowercase, and fix 'similar' comparison case sensitivity (#2760) --- .../common/bulkupdate/comparison/StandardComparison.java | 3 +-- .../me/lucko/luckperms/common/node/types/Inheritance.java | 8 +++----- .../java/me/lucko/luckperms/common/node/types/Meta.java | 6 ++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparison/StandardComparison.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparison/StandardComparison.java index f634caab0..3152515e1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparison/StandardComparison.java +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparison/StandardComparison.java @@ -100,14 +100,13 @@ public static StandardComparison parseComparison(String s) { } static Pattern compilePatternForLikeSyntax(String expression) { - expression = expression.toLowerCase(); expression = expression.replace(".", "\\."); // convert from SQL LIKE syntax to regex expression = expression.replace(WILDCARD_ONE, "."); expression = expression.replace(WILDCARD, ".*"); - return Pattern.compile(expression); + return Pattern.compile(expression, Pattern.CASE_INSENSITIVE); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java b/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java index df6aacd70..6bf1c09ce 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java @@ -27,12 +27,10 @@ import me.lucko.luckperms.common.node.AbstractNode; import me.lucko.luckperms.common.node.AbstractNodeBuilder; - import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.model.group.Group; import net.luckperms.api.node.metadata.NodeMetadataKey; import net.luckperms.api.node.types.InheritanceNode; - import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -44,7 +42,7 @@ public class Inheritance extends AbstractNode, Object> metadata) { super(key(groupName), value, expireAt, contexts, metadata); - this.groupName = groupName; + this.groupName = groupName.toLowerCase(); } @Override @@ -96,7 +94,7 @@ public Builder(String groupName, boolean value, long expireAt, ImmutableContextS @Override public @NonNull Builder group(@NonNull String group) { - this.groupName = Objects.requireNonNull(group, "group").toLowerCase(); + this.groupName = Objects.requireNonNull(group, "group"); return this; } diff --git a/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java b/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java index 1d7c6264c..cfebd298b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java @@ -28,11 +28,9 @@ import me.lucko.luckperms.common.node.AbstractNode; import me.lucko.luckperms.common.node.AbstractNodeBuilder; import me.lucko.luckperms.common.node.factory.Delimiters; - import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.node.metadata.NodeMetadataKey; import net.luckperms.api.node.types.MetaNode; - import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -45,7 +43,7 @@ public class Meta extends AbstractNode implements Me private static final String NODE_MARKER = NODE_KEY + "."; public static String key(String key, String value) { - return NODE_MARKER + Delimiters.escapeCharacters(key) + AbstractNode.NODE_SEPARATOR + Delimiters.escapeCharacters(value); + return NODE_MARKER + Delimiters.escapeCharacters(key).toLowerCase() + AbstractNode.NODE_SEPARATOR + Delimiters.escapeCharacters(value); } public static Builder builder() { @@ -61,7 +59,7 @@ public static Builder builder(String key, String value) { public Meta(String metaKey, String metaValue, boolean value, long expireAt, ImmutableContextSet contexts, Map, Object> metadata) { super(key(metaKey, metaValue), value, expireAt, contexts, metadata); - this.metaKey = metaKey; + this.metaKey = metaKey.toLowerCase(); this.metaValue = metaValue; } From 39a5ccf9a99ea819971814de10aa266d7fd792db Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 9 Dec 2020 10:46:22 +0000 Subject: [PATCH 11/38] Some tidying up --- .../lucko/luckperms/common/node/types/Inheritance.java | 2 ++ .../me/lucko/luckperms/common/node/types/Meta.java | 2 ++ .../me/lucko/luckperms/common/query/FlagUtils.java | 10 +++++----- .../common/query/QueryOptionsBuilderImpl.java | 6 +++--- .../lucko/luckperms/common/query/QueryOptionsImpl.java | 4 ++-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java b/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java index 6bf1c09ce..ba0d182ac 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/types/Inheritance.java @@ -27,10 +27,12 @@ import me.lucko.luckperms.common.node.AbstractNode; import me.lucko.luckperms.common.node.AbstractNodeBuilder; + import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.model.group.Group; import net.luckperms.api.node.metadata.NodeMetadataKey; import net.luckperms.api.node.types.InheritanceNode; + import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java b/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java index cfebd298b..d21a24a97 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/types/Meta.java @@ -28,9 +28,11 @@ import me.lucko.luckperms.common.node.AbstractNode; import me.lucko.luckperms.common.node.AbstractNodeBuilder; import me.lucko.luckperms.common.node.factory.Delimiters; + import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.node.metadata.NodeMetadataKey; import net.luckperms.api.node.types.MetaNode; + import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java b/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java index a2e73a661..a27f896fe 100644 --- a/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java +++ b/common/src/main/java/me/lucko/luckperms/common/query/FlagUtils.java @@ -33,9 +33,9 @@ final class FlagUtils { private FlagUtils() {} - private static final EnumSet DEFAULT_FLAGS_SET = EnumSet.allOf(Flag.class); - private static final int DEFAULT_FLAGS_SIZE = DEFAULT_FLAGS_SET.size(); - static final byte DEFAULT_FLAGS = toByte0(DEFAULT_FLAGS_SET); + private static final EnumSet ALL_FLAGS_SET = EnumSet.allOf(Flag.class); + private static final int ALL_FLAGS_SIZE = ALL_FLAGS_SET.size(); + static final byte ALL_FLAGS = toByte0(ALL_FLAGS_SET); /* bitwise utility methods */ @@ -45,8 +45,8 @@ static boolean read(byte b, Flag setting) { static byte toByte(Set settings) { // fast path for the default set of flags. - if (settings.size() == DEFAULT_FLAGS_SIZE) { - return DEFAULT_FLAGS; + if (settings.size() == ALL_FLAGS_SIZE) { + return ALL_FLAGS; } return toByte0(settings); } diff --git a/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsBuilderImpl.java b/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsBuilderImpl.java index 8734b44a0..539a97432 100644 --- a/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsBuilderImpl.java +++ b/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsBuilderImpl.java @@ -54,7 +54,7 @@ public class QueryOptionsBuilderImpl implements QueryOptions.Builder { public QueryOptionsBuilderImpl(QueryMode mode) { this.mode = mode; this.context = mode == QueryMode.CONTEXTUAL ? ImmutableContextSetImpl.EMPTY : null; - this.flags = FlagUtils.DEFAULT_FLAGS; + this.flags = FlagUtils.ALL_FLAGS; this.flagsSet = null; this.options = null; this.copyOptions = false; @@ -150,13 +150,13 @@ public QueryOptionsBuilderImpl(QueryMode mode) { if (this.options == null) { if (this.mode == QueryMode.NON_CONTEXTUAL) { - if (FlagUtils.DEFAULT_FLAGS == flags) { + if (FlagUtils.ALL_FLAGS == flags) { // mode same, contexts null, flags same, options null // so therefore, equal to default - return that instead! return QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL; } } else if (this.mode == QueryMode.CONTEXTUAL) { - if (FlagUtils.DEFAULT_FLAGS == flags && this.context.isEmpty()) { + if (FlagUtils.ALL_FLAGS == flags && this.context.isEmpty()) { // mode same, contexts empty, flags same, options null // so therefore, equal to default - return that instead! return QueryOptionsImpl.DEFAULT_CONTEXTUAL; diff --git a/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java b/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java index 46f3ef6aa..20fd64f25 100644 --- a/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java +++ b/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java @@ -47,8 +47,8 @@ import java.util.Set; public class QueryOptionsImpl implements QueryOptions { - public static final QueryOptions DEFAULT_CONTEXTUAL = new QueryOptionsImpl(QueryMode.CONTEXTUAL, ImmutableContextSetImpl.EMPTY, FlagUtils.DEFAULT_FLAGS, null); - public static final QueryOptions DEFAULT_NON_CONTEXTUAL = new QueryOptionsImpl(QueryMode.NON_CONTEXTUAL, null, FlagUtils.DEFAULT_FLAGS, null); + public static final QueryOptions DEFAULT_CONTEXTUAL = new QueryOptionsImpl(QueryMode.CONTEXTUAL, ImmutableContextSetImpl.EMPTY, FlagUtils.ALL_FLAGS, null); + public static final QueryOptions DEFAULT_NON_CONTEXTUAL = new QueryOptionsImpl(QueryMode.NON_CONTEXTUAL, null, FlagUtils.ALL_FLAGS, null); // state private final QueryMode mode; From 17f67f6e13764c5cf2bbcccd70ad9c2816dd53a0 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 9 Dec 2020 18:08:17 +0000 Subject: [PATCH 12/38] Use case insensitive LIKE command on H2 databases (#2760) --- .../implementation/sql/connection/file/H2ConnectionFactory.java | 2 +- .../sql/connection/file/SqliteConnectionFactory.java | 2 +- .../sql/connection/hikari/MariaDbConnectionFactory.java | 2 +- .../sql/connection/hikari/MySqlConnectionFactory.java | 2 +- .../sql/connection/hikari/PostgreConnectionFactory.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java index 193a142e0..7ca1f417d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/H2ConnectionFactory.java @@ -83,6 +83,6 @@ protected Path getWriteFile() { @Override public Function getStatementProcessor() { - return s -> s.replace("'", "`"); + return s -> s.replace('\'', '`').replace("LIKE", "ILIKE"); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/SqliteConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/SqliteConnectionFactory.java index a2f1ba375..e5caf4d75 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/SqliteConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/SqliteConnectionFactory.java @@ -76,6 +76,6 @@ protected Connection createConnection(Path file) throws SQLException { @Override public Function getStatementProcessor() { - return s -> s.replace("'", "`"); + return s -> s.replace('\'', '`'); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MariaDbConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MariaDbConnectionFactory.java index 26c723515..092e36f74 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MariaDbConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MariaDbConnectionFactory.java @@ -71,6 +71,6 @@ protected void setProperties(HikariConfig config, Map properties @Override public Function getStatementProcessor() { - return s -> s.replace("'", "`"); // use backticks for quotes + return s -> s.replace('\'', '`'); // use backticks for quotes } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MySqlConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MySqlConnectionFactory.java index 6f051b5bc..4269fff96 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MySqlConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/MySqlConnectionFactory.java @@ -105,6 +105,6 @@ protected void overrideProperties(Map properties) { @Override public Function getStatementProcessor() { - return s -> s.replace("'", "`"); // use backticks for quotes + return s -> s.replace('\'', '`'); // use backticks for quotes } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/PostgreConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/PostgreConnectionFactory.java index 176df1823..2bdf4cf51 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/PostgreConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/PostgreConnectionFactory.java @@ -68,6 +68,6 @@ protected void overrideProperties(Map properties) { @Override public Function getStatementProcessor() { - return s -> s.replace("'", "\""); + return s -> s.replace('\'', '"'); } } From 45188c633439be22acf618cbe4a45d628aa1d1b0 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 9 Dec 2020 18:21:54 +0000 Subject: [PATCH 13/38] Fix NPE from null path in AbstractFileWatcher (#2759) --- .../implementation/file/watcher/AbstractFileWatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/watcher/AbstractFileWatcher.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/watcher/AbstractFileWatcher.java index d8a29cc47..b2dfa830c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/watcher/AbstractFileWatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/watcher/AbstractFileWatcher.java @@ -145,7 +145,7 @@ public final void runEventProcessingLoop() { Path context = event.context(); // ignore contexts with a name count of zero - if (context.getNameCount() == 0) { + if (context == null || context.getNameCount() == 0) { continue; } From 97d1deec9c5f1cbf19fc7811d0c3b4567dea35ea Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 11 Dec 2020 10:38:57 +0000 Subject: [PATCH 14/38] Slightly optimize weight and display name lookups --- .../commands/generic/meta/MetaInfo.java | 6 +--- .../lucko/luckperms/common/model/Group.java | 9 ++---- .../luckperms/common/model/WeightCache.java | 30 ++++++++----------- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java index 97907d9fd..2712ae22e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java @@ -72,11 +72,7 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHo Set meta = new LinkedHashSet<>(); // Collect data - for (Node node : target.resolveInheritedNodes(QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL)) { - if (!NodeType.META_OR_CHAT_META.matches(node)) { - continue; - } - + for (Node node : target.resolveInheritedNodes(NodeType.META_OR_CHAT_META, QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL)) { if (node instanceof PrefixNode) { PrefixNode pn = (PrefixNode) node; prefixes.add(Maps.immutableEntry(pn.getPriority(), pn)); diff --git a/common/src/main/java/me/lucko/luckperms/common/model/Group.java b/common/src/main/java/me/lucko/luckperms/common/model/Group.java index 7f18071b2..af1273472 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/Group.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/Group.java @@ -34,7 +34,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeType; import net.luckperms.api.node.types.DisplayNameNode; import net.luckperms.api.query.QueryOptions; @@ -137,11 +137,8 @@ public Optional getDisplayName() { public Optional calculateDisplayName(QueryOptions queryOptions) { // query for a displayname node - for (Node n : getOwnNodes(queryOptions)) { - if (n instanceof DisplayNameNode) { - DisplayNameNode displayNameNode = (DisplayNameNode) n; - return Optional.of(displayNameNode.getDisplayName()); - } + for (DisplayNameNode n : getOwnNodes(NodeType.DISPLAY_NAME, queryOptions)) { + return Optional.of(n.getDisplayName()); } // fallback to config diff --git a/common/src/main/java/me/lucko/luckperms/common/model/WeightCache.java b/common/src/main/java/me/lucko/luckperms/common/model/WeightCache.java index 420904b96..c3231afef 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/WeightCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/WeightCache.java @@ -29,7 +29,7 @@ import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.query.QueryOptionsImpl; -import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeType; import net.luckperms.api.node.types.WeightNode; import org.checkerframework.checker.nullness.qual.NonNull; @@ -50,29 +50,25 @@ public WeightCache(Group group) { @Override protected @NonNull OptionalInt supply() { boolean seen = false; - int best = 0; - for (Node n : this.group.getOwnNodes(QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL)) { - if (n instanceof WeightNode) { - WeightNode weightNode = (WeightNode) n; - int value = weightNode.getWeight(); + int weight = 0; - if (!seen || value > best) { - seen = true; - best = value; - } + for (WeightNode n : this.group.getOwnNodes(NodeType.WEIGHT, QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL)) { + int value = n.getWeight(); + if (!seen || value > weight) { + seen = true; + weight = value; } } - OptionalInt weight = seen ? OptionalInt.of(best) : OptionalInt.empty(); - - if (!weight.isPresent()) { + if (!seen) { Map configWeights = this.group.getPlugin().getConfiguration().get(ConfigKeys.GROUP_WEIGHTS); - Integer w = configWeights.get(this.group.getObjectName().toLowerCase()); - if (w != null) { - weight = OptionalInt.of(w); + Integer value = configWeights.get(this.group.getObjectName().toLowerCase()); + if (value != null) { + seen = true; + weight = value; } } - return weight; + return seen ? OptionalInt.of(weight) : OptionalInt.empty(); } } From 478fddc4866fdf5191500ed9a33361bda00a9d30 Mon Sep 17 00:00:00 2001 From: lucko Date: Sun, 13 Dec 2020 13:08:15 +0000 Subject: [PATCH 15/38] Track individual changes to users/groups instead of writing in full on each save (#2767) --- .../generic/parent/ParentClearTrack.java | 2 +- .../commands/generic/parent/ParentRemove.java | 2 +- .../commands/group/GroupListMembers.java | 2 +- .../lucko/luckperms/common/model/NodeMap.java | 467 ------------------ .../common/model/PermissionHolder.java | 58 +-- .../lucko/luckperms/common/model/Track.java | 8 - .../manager/user/AbstractUserManager.java | 74 ++- .../model/manager/user/UserManager.java | 14 +- .../common/model/nodemap/MutateResult.java | 152 ++++++ .../common/model/nodemap/NodeMap.java | 161 ++++++ .../common/model/nodemap/NodeMapBase.java | 243 +++++++++ .../common/model/nodemap/NodeMapMutable.java | 404 +++++++++++++++ .../common/model/nodemap/RecordedNodeMap.java | 187 +++++++ .../file/AbstractConfigurateStorage.java | 87 +--- .../implementation/mongodb/MongoStorage.java | 208 +++----- .../storage/implementation/sql/SqlNode.java | 195 -------- .../storage/implementation/sql/SqlRowId.java | 57 +++ .../implementation/sql/SqlStorage.java | 414 +++++++--------- .../common/tasks/ExpireTemporaryTask.java | 24 - .../model/manager/SpongeGroupManager.java | 5 - .../model/manager/SpongeUserManager.java | 5 - 21 files changed, 1543 insertions(+), 1226 deletions(-) delete mode 100644 common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/model/nodemap/MutateResult.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMap.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapBase.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/model/nodemap/RecordedNodeMap.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlNode.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlRowId.java diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentClearTrack.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentClearTrack.java index 6787a87cc..8a7ed90d1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentClearTrack.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentClearTrack.java @@ -94,7 +94,7 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHo target.removeIf(DataType.NORMAL, context.isEmpty() ? null : context, NodeType.INHERITANCE.predicate(n -> track.containsGroup(n.getGroupName())), false); if (target.getType() == HolderType.USER) { - plugin.getUserManager().giveDefaultIfNeeded(((User) target), false); + plugin.getUserManager().giveDefaultIfNeeded(((User) target)); } int changed = before - target.normalData().size(); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemove.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemove.java index 8727fd716..ced77c54c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemove.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemove.java @@ -101,7 +101,7 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHo .build().submit(plugin, sender); if (target.getType() == HolderType.USER) { - plugin.getUserManager().giveDefaultIfNeeded(((User) target), false); + plugin.getUserManager().giveDefaultIfNeeded(((User) target)); } StorageAssistant.save(target, sender, plugin); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java index 10c41fee3..c58a13011 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java @@ -84,7 +84,7 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Group target if (target.getName().equals(GroupManager.DEFAULT_GROUP_NAME)) { // include all non-saved online players in the results for (User user : plugin.getUserManager().getAll().values()) { - if (!plugin.getUserManager().shouldSave(user)) { + if (!plugin.getUserManager().isNonDefaultUser(user)) { matchedUsers.add(NodeEntry.of(user.getUniqueId(), node)); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java b/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java deleted file mode 100644 index 165e93e99..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * 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.model; - -import com.google.common.collect.ImmutableSet; - -import me.lucko.luckperms.common.config.ConfigKeys; -import me.lucko.luckperms.common.context.ContextSetComparator; -import me.lucko.luckperms.common.node.comparator.NodeComparator; -import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator; - -import net.luckperms.api.context.ContextSatisfyMode; -import net.luckperms.api.context.ContextSet; -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.context.ImmutableContextSet; -import net.luckperms.api.node.Node; -import net.luckperms.api.node.NodeEqualityPredicate; -import net.luckperms.api.node.NodeType; -import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata; -import net.luckperms.api.node.types.InheritanceNode; -import net.luckperms.api.query.Flag; -import net.luckperms.api.query.QueryOptions; - -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; - -/** - * A map of nodes held by a {@link PermissionHolder}. - * - *

Permissions are stored in Multimaps, with the context of the node being the key, and the actual Node object being - * the value. The keys (context sets) are ordered according to their weight {@link ContextSetComparator}, and the values - * are ordered according to the priority of the node, according to {@link NodeComparator}.

- * - *

Each holder has two of these maps, one for enduring and transient nodes.

- */ -public final class NodeMap { - private static final Function> VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse()); - private static final Function> INHERITANCE_VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse()); - - /** - * The holder which this map is for - */ - private final PermissionHolder holder; - - /** - * The backing data map. - * - *

Nodes are mapped by the result of {@link Node#getContexts()}, and keys are sorted by the weight of the - * ContextSet. ContextSets are ordered first by the presence of a server key, then by the presence of a world - * key, and finally by the overall size of the set. Nodes are ordered according to the priority rules - * defined in {@link NodeComparator}.

- */ - private final SortedMap> map = new ConcurrentSkipListMap<>(ContextSetComparator.reverse()); - - /** - * Copy of {@link #map} which only contains group nodes - * @see InheritanceNode - */ - private final SortedMap> inheritanceMap = new ConcurrentSkipListMap<>(ContextSetComparator.reverse()); - - NodeMap(PermissionHolder holder) { - this.holder = holder; - } - - public boolean isEmpty() { - return this.map.isEmpty(); - } - - public int size() { - int size = 0; - for (SortedSet values : this.map.values()) { - size += values.size(); - } - return size; - } - - public List asList() { - List list = new ArrayList<>(); - copyTo(list); - return list; - } - - public LinkedHashSet asSet() { - LinkedHashSet set = new LinkedHashSet<>(); - copyTo(set); - return set; - } - - public SortedSet asSortedSet() { - SortedSet set = new TreeSet<>(NodeWithContextComparator.reverse()); - copyTo(set); - return set; - } - - public ImmutableSet asImmutableSet() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (SortedSet values : this.map.values()) { - builder.addAll(values); - } - return builder.build(); - } - - public Map> asMap() { - Map> map = new HashMap<>(); - for (Map.Entry> e : this.map.entrySet()) { - map.put(e.getKey(), new ArrayList<>(e.getValue())); - } - return map; - } - - public List inheritanceAsList() { - List set = new ArrayList<>(); - copyInheritanceNodesTo(set); - return set; - } - - public LinkedHashSet inheritanceAsSet() { - LinkedHashSet set = new LinkedHashSet<>(); - copyInheritanceNodesTo(set); - return set; - } - - public SortedSet inheritanceAsSortedSet() { - SortedSet set = new TreeSet<>(NodeWithContextComparator.reverse()); - copyInheritanceNodesTo(set); - return set; - } - - public Map> inheritanceAsMap() { - Map> map = new HashMap<>(); - for (Map.Entry> e : this.inheritanceMap.entrySet()) { - map.put(e.getKey(), new ArrayList<>(e.getValue())); - } - return map; - } - - private static boolean flagExcludeTest(Flag flag, String contextKey, QueryOptions filter, ImmutableContextSet contextSet) { - // return true (negative result) if the explicit *include* flag is not set, and if the context set doesn't contain the required context key. - return !filter.flag(flag) && !contextSet.containsKey(contextKey); - } - - private static boolean normalNodesExcludeTest(QueryOptions filter, ImmutableContextSet contextSet) { - // return true (negative result) if normal nodes should not be included due to the lack of a server/world context. - return flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) || - flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet); - } - - private static boolean inheritanceNodesIncludeTest(QueryOptions filter, ImmutableContextSet contextSet) { - // return true (positive result) if inheritance nodes should be included, due to the lack of any flags preventing their inclusion. - return !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) && - !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet); - } - - private ContextSatisfyMode defaultSatisfyMode() { - return this.holder.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE); - } - - public void forEach(Consumer consumer) { - for (SortedSet values : this.map.values()) { - values.forEach(consumer); - } - } - - public void forEach(QueryOptions filter, Consumer consumer) { - for (Map.Entry> e : this.map.entrySet()) { - if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { - continue; - } - - if (normalNodesExcludeTest(filter, e.getKey())) { - if (inheritanceNodesIncludeTest(filter, e.getKey())) { - // only copy inheritance nodes. - SortedSet inheritanceNodes = this.inheritanceMap.get(e.getKey()); - if (inheritanceNodes != null) { - inheritanceNodes.forEach(consumer); - } - } - } else { - e.getValue().forEach(consumer); - } - } - } - - public void copyTo(Collection collection) { - for (SortedSet values : this.map.values()) { - collection.addAll(values); - } - } - - public void copyTo(Collection collection, QueryOptions filter) { - for (Map.Entry> e : this.map.entrySet()) { - if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { - continue; - } - - if (normalNodesExcludeTest(filter, e.getKey())) { - if (inheritanceNodesIncludeTest(filter, e.getKey())) { - // only copy inheritance nodes. - SortedSet inheritanceNodes = this.inheritanceMap.get(e.getKey()); - if (inheritanceNodes != null) { - collection.addAll(inheritanceNodes); - } - } - } else { - collection.addAll(e.getValue()); - } - } - } - - public void copyTo(Collection collection, NodeType type, QueryOptions filter) { - for (Map.Entry> e : this.map.entrySet()) { - if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { - continue; - } - - if (normalNodesExcludeTest(filter, e.getKey())) { - if (inheritanceNodesIncludeTest(filter, e.getKey())) { - // only copy inheritance nodes. - if (type == NodeType.INHERITANCE) { - SortedSet inheritanceNodes = this.inheritanceMap.get(e.getKey()); - if (inheritanceNodes != null) { - for (InheritanceNode node : inheritanceNodes) { - collection.add(type.cast(node)); - } - } - } - } - } else { - for (Node node : e.getValue()) { - if (type.matches(node)) { - collection.add(type.cast(node)); - } - } - } - } - } - - public void copyInheritanceNodesTo(Collection collection) { - for (SortedSet values : this.inheritanceMap.values()) { - collection.addAll(values); - } - } - - public void copyInheritanceNodesTo(Collection collection, QueryOptions filter) { - for (Map.Entry> e : this.inheritanceMap.entrySet()) { - if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { - continue; - } - - if (inheritanceNodesIncludeTest(filter, e.getKey())) { - collection.addAll(e.getValue()); - } - } - } - - public Collection nodesInContext(ContextSet context) { - final SortedSet values = this.map.get(context.immutableCopy()); - if (values == null) { - return ImmutableSet.of(); - } - return new ArrayList<>(values); - } - - public Collection inheritanceNodesInContext(ContextSet context) { - final SortedSet values = this.inheritanceMap.get(context.immutableCopy()); - if (values == null) { - return ImmutableSet.of(); - } - return new ArrayList<>(values); - } - - private Node localise(Node node) { - Optional metadata = node.getMetadata(InheritanceOriginMetadata.KEY); - if (metadata.isPresent() && metadata.get().getOrigin().equals(this.holder.getIdentifier())) { - return node; - } - - return node.toBuilder().withMetadata(InheritanceOriginMetadata.KEY, new InheritanceOrigin(this.holder.getIdentifier())).build(); - } - - void add(Node node) { - ImmutableContextSet context = node.getContexts(); - Node n = localise(node); - - SortedSet nodesInContext = this.map.computeIfAbsent(context, VALUE_SET_SUPPLIER); - nodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)); - nodesInContext.add(n); - - if (n instanceof InheritanceNode) { - SortedSet inheritanceNodesInContext = this.inheritanceMap.computeIfAbsent(context, INHERITANCE_VALUE_SET_SUPPLIER); - inheritanceNodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)); - if (n.getValue()) { - inheritanceNodesInContext.add((InheritanceNode) n); - } - } - } - - void remove(Node node) { - ImmutableContextSet context = node.getContexts(); - SortedSet nodesInContext = this.map.get(context); - if (nodesInContext != null) { - nodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)); - } - - if (node instanceof InheritanceNode) { - SortedSet inheritanceNodesInContext = this.inheritanceMap.get(context); - if (inheritanceNodesInContext != null) { - inheritanceNodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)); - } - } - } - - private void removeExact(Node node) { - ImmutableContextSet context = node.getContexts(); - SortedSet nodesInContext = this.map.get(context); - if (nodesInContext != null) { - nodesInContext.remove(node); - } - - if (node instanceof InheritanceNode && node.getValue()) { - SortedSet inheritanceNodesInContext = this.inheritanceMap.get(context); - if (inheritanceNodesInContext != null) { - inheritanceNodesInContext.remove(node); - } - } - } - - void replace(Node node, Node previous) { - removeExact(previous); - add(node); - } - - void clear() { - this.map.clear(); - this.inheritanceMap.clear(); - } - - void clear(ContextSet contextSet) { - ImmutableContextSet context = contextSet.immutableCopy(); - this.map.remove(context); - this.inheritanceMap.remove(context); - } - - void setContent(Iterable set) { - this.map.clear(); - this.inheritanceMap.clear(); - mergeContent(set); - } - - void setContent(Stream stream) { - this.map.clear(); - this.inheritanceMap.clear(); - mergeContent(stream); - } - - void mergeContent(Iterable set) { - for (Node n : set) { - add(n); - } - } - - void mergeContent(Stream stream) { - stream.forEach(this::add); - } - - boolean removeIf(Predicate predicate) { - boolean success = false; - for (SortedSet valueSet : this.map.values()) { - if (valueSet.removeIf(predicate)) { - success = true; - } - } - for (SortedSet valueSet : this.inheritanceMap.values()) { - valueSet.removeIf(predicate); - } - return success; - } - - boolean removeIf(ContextSet contextSet, Predicate predicate) { - ImmutableContextSet context = contextSet.immutableCopy(); - - boolean success = false; - - SortedSet nodesInContext = this.map.get(context); - if (nodesInContext != null) { - success = nodesInContext.removeIf(predicate); - } - - SortedSet inheritanceNodesInContext = this.inheritanceMap.get(context); - if (inheritanceNodesInContext != null) { - inheritanceNodesInContext.removeIf(predicate); - } - - return success; - } - - boolean auditTemporaryNodes(@Nullable Set removed) { - boolean work = false; - - for (SortedSet valueSet : this.map.values()) { - Iterator it = valueSet.iterator(); - while (it.hasNext()) { - Node entry = it.next(); - if (!entry.hasExpired()) { - continue; - } - - // remove - if (removed != null) { - removed.add(entry); - } - if (entry instanceof InheritanceNode && entry.getValue()) { - SortedSet inheritanceNodesInContext = this.inheritanceMap.get(entry.getContexts()); - if (inheritanceNodesInContext != null) { - inheritanceNodesInContext.remove(entry); - } - } - it.remove(); - work = true; - } - } - - return work; - } - -} diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java index 61d642b9e..0f0836cc8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java @@ -32,6 +32,10 @@ import me.lucko.luckperms.common.cacheddata.type.MetaAccumulator; import me.lucko.luckperms.common.inheritance.InheritanceComparator; import me.lucko.luckperms.common.inheritance.InheritanceGraph; +import me.lucko.luckperms.common.model.nodemap.MutateResult; +import me.lucko.luckperms.common.model.nodemap.NodeMap; +import me.lucko.luckperms.common.model.nodemap.NodeMapMutable; +import me.lucko.luckperms.common.model.nodemap.RecordedNodeMap; import me.lucko.luckperms.common.node.NodeEquality; import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; @@ -59,18 +63,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.OptionalInt; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.IntFunction; import java.util.function.Predicate; -import java.util.stream.Stream; /** * Represents an object that can hold permissions, (a user or group) @@ -112,7 +111,7 @@ public abstract class PermissionHolder { * * @see #normalData() */ - private final NodeMap normalNodes = new NodeMap(this); + private final RecordedNodeMap normalNodes = new RecordedNodeMap(new NodeMapMutable(this)); /** * The holders transient nodes. @@ -124,13 +123,7 @@ public abstract class PermissionHolder { * * @see #transientData() */ - private final NodeMap transientNodes = new NodeMap(this); - - /** - * Lock used by Storage implementations to prevent concurrent read/writes - * @see #getIoLock() - */ - private final Lock ioLock = new ReentrantLock(); + private final NodeMap transientNodes = new NodeMapMutable(this); /** * Comparator used to ordering groups when calculating inheritance @@ -152,10 +145,6 @@ public LuckPermsPlugin getPlugin() { return this.plugin; } - public Lock getIoLock() { - return this.ioLock; - } - public Comparator getInheritanceComparator() { return this.inheritanceComparator; } @@ -171,7 +160,7 @@ public NodeMap getData(DataType type) { } } - public NodeMap normalData() { + public RecordedNodeMap normalData() { return this.normalNodes; } @@ -237,18 +226,20 @@ protected void invalidateCache() { getPlugin().getEventDispatcher().dispatchDataRecalculate(this); } - public void setNodes(DataType type, Iterable set) { - getData(type).setContent(set); + public void loadNodesFromStorage(Iterable set) { + // TODO: should we attempt to "replay" existing changes on top of the new data? + normalData().discardChanges(); + normalData().bypass().setContent(set); invalidateCache(); } - public void setNodes(DataType type, Stream stream) { - getData(type).setContent(stream); + public void setNodes(DataType type, Iterable set) { + getData(type).setContent(set); invalidateCache(); } public void mergeNodes(DataType type, Iterable set) { - getData(type).mergeContent(set); + getData(type).addAll(set); invalidateCache(); } @@ -436,20 +427,19 @@ public boolean auditTemporaryNodes() { private boolean auditTemporaryNodes(DataType dataType) { ImmutableSet before = getData(dataType).asImmutableSet(); - Set removed = new HashSet<>(); - boolean work = getData(dataType).auditTemporaryNodes(removed); - if (work) { + MutateResult result = getData(dataType).removeIf(Node::hasExpired); + if (!result.isEmpty()) { // call event ImmutableSet after = getData(dataType).asImmutableSet(); - for (Node r : removed) { + for (Node r : result.getRemoved()) { this.plugin.getEventDispatcher().dispatchNodeRemove(r, this, dataType, before, after); } // invalidate invalidateCache(); } - return work; + return !result.isEmpty(); } public Tristate hasNode(DataType type, Node node, NodeEqualityPredicate equalityPredicate) { @@ -522,7 +512,7 @@ public DataMutateResult.WithMergedNode setNode(DataType dataType, Node node, Tem if (newNode != null) { // Remove the old Node & add the new one. ImmutableSet before = data.asImmutableSet(); - data.replace(newNode, otherMatch); + data.removeThenAdd(otherMatch, newNode); ImmutableSet after = data.asImmutableSet(); this.plugin.getEventDispatcher().dispatchNodeAdd(newNode, this, dataType, before, after); @@ -572,7 +562,7 @@ public DataMutateResult.WithMergedNode unsetNode(DataType dataType, Node node, @ // Remove the old Node & add the new one. ImmutableSet before = data.asImmutableSet(); - data.replace(newNode, otherMatch); + data.removeThenAdd(otherMatch, newNode); ImmutableSet after = data.asImmutableSet(); this.plugin.getEventDispatcher().dispatchNodeRemove(otherMatch, this, dataType, before, after); @@ -594,17 +584,17 @@ public boolean removeIf(DataType dataType, @Nullable ContextSet contextSet, Pred ImmutableSet before = data.asImmutableSet(); if (contextSet == null) { - if (!data.removeIf(predicate)) { + if (data.removeIf(predicate).isEmpty()) { return false; } } else { - if (!data.removeIf(contextSet, predicate)) { + if (data.removeIf(contextSet, predicate).isEmpty()) { return false; } } if (getType() == HolderType.USER && giveDefault) { - getPlugin().getUserManager().giveDefaultIfNeeded((User) this, false); + getPlugin().getUserManager().giveDefaultIfNeeded((User) this); } ImmutableSet after = data.asImmutableSet(); @@ -626,7 +616,7 @@ public boolean clearNodes(DataType dataType, ContextSet contextSet, boolean give } if (getType() == HolderType.USER && giveDefault) { - getPlugin().getUserManager().giveDefaultIfNeeded((User) this, false); + getPlugin().getUserManager().giveDefaultIfNeeded((User) this); } ImmutableSet after = data.asImmutableSet(); diff --git a/common/src/main/java/me/lucko/luckperms/common/model/Track.java b/common/src/main/java/me/lucko/luckperms/common/model/Track.java index a9a894e09..5683bcdc9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/Track.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/Track.java @@ -47,8 +47,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -61,8 +59,6 @@ public final class Track { private final LuckPermsPlugin plugin; - private final Lock ioLock = new ReentrantLock(); - /** * The groups within this track */ @@ -79,10 +75,6 @@ public String getName() { return this.name; } - public Lock getIoLock() { - return this.ioLock; - } - public ApiTrack getApiProxy() { return this.apiProxy; } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java index f32c76a58..c25c76798 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java @@ -79,8 +79,8 @@ public T getByUsername(String name) { } @Override - public boolean giveDefaultIfNeeded(User user, boolean save) { - boolean work = false; + public boolean giveDefaultIfNeeded(User user) { + boolean requireSave = false; Collection globalGroups = user.normalData().inheritanceNodesInContext(ImmutableContextSetImpl.EMPTY); @@ -106,7 +106,7 @@ public boolean giveDefaultIfNeeded(User user, boolean save) { // if the group is null, it'll be resolved in the next step if (group != null) { user.getPrimaryGroup().setStoredValue(group); - work = true; + requireSave = true; } } } @@ -120,14 +120,39 @@ public boolean giveDefaultIfNeeded(User user, boolean save) { if (!hasGroup) { user.getPrimaryGroup().setStoredValue(GroupManager.DEFAULT_GROUP_NAME); user.setNode(DataType.NORMAL, Inheritance.builder(GroupManager.DEFAULT_GROUP_NAME).build(), false); - work = true; + requireSave = true; } - if (work && save) { - this.plugin.getStorage().saveUser(user); + return requireSave; + } + + @Override + public boolean isNonDefaultUser(User user) { + if (user.normalData().size() != 1) { + return true; + } + + List nodes = user.normalData().asList(); + if (nodes.size() != 1) { + return true; + } + + Node onlyNode = nodes.iterator().next(); + if (!isDefaultNode(onlyNode)) { + return true; } - return work; + // Not in the default primary group + return !user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME); + } + + @Override + public boolean isDefaultNode(Node node) { + return node instanceof InheritanceNode && + node.getValue() && + !node.hasExpiry() && + node.getContexts().isEmpty() && + ((InheritanceNode) node).getGroupName().equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME); } @Override @@ -157,39 +182,4 @@ public void invalidateAllPermissionCalculators() { getAll().values().forEach(u -> u.getCachedData().invalidatePermissionCalculators()); } - /** - * Check whether the user's state indicates that they should be persisted to storage. - * - * @param user the user to check - * @return true if the user should be saved - */ - @Override - public boolean shouldSave(User user) { - if (user.normalData().size() != 1) { - return true; - } - - List nodes = user.normalData().asList(); - if (nodes.size() != 1) { - return true; - } - - Node onlyNode = nodes.iterator().next(); - if (!(onlyNode instanceof InheritanceNode)) { - return true; - } - - if (onlyNode.hasExpiry() || !onlyNode.getContexts().isEmpty()) { - return true; - } - - if (!((InheritanceNode) onlyNode).getGroupName().equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) { - // The user's only node is not the default group one. - return true; - } - - - // Not in the default primary group - return !user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME); - } } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java index 8b9c02c44..7b64a2561 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java @@ -29,6 +29,8 @@ import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.manager.Manager; +import net.luckperms.api.node.Node; + import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -49,7 +51,7 @@ public interface UserManager extends Manager { * * @param user the user to give to */ - boolean giveDefaultIfNeeded(User user, boolean save); + boolean giveDefaultIfNeeded(User user); /** * Check whether the user's state indicates that they should be persisted to storage. @@ -57,7 +59,15 @@ public interface UserManager extends Manager { * @param user the user to check * @return true if the user should be saved */ - boolean shouldSave(User user); + boolean isNonDefaultUser(User user); + + /** + * Gets whether the given node is a default node given by {@link #giveDefaultIfNeeded(User)}. + * + * @param node the node + * @return true if it is the default node + */ + boolean isDefaultNode(Node node); /** * Gets the instance responsible for unloading unneeded users. diff --git a/common/src/main/java/me/lucko/luckperms/common/model/nodemap/MutateResult.java b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/MutateResult.java new file mode 100644 index 000000000..aeb554c1f --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/MutateResult.java @@ -0,0 +1,152 @@ +/* + * 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.model.nodemap; + +import net.luckperms.api.node.Node; + +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Records a log of the changes that occur as a result of a {@link NodeMap} mutation(s). + */ +public class MutateResult { + private final LinkedHashSet changes = new LinkedHashSet<>(); + + public Set getChanges() { + return this.changes; + } + + public Set getChanges(ChangeType type) { + Set changes = new LinkedHashSet<>(this.changes.size()); + for (Change change : this.changes) { + if (change.getType() == type) { + changes.add(change.getNode()); + } + } + return changes; + } + + void clear() { + this.changes.clear(); + } + + public boolean isEmpty() { + return this.changes.isEmpty(); + } + + public Set getAdded() { + return getChanges(ChangeType.ADD); + } + + public Set getRemoved() { + return getChanges(ChangeType.REMOVE); + } + + private void recordChange(Change change) { + // This method is the magic of this class. + // When tracking, we want to ignore changes that cancel each other out, and only + // keep track of the net difference. + // e.g. adding then removing the same node = zero net change, so ignore it. + + if (this.changes.remove(change.inverse())) { + return; + } + this.changes.add(change); + } + + public void recordChange(ChangeType type, Node node) { + recordChange(new Change(type, node)); + } + + public void recordChanges(ChangeType type, Iterable nodes) { + for (Node node : nodes) { + recordChange(new Change(type, node)); + } + } + + public MutateResult mergeFrom(MutateResult other) { + for (Change change : other.changes) { + recordChange(change); + } + return this; + } + + @Override + public String toString() { + return "MutateResult{changes=" + this.changes + '}'; + } + + public static final class Change { + private final ChangeType type; + private final Node node; + + public Change(ChangeType type, Node node) { + this.type = type; + this.node = node; + } + + public ChangeType getType() { + return this.type; + } + + public Node getNode() { + return this.node; + } + + public Change inverse() { + return new Change(this.type.inverse(), this.node); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Change change = (Change) o; + return this.type == change.type && this.node.equals(change.node); + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.node); + } + + @Override + public String toString() { + return "Change{type=" + this.type + ", node=" + this.node + '}'; + } + } + + public enum ChangeType { + ADD, REMOVE; + + public ChangeType inverse() { + return this == ADD ? REMOVE : ADD; + } + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMap.java b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMap.java new file mode 100644 index 000000000..117045cac --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMap.java @@ -0,0 +1,161 @@ +/* + * 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.model.nodemap; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; + +import me.lucko.luckperms.common.model.PermissionHolder; +import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator; + +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeType; +import net.luckperms.api.node.types.InheritanceNode; +import net.luckperms.api.query.QueryOptions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * A map of nodes held by a {@link PermissionHolder}. + */ +public interface NodeMap { + + boolean isEmpty(); + + int size(); + + default List asList() { + List list = new ArrayList<>(); + copyTo(list); + return list; + } + + default LinkedHashSet asSet() { + LinkedHashSet set = new LinkedHashSet<>(); + copyTo(set); + return set; + } + + default SortedSet asSortedSet() { + SortedSet set = new TreeSet<>(NodeWithContextComparator.reverse()); + copyTo(set); + return set; + } + + default ImmutableSet asImmutableSet() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + copyTo(builder); + return builder.build(); + } + + Map> asMap(); + + default List inheritanceAsList() { + List set = new ArrayList<>(); + copyInheritanceNodesTo(set); + return set; + } + + default LinkedHashSet inheritanceAsSet() { + LinkedHashSet set = new LinkedHashSet<>(); + copyInheritanceNodesTo(set); + return set; + } + + default SortedSet inheritanceAsSortedSet() { + SortedSet set = new TreeSet<>(NodeWithContextComparator.reverse()); + copyInheritanceNodesTo(set); + return set; + } + + default ImmutableSet inheritanceAsImmutableSet() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + copyInheritanceNodesTo(builder); + return builder.build(); + } + + Map> inheritanceAsMap(); + + void forEach(Consumer consumer); + + void forEach(QueryOptions filter, Consumer consumer); + + void copyTo(Collection collection); + + void copyTo(ImmutableCollection.Builder collection); + + void copyTo(Collection collection, QueryOptions filter); + + void copyTo(Collection collection, NodeType type, QueryOptions filter); + + void copyInheritanceNodesTo(Collection collection); + + void copyInheritanceNodesTo(ImmutableCollection.Builder collection); + + void copyInheritanceNodesTo(Collection collection, QueryOptions filter); + + Collection nodesInContext(ContextSet context); + + Collection inheritanceNodesInContext(ContextSet context); + + // mutate methods + + MutateResult add(Node nodeWithoutInheritanceOrigin); + + MutateResult remove(Node node); + + MutateResult removeExact(Node node); + + MutateResult removeIf(Predicate predicate); + + MutateResult removeIf(ContextSet contextSet, Predicate predicate); + + MutateResult removeThenAdd(Node nodeToRemove, Node nodeToAdd); + + MutateResult clear(); + + MutateResult clear(ContextSet contextSet); + + MutateResult setContent(Iterable set); + + MutateResult setContent(Stream stream); + + MutateResult addAll(Iterable set); + + MutateResult addAll(Stream stream); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapBase.java b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapBase.java new file mode 100644 index 000000000..3274e8a01 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapBase.java @@ -0,0 +1,243 @@ +/* + * 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.model.nodemap; + +import com.google.common.collect.ImmutableCollection; + +import net.luckperms.api.context.ContextSatisfyMode; +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.DefaultContextKeys; +import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeType; +import net.luckperms.api.node.types.InheritanceNode; +import net.luckperms.api.query.Flag; +import net.luckperms.api.query.QueryOptions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.function.Consumer; + +/** + * Base implementation of {@link NodeMap} query methods. + */ +abstract class NodeMapBase implements NodeMap { + + NodeMapBase() { + + } + + protected abstract SortedMap> map(); + + protected abstract SortedMap> inheritanceMap(); + + protected abstract ContextSatisfyMode defaultSatisfyMode(); + + @Override + public boolean isEmpty() { + return map().isEmpty(); + } + + @Override + public int size() { + int size = 0; + for (SortedSet values : map().values()) { + size += values.size(); + } + return size; + } + + @Override + public Map> asMap() { + Map> map = new HashMap<>(); + for (Map.Entry> e : map().entrySet()) { + map.put(e.getKey(), new ArrayList<>(e.getValue())); + } + return map; + } + + @Override + public Map> inheritanceAsMap() { + Map> map = new HashMap<>(); + for (Map.Entry> e : inheritanceMap().entrySet()) { + map.put(e.getKey(), new ArrayList<>(e.getValue())); + } + return map; + } + + @Override + public void forEach(Consumer consumer) { + for (SortedSet values : map().values()) { + values.forEach(consumer); + } + } + + @Override + public void forEach(QueryOptions filter, Consumer consumer) { + for (Map.Entry> e : map().entrySet()) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { + continue; + } + + if (normalNodesExcludeTest(filter, e.getKey())) { + if (inheritanceNodesIncludeTest(filter, e.getKey())) { + SortedSet inheritanceNodes = inheritanceMap().get(e.getKey()); + if (inheritanceNodes != null) { + inheritanceNodes.forEach(consumer); + } + } + } else { + e.getValue().forEach(consumer); + } + } + } + + @Override + public void copyTo(Collection collection) { + for (SortedSet values : map().values()) { + collection.addAll(values); + } + } + + @Override + public void copyTo(ImmutableCollection.Builder collection) { + for (SortedSet values : map().values()) { + collection.addAll(values); + } + } + + @Override + public void copyTo(Collection collection, QueryOptions filter) { + for (Map.Entry> e : map().entrySet()) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { + continue; + } + + if (normalNodesExcludeTest(filter, e.getKey())) { + if (inheritanceNodesIncludeTest(filter, e.getKey())) { + SortedSet inheritanceNodes = inheritanceMap().get(e.getKey()); + if (inheritanceNodes != null) { + collection.addAll(inheritanceNodes); + } + } + } else { + collection.addAll(e.getValue()); + } + } + } + + @Override + public void copyTo(Collection collection, NodeType type, QueryOptions filter) { + if (type == NodeType.INHERITANCE) { + //noinspection unchecked + copyInheritanceNodesTo((Collection) collection, filter); + return; + } + + for (Map.Entry> e : map().entrySet()) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { + continue; + } + + if (normalNodesExcludeTest(filter, e.getKey())) { + continue; + } + + for (Node node : e.getValue()) { + if (type.matches(node)) { + collection.add(type.cast(node)); + } + } + } + } + + @Override + public void copyInheritanceNodesTo(Collection collection) { + for (SortedSet values : inheritanceMap().values()) { + collection.addAll(values); + } + } + + @Override + public void copyInheritanceNodesTo(ImmutableCollection.Builder collection) { + for (SortedSet values : inheritanceMap().values()) { + collection.addAll(values); + } + } + + @Override + public void copyInheritanceNodesTo(Collection collection, QueryOptions filter) { + for (Map.Entry> e : inheritanceMap().entrySet()) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { + continue; + } + + if (inheritanceNodesIncludeTest(filter, e.getKey())) { + collection.addAll(e.getValue()); + } + } + } + + @Override + public Collection nodesInContext(ContextSet context) { + return copy(map().get(context.immutableCopy())); + } + + @Override + public Collection inheritanceNodesInContext(ContextSet context) { + return copy(inheritanceMap().get(context.immutableCopy())); + } + + private static Collection copy(Collection collection) { + if (collection == null) { + return Collections.emptySet(); + } + return new ArrayList<>(collection); + } + + private static boolean flagExcludeTest(Flag flag, String contextKey, QueryOptions filter, ImmutableContextSet contextSet) { + // return true (negative result) if the explicit *include* flag is not set, and if the context set doesn't contain the required context key. + return !filter.flag(flag) && !contextSet.containsKey(contextKey); + } + + private static boolean normalNodesExcludeTest(QueryOptions filter, ImmutableContextSet contextSet) { + // return true (negative result) if normal nodes should not be included due to the lack of a server/world context. + return flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) || + flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet); + } + + private static boolean inheritanceNodesIncludeTest(QueryOptions filter, ImmutableContextSet contextSet) { + // return true (positive result) if inheritance nodes should be included, due to the lack of any flags preventing their inclusion. + return !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) && + !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java new file mode 100644 index 000000000..829fd6633 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java @@ -0,0 +1,404 @@ +/* + * 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.model.nodemap; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.context.ContextSetComparator; +import me.lucko.luckperms.common.model.InheritanceOrigin; +import me.lucko.luckperms.common.model.PermissionHolder; +import me.lucko.luckperms.common.model.nodemap.MutateResult.ChangeType; +import me.lucko.luckperms.common.node.comparator.NodeComparator; + +import net.luckperms.api.context.ContextSatisfyMode; +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeEqualityPredicate; +import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata; +import net.luckperms.api.node.types.InheritanceNode; + +import java.util.Iterator; +import java.util.Optional; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class NodeMapMutable extends NodeMapBase { + + // Used in calls to Map#computeIfAbsent to make them behave like a LoadingMap/Cache + // The key (ImmutableContextSet) isn't actually used - these are more like suppliers than functions + private static final Function> VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse()); + private static final Function> INHERITANCE_VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse()); + + // Creates the Map instances used by this.map and this.inheritanceMap + private static SortedMap> createMap() { + return new ConcurrentSkipListMap<>(ContextSetComparator.reverse()); + } + + /* + * Nodes are inserted into the maps using Node#getContexts() as the key. + * The context set keys are ordered according to the rules of ContextSetComparator. + * The node values are ordered according to the priority rules defined in NodeComparator. + * + * We use our own "multimap"-like implementation here because guava's is not thread safe. + * + * The map fields aren't final because they are replaced when large updates (e.g. clear) + * are performed. We do this so there's no risk that the read methods will see an inconsistent + * state in the middle of an update from the DB. (see below comment about locking - we don't + * lock for reads!) + */ + private SortedMap> map = createMap(); + private SortedMap> inheritanceMap = createMap(); + + /** + * This lock is used whilst performing mutations, but *not* reads. + * + * The maps themselves are thread safe, so for querying, we just allow + * the read methods to do whatever they want without any locking. + * However, we want mutations to be atomic, so we use the lock to ensure that happens. + */ + private final Lock lock = new ReentrantLock(); + + protected final PermissionHolder holder; + + public NodeMapMutable(PermissionHolder holder) { + this.holder = holder; + } + + @Override + protected SortedMap> map() { + return this.map; + } + + @Override + protected SortedMap> inheritanceMap() { + return this.inheritanceMap; + } + + @Override + protected ContextSatisfyMode defaultSatisfyMode() { + return this.holder.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE); + } + + private Node addInheritanceOrigin(Node node) { + Optional metadata = node.getMetadata(InheritanceOriginMetadata.KEY); + if (metadata.isPresent() && metadata.get().getOrigin().equals(this.holder.getIdentifier())) { + return node; + } + + return node.toBuilder().withMetadata(InheritanceOriginMetadata.KEY, new InheritanceOrigin(this.holder.getIdentifier())).build(); + } + + @Override + public MutateResult add(Node nodeWithoutInheritanceOrigin) { + Node node = addInheritanceOrigin(nodeWithoutInheritanceOrigin); + + ImmutableContextSet context = node.getContexts(); + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + SortedSet nodes = this.map.computeIfAbsent(context, VALUE_SET_SUPPLIER); + + // add the new node to the set - if it was already there, return + if (!nodes.add(node)) { + return result; + } + + // mark that we added the node in the results + result.recordChange(ChangeType.ADD, node); + + // remove any others that were in the set already with a different value/expiry time + removeMatching(nodes.iterator(), node, result); + + // update the inheritanceMap too if necessary + if (node instanceof InheritanceNode) { + SortedSet inhNodes = this.inheritanceMap.computeIfAbsent(context, INHERITANCE_VALUE_SET_SUPPLIER); + // remove existing.. + inhNodes.removeIf(el -> node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)); + // .. & add + if (node.getValue()) { + inhNodes.add((InheritanceNode) node); + } + } + + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult remove(Node node) { + ImmutableContextSet context = node.getContexts(); + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + SortedSet nodes = this.map.get(context); + if (nodes == null) { + return result; + } + + // remove any nodes that match, record to results + removeMatching(nodes.iterator(), node, result); + + // update inheritance map too + if (node instanceof InheritanceNode) { + SortedSet inhNodes = this.inheritanceMap.get(context); + if (inhNodes != null) { + inhNodes.removeIf(el -> node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)); + } + } + + } finally { + this.lock.unlock(); + } + + return result; + } + + private static void removeMatching(Iterator it, Node node, MutateResult result) { + while (it.hasNext()) { + Node el = it.next(); + if (el != node && node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)) { + it.remove(); + result.recordChange(ChangeType.REMOVE, el); + } + } + } + + @Override + public MutateResult removeExact(Node node) { + ImmutableContextSet context = node.getContexts(); + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + SortedSet nodes = this.map.get(context); + if (nodes == null) { + return result; + } + + // try to remove an exact match + if (nodes.remove(node)) { + // if we removed something, record to results + result.recordChange(ChangeType.REMOVE, node); + + // update inheritance map too if necessary + if (node instanceof InheritanceNode && node.getValue()) { + SortedSet inhNodes = this.inheritanceMap.get(context); + if (inhNodes != null) { + inhNodes.remove(node); + } + } + } + + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult removeIf(Predicate predicate) { + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + for (SortedSet nodes : this.map.values()) { + removeMatching(nodes.iterator(), predicate, result); + } + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult removeIf(ContextSet contextSet, Predicate predicate) { + ImmutableContextSet context = contextSet.immutableCopy(); + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + SortedSet nodes = this.map.get(context); + if (nodes == null) { + return result; + } + removeMatching(nodes.iterator(), predicate, result); + } finally { + this.lock.unlock(); + } + + return result; + } + + private void removeMatching(Iterator it, Predicate predicate, MutateResult result) { + while (it.hasNext()) { + Node node = it.next(); + + // if the predicate passes, remove the node from the set & record to results + if (predicate.test(node)) { + it.remove(); + result.recordChange(ChangeType.REMOVE, node); + + // update inheritance map too if necessary + if (node instanceof InheritanceNode && node.getValue()) { + SortedSet inhNodes = this.inheritanceMap.get(node.getContexts()); + if (inhNodes != null) { + inhNodes.remove(node); + } + } + } + } + } + + @Override + public MutateResult removeThenAdd(Node nodeToRemove, Node nodeToAdd) { + if (nodeToAdd.equals(nodeToRemove)) { + return new MutateResult(); + } + + this.lock.lock(); + try { + return removeExact(nodeToRemove).mergeFrom(add(nodeToAdd)); + } finally { + this.lock.unlock(); + } + } + + @Override + public MutateResult clear() { + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + // log removals + for (SortedSet nodes : this.map.values()) { + result.recordChanges(ChangeType.REMOVE, nodes); + } + + // replace the map - this means any client reading async won't be affected + // by any race conditions between this call to clear and any subsequent call to setContent + this.map = createMap(); + this.inheritanceMap = createMap(); + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult clear(ContextSet contextSet) { + ImmutableContextSet context = contextSet.immutableCopy(); + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + SortedSet removed = this.map.remove(context); + if (removed != null) { + result.recordChanges(ChangeType.REMOVE, removed); + this.inheritanceMap.remove(context); + } + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult setContent(Iterable set) { + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + result.mergeFrom(clear()); + result.mergeFrom(addAll(set)); + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult setContent(Stream stream) { + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + result.mergeFrom(clear()); + result.mergeFrom(addAll(stream)); + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult addAll(Iterable set) { + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + for (Node n : set) { + result.mergeFrom(add(n)); + } + } finally { + this.lock.unlock(); + } + + return result; + } + + @Override + public MutateResult addAll(Stream stream) { + MutateResult result = new MutateResult(); + + this.lock.lock(); + try { + stream.forEach(n -> result.mergeFrom(add(n))); + } finally { + this.lock.unlock(); + } + + return result; + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/model/nodemap/RecordedNodeMap.java b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/RecordedNodeMap.java new file mode 100644 index 000000000..10d24cad9 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/RecordedNodeMap.java @@ -0,0 +1,187 @@ +/* + * 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.model.nodemap; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; + +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeType; +import net.luckperms.api.node.types.InheritanceNode; +import net.luckperms.api.query.QueryOptions; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * A forwarding {@link NodeMap} that records all mutations and keeps them in a log. + */ +public class RecordedNodeMap implements NodeMap { + + private final NodeMap delegate; + private final Lock lock = new ReentrantLock(); + private MutateResult changes = new MutateResult(); + + public RecordedNodeMap(NodeMap delegate) { + this.delegate = delegate; + } + + public NodeMap bypass() { + return this.delegate; + } + + public void discardChanges() { + this.lock.lock(); + try { + this.changes.clear(); + } finally { + this.lock.unlock(); + } + } + + public MutateResult exportChanges(Predicate onlyIf) { + this.lock.lock(); + try { + MutateResult existing = this.changes; + if (onlyIf.test(existing)) { + this.changes = new MutateResult(); + return existing; + } + return null; + } finally { + this.lock.unlock(); + } + } + + private MutateResult record(MutateResult result) { + this.lock.lock(); + try { + this.changes.mergeFrom(result); + } finally { + this.lock.unlock(); + } + return result; + } + + // delegate, but pass the result through #record(MutateResult) + + @Override + public MutateResult add(Node nodeWithoutInheritanceOrigin) { + return record(this.delegate.add(nodeWithoutInheritanceOrigin)); + } + + @Override + public MutateResult remove(Node node) { + return record(this.delegate.remove(node)); + } + + @Override + public MutateResult removeExact(Node node) { + return record(this.delegate.removeExact(node)); + } + + @Override + public MutateResult removeIf(Predicate predicate) { + return record(this.delegate.removeIf(predicate)); + } + + @Override + public MutateResult removeIf(ContextSet contextSet, Predicate predicate) { + return record(this.delegate.removeIf(contextSet, predicate)); + } + + @Override + public MutateResult removeThenAdd(Node nodeToRemove, Node nodeToAdd) { + return record(this.delegate.removeThenAdd(nodeToRemove, nodeToAdd)); + } + + @Override + public MutateResult clear() { + return record(this.delegate.clear()); + } + + @Override + public MutateResult clear(ContextSet contextSet) { + return record(this.delegate.clear(contextSet)); + } + + @Override + public MutateResult setContent(Iterable set) { + return record(this.delegate.setContent(set)); + } + + @Override + public MutateResult setContent(Stream stream) { + return record(this.delegate.setContent(stream)); + } + + @Override + public MutateResult addAll(Iterable set) { + return record(this.delegate.addAll(set)); + } + + @Override + public MutateResult addAll(Stream stream) { + return record(this.delegate.addAll(stream)); + } + + // just plain delegation + + @Override public boolean isEmpty() { return this.delegate.isEmpty(); } + @Override public int size() { return this.delegate.size(); } + @Override public List asList() { return this.delegate.asList(); } + @Override public LinkedHashSet asSet() { return this.delegate.asSet(); } + @Override public SortedSet asSortedSet() { return this.delegate.asSortedSet(); } + @Override public ImmutableSet asImmutableSet() { return this.delegate.asImmutableSet(); } + @Override public Map> asMap() { return this.delegate.asMap(); } + @Override public List inheritanceAsList() { return this.delegate.inheritanceAsList(); } + @Override public LinkedHashSet inheritanceAsSet() { return this.delegate.inheritanceAsSet(); } + @Override public SortedSet inheritanceAsSortedSet() { return this.delegate.inheritanceAsSortedSet(); } + @Override public ImmutableSet inheritanceAsImmutableSet() { return this.delegate.inheritanceAsImmutableSet(); } + @Override public Map> inheritanceAsMap() { return this.delegate.inheritanceAsMap(); } + @Override public void forEach(Consumer consumer) { this.delegate.forEach(consumer); } + @Override public void forEach(QueryOptions filter, Consumer consumer) { this.delegate.forEach(filter, consumer); } + @Override public void copyTo(Collection collection) { this.delegate.copyTo(collection); } + @Override public void copyTo(ImmutableCollection.Builder collection) { this.delegate.copyTo(collection); } + @Override public void copyTo(Collection collection, QueryOptions filter) { this.delegate.copyTo(collection, filter); } + @Override public void copyTo(Collection collection, NodeType type, QueryOptions filter) { this.delegate.copyTo(collection, type, filter); } + @Override public void copyInheritanceNodesTo(Collection collection) { this.delegate.copyInheritanceNodesTo(collection); } + @Override public void copyInheritanceNodesTo(ImmutableCollection.Builder collection) { this.delegate.copyInheritanceNodesTo(collection); } + @Override public void copyInheritanceNodesTo(Collection collection, QueryOptions filter) { this.delegate.copyInheritanceNodesTo(collection, filter); } + @Override public Collection nodesInContext(ContextSet context) { return this.delegate.nodesInContext(context); } + @Override public Collection inheritanceNodesInContext(ContextSet context) { return this.delegate.inheritanceNodesInContext(context); } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java index f1e352a95..c268fa031 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java @@ -55,7 +55,6 @@ import net.luckperms.api.context.DefaultContextKeys; import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.model.PlayerSaveResult; -import net.luckperms.api.model.data.DataType; import net.luckperms.api.node.Node; import net.luckperms.api.node.NodeBuilder; import net.luckperms.api.node.NodeType; @@ -193,44 +192,38 @@ protected boolean processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode nod @Override public User loadUser(UUID uniqueId, String username) { User user = this.plugin.getUserManager().getOrMake(uniqueId, username); - user.getIoLock().lock(); try { ConfigurationNode object = readFile(StorageLocation.USER, uniqueId.toString()); if (object != null) { String name = object.getNode("name").getString(); user.getPrimaryGroup().setStoredValue(object.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").getString()); - - user.setNodes(DataType.NORMAL, readNodes(object)); user.setUsername(name, true); - boolean save = this.plugin.getUserManager().giveDefaultIfNeeded(user, false); - if (user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name))) { - save = true; - } + user.loadNodesFromStorage(readNodes(object)); + this.plugin.getUserManager().giveDefaultIfNeeded(user); - if (save | user.auditTemporaryNodes()) { + boolean updatedUsername = user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name)); + if (updatedUsername | user.auditTemporaryNodes()) { saveUser(user); } } else { - if (this.plugin.getUserManager().shouldSave(user)) { - user.clearNodes(DataType.NORMAL, null, true); + if (this.plugin.getUserManager().isNonDefaultUser(user)) { + user.loadNodesFromStorage(Collections.emptyList()); user.getPrimaryGroup().setStoredValue(null); - this.plugin.getUserManager().giveDefaultIfNeeded(user, false); + this.plugin.getUserManager().giveDefaultIfNeeded(user); } } } catch (Exception e) { throw reportException(uniqueId.toString(), e); - } finally { - user.getIoLock().unlock(); } return user; } @Override public void saveUser(User user) { - user.getIoLock().lock(); + user.normalData().discardChanges(); try { - if (!this.plugin.getUserManager().shouldSave(user)) { + if (!this.plugin.getUserManager().isNonDefaultUser(user)) { saveFile(StorageLocation.USER, user.getUniqueId().toString(), null); } else { ConfigurationNode data = SimpleConfigurationNode.root(); @@ -245,20 +238,17 @@ public void saveUser(User user) { } } catch (Exception e) { throw reportException(user.getUniqueId().toString(), e); - } finally { - user.getIoLock().unlock(); } } @Override public Group createAndLoadGroup(String name) { Group group = this.plugin.getGroupManager().getOrMake(name); - group.getIoLock().lock(); try { ConfigurationNode object = readFile(StorageLocation.GROUP, name); if (object != null) { - group.setNodes(DataType.NORMAL, readNodes(object)); + group.loadNodesFromStorage(readNodes(object)); } else { ConfigurationNode data = SimpleConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { @@ -270,19 +260,12 @@ public Group createAndLoadGroup(String name) { } } catch (Exception e) { throw reportException(name, e); - } finally { - group.getIoLock().unlock(); } return group; } @Override public Optional loadGroup(String name) { - Group group = this.plugin.getGroupManager().getIfLoaded(name); - if (group != null) { - group.getIoLock().lock(); - } - try { ConfigurationNode object = readFile(StorageLocation.GROUP, name); @@ -290,26 +273,17 @@ public Optional loadGroup(String name) { return Optional.empty(); } - if (group == null) { - group = this.plugin.getGroupManager().getOrMake(name); - group.getIoLock().lock(); - } - - group.setNodes(DataType.NORMAL, readNodes(object)); - + Group group = this.plugin.getGroupManager().getOrMake(name); + group.loadNodesFromStorage(readNodes(object)); + return Optional.of(group); } catch (Exception e) { throw reportException(name, e); - } finally { - if (group != null) { - group.getIoLock().unlock(); - } } - return Optional.of(group); } @Override public void saveGroup(Group group) { - group.getIoLock().lock(); + group.normalData().discardChanges(); try { ConfigurationNode data = SimpleConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { @@ -320,20 +294,15 @@ public void saveGroup(Group group) { saveFile(StorageLocation.GROUP, group.getName(), data); } catch (Exception e) { throw reportException(group.getName(), e); - } finally { - group.getIoLock().unlock(); } } @Override public void deleteGroup(Group group) { - group.getIoLock().lock(); try { saveFile(StorageLocation.GROUP, group.getName(), null); } catch (Exception e) { throw reportException(group.getName(), e); - } finally { - group.getIoLock().unlock(); } this.plugin.getGroupManager().unload(group.getName()); } @@ -341,7 +310,6 @@ public void deleteGroup(Group group) { @Override public Track createAndLoadTrack(String name) { Track track = this.plugin.getTrackManager().getOrMake(name); - track.getIoLock().lock(); try { ConfigurationNode object = readFile(StorageLocation.TRACK, name); @@ -362,19 +330,12 @@ public Track createAndLoadTrack(String name) { } catch (Exception e) { throw reportException(name, e); - } finally { - track.getIoLock().unlock(); } return track; } @Override public Optional loadTrack(String name) { - Track track = this.plugin.getTrackManager().getIfLoaded(name); - if (track != null) { - track.getIoLock().lock(); - } - try { ConfigurationNode object = readFile(StorageLocation.TRACK, name); @@ -382,30 +343,19 @@ public Optional loadTrack(String name) { return Optional.empty(); } - if (track == null) { - track = this.plugin.getTrackManager().getOrMake(name); - track.getIoLock().lock(); - } - + Track track = this.plugin.getTrackManager().getOrMake(name); List groups = object.getNode("groups").getChildrenList().stream() .map(ConfigurationNode::getString) .collect(ImmutableCollectors.toList()); - track.setGroups(groups); - + return Optional.of(track); } catch (Exception e) { throw reportException(name, e); - } finally { - if (track != null) { - track.getIoLock().unlock(); - } } - return Optional.of(track); } @Override public void saveTrack(Track track) { - track.getIoLock().lock(); try { ConfigurationNode data = SimpleConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { @@ -415,20 +365,15 @@ public void saveTrack(Track track) { saveFile(StorageLocation.TRACK, track.getName(), data); } catch (Exception e) { throw reportException(track.getName(), e); - } finally { - track.getIoLock().unlock(); } } @Override public void deleteTrack(Track track) { - track.getIoLock().lock(); try { saveFile(StorageLocation.TRACK, track.getName(), null); } catch (Exception e) { throw reportException(track.getName(), e); - } finally { - track.getIoLock().unlock(); } this.plugin.getTrackManager().unload(track.getName()); } 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 51ad65bbd..71fe6a4a4 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 @@ -64,7 +64,6 @@ import net.luckperms.api.context.DefaultContextKeys; import net.luckperms.api.context.MutableContextSet; import net.luckperms.api.model.PlayerSaveResult; -import net.luckperms.api.model.data.DataType; import net.luckperms.api.node.Node; import net.luckperms.api.node.NodeBuilder; @@ -72,6 +71,7 @@ import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -298,53 +298,43 @@ private Document processBulkUpdate(Document document, BulkUpdate bulkUpdate, Hol @Override public User loadUser(UUID uniqueId, String username) { User user = this.plugin.getUserManager().getOrMake(uniqueId, username); - user.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "users"); - try (MongoCursor cursor = c.find(new Document("_id", user.getUniqueId())).iterator()) { - if (cursor.hasNext()) { - // User exists, let's load. - Document d = cursor.next(); + MongoCollection c = this.database.getCollection(this.prefix + "users"); + try (MongoCursor cursor = c.find(new Document("_id", user.getUniqueId())).iterator()) { + if (cursor.hasNext()) { + // User exists, let's load. + Document d = cursor.next(); + String name = d.getString("name"); - String name = d.getString("name"); - user.getPrimaryGroup().setStoredValue(d.getString("primaryGroup")); - user.setNodes(DataType.NORMAL, nodesFromDoc(d)); - user.setUsername(name, true); + user.getPrimaryGroup().setStoredValue(d.getString("primaryGroup")); + user.setUsername(name, true); - boolean save = this.plugin.getUserManager().giveDefaultIfNeeded(user, false); - if (user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name))) { - save = true; - } + user.loadNodesFromStorage(nodesFromDoc(d)); + this.plugin.getUserManager().giveDefaultIfNeeded(user); - if (save | user.auditTemporaryNodes()) { - c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user)); - } - } else { - if (this.plugin.getUserManager().shouldSave(user)) { - user.clearNodes(DataType.NORMAL, null, true); - user.getPrimaryGroup().setStoredValue(null); - this.plugin.getUserManager().giveDefaultIfNeeded(user, false); - } + + boolean updatedUsername = user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name)); + if (updatedUsername | user.auditTemporaryNodes()) { + c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user)); + } + } else { + if (this.plugin.getUserManager().isNonDefaultUser(user)) { + user.loadNodesFromStorage(Collections.emptyList()); + user.getPrimaryGroup().setStoredValue(null); + this.plugin.getUserManager().giveDefaultIfNeeded(user); } } - } finally { - user.getIoLock().unlock(); } return user; } @Override public void saveUser(User user) { - user.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "users"); - if (!this.plugin.getUserManager().shouldSave(user)) { - c.deleteOne(new Document("_id", user.getUniqueId())); - } else { - c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user), new ReplaceOptions().upsert(true)); - } - } finally { - user.getIoLock().unlock(); + MongoCollection c = this.database.getCollection(this.prefix + "users"); + user.normalData().discardChanges(); + if (!this.plugin.getUserManager().isNonDefaultUser(user)) { + c.deleteOne(new Document("_id", user.getUniqueId())); + } else { + c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user), new ReplaceOptions().upsert(true)); } } @@ -388,50 +378,31 @@ public List> searchUserNodes(ConstraintNodeM @Override public Group createAndLoadGroup(String name) { Group group = this.plugin.getGroupManager().getOrMake(name); - group.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "groups"); - try (MongoCursor cursor = c.find(new Document("_id", group.getName())).iterator()) { - if (cursor.hasNext()) { - Document d = cursor.next(); - group.setNodes(DataType.NORMAL, nodesFromDoc(d)); - } else { - c.insertOne(groupToDoc(group)); - } + MongoCollection c = this.database.getCollection(this.prefix + "groups"); + try (MongoCursor cursor = c.find(new Document("_id", group.getName())).iterator()) { + if (cursor.hasNext()) { + Document d = cursor.next(); + group.loadNodesFromStorage(nodesFromDoc(d)); + } else { + c.insertOne(groupToDoc(group)); } - } finally { - group.getIoLock().unlock(); } return group; } @Override public Optional loadGroup(String name) { - Group group = this.plugin.getGroupManager().getIfLoaded(name); - if (group != null) { - group.getIoLock().lock(); - } - try { - MongoCollection c = this.database.getCollection(this.prefix + "groups"); - try (MongoCursor cursor = c.find(new Document("_id", name)).iterator()) { - if (!cursor.hasNext()) { - return Optional.empty(); - } - - if (group == null) { - group = this.plugin.getGroupManager().getOrMake(name); - group.getIoLock().lock(); - } - - Document d = cursor.next(); - group.setNodes(DataType.NORMAL, nodesFromDoc(d)); - } - } finally { - if (group != null) { - group.getIoLock().unlock(); + MongoCollection c = this.database.getCollection(this.prefix + "groups"); + try (MongoCursor cursor = c.find(new Document("_id", name)).iterator()) { + if (!cursor.hasNext()) { + return Optional.empty(); } + + Group group = this.plugin.getGroupManager().getOrMake(name); + Document d = cursor.next(); + group.loadNodesFromStorage(nodesFromDoc(d)); + return Optional.of(group); } - return Optional.of(group); } @Override @@ -454,24 +425,15 @@ public void loadAllGroups() { @Override public void saveGroup(Group group) { - group.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "groups"); - c.replaceOne(new Document("_id", group.getName()), groupToDoc(group), new ReplaceOptions().upsert(true)); - } finally { - group.getIoLock().unlock(); - } + MongoCollection c = this.database.getCollection(this.prefix + "groups"); + group.normalData().discardChanges(); + c.replaceOne(new Document("_id", group.getName()), groupToDoc(group), new ReplaceOptions().upsert(true)); } @Override public void deleteGroup(Group group) { - group.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "groups"); - c.deleteOne(new Document("_id", group.getName())); - } finally { - group.getIoLock().unlock(); - } + MongoCollection c = this.database.getCollection(this.prefix + "groups"); + c.deleteOne(new Document("_id", group.getName())); } @Override @@ -498,53 +460,33 @@ public List> searchGroupNodes(ConstraintNo @Override public Track createAndLoadTrack(String name) { Track track = this.plugin.getTrackManager().getOrMake(name); - track.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "tracks"); - try (MongoCursor cursor = c.find(new Document("_id", track.getName())).iterator()) { - if (!cursor.hasNext()) { - c.insertOne(trackToDoc(track)); - } else { - Document d = cursor.next(); - //noinspection unchecked - track.setGroups((List) d.get("groups")); - } + MongoCollection c = this.database.getCollection(this.prefix + "tracks"); + try (MongoCursor cursor = c.find(new Document("_id", track.getName())).iterator()) { + if (!cursor.hasNext()) { + c.insertOne(trackToDoc(track)); + } else { + Document d = cursor.next(); + //noinspection unchecked + track.setGroups((List) d.get("groups")); } - } finally { - track.getIoLock().unlock(); } return track; } @Override public Optional loadTrack(String name) { - Track track = this.plugin.getTrackManager().getIfLoaded(name); - if (track != null) { - track.getIoLock().lock(); - } - - try { - MongoCollection c = this.database.getCollection(this.prefix + "tracks"); - try (MongoCursor cursor = c.find(new Document("_id", name)).iterator()) { - if (!cursor.hasNext()) { - return Optional.empty(); - } - - if (track == null) { - track = this.plugin.getTrackManager().getOrMake(name); - track.getIoLock().lock(); - } - - Document d = cursor.next(); - //noinspection unchecked - track.setGroups((List) d.get("groups")); - } - } finally { - if (track != null) { - track.getIoLock().unlock(); + MongoCollection c = this.database.getCollection(this.prefix + "tracks"); + try (MongoCursor cursor = c.find(new Document("_id", name)).iterator()) { + if (!cursor.hasNext()) { + return Optional.empty(); } + + Track track = this.plugin.getTrackManager().getOrMake(name); + Document d = cursor.next(); + //noinspection unchecked + track.setGroups((List) d.get("groups")); + return Optional.of(track); } - return Optional.of(track); } @Override @@ -567,24 +509,14 @@ public void loadAllTracks() { @Override public void saveTrack(Track track) { - track.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "tracks"); - c.replaceOne(new Document("_id", track.getName()), trackToDoc(track)); - } finally { - track.getIoLock().unlock(); - } + MongoCollection c = this.database.getCollection(this.prefix + "tracks"); + c.replaceOne(new Document("_id", track.getName()), trackToDoc(track)); } @Override public void deleteTrack(Track track) { - track.getIoLock().lock(); - try { - MongoCollection c = this.database.getCollection(this.prefix + "tracks"); - c.deleteOne(new Document("_id", track.getName())); - } finally { - track.getIoLock().unlock(); - } + MongoCollection c = this.database.getCollection(this.prefix + "tracks"); + c.deleteOne(new Document("_id", track.getName())); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlNode.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlNode.java deleted file mode 100644 index 484a9611f..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlNode.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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.implementation.sql; - -import com.google.common.base.Strings; - -import me.lucko.luckperms.common.context.ContextSetJsonSerializer; -import me.lucko.luckperms.common.node.factory.NodeBuilders; -import me.lucko.luckperms.common.util.gson.GsonProvider; - -import net.luckperms.api.context.ContextSet; -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.context.ImmutableContextSet; -import net.luckperms.api.context.MutableContextSet; -import net.luckperms.api.node.Node; - -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -/** - * A version of {@link Node}, more closely following the model used by the SQL - * datastore. - * - * All values are non-null. - */ -public final class SqlNode { - - public static final int NULL_ID = -1; - - public static SqlNode fromNode(Node node) { - ContextSet contexts = node.getContexts(); - - Set servers = contexts.getValues(DefaultContextKeys.SERVER_KEY); - Optional firstServer = servers.stream().sorted().findFirst(); - - String server; - if (firstServer.isPresent()) { - server = firstServer.get(); - MutableContextSet mutableContextSet = contexts.mutableCopy(); - mutableContextSet.remove(DefaultContextKeys.SERVER_KEY, server); - contexts = mutableContextSet; - } else { - server = "global"; - } - - Set worlds = contexts.getValues(DefaultContextKeys.WORLD_KEY); - Optional firstWorld = worlds.stream().sorted().findFirst(); - - String world; - if (firstWorld.isPresent()) { - world = firstWorld.get(); - MutableContextSet mutableContextSet = contexts instanceof MutableContextSet ? (MutableContextSet) contexts : contexts.mutableCopy(); - mutableContextSet.remove(DefaultContextKeys.WORLD_KEY, world); - contexts = mutableContextSet; - } else { - world = "global"; - } - - - long expiry = node.hasExpiry() ? node.getExpiry().getEpochSecond() : 0L; - return new SqlNode(node.getKey(), node.getValue(), server, world, expiry, contexts.immutableCopy(), NULL_ID); - } - - public static SqlNode fromSqlFields(long sqlId, String permission, boolean value, String server, String world, long expiry, String contexts) { - if (Strings.isNullOrEmpty(server)) { - server = "global"; - } - if (Strings.isNullOrEmpty(world)) { - world = "global"; - } - - return new SqlNode(permission, value, server, world, expiry, ContextSetJsonSerializer.deserialize(GsonProvider.normal(), contexts).immutableCopy(), sqlId); - } - - private final String permission; - private final boolean value; - private final String server; - private final String world; - private final long expiry; - private final ImmutableContextSet contexts; - private final long sqlId; - - private SqlNode(String permission, boolean value, String server, String world, long expiry, ImmutableContextSet contexts, long sqlId) { - this.permission = Objects.requireNonNull(permission, "permission"); - this.value = value; - this.server = Objects.requireNonNull(server, "server"); - this.world = Objects.requireNonNull(world, "world"); - this.expiry = expiry; - this.contexts = Objects.requireNonNull(contexts, "contexts"); - this.sqlId = sqlId; - } - - public Node toNode() { - return NodeBuilders.determineMostApplicable(this.permission) - .value(this.value) - .withContext(DefaultContextKeys.SERVER_KEY, this.server) - .withContext(DefaultContextKeys.WORLD_KEY, this.world) - .expiry(this.expiry) - .withContext(this.contexts) - .build(); - } - - public String getPermission() { - return this.permission; - } - - public boolean getValue() { - return this.value; - } - - public String getServer() { - return this.server; - } - - public String getWorld() { - return this.world; - } - - public long getExpiry() { - return this.expiry; - } - - public ImmutableContextSet getContexts() { - return this.contexts; - } - - public long getSqlId() { - if (this.sqlId == NULL_ID) { - throw new IllegalStateException("sql id not set"); - } - return this.sqlId; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof SqlNode)) return false; - final SqlNode other = (SqlNode) o; - - return this.getPermission().equals(other.getPermission()) && - this.getValue() == other.getValue() && - this.getServer().equals(other.getServer()) && - this.getWorld().equals(other.getWorld()) && - this.getExpiry() == other.getExpiry() && - this.getContexts().equals(other.getContexts()); - } - - @Override - public int hashCode() { - final int PRIME = 59; - int result = 1; - result = result * PRIME + this.getPermission().hashCode(); - result = result * PRIME + Boolean.hashCode(this.getValue()); - result = result * PRIME + this.getServer().hashCode(); - result = result * PRIME + this.getWorld().hashCode(); - result = result * PRIME + Long.hashCode(this.getExpiry()); - result = result * PRIME + this.getContexts().hashCode(); - return result; - } - - @Override - public String toString() { - return "NodeModel(" + - "permission=" + this.getPermission() + ", " + - "value=" + this.getValue() + ", " + - "server=" + this.getServer() + ", " + - "world=" + this.getWorld() + ", " + - "expiry=" + this.getExpiry() + ", " + - "contexts=" + this.getContexts() + ")"; - } -} diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlRowId.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlRowId.java new file mode 100644 index 000000000..9e1bc01cb --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlRowId.java @@ -0,0 +1,57 @@ +/* + * 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.implementation.sql; + +import net.luckperms.api.node.metadata.NodeMetadataKey; + +public final class SqlRowId { + public static final NodeMetadataKey KEY = NodeMetadataKey.of("sqlrowid", SqlRowId.class); + + private final long rowId; + + public SqlRowId(long rowId) { + this.rowId = rowId; + } + + public long getRowId() { + return this.rowId; + } + + @Override + public boolean equals(Object o) { + return this == o || (o instanceof SqlRowId && this.rowId == ((SqlRowId) o).rowId); + } + + @Override + public int hashCode() { + return Long.hashCode(this.rowId); + } + + @Override + public String toString() { + return "SqlRowId{rowId=" + this.rowId + '}'; + } +} 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 0110cf72f..8ae7a7bec 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 @@ -25,6 +25,7 @@ package me.lucko.luckperms.common.storage.implementation.sql; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.gson.reflect.TypeToken; @@ -38,6 +39,8 @@ import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.manager.group.GroupManager; +import me.lucko.luckperms.common.model.nodemap.MutateResult; +import me.lucko.luckperms.common.node.factory.NodeBuilders; import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.storage.implementation.StorageImplementation; @@ -49,8 +52,9 @@ import net.kyori.adventure.text.Component; import net.luckperms.api.actionlog.Action; +import net.luckperms.api.context.DefaultContextKeys; +import net.luckperms.api.context.MutableContextSet; import net.luckperms.api.model.PlayerSaveResult; -import net.luckperms.api.model.data.DataType; import net.luckperms.api.node.Node; import java.io.IOException; @@ -80,6 +84,7 @@ public class SqlStorage implements StorageImplementation { private static final String USER_PERMISSIONS_SELECT = "SELECT id, permission, value, server, world, expiry, contexts FROM '{prefix}user_permissions' WHERE uuid=?"; private static final String USER_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM '{prefix}user_permissions' WHERE id=?"; + private static final String USER_PERMISSIONS_DELETE_SPECIFIC_PROPS = "DELETE FROM '{prefix}user_permissions' WHERE uuid=? AND permission=? AND value=? AND server=? AND world=? AND expiry=? AND contexts=?"; private static final String USER_PERMISSIONS_DELETE = "DELETE FROM '{prefix}user_permissions' WHERE uuid=?"; private static final String USER_PERMISSIONS_INSERT = "INSERT INTO '{prefix}user_permissions' (uuid, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)"; private static final String USER_PERMISSIONS_SELECT_DISTINCT = "SELECT DISTINCT uuid FROM '{prefix}user_permissions'"; @@ -99,6 +104,7 @@ public class SqlStorage implements StorageImplementation { private static final String GROUP_PERMISSIONS_SELECT = "SELECT id, permission, value, server, world, expiry, contexts FROM '{prefix}group_permissions' WHERE name=?"; private static final String GROUP_PERMISSIONS_SELECT_ALL = "SELECT name, id, permission, value, server, world, expiry, contexts FROM '{prefix}group_permissions'"; private static final String GROUP_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM '{prefix}group_permissions' WHERE id=?"; + private static final String GROUP_PERMISSIONS_DELETE_SPECIFIC_PROPS = "DELETE FROM '{prefix}group_permissions' WHERE name=? AND permission=? AND value=? AND server=? AND world=? AND expiry=? AND contexts=?"; private static final String GROUP_PERMISSIONS_DELETE = "DELETE FROM '{prefix}group_permissions' WHERE name=?"; private static final String GROUP_PERMISSIONS_INSERT = "INSERT INTO '{prefix}group_permissions' (name, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)"; private static final String GROUP_PERMISSIONS_SELECT_PERMISSION = "SELECT name, id, permission, value, server, world, expiry, contexts FROM '{prefix}group_permissions' WHERE "; @@ -313,82 +319,64 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws SQLException { @Override public User loadUser(UUID uniqueId, String username) throws SQLException { User user = this.plugin.getUserManager().getOrMake(uniqueId, username); - user.getIoLock().lock(); - try { - List nodes; - String primaryGroup = null; - String savedUsername = null; - try (Connection c = this.connectionFactory.getConnection()) { - nodes = selectUserPermissions(new ArrayList<>(), c, user.getUniqueId()); + List nodes; + SqlPlayerData playerData; - SqlPlayerData playerData = selectPlayerData(c, user.getUniqueId()); - if (playerData != null) { - primaryGroup = playerData.primaryGroup; - savedUsername = playerData.username; - } - } + try (Connection c = this.connectionFactory.getConnection()) { + nodes = selectUserPermissions(c, user.getUniqueId()); + playerData = selectPlayerData(c, user.getUniqueId()); + } - // update username & primary group - if (primaryGroup == null) { - primaryGroup = GroupManager.DEFAULT_GROUP_NAME; + if (playerData != null) { + if (playerData.primaryGroup != null) { + user.getPrimaryGroup().setStoredValue(playerData.primaryGroup); + } else { + user.getPrimaryGroup().setStoredValue(GroupManager.DEFAULT_GROUP_NAME); } - user.getPrimaryGroup().setStoredValue(primaryGroup); - // Update their username to what was in the storage if the one in the local instance is null - user.setUsername(savedUsername, true); - - if (!nodes.isEmpty()) { - user.setNodes(DataType.NORMAL, nodes.stream().map(SqlNode::toNode)); + user.setUsername(playerData.username, true); + } - // Save back to the store if data they were given any defaults or had permissions expire - if (this.plugin.getUserManager().giveDefaultIfNeeded(user, false) | user.auditTemporaryNodes()) { - // This should be fine, as the lock will be acquired by the same thread. - saveUser(user); - } + user.loadNodesFromStorage(nodes); + this.plugin.getUserManager().giveDefaultIfNeeded(user); - } else { - if (this.plugin.getUserManager().shouldSave(user)) { - user.clearNodes(DataType.NORMAL, null, true); - user.getPrimaryGroup().setStoredValue(null); - this.plugin.getUserManager().giveDefaultIfNeeded(user, false); - } - } - } finally { - user.getIoLock().unlock(); + if (user.auditTemporaryNodes()) { + saveUser(user); } + return user; } @Override public void saveUser(User user) throws SQLException { - user.getIoLock().lock(); - try { - if (!this.plugin.getUserManager().shouldSave(user)) { - try (Connection c = this.connectionFactory.getConnection()) { - deleteUser(c, user.getUniqueId()); - } - return; + MutateResult changes = user.normalData().exportChanges(results -> { + if (this.plugin.getUserManager().isNonDefaultUser(user)) { + return true; } - Set remote; - try (Connection c = this.connectionFactory.getConnection()) { - remote = selectUserPermissions(new HashSet<>(), c, user.getUniqueId()); + // if the only change is adding the default node, we don't need to export + if (results.getChanges().size() == 1) { + MutateResult.Change onlyChange = results.getChanges().iterator().next(); + return !(onlyChange.getType() == MutateResult.ChangeType.ADD && this.plugin.getUserManager().isDefaultNode(onlyChange.getNode())); } - Set local = user.normalData().asList().stream().map(SqlNode::fromNode).collect(Collectors.toSet()); - Set missingFromRemote = getMissingFromRemote(local, remote); - Set missingFromLocal = getMissingFromLocal(local, remote); + return true; + }); + if (changes == null) { try (Connection c = this.connectionFactory.getConnection()) { - updateUserPermissions(c, user.getUniqueId(), missingFromRemote, missingFromLocal); - insertPlayerData(c, user.getUniqueId(), new SqlPlayerData( - user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME), - user.getUsername().orElse("null").toLowerCase() - )); + deleteUser(c, user.getUniqueId()); } - } finally { - user.getIoLock().unlock(); + return; + } + + try (Connection c = this.connectionFactory.getConnection()) { + updateUserPermissions(c, user.getUniqueId(), changes.getAdded(), changes.getRemoved()); + insertPlayerData(c, user.getUniqueId(), new SqlPlayerData( + user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME), + user.getUsername().orElse("null").toLowerCase() + )); } } @@ -421,7 +409,7 @@ public List> searchUserNodes(ConstraintNodeM try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { UUID holder = UUID.fromString(rs.getString("uuid")); - Node node = readNode(rs).toNode(); + Node node = readNode(rs); N match = constraint.filterConstraintMatch(node); if (match != null) { @@ -460,45 +448,27 @@ public Optional loadGroup(String name) throws SQLException { } Group group = this.plugin.getGroupManager().getOrMake(name); - group.getIoLock().lock(); - try { - List nodes; - try (Connection c = this.connectionFactory.getConnection()) { - nodes = selectGroupPermissions(new ArrayList<>(), c, group.getName()); - } - - if (!nodes.isEmpty()) { - group.setNodes(DataType.NORMAL, nodes.stream().map(SqlNode::toNode)); - } else { - group.clearNodes(DataType.NORMAL, null, false); - } - } finally { - group.getIoLock().unlock(); + List nodes; + try (Connection c = this.connectionFactory.getConnection()) { + nodes = selectGroupPermissions(c, group.getName()); } + + group.loadNodesFromStorage(nodes); return Optional.of(group); } @Override public void loadAllGroups() throws SQLException { - Map> groups = new HashMap<>(); + Map> groups = new HashMap<>(); try (Connection c = this.connectionFactory.getConnection()) { selectGroups(c).forEach(name -> groups.put(name, new ArrayList<>())); selectAllGroupPermissions(groups, c); } - for (Map.Entry> entry : groups.entrySet()) { + for (Map.Entry> entry : groups.entrySet()) { Group group = this.plugin.getGroupManager().getOrMake(entry.getKey()); - group.getIoLock().lock(); - try { - Collection nodes = entry.getValue(); - if (!nodes.isEmpty()) { - group.setNodes(DataType.NORMAL, nodes.stream().map(SqlNode::toNode)); - } else { - group.clearNodes(DataType.NORMAL, null, false); - } - } finally { - group.getIoLock().unlock(); - } + Collection nodes = entry.getValue(); + group.loadNodesFromStorage(nodes); } this.plugin.getGroupManager().retainAll(groups.keySet()); @@ -506,48 +476,24 @@ public void loadAllGroups() throws SQLException { @Override public void saveGroup(Group group) throws SQLException { - group.getIoLock().lock(); - try { - if (group.normalData().isEmpty()) { - try (Connection c = this.connectionFactory.getConnection()) { - deleteGroupPermissions(c, group.getName()); - } - return; - } + MutateResult changes = group.normalData().exportChanges(c -> true); - Set remote; + if (!changes.isEmpty()) { try (Connection c = this.connectionFactory.getConnection()) { - remote = selectGroupPermissions(new HashSet<>(), c, group.getName()); + updateGroupPermissions(c, group.getName(), changes.getAdded(), changes.getRemoved()); } - - Set local = group.normalData().asList().stream().map(SqlNode::fromNode).collect(Collectors.toSet()); - Set missingFromRemote = getMissingFromRemote(local, remote); - Set missingFromLocal = getMissingFromLocal(local, remote); - - if (!missingFromLocal.isEmpty() || !missingFromRemote.isEmpty()) { - try (Connection c = this.connectionFactory.getConnection()) { - updateGroupPermissions(c, group.getName(), missingFromRemote, missingFromLocal); - } - } - } finally { - group.getIoLock().unlock(); } } @Override public void deleteGroup(Group group) throws SQLException { - group.getIoLock().lock(); - try { - try (Connection c = this.connectionFactory.getConnection()) { - deleteGroupPermissions(c, group.getName()); + try (Connection c = this.connectionFactory.getConnection()) { + deleteGroupPermissions(c, group.getName()); - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_DELETE))) { - ps.setString(1, group.getName()); - ps.execute(); - } + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_DELETE))) { + ps.setString(1, group.getName()); + ps.execute(); } - } finally { - group.getIoLock().unlock(); } this.plugin.getGroupManager().unload(group.getName()); @@ -564,7 +510,7 @@ public List> searchGroupNodes(ConstraintNo try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { String holder = rs.getString("name"); - Node node = readNode(rs).toNode(); + Node node = readNode(rs); N match = constraint.filterConstraintMatch(node); if (match != null) { @@ -580,22 +526,17 @@ public List> searchGroupNodes(ConstraintNo @Override public Track createAndLoadTrack(String name) throws SQLException { Track track = this.plugin.getTrackManager().getOrMake(name); - track.getIoLock().lock(); - try { - List groups; - try (Connection c = this.connectionFactory.getConnection()) { - groups = selectTrack(c, track.getName()); - } + List groups; + try (Connection c = this.connectionFactory.getConnection()) { + groups = selectTrack(c, track.getName()); + } - if (groups != null) { - track.setGroups(groups); - } else { - try (Connection c = this.connectionFactory.getConnection()) { - insertTrack(c, track.getName(), track.getGroups()); - } + if (groups != null) { + track.setGroups(groups); + } else { + try (Connection c = this.connectionFactory.getConnection()) { + insertTrack(c, track.getName(), track.getGroups()); } - } finally { - track.getIoLock().unlock(); } return track; } @@ -612,17 +553,12 @@ public Optional loadTrack(String name) throws SQLException { } Track track = this.plugin.getTrackManager().getOrMake(name); - track.getIoLock().lock(); - try { - List groups; - try (Connection c = this.connectionFactory.getConnection()) { - groups = selectTrack(c, name); - } - - track.setGroups(groups); - } finally { - track.getIoLock().unlock(); + List groups; + try (Connection c = this.connectionFactory.getConnection()) { + groups = selectTrack(c, name); } + + track.setGroups(groups); return Optional.of(track); } @@ -634,13 +570,8 @@ public void loadAllTracks() throws SQLException { for (String trackName : tracks) { Track track = this.plugin.getTrackManager().getOrMake(trackName); - track.getIoLock().lock(); - try { - List groups = selectTrack(c, trackName); - track.setGroups(groups); - } finally { - track.getIoLock().unlock(); - } + List groups = selectTrack(c, trackName); + track.setGroups(groups); } } @@ -649,28 +580,18 @@ public void loadAllTracks() throws SQLException { @Override public void saveTrack(Track track) throws SQLException { - track.getIoLock().lock(); - try { - try (Connection c = this.connectionFactory.getConnection()) { - updateTrack(c, track.getName(), track.getGroups()); - } - } finally { - track.getIoLock().unlock(); + try (Connection c = this.connectionFactory.getConnection()) { + updateTrack(c, track.getName(), track.getGroups()); } } @Override public void deleteTrack(Track track) throws SQLException { - track.getIoLock().lock(); - try { - try (Connection c = this.connectionFactory.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_DELETE))) { - ps.setString(1, track.getName()); - ps.execute(); - } + try (Connection c = this.connectionFactory.getConnection()) { + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_DELETE))) { + ps.setString(1, track.getName()); + ps.execute(); } - } finally { - track.getIoLock().unlock(); } this.plugin.getTrackManager().unload(track.getName()); @@ -797,7 +718,7 @@ private static LoggedAction readAction(ResultSet rs) throws SQLException { .build(); } - private static SqlNode readNode(ResultSet rs) throws SQLException { + private static Node readNode(ResultSet rs) throws SQLException { long id = rs.getLong("id"); String permission = rs.getString("permission"); boolean value = rs.getBoolean("value"); @@ -805,33 +726,105 @@ private static SqlNode readNode(ResultSet rs) throws SQLException { String world = rs.getString("world"); long expiry = rs.getLong("expiry"); String contexts = rs.getString("contexts"); - return SqlNode.fromSqlFields(id, permission, value, server, world, expiry, contexts); + + if (Strings.isNullOrEmpty(server)) { + server = "global"; + } + if (Strings.isNullOrEmpty(world)) { + world = "global"; + } + + return NodeBuilders.determineMostApplicable(permission) + .value(value) + .withContext(DefaultContextKeys.SERVER_KEY, server) + .withContext(DefaultContextKeys.WORLD_KEY, world) + .expiry(expiry) + .withContext(ContextSetJsonSerializer.deserialize(GsonProvider.normal(), contexts).immutableCopy()) + .withMetadata(SqlRowId.KEY, new SqlRowId(id)) + .build(); + } + + private static String getFirstContextValue(MutableContextSet set, String key) { + Set values = set.getValues(key); + String value = values.stream().sorted().findFirst().orElse(null); + if (value != null) { + set.remove(key, value); + } else { + value = "global"; + } + return value; + } + + private static void writeNode(Node node, PreparedStatement ps) throws SQLException { + MutableContextSet contexts = node.getContexts().mutableCopy(); + String server = getFirstContextValue(contexts, DefaultContextKeys.SERVER_KEY); + String world = getFirstContextValue(contexts, DefaultContextKeys.WORLD_KEY); + long expiry = node.hasExpiry() ? node.getExpiry().getEpochSecond() : 0L; + + ps.setString(2, node.getKey()); + ps.setBoolean(3, node.getValue()); + ps.setString(4, server); + ps.setString(5, world); + ps.setLong(6, expiry); + ps.setString(7, GsonProvider.normal().toJson(ContextSetJsonSerializer.serialize(contexts))); } - private static void writeNode(SqlNode nd, PreparedStatement ps) throws SQLException { - ps.setString(2, nd.getPermission()); - ps.setBoolean(3, nd.getValue()); - ps.setString(4, nd.getServer()); - ps.setString(5, nd.getWorld()); - ps.setLong(6, nd.getExpiry()); - ps.setString(7, GsonProvider.normal().toJson(ContextSetJsonSerializer.serialize(nd.getContexts()))); + private void updateUserPermissions(Connection c, UUID user, Set add, Set delete) throws SQLException { + updatePermissions(c, user.toString(), add, delete, USER_PERMISSIONS_DELETE_SPECIFIC, USER_PERMISSIONS_DELETE_SPECIFIC_PROPS, USER_PERMISSIONS_INSERT); } - private static Set getMissingFromRemote(Set local, Set remote) { - // entries in local but not remote need to be added - Set missingFromRemote = new HashSet<>(local); - missingFromRemote.removeAll(remote); - return missingFromRemote; + private void updateGroupPermissions(Connection c, String group, Set add, Set delete) throws SQLException { + updatePermissions(c, group, add, delete, GROUP_PERMISSIONS_DELETE_SPECIFIC, GROUP_PERMISSIONS_DELETE_SPECIFIC_PROPS, GROUP_PERMISSIONS_INSERT); } - private static Set getMissingFromLocal(Set local, Set remote) { - // entries in remote but not local need to be removed - Set missingFromLocal = new HashSet<>(remote); - missingFromLocal.removeAll(local); - return missingFromLocal; + private void updatePermissions(Connection c, String holder, Set add, Set delete, String deleteSpecificQuery, String deleteQuery, String insertQuery) throws SQLException { + if (!delete.isEmpty()) { + List deleteRows = new ArrayList<>(delete.size()); + List deleteNodes = new ArrayList<>(delete.size()); + for (Node node : delete) { + SqlRowId rowId = node.getMetadata(SqlRowId.KEY).orElse(null); + if (rowId != null) { + deleteRows.add(rowId.getRowId()); + } else { + deleteNodes.add(node); + } + } + + if (!deleteRows.isEmpty()) { + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteSpecificQuery))) { + for (Long id : deleteRows) { + ps.setLong(1, id); + ps.addBatch(); + } + ps.executeBatch(); + } + } + if (!deleteNodes.isEmpty()) { + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteQuery))) { + for (Node node : deleteNodes) { + ps.setString(1, holder); + writeNode(node, ps); + ps.addBatch(); + } + ps.executeBatch(); + } + } + } + + if (!add.isEmpty()) { + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(insertQuery))) { + for (Node node : add) { + ps.setString(1, holder); + writeNode(node, ps); + ps.addBatch(); + } + ps.executeBatch(); + } + } } - private > T selectUserPermissions(T nodes, Connection c, UUID user) throws SQLException { + private List selectUserPermissions(Connection c, UUID user) throws SQLException { + List nodes = new ArrayList<>(); try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT))) { ps.setString(1, user.toString()); try (ResultSet rs = ps.executeQuery()) { @@ -868,28 +861,6 @@ private void deleteUser(Connection c, UUID user) throws SQLException { } } - private void updateUserPermissions(Connection c, UUID user, Set add, Set delete) throws SQLException { - if (!delete.isEmpty()) { - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_DELETE_SPECIFIC))) { - for (SqlNode node : delete) { - ps.setLong(1, node.getSqlId()); - ps.addBatch(); - } - ps.executeBatch(); - } - } - if (!add.isEmpty()) { - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_INSERT))) { - for (SqlNode node : add) { - ps.setString(1, user.toString()); - writeNode(node, ps); - ps.addBatch(); - } - ps.executeBatch(); - } - } - } - private void insertPlayerData(Connection c, UUID user, SqlPlayerData data) throws SQLException { boolean hasPrimaryGroupSaved; try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) { @@ -929,7 +900,8 @@ private Set selectGroups(Connection c) throws SQLException { return groups; } - private > T selectGroupPermissions(T nodes, Connection c, String group) throws SQLException { + private List selectGroupPermissions(Connection c, String group) throws SQLException { + List nodes = new ArrayList<>(); try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT))) { ps.setString(1, group); try (ResultSet rs = ps.executeQuery()) { @@ -941,12 +913,12 @@ private > T selectGroupPermissions(T nodes, Connec return nodes; } - private void selectAllGroupPermissions(Map> nodes, Connection c) throws SQLException { + private void selectAllGroupPermissions(Map> nodes, Connection c) throws SQLException { try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT_ALL))) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { String holder = rs.getString("name"); - Collection list = nodes.get(holder); + Collection list = nodes.get(holder); if (list != null) { list.add(readNode(rs)); } @@ -962,28 +934,6 @@ private void deleteGroupPermissions(Connection c, String group) throws SQLExcept } } - private void updateGroupPermissions(Connection c, String group, Set add, Set delete) throws SQLException { - if (!delete.isEmpty()) { - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_DELETE_SPECIFIC))) { - for (SqlNode node : delete) { - ps.setLong(1, node.getSqlId()); - ps.addBatch(); - } - ps.executeBatch(); - } - } - if (!add.isEmpty()) { - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_INSERT))) { - for (SqlNode node : add) { - ps.setString(1, group); - writeNode(node, ps); - ps.addBatch(); - } - ps.executeBatch(); - } - } - } - private List selectTrack(Connection c, String name) throws SQLException { String groups; try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT))) { diff --git a/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java b/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java index 628f26783..1558521df 100644 --- a/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java +++ b/common/src/main/java/me/lucko/luckperms/common/tasks/ExpireTemporaryTask.java @@ -26,12 +26,9 @@ package me.lucko.luckperms.common.tasks; import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import java.util.concurrent.locks.Lock; - public class ExpireTemporaryTask implements Runnable { private final LuckPermsPlugin plugin; @@ -43,9 +40,6 @@ public ExpireTemporaryTask(LuckPermsPlugin plugin) { public void run() { boolean groupChanges = false; for (Group group : this.plugin.getGroupManager().getAll().values()) { - if (shouldSkip(group)) { - continue; - } if (group.auditTemporaryNodes()) { this.plugin.getStorage().saveGroup(group); groupChanges = true; @@ -53,9 +47,6 @@ public void run() { } for (User user : this.plugin.getUserManager().getAll().values()) { - if (shouldSkip(user)) { - continue; - } if (user.auditTemporaryNodes()) { this.plugin.getStorage().saveUser(user); } @@ -67,19 +58,4 @@ public void run() { } } - // return true if the holder's io lock is currently held, false otherwise - private static boolean shouldSkip(PermissionHolder holder) { - Lock lock = holder.getIoLock(); - - // if the holder is currently being manipulated by the storage impl, - // don't attempt to audit temporary permissions - if (!lock.tryLock()) { - // if #tryLock returns false, it means it's held by something else - return true; - } - - // immediately release the lock & return false - lock.unlock(); - return false; - } } \ No newline at end of file diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeGroupManager.java b/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeGroupManager.java index 08044f1ef..161ee9ef5 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeGroupManager.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeGroupManager.java @@ -77,11 +77,6 @@ public SpongeGroupManager(LPSpongePlugin plugin) { .build(s -> { SpongeGroup group = getIfLoaded(s); if (group != null) { - // they're already loaded, but the data might not actually be there yet - // if stuff is being loaded, then the user's i/o lock will be locked by the storage impl - group.getIoLock().lock(); - group.getIoLock().unlock(); - return group.sponge(); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeUserManager.java b/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeUserManager.java index d408329b0..fe200f929 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeUserManager.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/model/manager/SpongeUserManager.java @@ -83,11 +83,6 @@ public SpongeUserManager(LPSpongePlugin plugin) { // check if the user instance is already loaded. SpongeUser user = getIfLoaded(u); if (user != null) { - // they're already loaded, but the data might not actually be there yet - // if stuff is being loaded, then the user's i/o lock will be locked by the storage impl - user.getIoLock().lock(); - user.getIoLock().unlock(); - return user.sponge(); } From 7bc422c83f91357e3ae42f5f0bb3c1d9dd6e7a83 Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 14 Dec 2020 14:54:12 +0000 Subject: [PATCH 16/38] Add soft-dependency on ViaVersion to avoid warning msg (#2771) --- bukkit/src/main/resources/plugin.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index 7493dab1c..1307287fb 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -3,7 +3,6 @@ version: ${pluginVersion} description: A permissions plugin author: Luck website: https://luckperms.net - main: me.lucko.luckperms.bukkit.LPBukkitBootstrap load: STARTUP @@ -12,14 +11,17 @@ load: STARTUP # LP is still compatible with pre-1.13 releases. api-version: 1.13 -# This means that all plugins that (soft-)depend on Vault, depend on LuckPerms too. -# It in turn fixes issues where plugins using Vault cache the provided instance -# when their plugin enables, or when they check for the presence of a service -# provider, before LuckPerms has enabled. +# Load LuckPerms before Vault. This means that all plugins that (soft-)depend +# on Vault depend on LuckPerms too. +# +# This fixes issues caused by plugins obtaining the Vault service provider instance +# only once when they initially enable. (if they haven't depended on LP, our registration +# won't be there yet) loadbefore: [Vault] # Soft depend on LilyPad for messaging service impl -softdepend: [LilyPad-Connect] +# Soft depend on ViaVersion for adventure protocol facet +softdepend: [LilyPad-Connect, ViaVersion] commands: luckperms: From e6a5cb06afc7e53b8634545f408e310b52e62d45 Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 14 Dec 2020 15:11:49 +0000 Subject: [PATCH 17/38] Include note in verbose command-exec-as response if there were no matches (#2734) --- .../common/commands/misc/VerboseCommand.java | 8 ++++++-- .../lucko/luckperms/common/locale/Message.java | 15 +++++++++++++++ .../common/verbose/VerboseListener.java | 16 ++++------------ .../src/main/resources/luckperms_en.properties | 3 +++ 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java index df6759a0d..2c8d8f793 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/VerboseCommand.java @@ -99,9 +99,13 @@ public CommandResult execute(LuckPermsPlugin plugin, Sender sender, ArgumentList verboseHandler.registerListener(sender, VerboseFilter.acceptAll(), true); executor.performCommand(command); - verboseHandler.unregisterListener(sender); - Message.VERBOSE_OFF_COMMAND.send(sender); + VerboseListener listener = verboseHandler.unregisterListener(sender); + if (listener.getMatchedCount() == 0) { + Message.VERBOSE_OFF_COMMAND_NO_CHECKS.send(sender); + } else { + Message.VERBOSE_OFF_COMMAND.send(sender); + } }); return CommandResult.SUCCESS; diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java index c2027ee6c..e87e44913 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java @@ -586,6 +586,21 @@ static TextComponent prefixed(ComponentLike component) { .append(FULL_STOP) ); + Args0 VERBOSE_OFF_COMMAND_NO_CHECKS = () -> join(newline(), + // &bThe command execution completed, but no permission checks were made. + // &7This might be because the plugin runs commands in the background (async). You can still use verbose manually to detect checks made like this. + prefixed(translatable() + .key("luckperms.command.verbose.command.no-checks") + .color(AQUA) + .append(FULL_STOP)), + prefixed(text() + .color(GRAY) + .append(translatable("luckperms.command.verbose.command.possibly-async")) + .append(FULL_STOP) + .append(translatable("luckperms.command.verbose.command.try-again-manually")) + .append(FULL_STOP)) + ); + Args0 VERBOSE_RECORDING_ON = () -> prefixed(translatable() // "&bVerbose recording &aenabled &bfor checks matching &aANY&b." .key("luckperms.command.verbose.enabled-recording") diff --git a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java index a0fada13e..5ebd77971 100644 --- a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java +++ b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java @@ -47,7 +47,6 @@ import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.luckperms.api.query.QueryMode; -import net.luckperms.api.util.Tristate; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -323,18 +322,11 @@ public String uploadPasteData(BytebinClient bytebin) throws IOException, Unsucce return bytebin.postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE, false).key(); } - private static String getTristateColor(Tristate tristate) { - switch (tristate) { - case TRUE: - return "&2"; - case FALSE: - return "&c"; - default: - return "&7"; - } - } - public Sender getNotifiedSender() { return this.notifiedSender; } + + public int getMatchedCount() { + return this.matchedCounter.get(); + } } diff --git a/common/src/main/resources/luckperms_en.properties b/common/src/main/resources/luckperms_en.properties index d5600daec..e54be48dd 100644 --- a/common/src/main/resources/luckperms_en.properties +++ b/common/src/main/resources/luckperms_en.properties @@ -107,6 +107,9 @@ luckperms.command.verbose.enabled=Verbose logging {0} for checks matching {1} luckperms.command.verbose.command-exec=Forcing {0} to execute command {1} and reporting all checks made... luckperms.command.verbose.off=Verbose logging {0} luckperms.command.verbose.command-exec-complete=Command execution complete +luckperms.command.verbose.command.no-checks=The command execution completed, but no permission checks were made +luckperms.command.verbose.command.possibly-async=This might be because the plugin runs commands in the background (async) +luckperms.command.verbose.command.try-again-manually=You can still use verbose manually to detect checks made like this luckperms.command.verbose.enabled-recording=Verbose recording {0} for checks matching {1} luckperms.command.verbose.uploading=Verbose logging {0}, uploading results... luckperms.command.verbose.url=Verbose results URL From 16fbf566f316c84a0f1d353021ef397862fd411f Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 14 Dec 2020 15:22:41 +0000 Subject: [PATCH 18/38] Ensure child permissions are handled as lowercase when resolving (#2761) --- .../bukkit/inject/server/LuckPermsPermissionMap.java | 8 +++++--- .../nukkit/inject/server/LuckPermsPermissionMap.java | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LuckPermsPermissionMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LuckPermsPermissionMap.java index 90ddb3f73..451b946e2 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LuckPermsPermissionMap.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LuckPermsPermissionMap.java @@ -180,16 +180,18 @@ private void resolveChildren(Map accumulator, Map accumulator, Map Date: Mon, 14 Dec 2020 16:07:20 +0000 Subject: [PATCH 19/38] Mark a number of API methods as NonExtendable --- api/build.gradle | 3 ++- api/src/main/java/net/luckperms/api/node/Node.java | 2 ++ .../main/java/net/luckperms/api/node/matcher/NodeMatcher.java | 4 ++++ .../java/net/luckperms/api/node/metadata/NodeMetadataKey.java | 2 ++ api/src/main/java/net/luckperms/api/query/OptionKey.java | 2 ++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/build.gradle b/api/build.gradle index f7268878d..eabc5bf12 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -2,7 +2,8 @@ group = 'net.luckperms' project.version = '5.2' dependencies { - compileOnly 'org.checkerframework:checker-qual:2.5.5' + compileOnly 'org.checkerframework:checker-qual:3.8.0' + compileOnly 'org.jetbrains:annotations:20.1.0' } // Only used occasionally for deployment - not needed for normal builds. diff --git a/api/src/main/java/net/luckperms/api/node/Node.java b/api/src/main/java/net/luckperms/api/node/Node.java index e8cd7e7ff..b9013c101 100644 --- a/api/src/main/java/net/luckperms/api/node/Node.java +++ b/api/src/main/java/net/luckperms/api/node/Node.java @@ -39,6 +39,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.ApiStatus.NonExtendable; import java.time.Duration; import java.time.Instant; @@ -97,6 +98,7 @@ * *

The core node state must be immutable in all implementations.

*/ +@NonExtendable public interface Node { /** diff --git a/api/src/main/java/net/luckperms/api/node/matcher/NodeMatcher.java b/api/src/main/java/net/luckperms/api/node/matcher/NodeMatcher.java index f735fec8c..0b5ed8029 100644 --- a/api/src/main/java/net/luckperms/api/node/matcher/NodeMatcher.java +++ b/api/src/main/java/net/luckperms/api/node/matcher/NodeMatcher.java @@ -32,15 +32,19 @@ import net.luckperms.api.node.types.MetaNode; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus.NonExtendable; import java.util.function.Predicate; /** * A predicate which matches certain {@link Node}s. * + *

API users should not implement this interface directly.

+ * * @param the node type matched * @since 5.1 */ +@NonExtendable public interface NodeMatcher extends Predicate { /** diff --git a/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java b/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java index 9e14d320b..0717979ba 100644 --- a/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java +++ b/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java @@ -26,6 +26,7 @@ package net.luckperms.api.node.metadata; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus.NonExtendable; import java.util.Objects; @@ -40,6 +41,7 @@ * * @param the metadata type */ +@NonExtendable public interface NodeMetadataKey { /** diff --git a/api/src/main/java/net/luckperms/api/query/OptionKey.java b/api/src/main/java/net/luckperms/api/query/OptionKey.java index 0ffd1ce89..609a0db52 100644 --- a/api/src/main/java/net/luckperms/api/query/OptionKey.java +++ b/api/src/main/java/net/luckperms/api/query/OptionKey.java @@ -28,6 +28,7 @@ import net.luckperms.api.node.metadata.NodeMetadataKey; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus.NonExtendable; import java.util.Objects; @@ -41,6 +42,7 @@ * * @param the option type */ +@NonExtendable public interface OptionKey { /** From fa89236e486502c16cf85986e6c8bb480b58eb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20L=C3=B3pez?= Date: Mon, 14 Dec 2020 18:49:25 -0300 Subject: [PATCH 20/38] Fix translation key used for `parent clear` (#2773) --- .../src/main/java/me/lucko/luckperms/common/locale/Message.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java index e87e44913..e9b81b71c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java @@ -2041,7 +2041,7 @@ static TextComponent prefixed(ComponentLike component) { Args3 PARENT_CLEAR_SUCCESS = (holder, context, removeCount) -> prefixed(translatable() // "&b{}&a's parents were cleared in context {}&a. (&b{}&a nodes were removed.)" - .key("luckperms.command.generic.parent.clear-track") + .key("luckperms.command.generic.parent.clear") .color(GREEN) .args( text().color(AQUA).append(holder.getFormattedDisplayName()), From 8ef871f6dbcd255109c2f3451d858422bd4c2bf6 Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 15 Dec 2020 14:30:54 +0000 Subject: [PATCH 21/38] Fix bug with NodeMap#remove (#2772) --- .../common/model/nodemap/NodeMapMutable.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java index 829fd6633..9b500c15f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/nodemap/NodeMapMutable.java @@ -138,7 +138,7 @@ public MutateResult add(Node nodeWithoutInheritanceOrigin) { result.recordChange(ChangeType.ADD, node); // remove any others that were in the set already with a different value/expiry time - removeMatching(nodes.iterator(), node, result); + removeMatchingButNotSame(nodes.iterator(), node, result); // update the inheritanceMap too if necessary if (node instanceof InheritanceNode) { @@ -189,6 +189,16 @@ public MutateResult remove(Node node) { } private static void removeMatching(Iterator it, Node node, MutateResult result) { + while (it.hasNext()) { + Node el = it.next(); + if (node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)) { + it.remove(); + result.recordChange(ChangeType.REMOVE, el); + } + } + } + + private static void removeMatchingButNotSame(Iterator it, Node node, MutateResult result) { while (it.hasNext()) { Node el = it.next(); if (el != node && node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)) { From c28b1949a99e2be5d50f8c3d5f55bb3e95790770 Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 18 Dec 2020 19:17:01 +0000 Subject: [PATCH 22/38] Process 'give default if needed' when saving instead of after each change made in the API (#2775) --- .../common/api/implementation/ApiPermissionHolder.java | 8 ++++---- .../common/api/implementation/ApiUserManager.java | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java index d980f64e7..e35e5c086 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java @@ -222,7 +222,7 @@ private class NodeMapImpl implements NodeMap { @Override public void clear() { - if (ApiPermissionHolder.this.handle.clearNodes(this.dataType, null, true)) { + if (ApiPermissionHolder.this.handle.clearNodes(this.dataType, null, false)) { onNodeChange(); } } @@ -230,7 +230,7 @@ public void clear() { @Override public void clear(@NonNull Predicate test) { Objects.requireNonNull(test, "test"); - if (ApiPermissionHolder.this.handle.removeIf(this.dataType, null, test, true)) { + if (ApiPermissionHolder.this.handle.removeIf(this.dataType, null, test, false)) { onNodeChange(); } } @@ -239,7 +239,7 @@ public void clear(@NonNull Predicate test) { @Override public void clear(@NonNull ContextSet contextSet) { Objects.requireNonNull(contextSet, "contextSet"); - if (ApiPermissionHolder.this.handle.clearNodes(this.dataType, contextSet, true)) { + if (ApiPermissionHolder.this.handle.clearNodes(this.dataType, contextSet, false)) { onNodeChange(); } } @@ -248,7 +248,7 @@ public void clear(@NonNull ContextSet contextSet) { public void clear(@NonNull ContextSet contextSet, @NonNull Predicate test) { Objects.requireNonNull(contextSet, "contextSet"); Objects.requireNonNull(test, "test"); - if (ApiPermissionHolder.this.handle.removeIf(this.dataType, contextSet, test, true)) { + if (ApiPermissionHolder.this.handle.removeIf(this.dataType, contextSet, test, false)) { onNodeChange(); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiUserManager.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiUserManager.java index ad774bc03..e4d6b74f4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiUserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiUserManager.java @@ -90,8 +90,9 @@ protected net.luckperms.api.model.user.User proxy(User internal) { @Override public @NonNull CompletableFuture saveUser(net.luckperms.api.model.user.@NonNull User user) { - Objects.requireNonNull(user, "user"); - return this.plugin.getStorage().saveUser(ApiUser.cast(user)); + User internal = ApiUser.cast(Objects.requireNonNull(user, "user")); + this.plugin.getUserManager().giveDefaultIfNeeded(internal); + return this.plugin.getStorage().saveUser(internal); } @Override From abb608b85669e57c13a04cb99a1aa34eb1081cb9 Mon Sep 17 00:00:00 2001 From: Luck Date: Sun, 20 Dec 2020 15:45:57 +0000 Subject: [PATCH 23/38] Support Bukkit-Forge hacks which add enum constants at runtime (#2779) --- .../java/me/lucko/luckperms/common/util/EnumNamer.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/util/EnumNamer.java b/common/src/main/java/me/lucko/luckperms/common/util/EnumNamer.java index 183c056ac..586b62326 100644 --- a/common/src/main/java/me/lucko/luckperms/common/util/EnumNamer.java +++ b/common/src/main/java/me/lucko/luckperms/common/util/EnumNamer.java @@ -38,6 +38,7 @@ public class EnumNamer> { public static final Function, String> LOWER_CASE_NAME = value -> value.name().toLowerCase(); private final String[] names; + private final Function namingFunction; public EnumNamer(Class enumClass, Map definedNames, Function namingFunction) { E[] values = enumClass.getEnumConstants(); @@ -49,6 +50,7 @@ public EnumNamer(Class enumClass, Map definedNames, Functi } this.names[value.ordinal()] = name; } + this.namingFunction = namingFunction; } public EnumNamer(Class enumClass, Function namingFunction) { @@ -56,7 +58,12 @@ public EnumNamer(Class enumClass, Function namingFunction) } public String name(E value) { - return this.names[value.ordinal()]; + int ordinal = value.ordinal(); + // support the Bukkit-Forge hack where enum constants are added at runtime... + if (ordinal >= this.names.length) { + return this.namingFunction.apply(value); + } + return this.names[ordinal]; } } From 142cdc8e89f6352b4b053f2bb748594c17a2ad0d Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 22 Dec 2020 14:40:29 +0000 Subject: [PATCH 24/38] Add some unit tests This is a start at least.. I have some catching up to do! --- common/build.gradle | 7 ++ .../expression/BooleanExpressionCompiler.java | 20 +--- .../common/util/DurationParserTest.java | 91 +++++++++++++++++++ .../luckperms/common/util/EnumNamerTest.java | 28 ++++++ .../luckperms/common/util/IteratorsTest.java | 49 ++++++++++ .../luckperms/common/util/PaginatedTest.java | 45 +++++++++ .../common/verbose/BooleanExpressionTest.java | 28 ++++++ 7 files changed, 250 insertions(+), 18 deletions(-) create mode 100644 common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.java create mode 100644 common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.java create mode 100644 common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.java create mode 100644 common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.java create mode 100644 common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.java diff --git a/common/build.gradle b/common/build.gradle index 81580959d..5d6929100 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,4 +1,11 @@ +test { + useJUnitPlatform() +} + dependencies { + testCompile 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testCompile 'org.junit.jupiter:junit-jupiter-engine:5.7.0' + compile project(':api') compile 'org.checkerframework:checker-qual:2.5.5' diff --git a/common/src/main/java/me/lucko/luckperms/common/verbose/expression/BooleanExpressionCompiler.java b/common/src/main/java/me/lucko/luckperms/common/verbose/expression/BooleanExpressionCompiler.java index 8c43e66e9..1e7d242ea 100644 --- a/common/src/main/java/me/lucko/luckperms/common/verbose/expression/BooleanExpressionCompiler.java +++ b/common/src/main/java/me/lucko/luckperms/common/verbose/expression/BooleanExpressionCompiler.java @@ -227,8 +227,8 @@ private static final class Lexer extends AbstractIterator { Lexer(String expression) { this.tokenizer = new StreamTokenizer(new StringReader(expression)); this.tokenizer.resetSyntax(); - this.tokenizer.wordChars('!', '~'); // all ascii characters - this.tokenizer.whitespaceChars('\u0000', ' '); + this.tokenizer.whitespaceChars('\u0000', '\u0020'); + this.tokenizer.wordChars('\u0021', '\u007E'); "()&|!".chars().forEach(this.tokenizer::ordinaryChar); } @@ -278,20 +278,4 @@ private static final class VariableToken implements Token { } } - /* - private static void assertion(String expression, boolean expected) { - if (compile(expression).eval(var -> var.equals("true")) != expected) { - throw new AssertionError(expression + " is not " + expected); - } - } - - public static void main(String[] args) { - assertion("false & false | true", true); - assertion("false & (false | true)", false); - assertion("true | false & false", true); - assertion("(true | false) & false", false); - assertion("(true & ((true | false) & !(true & false)))", true); - } - */ - } diff --git a/common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.java b/common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.java new file mode 100644 index 000000000..38a6f1c04 --- /dev/null +++ b/common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.java @@ -0,0 +1,91 @@ +package me.lucko.luckperms.common.util; + +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DurationParserTest { + + private static void test(Duration expected, String input) { + assertEquals(expected, DurationParser.parseDuration(input)); + } + + @Test + void testSimple() { + test(ChronoUnit.YEARS.getDuration().multipliedBy(2), "2y"); + test(ChronoUnit.YEARS.getDuration().multipliedBy(3), "3year"); + test(ChronoUnit.YEARS.getDuration().multipliedBy(4), "4years"); + test(ChronoUnit.YEARS.getDuration().multipliedBy(2), "2 y"); + test(ChronoUnit.YEARS.getDuration().multipliedBy(3), "3 year"); + test(ChronoUnit.YEARS.getDuration().multipliedBy(4), "4 years"); + + test(ChronoUnit.MONTHS.getDuration().multipliedBy(2), "2mo"); + test(ChronoUnit.MONTHS.getDuration().multipliedBy(3), "3month"); + test(ChronoUnit.MONTHS.getDuration().multipliedBy(4), "4months"); + test(ChronoUnit.MONTHS.getDuration().multipliedBy(2), "2 mo"); + test(ChronoUnit.MONTHS.getDuration().multipliedBy(3), "3 month"); + test(ChronoUnit.MONTHS.getDuration().multipliedBy(4), "4 months"); + + test(Duration.ofDays(7 * 2), "2w"); + test(Duration.ofDays(7 * 3), "3week"); + test(Duration.ofDays(7 * 4), "4weeks"); + test(Duration.ofDays(7 * 2), "2 w"); + test(Duration.ofDays(7 * 3), "3 week"); + test(Duration.ofDays(7 * 4), "4 weeks"); + + test(Duration.ofDays(2), "2d"); + test(Duration.ofDays(3), "3day"); + test(Duration.ofDays(4), "4days"); + test(Duration.ofDays(2), "2 d"); + test(Duration.ofDays(3), "3 day"); + test(Duration.ofDays(4), "4 days"); + + test(Duration.ofHours(2), "2h"); + test(Duration.ofHours(3), "3hour"); + test(Duration.ofHours(4), "4hours"); + test(Duration.ofHours(2), "2 h"); + test(Duration.ofHours(3), "3 hour"); + test(Duration.ofHours(4), "4 hours"); + + test(Duration.ofMinutes(2), "2m"); + test(Duration.ofMinutes(3), "3min"); + test(Duration.ofMinutes(4), "4mins"); + test(Duration.ofMinutes(5), "5minute"); + test(Duration.ofMinutes(6), "6minutes"); + test(Duration.ofMinutes(2), "2 m"); + test(Duration.ofMinutes(3), "3 min"); + test(Duration.ofMinutes(4), "4 mins"); + test(Duration.ofMinutes(5), "5 minute"); + test(Duration.ofMinutes(6), "6 minutes"); + + test(Duration.ofSeconds(2), "2s"); + test(Duration.ofSeconds(3), "3sec"); + test(Duration.ofSeconds(4), "4secs"); + test(Duration.ofSeconds(5), "5second"); + test(Duration.ofSeconds(6), "6seconds"); + test(Duration.ofSeconds(2), "2 s"); + test(Duration.ofSeconds(3), "3 sec"); + test(Duration.ofSeconds(4), "4 secs"); + test(Duration.ofSeconds(5), "5 second"); + test(Duration.ofSeconds(6), "6 seconds"); + } + + @Test + void testCombined() { + Duration expected = ChronoUnit.YEARS.getDuration().multipliedBy(5) + .plus(ChronoUnit.MONTHS.getDuration().multipliedBy(4)) + .plus(ChronoUnit.WEEKS.getDuration().multipliedBy(3)) + .plusDays(2) + .plusHours(1) + .plusMinutes(6) + .plusSeconds(7); + + test(expected, "5y 4mo 3w 2d 1h 6m 7s"); + test(expected, "5y4mo3w2d1h6m7s"); + test(expected, "5 years 4 months 3 weeks 2 days 1 hour 6 minutes 7 seconds"); + } + +} diff --git a/common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.java b/common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.java new file mode 100644 index 000000000..7965b1082 --- /dev/null +++ b/common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.java @@ -0,0 +1,28 @@ +package me.lucko.luckperms.common.util; + +import com.google.common.collect.ImmutableMap; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EnumNamerTest { + + @Test + void testSimple() { + EnumNamer namer = new EnumNamer<>( + TestEnum.class, + ImmutableMap.of(TestEnum.THING, "hi"), + v -> v.name().toLowerCase().replace('_', '-') + ); + + assertEquals("test", namer.name(TestEnum.TEST)); + assertEquals("a-test", namer.name(TestEnum.A_TEST)); + assertEquals("hi", namer.name(TestEnum.THING)); + } + + enum TestEnum { + TEST, A_TEST, THING + } + +} diff --git a/common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.java b/common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.java new file mode 100644 index 000000000..b387219a3 --- /dev/null +++ b/common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.java @@ -0,0 +1,49 @@ +package me.lucko.luckperms.common.util; + +import com.google.common.collect.ImmutableList; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class IteratorsTest { + + @Test + void testDivideEmpty() { + assertEquals(ImmutableList.of(), Iterators.divideIterable(ImmutableList.of(), 2)); + } + + @Test + void testDivideSimple() { + List> expected = ImmutableList.of( + ImmutableList.of("one", "two"), + ImmutableList.of("three", "four"), + ImmutableList.of("five") + + ); + List> actual = Iterators.divideIterable( + ImmutableList.of("one", "two", "three", "four", "five"), + 2 + ); + + assertEquals(expected, actual); + } + + @Test + void testDivideBoundary() { + List> expected = ImmutableList.of( + ImmutableList.of("one", "two"), + ImmutableList.of("three", "four") + + ); + List> actual = Iterators.divideIterable( + ImmutableList.of("one", "two", "three", "four"), + 2 + ); + + assertEquals(expected, actual); + } + +} diff --git a/common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.java b/common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.java new file mode 100644 index 000000000..ab21e8fbd --- /dev/null +++ b/common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.java @@ -0,0 +1,45 @@ +package me.lucko.luckperms.common.util; + +import com.google.common.collect.ImmutableList; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class PaginatedTest { + + @Test + void testSimple() { + Paginated paginated = new Paginated<>(ImmutableList.of("one", "two", "three", "four", "five")); + assertEquals(3, paginated.getMaxPages(2)); + assertEquals(1, paginated.getMaxPages(5)); + assertEquals(1, paginated.getMaxPages(6)); + + List> page1 = paginated.getPage(1, 2); + assertEquals(2, page1.size()); + assertEquals("one", page1.get(0).value()); + assertEquals(1, page1.get(0).position()); + assertEquals("two", page1.get(1).value()); + assertEquals(2, page1.get(1).position()); + + List> page2 = paginated.getPage(2, 2); + assertEquals(2, page2.size()); + assertEquals("three", page2.get(0).value()); + assertEquals(3, page2.get(0).position()); + assertEquals("four", page2.get(1).value()); + assertEquals(4, page2.get(1).position()); + + List> page3 = paginated.getPage(3, 2); + assertEquals(1, page3.size()); + assertEquals("five", page3.get(0).value()); + assertEquals(5, page3.get(0).position()); + + assertThrows(IllegalArgumentException.class, () -> paginated.getPage(4, 2)); + assertThrows(IllegalArgumentException.class, () -> paginated.getPage(0, 2)); + assertThrows(IllegalArgumentException.class, () -> paginated.getPage(-1, 2)); + } + +} diff --git a/common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.java b/common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.java new file mode 100644 index 000000000..1dd5d35cb --- /dev/null +++ b/common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.java @@ -0,0 +1,28 @@ +package me.lucko.luckperms.common.verbose; + +import me.lucko.luckperms.common.verbose.expression.BooleanExpressionCompiler; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BooleanExpressionTest { + + private static void test(String expression, boolean expected) { + assertEquals( + expected, + BooleanExpressionCompiler.compile(expression).eval(var -> var.equals("true")), + expression + " is not " + expected + ); + } + + @Test + void testBrackets() { + test("false & false | true", true); + test("false & (false | true)", false); + test("true | false & false", true); + test("(true | false) & false", false); + test("(true & ((true | false) & !(true & false)))", true); + } + +} From 5c443338927104da858beca9d1306628671bdcac Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 22 Dec 2020 14:53:22 +0000 Subject: [PATCH 25/38] Move migration commands to a separate jar --- bukkit/build.gradle | 19 - .../bukkit/migration/BukkitUuids.java | 54 --- .../migration/MigrationBPermissions.java | 236 ---------- .../migration/MigrationGroupManager.java | 269 ----------- .../migration/MigrationPermissionsBukkit.java | 169 ------- .../migration/MigrationPermissionsEx.java | 418 ------------------ .../bukkit/migration/MigrationPowerRanks.java | 148 ------- .../migration/MigrationPowerfulPerms.java | 401 ----------------- .../migration/MigrationZPermissions.java | 238 ---------- bukkit/src/main/resources/luckperms.commodore | 3 - bungee/build.gradle | 11 - .../migration/MigrationBungeePerms.java | 176 -------- .../luckperms/common/backup/Exporter.java | 34 +- .../common/command/CommandManager.java | 2 - .../command/access/CommandPermission.java | 1 - .../common/command/spec/CommandSpec.java | 13 - .../migration/MigrationParentCommand.java | 143 ------ .../commands/migration/MigrationUtils.java | 66 --- .../luckperms/common/locale/Message.java | 32 -- .../luckperms/common/util/ProgressLogger.java | 87 ---- .../main/resources/luckperms_en.properties | 11 - .../common/util/DurationParserTest.java | 25 ++ .../luckperms/common/util/EnumNamerTest.java | 25 ++ .../luckperms/common/util/IteratorsTest.java | 25 ++ .../luckperms/common/util/PaginatedTest.java | 27 +- .../common/verbose/BooleanExpressionTest.java | 25 ++ 26 files changed, 157 insertions(+), 2501 deletions(-) delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/BukkitUuids.java delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationBPermissions.java delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationGroupManager.java delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsBukkit.java delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsEx.java delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java delete mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationZPermissions.java delete mode 100644 bungee/src/main/java/me/lucko/luckperms/bungee/migration/MigrationBungeePerms.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationUtils.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/util/ProgressLogger.java diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 243ce791c..8b54c2d00 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -17,25 +17,6 @@ dependencies { exclude(module: 'bukkit') } compileOnly 'lilypad.client.connect:api:0.0.1-SNAPSHOT' - - // migration plugins - compileOnly 'org.tyrannyofheaven.bukkit:zPermissions:1.3' - compileOnly('ru.tehkode:PermissionsEx:1.23.5') { - exclude(module: 'bukkit') - exclude(module: 'updater') - exclude(module: 'commons-dbcp') - exclude(module: 'AccountsClient') - } - compileOnly 'com.github.gustav9797:PowerfulPermsAPI:4.5.2' - compileOnly 'org.anjocaido:GroupManager:1.4' - compileOnly('nl.svenar:powerranks:1.9.2') { - exclude(module: 'nametagedit') - } - compileOnly 'de.bananaco:bpermissions-api:2.12' - compileOnly('com.platymuus:bukkit-permissions:2.5') { - exclude(module: 'bukkit') - exclude(module: 'metrics') - } } processResources { diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/BukkitUuids.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/BukkitUuids.java deleted file mode 100644 index 82d7086c1..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/BukkitUuids.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.bukkit.migration; - -import me.lucko.luckperms.common.util.ProgressLogger; -import me.lucko.luckperms.common.util.Uuids; - -import org.bukkit.Bukkit; - -import java.util.UUID; - -public final class BukkitUuids { - private BukkitUuids() {} - - public static UUID lookupUuid(ProgressLogger log, String s) { - UUID uuid = Uuids.parse(s); - if (uuid == null) { - try { - //noinspection deprecation - uuid = Bukkit.getOfflinePlayer(s).getUniqueId(); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - if (uuid == null) { - log.logError("Unable to get a UUID for user identifier: " + s); - } - return uuid; - } - -} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationBPermissions.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationBPermissions.java deleted file mode 100644 index 1188e3179..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationBPermissions.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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.bukkit.migration; - -import de.bananaco.bpermissions.api.Calculable; -import de.bananaco.bpermissions.api.CalculableType; -import de.bananaco.bpermissions.api.Permission; -import de.bananaco.bpermissions.api.World; -import de.bananaco.bpermissions.api.WorldManager; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.model.manager.group.GroupManager; -import me.lucko.luckperms.common.node.factory.NodeBuilders; -import me.lucko.luckperms.common.node.types.Inheritance; -import me.lucko.luckperms.common.node.types.Meta; -import me.lucko.luckperms.common.node.types.Prefix; -import me.lucko.luckperms.common.node.types.Suffix; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Iterators; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; - -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; - -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -public class MigrationBPermissions extends ChildCommand { - private static final Field UCONFIG_FIELD; - static { - try { - UCONFIG_FIELD = Class.forName("de.bananaco.bpermissions.imp.YamlWorld").getDeclaredField("uconfig"); - UCONFIG_FIELD.setAccessible(true); - } catch (ClassNotFoundException | NoSuchFieldException e) { - throw new ExceptionInInitializerError(e); - } - } - - public MigrationBPermissions() { - super(CommandSpec.MIGRATION_COMMAND, "bpermissions", CommandPermission.MIGRATION, Predicates.alwaysFalse()); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "bPermissions"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - WorldManager worldManager = WorldManager.getInstance(); - if (worldManager == null) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - log.log("Forcing the plugin to load all data. This could take a while."); - for (World world : worldManager.getAllWorlds()) { - log.log("Loading users in world " + world.getName()); - - YamlConfiguration yamlWorldUsers = null; - try { - yamlWorldUsers = (YamlConfiguration) UCONFIG_FIELD.get(world); - } catch (Throwable t) { - t.printStackTrace(); - } - - if (yamlWorldUsers == null) { - continue; - } - - ConfigurationSection configSection = yamlWorldUsers.getConfigurationSection("users"); - if (configSection == null) { - continue; - } - - Set users = configSection.getKeys(false); - if (users == null) { - log.logError("Couldn't get a list of users."); - return CommandResult.FAILURE; - } - AtomicInteger userLoadCount = new AtomicInteger(0); - for (String user : users) { - world.loadOne(user, CalculableType.USER); - log.logProgress("Forcefully loaded {} users so far.", userLoadCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - } - } - log.log("Forcefully loaded all users."); - - // Migrate one world at a time. - log.log("Starting world migration."); - Iterators.tryIterate(worldManager.getAllWorlds(), world -> { - log.log("Migrating world: " + world.getName()); - - // Migrate all groups - log.log("Starting group migration in world " + world.getName() + "."); - AtomicInteger groupCount = new AtomicInteger(0); - - Iterators.tryIterate(world.getAll(CalculableType.GROUP), group -> { - String groupName = MigrationUtils.standardizeName(group.getName()); - if (group.getName().equalsIgnoreCase(world.getDefaultGroup())) { - groupName = GroupManager.DEFAULT_GROUP_NAME; - } - - // Make a LuckPerms group for the one being migrated. - Group lpGroup = plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join(); - - MigrationUtils.setGroupWeight(lpGroup, group.getPriority()); - migrateHolder(world, group, lpGroup); - - plugin.getStorage().saveGroup(lpGroup); - - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - }); - log.log("Migrated " + groupCount.get() + " groups in world " + world.getName() + "."); - - - // Migrate all users - log.log("Starting user migration in world " + world.getName() + "."); - AtomicInteger userCount = new AtomicInteger(0); - Iterators.tryIterate(world.getAll(CalculableType.USER), user -> { - // There is no mention of UUIDs in the API. I assume that name = uuid. idk? - UUID uuid = BukkitUuids.lookupUuid(log, user.getName()); - if (uuid == null) { - return; - } - - // Make a LuckPerms user for the one being migrated. - User lpUser = plugin.getStorage().loadUser(uuid, null).join(); - - migrateHolder(world, user, lpUser); - - plugin.getStorage().saveUser(lpUser); - plugin.getUserManager().getHouseKeeper().cleanup(lpUser.getUniqueId()); - - log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - - log.log("Migrated " + userCount.get() + " users in world " + world.getName() + "."); - }); - - log.log("Success! Migration complete."); - log.log("Don't forget to remove the bPermissions jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } - - private static void migrateHolder(World world, Calculable c, PermissionHolder holder) { - // Migrate the groups permissions in this world - for (Permission p : c.getPermissions()) { - if (p.name().isEmpty()) { - continue; - } - holder.setNode(DataType.NORMAL, NodeBuilders.determineMostApplicable(p.name()).value(p.isTrue()).withContext(DefaultContextKeys.SERVER_KEY, "global").withContext(DefaultContextKeys.WORLD_KEY, world.getName()).build(), true); - - // Include any child permissions - for (Map.Entry child : p.getChildren().entrySet()) { - if (child.getKey().isEmpty()) { - continue; - } - - holder.setNode(DataType.NORMAL, NodeBuilders.determineMostApplicable(child.getKey()).value((boolean) child.getValue()).withContext(DefaultContextKeys.SERVER_KEY, "global").withContext(DefaultContextKeys.WORLD_KEY, world.getName()).build(), true); - } - } - - // Migrate any inherited groups - c.getGroups().forEach(parent -> { - String parentName = MigrationUtils.standardizeName(parent.getName()); - if (parent.getName().equalsIgnoreCase(world.getDefaultGroup())) { - parentName = GroupManager.DEFAULT_GROUP_NAME; - } - - holder.setNode(DataType.NORMAL, Inheritance.builder(parentName).value(true).withContext(DefaultContextKeys.SERVER_KEY, "global").withContext(DefaultContextKeys.WORLD_KEY, world.getName()).build(), true); - }); - - // Migrate existing meta - for (Map.Entry meta : c.getMeta().entrySet()) { - if (meta.getKey().isEmpty() || meta.getValue().isEmpty()) { - continue; - } - - if (meta.getKey().equalsIgnoreCase("prefix")) { - holder.setNode(DataType.NORMAL, Prefix.builder(meta.getValue(), c.getPriority()).withContext(DefaultContextKeys.WORLD_KEY, world.getName()).build(), true); - continue; - } - - if (meta.getKey().equalsIgnoreCase("suffix")) { - holder.setNode(DataType.NORMAL, Suffix.builder(meta.getValue(), c.getPriority()).withContext(DefaultContextKeys.WORLD_KEY, world.getName()).build(), true); - continue; - } - - holder.setNode(DataType.NORMAL, Meta.builder(meta.getKey(), meta.getValue()).withContext(DefaultContextKeys.WORLD_KEY, world.getName()).build(), true); - } - } -} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationGroupManager.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationGroupManager.java deleted file mode 100644 index 8ceddfcc8..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationGroupManager.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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.bukkit.migration; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.model.UserIdentifier; -import me.lucko.luckperms.common.node.types.Inheritance; -import me.lucko.luckperms.common.node.types.Meta; -import me.lucko.luckperms.common.node.types.Prefix; -import me.lucko.luckperms.common.node.types.Suffix; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Iterators; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; -import me.lucko.luckperms.common.util.Uuids; - -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; -import net.luckperms.api.node.Node; - -import org.anjocaido.groupmanager.GlobalGroups; -import org.anjocaido.groupmanager.GroupManager; -import org.anjocaido.groupmanager.dataholder.WorldDataHolder; -import org.anjocaido.groupmanager.dataholder.worlds.WorldsHolder; -import org.bukkit.Bukkit; -import org.bukkit.World; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class MigrationGroupManager extends ChildCommand { - public MigrationGroupManager() { - super(CommandSpec.MIGRATION_GROUPMANAGER, "groupmanager", CommandPermission.MIGRATION, Predicates.is(0)); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "GroupManager"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - if (!args.get(0).equalsIgnoreCase("true") && !args.get(0).equalsIgnoreCase("false")) { - log.logError("Was expecting true/false, but got " + args.get(0) + " instead."); - return CommandResult.STATE_ERROR; - } - final boolean migrateAsGlobal = Boolean.parseBoolean(args.get(0)); - final Function worldMappingFunc = s -> migrateAsGlobal || s == null ? "global" : s; - - if (!Bukkit.getPluginManager().isPluginEnabled("GroupManager")) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - List worlds = Bukkit.getWorlds().stream().map(World::getName).map(String::toLowerCase).collect(Collectors.toList()); - GroupManager gm = (GroupManager) Bukkit.getPluginManager().getPlugin("GroupManager"); - - // Migrate Global Groups - log.log("Starting global group migration."); - GlobalGroups gg = GroupManager.getGlobalGroups(); - - AtomicInteger globalGroupCount = new AtomicInteger(0); - Iterators.tryIterate(gg.getGroupList(), g -> { - String groupName = MigrationUtils.standardizeName(g.getName()); - Group group = plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join(); - - for (String node : g.getPermissionList()) { - if (node.isEmpty()) continue; - group.setNode(DataType.NORMAL, MigrationUtils.parseNode(node, true).build(), true); - } - for (String s : g.getInherits()) { - if (s.isEmpty()) continue; - group.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(s)).build(), true); - } - - plugin.getStorage().saveGroup(group); - log.logAllProgress("Migrated {} groups so far.", globalGroupCount.incrementAndGet()); - }); - log.log("Migrated " + globalGroupCount.get() + " global groups"); - - // Collect data - Map> users = new HashMap<>(); - Map primaryGroups = new HashMap<>(); - Map> groups = new HashMap<>(); - - WorldsHolder wh = gm.getWorldsHolder(); - - // Collect data for all users and groups. - log.log("Collecting user and group data."); - Iterators.tryIterate(worlds, String::toLowerCase, world -> { - log.log("Querying world " + world); - - WorldDataHolder wdh = wh.getWorldData(world); - - AtomicInteger groupWorldCount = new AtomicInteger(0); - Iterators.tryIterate(wdh.getGroupList(), group -> { - String groupName = MigrationUtils.standardizeName(group.getName()); - - groups.putIfAbsent(groupName, new HashSet<>()); - - for (String node : group.getPermissionList()) { - if (node.isEmpty()) continue; - groups.get(groupName).add(MigrationUtils.parseNode(node, true).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } - for (String s : group.getInherits()) { - if (s.isEmpty()) continue; - groups.get(groupName).add(Inheritance.builder(MigrationUtils.standardizeName(s)).value(true).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } - - String[] metaKeys = group.getVariables().getVarKeyList(); - for (String key : metaKeys) { - String value = group.getVariables().getVarString(key); - key = key.toLowerCase(); - if (key.isEmpty() || value.isEmpty()) continue; - if (key.equals("build")) continue; - - if (key.equals("prefix")) { - groups.get(groupName).add(Prefix.builder(value, 50).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } else if (key.equals("suffix")) { - groups.get(groupName).add(Suffix.builder(value, 50).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } else { - groups.get(groupName).add(Meta.builder(key, value).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } - } - - log.logAllProgress("Migrated {} groups so far in world " + world, groupWorldCount.incrementAndGet()); - }); - log.log("Migrated " + groupWorldCount.get() + " groups in world " + world); - - AtomicInteger userWorldCount = new AtomicInteger(0); - Iterators.tryIterate(wdh.getUserList(), user -> { - UUID uuid = BukkitUuids.lookupUuid(log, user.getUUID()); - if (uuid == null) { - return; - } - - String lastName = user.getLastName(); - if (lastName != null && Uuids.parse(lastName) != null) { - lastName = null; - } - - UserIdentifier id = UserIdentifier.of(uuid, lastName); - - users.putIfAbsent(id, new HashSet<>()); - - for (String node : user.getPermissionList()) { - if (node.isEmpty()) continue; - users.get(id).add(MigrationUtils.parseNode(node, true).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } - - // Collect sub groups - String finalWorld = worldMappingFunc.apply(world); - users.get(id).addAll(user.subGroupListStringCopy().stream() - .filter(n -> !n.isEmpty()) - .map(MigrationUtils::standardizeName) - .map(n -> Inheritance.builder(n).value(true).withContext(DefaultContextKeys.WORLD_KEY, finalWorld).build()) - .collect(Collectors.toSet()) - ); - - // Get primary group - primaryGroups.put(uuid, MigrationUtils.standardizeName(user.getGroupName())); - - String[] metaKeys = user.getVariables().getVarKeyList(); - for (String key : metaKeys) { - String value = user.getVariables().getVarString(key); - key = key.toLowerCase(); - if (key.isEmpty() || value.isEmpty()) continue; - if (key.equals("build")) continue; - - if (key.equals("prefix")) { - users.get(id).add(Prefix.builder(value, 100).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } else if (key.equals("suffix")) { - users.get(id).add(Suffix.builder(value, 100).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } else { - users.get(id).add(Meta.builder(key, value).withContext(DefaultContextKeys.WORLD_KEY, worldMappingFunc.apply(world)).build()); - } - } - - log.logProgress("Migrated {} users so far in world " + world, userWorldCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - log.log("Migrated " + userWorldCount.get() + " users in world " + world); - }); - - log.log("All data has now been processed, now starting the import process."); - log.log("Found a total of " + users.size() + " users and " + groups.size() + " groups."); - - log.log("Starting group migration."); - AtomicInteger groupCount = new AtomicInteger(0); - Iterators.tryIterate(groups.entrySet(), e -> { - Group group = plugin.getStorage().createAndLoadGroup(e.getKey(), CreationCause.INTERNAL).join(); - - for (Node node : e.getValue()) { - group.setNode(DataType.NORMAL, node, true); - } - - plugin.getStorage().saveGroup(group); - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - }); - log.log("Migrated " + groupCount.get() + " groups"); - - log.log("Starting user migration."); - AtomicInteger userCount = new AtomicInteger(0); - Iterators.tryIterate(users.entrySet(), e -> { - User user = plugin.getStorage().loadUser(e.getKey().getUniqueId(), e.getKey().getUsername().orElse(null)).join(); - - for (Node node : e.getValue()) { - user.setNode(DataType.NORMAL, node, true); - } - - String primaryGroup = primaryGroups.get(e.getKey().getUniqueId()); - if (primaryGroup != null && !primaryGroup.isEmpty()) { - user.setNode(DataType.NORMAL, Inheritance.builder(primaryGroup).build(), true); - user.getPrimaryGroup().setStoredValue(primaryGroup); - user.unsetNode(DataType.NORMAL, Inheritance.builder(me.lucko.luckperms.common.model.manager.group.GroupManager.DEFAULT_GROUP_NAME).build()); - } - - plugin.getStorage().saveUser(user); - plugin.getUserManager().getHouseKeeper().cleanup(user.getUniqueId()); - log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - - log.log("Migrated " + userCount.get() + " users."); - log.log("Success! Migration complete."); - log.log("Don't forget to remove the GroupManager jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } -} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsBukkit.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsBukkit.java deleted file mode 100644 index d2d0407a5..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsBukkit.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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.bukkit.migration; - -import com.platymuus.bukkit.permissions.PermissionsPlugin; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.node.types.Inheritance; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Iterators; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; - -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; - -import java.util.List; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -public class MigrationPermissionsBukkit extends ChildCommand { - public MigrationPermissionsBukkit() { - super(CommandSpec.MIGRATION_COMMAND, "permissionsbukkit", CommandPermission.MIGRATION, Predicates.alwaysFalse()); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PermissionsBukkit"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - if (!Bukkit.getPluginManager().isPluginEnabled("PermissionsBukkit")) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - PermissionsPlugin permissionsBukkit = (PermissionsPlugin) Bukkit.getPluginManager().getPlugin("PermissionsBukkit"); - FileConfiguration config = permissionsBukkit.getConfig(); - - // Migrate all groups - log.log("Starting group migration."); - AtomicInteger groupCount = new AtomicInteger(0); - - ConfigurationSection groupsSection = config.getConfigurationSection("groups"); - - Iterators.tryIterate(groupsSection.getKeys(false), key -> { - final String groupName = MigrationUtils.standardizeName(key); - Group lpGroup = plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join(); - - // migrate data - if (groupsSection.isConfigurationSection(key)) { - migrate(lpGroup, groupsSection.getConfigurationSection(key)); - } - - plugin.getStorage().saveGroup(lpGroup).join(); - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - }); - log.log("Migrated " + groupCount.get() + " groups"); - - // Migrate all users - log.log("Starting user migration."); - AtomicInteger userCount = new AtomicInteger(0); - - ConfigurationSection usersSection = config.getConfigurationSection("users"); - - Iterators.tryIterate(usersSection.getKeys(false), key -> { - UUID uuid = BukkitUuids.lookupUuid(log, key); - if (uuid == null) { - return; - } - - User lpUser = plugin.getStorage().loadUser(uuid, null).join(); - - // migrate data - if (usersSection.isConfigurationSection(key)) { - migrate(lpUser, usersSection.getConfigurationSection(key)); - } - - plugin.getUserManager().getHouseKeeper().cleanup(lpUser.getUniqueId()); - plugin.getStorage().saveUser(lpUser); - log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - - log.log("Migrated " + userCount.get() + " users."); - log.log("Success! Migration complete."); - log.log("Don't forget to remove the PermissionsBukkit jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } - - private static void migrate(PermissionHolder holder, ConfigurationSection data) { - // migrate permissions - if (data.isConfigurationSection("permissions")) { - ConfigurationSection permsSection = data.getConfigurationSection("permissions"); - for (String perm : permsSection.getKeys(false)) { - boolean value = permsSection.getBoolean(perm); - holder.setNode(DataType.NORMAL, MigrationUtils.parseNode(perm, value).build(), true); - } - } - - if (data.isConfigurationSection("worlds")) { - ConfigurationSection worldSection = data.getConfigurationSection("worlds"); - for (String world : worldSection.getKeys(false)) { - if (worldSection.isConfigurationSection(world)) { - ConfigurationSection permsSection = worldSection.getConfigurationSection(world); - for (String perm : permsSection.getKeys(false)) { - boolean value = permsSection.getBoolean(perm); - holder.setNode(DataType.NORMAL, MigrationUtils.parseNode(perm, value).withContext(DefaultContextKeys.WORLD_KEY, world).build(), true); - } - } - } - } - - // migrate parents - if (data.isList("groups")) { - List groups = data.getStringList("groups"); - for (String group : groups) { - holder.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(group)).build(), true); - } - } - if (data.isList("inheritance")) { - List groups = data.getStringList("inheritance"); - for (String group : groups) { - holder.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(group)).build(), true); - } - } - } - -} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsEx.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsEx.java deleted file mode 100644 index 8bb92afa7..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPermissionsEx.java +++ /dev/null @@ -1,418 +0,0 @@ -/* - * 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.bukkit.migration; - -import com.google.common.base.Strings; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.Track; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.model.manager.group.GroupManager; -import me.lucko.luckperms.common.node.types.Inheritance; -import me.lucko.luckperms.common.node.types.Meta; -import me.lucko.luckperms.common.node.types.Prefix; -import me.lucko.luckperms.common.node.types.Suffix; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Iterators; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; - -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; -import net.luckperms.api.node.Node; -import net.luckperms.api.node.types.InheritanceNode; - -import org.bukkit.Bukkit; - -import ru.tehkode.permissions.NativeInterface; -import ru.tehkode.permissions.PermissionEntity; -import ru.tehkode.permissions.PermissionGroup; -import ru.tehkode.permissions.PermissionManager; -import ru.tehkode.permissions.PermissionUser; -import ru.tehkode.permissions.PermissionsData; -import ru.tehkode.permissions.bukkit.PermissionsEx; -import ru.tehkode.permissions.events.PermissionEvent; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -public class MigrationPermissionsEx extends ChildCommand { - private static final Method GET_DATA_METHOD; - private static final Field TIMED_PERMISSIONS_FIELD; - private static final Field TIMED_PERMISSIONS_TIME_FIELD; - private static final Field NATIVE_INTERFACE_FIELD; - static { - try { - GET_DATA_METHOD = PermissionEntity.class.getDeclaredMethod("getData"); - GET_DATA_METHOD.setAccessible(true); - - TIMED_PERMISSIONS_FIELD = PermissionEntity.class.getDeclaredField("timedPermissions"); - TIMED_PERMISSIONS_FIELD.setAccessible(true); - - TIMED_PERMISSIONS_TIME_FIELD = PermissionEntity.class.getDeclaredField("timedPermissionsTime"); - TIMED_PERMISSIONS_TIME_FIELD.setAccessible(true); - - NATIVE_INTERFACE_FIELD = PermissionManager.class.getDeclaredField("nativeI"); - NATIVE_INTERFACE_FIELD.setAccessible(true); - } catch (NoSuchMethodException | NoSuchFieldException e) { - throw new ExceptionInInitializerError(e); - } - } - - public MigrationPermissionsEx() { - super(CommandSpec.MIGRATION_COMMAND, "permissionsex", CommandPermission.MIGRATION, Predicates.alwaysFalse()); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PermissionsEx"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - if (!Bukkit.getPluginManager().isPluginEnabled("PermissionsEx")) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - PermissionsEx pex = (PermissionsEx) Bukkit.getPluginManager().getPlugin("PermissionsEx"); - PermissionManager manager = pex.getPermissionsManager(); - - // hack to work around accessing pex async - try { - disablePexEvents(manager); - } catch (ReflectiveOperationException e) { - e.printStackTrace(); - } - - log.log("Calculating group weightings."); - int i = 0; - for (PermissionGroup group : manager.getGroupList()) { - i = Math.max(i, group.getRank()); - } - int maxWeight = i + 5; - - // Migrate all groups. - log.log("Starting group migration."); - AtomicInteger groupCount = new AtomicInteger(0); - Set ladders = new HashSet<>(); - Iterators.tryIterate(manager.getGroupList(), group -> { - int groupWeight = maxWeight - group.getRank(); - - final String groupName = MigrationUtils.standardizeName(group.getName()); - Group lpGroup = plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join(); - - MigrationUtils.setGroupWeight(lpGroup, groupWeight); - - // migrate data - migrateEntity(group, lpGroup, groupWeight); - - // remember known ladders - if (group.isRanked()) { - ladders.add(group.getRankLadder().toLowerCase()); - } - - plugin.getStorage().saveGroup(lpGroup).join(); - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - }); - log.log("Migrated " + groupCount.get() + " groups"); - - // Migrate all ladders/tracks. - log.log("Starting tracks migration."); - for (String rankLadder : ladders) { - Track track = plugin.getStorage().createAndLoadTrack(rankLadder, CreationCause.INTERNAL).join(); - - // Get a list of all groups in a ladder - List ladder = manager.getRankLadder(rankLadder).entrySet().stream() - .sorted(Comparator.>comparingInt(Map.Entry::getKey).reversed()) - .map(e -> MigrationUtils.standardizeName(e.getValue().getName())) - .collect(Collectors.toList()); - - track.setGroups(ladder); - plugin.getStorage().saveTrack(track); - } - log.log("Migrated " + ladders.size() + " tracks"); - - // Migrate all users - log.log("Starting user migration."); - AtomicInteger userCount = new AtomicInteger(0); - - // Increment the max weight from the group migrations. All user meta should override. - int userWeight = maxWeight + 5; - - Collection userIdentifiers = manager.getBackend().getUserIdentifiers(); - Iterators.tryIterate(userIdentifiers, id -> { - PermissionUser user = new PermissionUser(id, manager.getBackend().getUserData(id), manager); - if (isUserEmpty(user)) { - return; - } - - UUID u = BukkitUuids.lookupUuid(log, id); - if (u == null) { - return; - } - - // load in a user instance - User lpUser = plugin.getStorage().loadUser(u, user.getName()).join(); - - // migrate data - migrateEntity(user, lpUser, userWeight); - - plugin.getUserManager().getHouseKeeper().cleanup(lpUser.getUniqueId()); - plugin.getStorage().saveUser(lpUser); - log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - - // re-enable events - try { - enablePexEvents(manager); - } catch (ReflectiveOperationException e) { - e.printStackTrace(); - } - - log.log("Migrated " + userCount.get() + " users."); - log.log("Success! Migration complete."); - log.log("Don't forget to remove the PermissionsEx jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } - - private static Map> getPermanentPermissions(PermissionEntity entity) { - try { - PermissionsData data = (PermissionsData) GET_DATA_METHOD.invoke(entity); - return data.getPermissionsMap(); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - private static boolean isUserEmpty(PermissionUser user) { - for (List permissions : user.getAllPermissions().values()) { - if (!permissions.isEmpty()) { - return false; - } - } - - for (List parents : user.getAllParents().values()) { - if (!parents.isEmpty()) { - return false; - } - } - - for (Map options : user.getAllOptions().values()) { - if (!options.isEmpty()) { - return false; - } - } - - return true; - } - - private static void migrateEntity(PermissionEntity entity, PermissionHolder holder, int weight) { - // migrate permanent permissions - for (Map.Entry> worldData : getPermanentPermissions(entity).entrySet()) { - String world = standardizeWorld(worldData.getKey()); - for (String node : worldData.getValue()) { - if (node.isEmpty()) continue; - holder.setNode(DataType.NORMAL, MigrationUtils.parseNode(node, true).withContext(DefaultContextKeys.WORLD_KEY, world).build(), true); - } - } - - // migrate temporary permissions - Map> timedPermissions; - Map timedPermissionsTime; - - try { - //noinspection unchecked - timedPermissions = (Map>) TIMED_PERMISSIONS_FIELD.get(entity); - //noinspection unchecked - timedPermissionsTime = (Map) TIMED_PERMISSIONS_TIME_FIELD.get(entity); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - - for (Map.Entry> worldData : timedPermissions.entrySet()) { - String world = standardizeWorld(worldData.getKey()); - for (String node : worldData.getValue()) { - if (node.isEmpty()) continue; - long expiry = timedPermissionsTime.getOrDefault(Strings.nullToEmpty(world) + ":" + node, 0L); - Node n = MigrationUtils.parseNode(node, true).withContext(DefaultContextKeys.WORLD_KEY, world).expiry(expiry).build(); - if (!n.hasExpired()) { - holder.setNode(DataType.NORMAL, n, true); - } - } - } - - // migrate parents - for (Map.Entry> worldData : entity.getAllParents().entrySet()) { - String world = standardizeWorld(worldData.getKey()); - - // keep track of primary group - String primary = null; - int primaryWeight = Integer.MAX_VALUE; - - for (PermissionGroup parent : worldData.getValue()) { - String parentName = parent.getName(); - long expiry = 0L; - - // check for temporary parent - if (entity instanceof PermissionUser) { - String expiryOption = entity.getOption("group-" + parentName + "-until", world); - if (expiryOption != null) { - try { - expiry = Long.parseLong(expiryOption); - } catch (NumberFormatException e) { - // ignore - } - } - } - - InheritanceNode n = Inheritance.builder(MigrationUtils.standardizeName(parentName)).withContext(DefaultContextKeys.WORLD_KEY, world).expiry(expiry).build(); - if (n.hasExpired()) { - continue; - } - - holder.setNode(DataType.NORMAL, n, true); - - // migrate primary groups - if (world.equals("global") && holder instanceof User && expiry == 0) { - if (parent.getRank() < primaryWeight) { - primary = parent.getName(); - primaryWeight = parent.getRank(); - } - } - } - - if (primary != null && !primary.isEmpty() && !primary.equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) { - User user = ((User) holder); - user.getPrimaryGroup().setStoredValue(primary); - holder.unsetNode(DataType.NORMAL, Inheritance.builder(GroupManager.DEFAULT_GROUP_NAME).build()); - } - } - - // migrate prefix / suffix - String prefix = entity.getOwnPrefix(); - String suffix = entity.getOwnSuffix(); - - if (prefix != null && !prefix.isEmpty()) { - holder.setNode(DataType.NORMAL, Prefix.builder(prefix, weight).build(), true); - } - - if (suffix != null && !suffix.isEmpty()) { - holder.setNode(DataType.NORMAL, Suffix.builder(suffix, weight).build(), true); - } - - // migrate options - for (Map.Entry> worldData : entity.getAllOptions().entrySet()) { - String world = standardizeWorld(worldData.getKey()); - for (Map.Entry opt : worldData.getValue().entrySet()) { - if (opt.getKey() == null || opt.getKey().isEmpty() || opt.getValue() == null || opt.getValue().isEmpty()) { - continue; - } - - String key = opt.getKey().toLowerCase(); - boolean ignore = key.equals("prefix") || - key.equals("suffix") || - key.equals("weight") || - key.equals("rank") || - key.equals("rank-ladder") || - key.equals("name") || - key.equals("username") || - (key.startsWith("group-") && key.endsWith("-until")); - - if (ignore) { - continue; - } - - holder.setNode(DataType.NORMAL, Meta.builder(opt.getKey(), opt.getValue()).withContext(DefaultContextKeys.WORLD_KEY, world).build(), true); - } - } - } - - private static String standardizeWorld(String world) { - if (world == null || world.isEmpty() || world.equals("*")) { - world = "global"; - } - return world.toLowerCase(); - } - - /* - * Hack to workaround issue with accessing PEX async. - * See: https://github.com/lucko/LuckPerms/issues/2102 - */ - - private static void disablePexEvents(PermissionManager manager) throws ReflectiveOperationException { - NativeInterface nativeInterface = (NativeInterface) NATIVE_INTERFACE_FIELD.get(manager); - NATIVE_INTERFACE_FIELD.set(manager, new DisabledEventsNativeInterface(nativeInterface)); - } - - private static void enablePexEvents(PermissionManager manager) throws ReflectiveOperationException { - NativeInterface nativeInterface = (NativeInterface) NATIVE_INTERFACE_FIELD.get(manager); - while (nativeInterface instanceof DisabledEventsNativeInterface) { - nativeInterface = ((DisabledEventsNativeInterface) nativeInterface).delegate; - NATIVE_INTERFACE_FIELD.set(manager, nativeInterface); - } - } - - private static final class DisabledEventsNativeInterface implements NativeInterface { - private final NativeInterface delegate; - - private DisabledEventsNativeInterface(NativeInterface delegate) { - this.delegate = delegate; - } - - @Override - public void callEvent(PermissionEvent permissionEvent) { - // do nothing! - } - - @Override public String UUIDToName(UUID uuid) { return this.delegate.UUIDToName(uuid); } - @Override public UUID nameToUUID(String s) { return this.delegate.nameToUUID(s); } - @Override public boolean isOnline(UUID uuid) { return this.delegate.isOnline(uuid); } - @Override public UUID getServerUUID() { return this.delegate.getServerUUID(); } - } -} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java deleted file mode 100644 index 13a22b45d..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerRanks.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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.bukkit.migration; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.locale.Message; -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.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; - -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; - -import nl.svenar.PowerRanks.Cache.CachedPlayers; -import nl.svenar.PowerRanks.Cache.PowerConfigurationSection; -import nl.svenar.PowerRanks.Data.Users; -import nl.svenar.PowerRanks.PowerRanks; -import nl.svenar.PowerRanks.api.PowerRanksAPI; - -import org.bukkit.Bukkit; - -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -public class MigrationPowerRanks extends ChildCommand { - public MigrationPowerRanks() { - super(CommandSpec.MIGRATION_COMMAND, "powerranks", CommandPermission.MIGRATION, Predicates.alwaysFalse()); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PowerRanks"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - if (!Bukkit.getPluginManager().isPluginEnabled("PowerRanks")) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - PowerRanks pr = (PowerRanks) Bukkit.getServer().getPluginManager().getPlugin("PowerRanks"); - PowerRanksAPI prApi = (pr).loadAPI(); - Users prUsers = new Users(pr); - - // Migrate all groups - log.log("Starting groups migration."); - Set ranks = prApi.getRanks(); - AtomicInteger groupCount = new AtomicInteger(0); - for (String rank : ranks) { - Group group = plugin.getStorage().createAndLoadGroup(rank, CreationCause.INTERNAL).join(); - - for (String node : prApi.getPermissions(rank)) { - if (node.isEmpty()) continue; - group.setNode(DataType.NORMAL, MigrationUtils.parseNode(node, true).build(), true); - } - - for (String parent : prApi.getInheritances(rank)) { - if (parent.isEmpty()) continue; - group.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(parent)).build(), true); - } - - plugin.getStorage().saveGroup(group); - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - } - log.log("Migrated " + groupCount.get() + " groups"); - - // Migrate all users - log.log("Starting user migration."); - Set playerUuids = prUsers.getCachedPlayers(); - AtomicInteger userCount = new AtomicInteger(0); - for (String uuidString : playerUuids) { - UUID uuid = BukkitUuids.lookupUuid(log, uuidString); - if (uuid == null) { - continue; - } - - User user = plugin.getStorage().loadUser(uuid, null).join(); - - user.setNode(DataType.NORMAL, Inheritance.builder(CachedPlayers.getString("players." + uuidString + ".rank")).build(), true); - - final PowerConfigurationSection subGroups = CachedPlayers.getConfigurationSection("players." + uuidString + ".subranks"); - if (subGroups != null) { - for (String subGroup : subGroups.getKeys(false)) { - Inheritance.Builder builder = Inheritance.builder(subGroup); - for (String worldName : CachedPlayers.getStringList("players." + uuidString + ".subranks." + subGroup + ".worlds")) { - if (!worldName.equalsIgnoreCase("all")) { - builder.withContext(DefaultContextKeys.WORLD_KEY, worldName); - } - } - user.setNode(DataType.NORMAL, builder.build(), true); - } - } - - for (String node : CachedPlayers.getStringList("players." + uuidString + ".permissions")) { - if (node.isEmpty()) continue; - user.setNode(DataType.NORMAL, MigrationUtils.parseNode(node, true).build(), true); - } - - user.getPrimaryGroup().setStoredValue(CachedPlayers.getString("players." + uuidString + ".rank")); - - plugin.getUserManager().getHouseKeeper().cleanup(user.getUniqueId()); - plugin.getStorage().saveUser(user); - log.logAllProgress("Migrated {} users so far.", userCount.incrementAndGet()); - } - - log.log("Migrated " + userCount.get() + " users."); - log.log("Success! Migration complete."); - log.log("Don't forget to remove the PowerRanks jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } -} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java deleted file mode 100644 index a594d448d..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * 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.bukkit.migration; - -import com.github.gustav9797.PowerfulPermsAPI.CachedGroup; -import com.github.gustav9797.PowerfulPermsAPI.Group; -import com.github.gustav9797.PowerfulPermsAPI.Permission; -import com.github.gustav9797.PowerfulPermsAPI.PermissionManager; -import com.github.gustav9797.PowerfulPermsAPI.PowerfulPermsPlugin; -import com.google.common.collect.ImmutableSet; -import com.zaxxer.hikari.HikariDataSource; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.config.ConfigKeys; -import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.model.manager.group.GroupManager; -import me.lucko.luckperms.common.node.factory.NodeBuilders; -import me.lucko.luckperms.common.node.types.Inheritance; -import me.lucko.luckperms.common.node.types.Prefix; -import me.lucko.luckperms.common.node.types.Suffix; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.storage.StorageType; -import me.lucko.luckperms.common.util.Iterators; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; - -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; -import net.luckperms.api.node.NodeBuilder; - -import org.bukkit.Bukkit; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; - -// Only supports the latest versions of the PP API. (it seems to change randomly almost every release) -public class MigrationPowerfulPerms extends ChildCommand { - public MigrationPowerfulPerms() { - super(CommandSpec.MIGRATION_POWERFULPERMS, "powerfulperms", CommandPermission.MIGRATION, Predicates.not(5)); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "PowerfulPerms"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - if (!Bukkit.getPluginManager().isPluginEnabled("PowerfulPerms")) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - StorageType type = plugin.getConfiguration().get(ConfigKeys.STORAGE_METHOD); - if (type != StorageType.MYSQL) { - // We need to load the Hikari/MySQL stuff. - plugin.getDependencyManager().loadStorageDependencies(ImmutableSet.of(StorageType.MYSQL)); - } - - String address = args.get(0); - String database = args.get(1); - String username = args.get(2); - String password = args.get(3); - String dbTable = args.get(4); - - // Find a list of UUIDs - log.log("Getting a list of UUIDs to migrate."); - Set uuids = new HashSet<>(); - - try (HikariSupplier hikari = new HikariSupplier(address, database, username, password)) { - hikari.setup("powerfulperms-migrator-pool"); - - try (Connection c = hikari.getConnection()) { - DatabaseMetaData meta = c.getMetaData(); - try (ResultSet rs = meta.getTables(null, null, dbTable, null)) { - if (!rs.next()) { - log.log("Error - Couldn't find table."); - return CommandResult.FAILURE; - } - } - } - - try (Connection c = hikari.getConnection()) { - try (PreparedStatement ps = c.prepareStatement("SELECT COLUMN_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=?")) { - ps.setString(1, dbTable); - try (ResultSet rs = ps.executeQuery()) { - log.log("Found table: " + dbTable); - while (rs.next()) { - log.log(rs.getString("COLUMN_NAME") + " - " + rs.getString("COLUMN_TYPE")); - } - } - } - try (PreparedStatement ps = c.prepareStatement("SELECT `uuid` FROM " + dbTable)) { - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - uuids.add(UUID.fromString(rs.getString("uuid"))); - } - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - if (uuids.isEmpty()) { - log.logError("Unable to find any UUIDs to migrate."); - return CommandResult.FAILURE; - } - - log.log("Found " + uuids.size() + " uuids. Starting migration."); - - PowerfulPermsPlugin ppPlugin = (PowerfulPermsPlugin) Bukkit.getPluginManager().getPlugin("PowerfulPerms"); - PermissionManager pm = ppPlugin.getPermissionManager(); - - Collection groups = pm.getGroups().values(); - - AtomicInteger maxWeight = new AtomicInteger(0); - - // Groups first. - log.log("Starting group migration."); - AtomicInteger groupCount = new AtomicInteger(0); - Iterators.tryIterate(groups, g -> { - maxWeight.set(Math.max(maxWeight.get(), g.getRank())); - - String groupName = MigrationUtils.standardizeName(g.getName()); - me.lucko.luckperms.common.model.Group group = plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join(); - - MigrationUtils.setGroupWeight(group, g.getRank()); - - for (Permission p : g.getOwnPermissions()) { - applyPerm(group, p); - } - - for (Group parent : g.getParents()) { - group.setNode(DataType.NORMAL, Inheritance.builder(parent.getName().toLowerCase()).build(), true); - } - - // server --> prefix afaik - for (Map.Entry prefix : g.getPrefixes().entrySet()) { - if (prefix.getValue().isEmpty()) continue; - - String server = prefix.getKey().toLowerCase(); - if (prefix.getKey().equals("*") || prefix.getKey().equals("all")) { - server = null; - } - - if (server != null) { - group.setNode(DataType.NORMAL, Prefix.builder(prefix.getValue(), g.getRank()).withContext(DefaultContextKeys.SERVER_KEY, server).build(), true); - } else { - group.setNode(DataType.NORMAL, Prefix.builder(prefix.getValue(), g.getRank()).build(), true); - } - } - - for (Map.Entry suffix : g.getSuffixes().entrySet()) { - if (suffix.getValue().isEmpty()) continue; - - String server = suffix.getKey().toLowerCase(); - if (suffix.getKey().equals("*") || suffix.getKey().equals("all")) { - server = null; - } - - if (server != null) { - group.setNode(DataType.NORMAL, Suffix.builder(suffix.getValue(), g.getRank()).withContext(DefaultContextKeys.SERVER_KEY, server).build(), true); - } else { - group.setNode(DataType.NORMAL, Suffix.builder(suffix.getValue(), g.getRank()).build(), true); - } - } - - plugin.getStorage().saveGroup(group); - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - }); - log.log("Migrated " + groupCount.get() + " groups"); - - // Migrate all users - log.log("Starting user migration."); - AtomicInteger userCount = new AtomicInteger(0); - - // Increment the max weight from the group migrations. All user meta should override. - maxWeight.addAndGet(5); - - // Migrate all users and their groups - Iterators.tryIterate(uuids, uuid -> { - - // Create a LuckPerms user for the UUID - User user = plugin.getStorage().loadUser(uuid, null).join(); - - List permissions = joinFuture(pm.getPlayerOwnPermissions(uuid)); - - for (Permission p : permissions) { - applyPerm(user, p); - } - - // server --> list of groups - Map> parents = joinFuture(pm.getPlayerOwnGroups(uuid)); - for (Map.Entry> parent : parents.entrySet()) { - String server = parent.getKey().toLowerCase(); - if (parent.getKey().equals("*") || parent.getKey().equals("all")) { - server = null; - } - - for (CachedGroup group : parent.getValue()) { - applyGroup(pm, user, group, server); - } - } - - String prefix = joinFuture(pm.getPlayerOwnPrefix(uuid)); - String suffix = joinFuture(pm.getPlayerOwnSuffix(uuid)); - - if (prefix != null && !prefix.isEmpty()) { - user.setNode(DataType.NORMAL, Prefix.builder(prefix, maxWeight.get()).build(), true); - } - - if (suffix != null && !suffix.isEmpty()) { - user.setNode(DataType.NORMAL, Suffix.builder(suffix, maxWeight.get()).build(), true); - } - - Group primaryGroup = joinFuture(pm.getPlayerPrimaryGroup(uuid)); - if (primaryGroup != null && primaryGroup.getName() != null) { - String primary = primaryGroup.getName().toLowerCase(); - if (!primary.equals(GroupManager.DEFAULT_GROUP_NAME)) { - user.setNode(DataType.NORMAL, Inheritance.builder(primary).build(), true); - user.getPrimaryGroup().setStoredValue(primary); - } - } - - plugin.getUserManager().getHouseKeeper().cleanup(user.getUniqueId()); - plugin.getStorage().saveUser(user); - log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - - log.log("Migrated " + userCount.get() + " users."); - log.log("Success! Migration complete."); - log.log("Don't forget to remove the PowerfulPerms jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } - - private void applyPerm(PermissionHolder holder, Permission p) { - String node = p.getPermissionString(); - boolean value = true; - if (node.startsWith("!") || node.startsWith("-")) { - node = node.substring(1); - value = false; - } - - if (node.isEmpty()) { - return; - } - - String server = p.getServer(); - if (server != null && (server.equalsIgnoreCase("all") || server.equalsIgnoreCase("*") || server.isEmpty())) { - server = null; - } - - String world = p.getWorld(); - if (world != null && (world.equalsIgnoreCase("all") || world.equalsIgnoreCase("*") || world.isEmpty())) { - world = null; - } - - long expireAt = 0L; - if (p.willExpire()) { - expireAt = p.getExpirationDate().getTime() / 1000L; - } - - if (world != null && server == null) { - server = "global"; - } - - NodeBuilder nb = NodeBuilders.determineMostApplicable(node).value(value); - if (expireAt != 0) nb.expiry(expireAt); - if (server != null) nb.withContext(DefaultContextKeys.SERVER_KEY, server); - if (world != null) nb.withContext(DefaultContextKeys.WORLD_KEY, world); - - holder.setNode(DataType.NORMAL, nb.build(), true); - } - - private void applyGroup(PermissionManager pm, PermissionHolder holder, CachedGroup g, String server) { - Group group = pm.getGroup(g.getGroupId()); - - long expireAt = 0L; - if (g.willExpire()) { - expireAt = g.getExpirationDate().getTime() / 1000L; - } - - NodeBuilder nb = Inheritance.builder(MigrationUtils.standardizeName(group.getName())); - - if (expireAt != 0) { - nb.expiry(expireAt); - } - - if (server != null) { - nb.withContext(DefaultContextKeys.SERVER_KEY, server); - } - - holder.setNode(DataType.NORMAL, nb.build(), true); - } - - private static T joinFuture(Future future) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - /** - * A simple hikari wrapper - */ - public static final class HikariSupplier implements AutoCloseable { - - private final String host; - private final String port; - private final String database; - private final String username; - private final String password; - - private HikariDataSource hikari; - - public HikariSupplier(String address, String database, String username, String password) { - this.database = database; - this.username = username; - this.password = password; - - String[] split = address.split(":"); - if (split.length != 2) { - throw new IllegalArgumentException("Address argument should be in the format hostname:port"); - } - - this.host = split[0]; - this.port = split[1]; - } - - public void setup(String poolName) { - this.hikari = new HikariDataSource(); - this.hikari.setPoolName(poolName); - this.hikari.setMaximumPoolSize(2); - this.hikari.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource"); - this.hikari.addDataSourceProperty("serverName", this.host); - this.hikari.addDataSourceProperty("port", this.port); - this.hikari.addDataSourceProperty("databaseName", this.database); - this.hikari.addDataSourceProperty("user", this.username); - this.hikari.addDataSourceProperty("password", this.password); - } - - @Override - public void close() { - this.hikari.close(); - } - - public Connection getConnection() throws SQLException { - return this.hikari.getConnection(); - } - } - - -} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationZPermissions.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationZPermissions.java deleted file mode 100644 index a62957df6..000000000 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationZPermissions.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 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.bukkit.migration; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.Track; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.node.factory.NodeBuilders; -import me.lucko.luckperms.common.node.types.Inheritance; -import me.lucko.luckperms.common.node.types.Meta; -import me.lucko.luckperms.common.node.types.Prefix; -import me.lucko.luckperms.common.node.types.Suffix; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Iterators; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; - -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; -import net.luckperms.api.node.Node; - -import org.bukkit.Bukkit; -import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService; -import org.tyrannyofheaven.bukkit.zPermissions.dao.PermissionService; -import org.tyrannyofheaven.bukkit.zPermissions.model.EntityMetadata; -import org.tyrannyofheaven.bukkit.zPermissions.model.Entry; -import org.tyrannyofheaven.bukkit.zPermissions.model.Membership; -import org.tyrannyofheaven.bukkit.zPermissions.model.PermissionEntity; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -public class MigrationZPermissions extends ChildCommand { - public MigrationZPermissions() { - super(CommandSpec.MIGRATION_COMMAND, "zpermissions", CommandPermission.MIGRATION, Predicates.alwaysFalse()); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "zPermissions"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - if (!Bukkit.getPluginManager().isPluginEnabled("zPermissions")) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - if (!Bukkit.getServicesManager().isProvidedFor(ZPermissionsService.class)) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - ZPermissionsService service = Bukkit.getServicesManager().getRegistration(ZPermissionsService.class).getProvider(); - - PermissionService internalService; - try { - Field psField = service.getClass().getDeclaredField("permissionService"); - psField.setAccessible(true); - internalService = (PermissionService) psField.get(service); - } catch (Exception e) { - e.printStackTrace(); - return CommandResult.FAILURE; - } - - // Migrate all groups - log.log("Starting group migration."); - - Map> userParents = new HashMap<>(); - - AtomicInteger groupCount = new AtomicInteger(0); - AtomicInteger maxWeight = new AtomicInteger(0); - Iterators.tryIterate(internalService.getEntities(true), entity -> { - String groupName = MigrationUtils.standardizeName(entity.getDisplayName()); - Group group = plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join(); - - int weight = entity.getPriority(); - maxWeight.set(Math.max(maxWeight.get(), weight)); - migrateEntity(group, entity, weight); - MigrationUtils.setGroupWeight(group, weight); - - // store user data for later - Set members = entity.getMemberships(); - for (Membership membership : members) { - UUID uuid = BukkitUuids.lookupUuid(log, membership.getMember()); - if (uuid == null) { - continue; - } - - Set nodes = userParents.computeIfAbsent(uuid, u -> new HashSet<>()); - if (membership.getExpiration() == null) { - nodes.add(Inheritance.builder(groupName).build()); - } else { - long expiry = membership.getExpiration().toInstant().getEpochSecond(); - nodes.add(Inheritance.builder(groupName).expiry(expiry).build()); - } - } - - plugin.getStorage().saveGroup(group); - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - }); - log.log("Migrated " + groupCount.get() + " groups"); - - // Migrate all tracks - log.log("Starting track migration."); - AtomicInteger trackCount = new AtomicInteger(0); - Iterators.tryIterate(service.getAllTracks(), t -> { - String trackName = MigrationUtils.standardizeName(t); - Track track = plugin.getStorage().createAndLoadTrack(trackName, CreationCause.INTERNAL).join(); - track.setGroups(service.getTrackGroups(t)); - plugin.getStorage().saveTrack(track); - - log.logAllProgress("Migrated {} tracks so far.", trackCount.incrementAndGet()); - }); - log.log("Migrated " + trackCount.get() + " tracks"); - - // Migrate all users. - log.log("Starting user migration."); - maxWeight.addAndGet(10); - AtomicInteger userCount = new AtomicInteger(0); - - Set usersToMigrate = new HashSet<>(userParents.keySet()); - usersToMigrate.addAll(service.getAllPlayersUUID()); - - Iterators.tryIterate(usersToMigrate, u -> { - PermissionEntity entity = internalService.getEntity(null, u, false); - - String username = null; - if (entity != null) { - username = entity.getDisplayName(); - } - - User user = plugin.getStorage().loadUser(u, username).join(); - - // migrate permissions & meta - if (entity != null) { - migrateEntity(user, entity, maxWeight.get()); - } - - // migrate groups - Set parents = userParents.get(u); - if (parents != null) { - parents.forEach(node -> user.setNode(DataType.NORMAL, node, true)); - } - - user.getPrimaryGroup().setStoredValue(MigrationUtils.standardizeName(service.getPlayerPrimaryGroup(u))); - - plugin.getUserManager().getHouseKeeper().cleanup(user.getUniqueId()); - plugin.getStorage().saveUser(user); - log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - - log.log("Migrated " + userCount.get() + " users."); - log.log("Success! Migration complete."); - log.log("Don't forget to remove the zPermissions jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } - - private void migrateEntity(PermissionHolder holder, PermissionEntity entity, int weight) { - for (Entry e : entity.getPermissions()) { - if (e.getPermission().isEmpty()) continue; - - if (e.getWorld() != null && !e.getWorld().getName().isEmpty()) { - holder.setNode(DataType.NORMAL, NodeBuilders.determineMostApplicable(e.getPermission()).value(e.isValue()).withContext(DefaultContextKeys.WORLD_KEY, e.getWorld().getName()).build(), true); - } else { - holder.setNode(DataType.NORMAL, NodeBuilders.determineMostApplicable(e.getPermission()).value(e.isValue()).build(), true); - } - } - - // only migrate inheritances for groups - if (entity.isGroup()) { - for (PermissionEntity inheritance : entity.getParents()) { - if (!inheritance.getDisplayName().equals(holder.getObjectName())) { - holder.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(inheritance.getDisplayName())).build(), true); - } - } - } - - for (EntityMetadata metadata : entity.getMetadata()) { - String key = metadata.getName().toLowerCase(); - Object value = metadata.getValue(); - - if (key.isEmpty() || value == null) continue; - - String valueString = value.toString(); - if (valueString.isEmpty()) continue; - - if (key.equals("prefix")) { - holder.setNode(DataType.NORMAL, Prefix.builder(valueString, weight).build(), true); - } else if (key.equals("suffix")) { - holder.setNode(DataType.NORMAL, Suffix.builder(valueString, weight).build(), true); - } else { - holder.setNode(DataType.NORMAL, Meta.builder(key, valueString).build(), true); - } - } - } -} diff --git a/bukkit/src/main/resources/luckperms.commodore b/bukkit/src/main/resources/luckperms.commodore index 7e4626141..e22f790e9 100644 --- a/bukkit/src/main/resources/luckperms.commodore +++ b/bukkit/src/main/resources/luckperms.commodore @@ -56,9 +56,6 @@ luckperms { } } } - migration { - plugin brigadier:string single_word; - } translations { install; } diff --git a/bungee/build.gradle b/bungee/build.gradle index 20dc9ab23..d601b85a4 100644 --- a/bungee/build.gradle +++ b/bungee/build.gradle @@ -8,17 +8,6 @@ dependencies { compileOnly 'net.md-5:bungeecord-api:1.15-SNAPSHOT' compileOnly 'me.lucko:adventure-platform-bungeecord:4.0.0' // re: this artifact - see note in common/build.gradle compileOnly 'com.imaginarycode.minecraft:RedisBungee:0.4' - - // migration plugins - compileOnly('net.alpenblock:BungeePerms:3.0') { - exclude(module: 'bungeecord-api') - exclude(module: 'lombok') - exclude(module: 'spigot-api') - exclude(module: 'worldedit') - exclude(module: 'Vault') - exclude(module: 'Essentials') - exclude(module: 'EssentialsX') - } } processResources { diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/migration/MigrationBungeePerms.java b/bungee/src/main/java/me/lucko/luckperms/bungee/migration/MigrationBungeePerms.java deleted file mode 100644 index 11ddd4273..000000000 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/migration/MigrationBungeePerms.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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.bungee.migration; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.commands.migration.MigrationUtils; -import me.lucko.luckperms.common.locale.Message; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.node.types.Inheritance; -import me.lucko.luckperms.common.node.types.Prefix; -import me.lucko.luckperms.common.node.types.Suffix; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Iterators; -import me.lucko.luckperms.common.util.Predicates; -import me.lucko.luckperms.common.util.ProgressLogger; - -import net.alpenblock.bungeeperms.BungeePerms; -import net.alpenblock.bungeeperms.Group; -import net.alpenblock.bungeeperms.PermEntity; -import net.alpenblock.bungeeperms.Server; -import net.alpenblock.bungeeperms.World; -import net.luckperms.api.context.DefaultContextKeys; -import net.luckperms.api.event.cause.CreationCause; -import net.luckperms.api.model.data.DataType; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -public class MigrationBungeePerms extends ChildCommand { - public MigrationBungeePerms() { - super(CommandSpec.MIGRATION_COMMAND, "bungeeperms", CommandPermission.MIGRATION, Predicates.alwaysFalse()); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object ignored, ArgumentList args, String label) { - ProgressLogger log = new ProgressLogger(Message.MIGRATION_LOG, Message.MIGRATION_LOG_PROGRESS, "BungeePerms"); - log.addListener(plugin.getConsoleSender()); - log.addListener(sender); - - log.log("Starting."); - - // Get BungeePerms instance - BungeePerms bp = BungeePerms.getInstance(); - if (bp == null) { - log.logError("Plugin not loaded."); - return CommandResult.STATE_ERROR; - } - - List groups = bp.getPermissionsManager().getBackEnd().loadGroups(); - - log.log("Calculating group weightings."); - int i = 0; - for (Group group : groups) { - i = Math.max(i, group.getRank()); - } - int maxWeight = i + 5; - - // Migrate all groups. - log.log("Starting group migration."); - AtomicInteger groupCount = new AtomicInteger(0); - Iterators.tryIterate(groups, g -> { - int groupWeight = maxWeight - g.getRank(); - - // Make a LuckPerms group for the one being migrated - String groupName = MigrationUtils.standardizeName(g.getName()); - me.lucko.luckperms.common.model.Group group = plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join(); - - MigrationUtils.setGroupWeight(group, groupWeight); - migrateHolder(g, g.getInheritances(), groupWeight, group); - - plugin.getStorage().saveGroup(group); - log.logAllProgress("Migrated {} groups so far.", groupCount.incrementAndGet()); - }); - log.log("Migrated " + groupCount.get() + " groups"); - - // Migrate all users. - log.log("Starting user migration."); - AtomicInteger userCount = new AtomicInteger(0); - - // Increment the max weight from the group migrations. All user meta should override. - int userWeight = maxWeight + 5; - - Iterators.tryIterate(bp.getPermissionsManager().getBackEnd().loadUsers(), u -> { - if (u.getUUID() == null) { - log.logError("Could not parse UUID for user: " + u.getName()); - return; - } - - // Make a LuckPerms user for the one being migrated. - me.lucko.luckperms.common.model.User user = plugin.getStorage().loadUser(u.getUUID(), u.getName()).join(); - - migrateHolder(u, u.getGroupsString(), userWeight, user); - - plugin.getStorage().saveUser(user); - plugin.getUserManager().getHouseKeeper().cleanup(user.getUniqueId()); - - log.logProgress("Migrated {} users so far.", userCount.incrementAndGet(), ProgressLogger.DEFAULT_NOTIFY_FREQUENCY); - }); - - log.log("Migrated " + userCount.get() + " users."); - log.log("Success! Migration complete."); - log.log("Don't forget to remove the BungeePerms jar from your plugins folder & restart the server. " + - "LuckPerms may not take over as the server permission handler until this is done."); - return CommandResult.SUCCESS; - } - - private static void migrateHolder(PermEntity entity, List parents, int weight, PermissionHolder holder) { - // Migrate global perms - for (String perm : entity.getPerms()) { - if (perm.isEmpty()) continue; - holder.setNode(DataType.NORMAL, MigrationUtils.parseNode(perm, true).build(), true); - } - - // Migrate per-server perms - for (Map.Entry e : entity.getServers().entrySet()) { - for (String perm : e.getValue().getPerms()) { - if (perm.isEmpty()) continue; - holder.setNode(DataType.NORMAL, MigrationUtils.parseNode(perm, true).withContext(DefaultContextKeys.SERVER_KEY, e.getKey()).build(), true); - } - - // Migrate per-world perms - for (Map.Entry we : e.getValue().getWorlds().entrySet()) { - for (String perm : we.getValue().getPerms()) { - if (perm.isEmpty()) continue; - holder.setNode(DataType.NORMAL, MigrationUtils.parseNode(perm, true).withContext(DefaultContextKeys.SERVER_KEY, e.getKey()).withContext(DefaultContextKeys.WORLD_KEY, we.getKey()).build(), true); - } - } - } - - // Migrate any parent groups - for (String inherit : parents) { - if (inherit.isEmpty()) continue; - holder.setNode(DataType.NORMAL, Inheritance.builder(MigrationUtils.standardizeName(inherit)).build(), true); - } - - // Migrate prefix and suffix - String prefix = entity.getPrefix(); - String suffix = entity.getSuffix(); - - if (prefix != null && !prefix.isEmpty()) { - holder.setNode(DataType.NORMAL, Prefix.builder(prefix, weight).build(), true); - } - if (suffix != null && !suffix.isEmpty()) { - holder.setNode(DataType.NORMAL, Suffix.builder(suffix, weight).build(), true); - } - } -} diff --git a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java index 197b6b22e..46d0184b1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java +++ b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java @@ -38,11 +38,12 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.storage.Storage; -import me.lucko.luckperms.common.util.ProgressLogger; import me.lucko.luckperms.common.util.gson.GsonProvider; import me.lucko.luckperms.common.util.gson.JArray; import me.lucko.luckperms.common.util.gson.JObject; +import net.kyori.adventure.text.Component; + import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -90,7 +91,7 @@ protected Exporter(LuckPermsPlugin plugin, Sender executor, boolean includeUsers this.includeUsers = includeUsers; this.includeGroups = includeGroups; - this.log = new ProgressLogger(Message.EXPORT_LOG, Message.EXPORT_LOG_PROGRESS); + this.log = new ProgressLogger(); this.log.addListener(plugin.getConsoleSender()); this.log.addListener(executor); } @@ -203,7 +204,7 @@ private JsonObject exportUsers() { break; } catch (TimeoutException e) { // still executing - send a progress report and continue waiting - this.log.logAllProgress("Exported {} users so far.", userCount.get()); + this.log.logProgress("Exported " + userCount.get() + " users so far."); continue; } @@ -272,4 +273,31 @@ protected void processOutput(JsonObject json) { } } } + + private static final class ProgressLogger { + private final Set listeners = new HashSet<>(); + + public void addListener(Sender sender) { + this.listeners.add(sender); + } + + public Set getListeners() { + return this.listeners; + } + + public void log(String msg) { + dispatchMessage(Message.EXPORT_LOG, msg); + } + + public void logProgress(String msg) { + dispatchMessage(Message.EXPORT_LOG_PROGRESS, msg); + } + + private void dispatchMessage(Message.Args1 messageType, String content) { + final Component message = messageType.build(content); + for (Sender s : this.listeners) { + s.sendMessage(message); + } + } + } } diff --git a/common/src/main/java/me/lucko/luckperms/common/command/CommandManager.java b/common/src/main/java/me/lucko/luckperms/common/command/CommandManager.java index 5d9493d62..41d104f88 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/CommandManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/CommandManager.java @@ -38,7 +38,6 @@ import me.lucko.luckperms.common.commands.group.GroupParentCommand; import me.lucko.luckperms.common.commands.group.ListGroups; import me.lucko.luckperms.common.commands.log.LogParentCommand; -import me.lucko.luckperms.common.commands.migration.MigrationParentCommand; import me.lucko.luckperms.common.commands.misc.ApplyEditsCommand; import me.lucko.luckperms.common.commands.misc.BulkUpdateCommand; import me.lucko.luckperms.common.commands.misc.CheckCommand; @@ -114,7 +113,6 @@ public CommandManager(LuckPermsPlugin plugin) { .add(new ExportCommand()) .add(new ReloadConfigCommand()) .add(new BulkUpdateCommand()) - .add(new MigrationParentCommand()) .add(new TranslationsCommand()) .add(new ApplyEditsCommand()) .add(new CreateGroup()) diff --git a/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java b/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java index 8bcec25b1..05b2d0ccf 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/access/CommandPermission.java @@ -45,7 +45,6 @@ public enum CommandPermission { RELOAD_CONFIG("reloadconfig", Type.NONE), BULK_UPDATE("bulkupdate", Type.NONE), APPLY_EDITS("applyedits", Type.NONE), - MIGRATION("migration", Type.NONE), TRANSLATIONS("translations", Type.NONE), CREATE_GROUP("creategroup", Type.NONE), diff --git a/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java b/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java index 532095af6..aeee95775 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java @@ -86,7 +86,6 @@ public enum CommandSpec { arg("action value", false), arg("constraint...", false) ), - MIGRATION("/%s migration"), TRANSLATIONS("/%s translations", arg("install", false) ), @@ -413,18 +412,6 @@ public enum CommandSpec { ), SPONGE_OPTION_CLEAR( arg("contexts...", false) - ), - - MIGRATION_COMMAND, - MIGRATION_GROUPMANAGER( - arg("migrate as global", true) - ), - MIGRATION_POWERFULPERMS( - arg("address", true), - arg("database", true), - arg("username", true), - arg("password", true), - arg("db table", true) ); private final String usage; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java deleted file mode 100644 index 9c1aea846..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationParentCommand.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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.commands.migration; - -import com.google.common.collect.ImmutableMap; - -import me.lucko.luckperms.common.command.CommandResult; -import me.lucko.luckperms.common.command.abstraction.ChildCommand; -import me.lucko.luckperms.common.command.abstraction.Command; -import me.lucko.luckperms.common.command.abstraction.ParentCommand; -import me.lucko.luckperms.common.command.access.CommandPermission; -import me.lucko.luckperms.common.command.spec.CommandSpec; -import me.lucko.luckperms.common.command.utils.ArgumentList; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.sender.Sender; -import me.lucko.luckperms.common.util.Predicates; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; - -public class MigrationParentCommand extends ParentCommand { - private static final Map PLUGINS = ImmutableMap.builder() - // bukkit - .put("org.anjocaido.groupmanager.GroupManager", "me.lucko.luckperms.bukkit.migration.MigrationGroupManager") - .put("ru.tehkode.permissions.bukkit.PermissionsEx", "me.lucko.luckperms.bukkit.migration.MigrationPermissionsEx") - .put("com.github.gustav9797.PowerfulPermsAPI.PowerfulPermsPlugin", "me.lucko.luckperms.bukkit.migration.MigrationPowerfulPerms") - .put("org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService", "me.lucko.luckperms.bukkit.migration.MigrationZPermissions") - .put("de.bananaco.bpermissions.api.WorldManager", "me.lucko.luckperms.bukkit.migration.MigrationBPermissions") - .put("com.platymuus.bukkit.permissions.PermissionsPlugin", "me.lucko.luckperms.bukkit.migration.MigrationPermissionsBukkit") - .put("nl.svenar.PowerRanks.PowerRanks", "me.lucko.luckperms.bukkit.migration.MigrationPowerRanks") - // bungee - .put("net.alpenblock.bungeeperms.BungeePerms", "me.lucko.luckperms.bungee.migration.MigrationBungeePerms") - .build(); - - private final ReentrantLock lock = new ReentrantLock(); - private List> commands = null; - private boolean display = true; - - public MigrationParentCommand() { - super(CommandSpec.MIGRATION, "Migration", Type.NO_TARGET_ARGUMENT, null); - } - - @Override - public synchronized @NonNull List> getChildren() { - if (this.commands == null) { - this.commands = getAvailableCommands(); - - // Add dummy command to show in the list. - if (this.commands.isEmpty()) { - this.display = false; - this.commands.add(new ChildCommand(CommandSpec.MIGRATION_COMMAND, "No available plugins to migrate from", CommandPermission.MIGRATION, Predicates.alwaysFalse()) { - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Object o, ArgumentList args, String label) { - return CommandResult.SUCCESS; - } - }); - } - } - return this.commands; - } - - @Override - public boolean isAuthorized(Sender sender) { - return sender.hasPermission(CommandPermission.MIGRATION); - } - - @Override - public boolean shouldDisplay() { - getChildren(); - return this.display; - } - - @SuppressWarnings("unchecked") - private static List> getAvailableCommands() { - List> available = new ArrayList<>(); - - for (Map.Entry plugin : PLUGINS.entrySet()) { - try { - Class.forName(plugin.getKey()); - available.add((ChildCommand) Class.forName(plugin.getValue()).getConstructor().newInstance()); - } catch (Throwable ignored) {} - } - - return available; - } - - @Override - protected ReentrantLock getLockForTarget(Void target) { - return this.lock; // share a lock between all migration commands - } - - /* Dummy */ - - @Override - protected List getTargets(LuckPermsPlugin plugin) { - // should never be called if we specify Type.NO_TARGET_ARGUMENT in the constructor - throw new UnsupportedOperationException(); - } - - @Override - protected Void parseTarget(String target, LuckPermsPlugin plugin, Sender sender) { - // should never be called if we specify Type.NO_TARGET_ARGUMENT in the constructor - throw new UnsupportedOperationException(); - } - - @Override - protected Object getTarget(Void target, LuckPermsPlugin plugin, Sender sender) { - return this; // can't return null, but we don't need a target - } - - @Override - protected void cleanup(Object o, LuckPermsPlugin plugin) { - - } - -} diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationUtils.java b/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationUtils.java deleted file mode 100644 index 5c4a23157..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/commands/migration/MigrationUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.commands.migration; - -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.node.factory.NodeBuilders; -import me.lucko.luckperms.common.node.types.Weight; - -import net.luckperms.api.model.data.DataType; -import net.luckperms.api.node.NodeBuilder; -import net.luckperms.api.node.NodeType; - -public final class MigrationUtils { - private MigrationUtils() {} - - public static NodeBuilder parseNode(String permission, boolean value) { - if (permission.length() > 1) { - if (permission.charAt(0) == '-' || permission.charAt(0) == '!') { - permission = permission.substring(1); - value = false; - } else if (permission.charAt(0) == '+') { - permission = permission.substring(1); - value = true; - } - } - - return NodeBuilders.determineMostApplicable(permission).value(value); - } - - public static void setGroupWeight(Group group, int weight) { - group.removeIf(DataType.NORMAL, null, NodeType.WEIGHT::matches, false); - group.setNode(DataType.NORMAL, Weight.builder(weight).build(), true); - } - - public static String standardizeName(String string) { - return string.trim() - .replace(':', '-') - .replace(' ', '-') - .replace('.', '-') - .toLowerCase(); - } - -} diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java index e9b81b71c..f346483c6 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java @@ -349,38 +349,6 @@ static TextComponent prefixed(ComponentLike component) { .append(text(msg, GRAY)) ); - Args2 MIGRATION_LOG = (pluginName, msg) -> prefixed(text() - // "&3MIGRATION &7[&3{}&7] &3&l> &f{}" - .append(translatable("luckperms.logs.migration-prefix", DARK_AQUA)) - .append(space()) - .append(text() - .color(GRAY) - .append(text('[')) - .append(text(pluginName, DARK_AQUA)) - .append(text(']')) - ) - .append(space()) - .append(text('>', DARK_AQUA, BOLD)) - .append(space()) - .append(text(msg, WHITE)) - ); - - Args2 MIGRATION_LOG_PROGRESS = (pluginName, msg) -> prefixed(text() - // "&3MIGRATION &7[&3{}&7] &3&l> &7{}" - .append(translatable("luckperms.logs.migration-prefix", DARK_AQUA)) - .append(space()) - .append(text() - .color(GRAY) - .append(text('[')) - .append(text(pluginName, DARK_AQUA)) - .append(text(']')) - ) - .append(space()) - .append(text('>', DARK_AQUA, BOLD)) - .append(space()) - .append(text(msg, GRAY)) - ); - Args0 COMMAND_NOT_RECOGNISED = () -> prefixed(translatable() // "&cCommand not recognised." .key("luckperms.commandsystem.command-not-recognised") diff --git a/common/src/main/java/me/lucko/luckperms/common/util/ProgressLogger.java b/common/src/main/java/me/lucko/luckperms/common/util/ProgressLogger.java deleted file mode 100644 index 10b2f3431..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/util/ProgressLogger.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.locale.Message; -import me.lucko.luckperms.common.sender.Sender; - -import net.kyori.adventure.text.Component; - -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; - -public class ProgressLogger { - public static final int DEFAULT_NOTIFY_FREQUENCY = 500; - - private final Function logMessage; - private final Function logProgressMessage; - - private final Set listeners = new HashSet<>(); - - public ProgressLogger(Message.Args1 logMessage, Message.Args1 logProgressMessage) { - this.logMessage = logMessage::build; - this.logProgressMessage = logProgressMessage::build; - } - - public ProgressLogger(Message.Args2 logMessage, Message.Args2 logProgressMessage, String logPrefixParam) { - this.logMessage = message -> logMessage.build(logPrefixParam, message); - this.logProgressMessage = message -> logProgressMessage.build(logPrefixParam, message); - } - - public void addListener(Sender sender) { - this.listeners.add(sender); - } - - public Set getListeners() { - return this.listeners; - } - - public void log(String msg) { - dispatchMessage(this.logMessage, msg); - } - - public void logError(String msg) { - dispatchMessage(this.logMessage, "Error -> " + msg); - } - - public void logAllProgress(String msg, int amount) { - dispatchMessage(this.logProgressMessage, msg.replace("{}", Integer.toString(amount))); - } - - public void logProgress(String msg, int amount, int notifyFrequency) { - if (amount % notifyFrequency == 0) { - logAllProgress(msg, amount); - } - } - - private void dispatchMessage(Function messageType, String content) { - final Component message = messageType.apply(content); - for (Sender s : this.listeners) { - s.sendMessage(message); - } - } -} diff --git a/common/src/main/resources/luckperms_en.properties b/common/src/main/resources/luckperms_en.properties index e54be48dd..961506f0b 100644 --- a/common/src/main/resources/luckperms_en.properties +++ b/common/src/main/resources/luckperms_en.properties @@ -1,7 +1,6 @@ luckperms.logs.actionlog-prefix=LOG luckperms.logs.verbose-prefix=VB luckperms.logs.export-prefix=EXPORT -luckperms.logs.migration-prefix=MIGRATION luckperms.commandsystem.available-commands=Use {0} to view available commands luckperms.commandsystem.command-not-recognised=Command not recognised luckperms.commandsystem.no-permission=You do not have permission to use this command! @@ -385,7 +384,6 @@ luckperms.usage.bulk-update.argument.action=the action to perform on the data. ( luckperms.usage.bulk-update.argument.action-field=the field to act upon. only required for ''update''. (''permission'', ''server'' or ''world'') luckperms.usage.bulk-update.argument.action-value=the value to replace with. only required for ''update''. luckperms.usage.bulk-update.argument.constraint=the constraints required for the update -luckperms.usage.migration.description=Migration commands luckperms.usage.translations.description=Manage translations luckperms.usage.translations.argument.install=subcommand to install translations luckperms.usage.apply-edits.description=Applies permission changes made from the web editor @@ -628,12 +626,3 @@ luckperms.usage.sponge-option-unset.argument.key=the key to unset luckperms.usage.sponge-option-unset.argument.contexts=the contexts to unset the key in luckperms.usage.sponge-option-clear.description=Clears the Subjects options luckperms.usage.sponge-option-clear.argument.contexts=the contexts to clear options in -luckperms.usage.migration-command.description=Migration command -luckperms.usage.migration-groupmanager.description=Migration command -luckperms.usage.migration-groupmanager.argument.migrate-as-global=if world permissions should be ignored, and just migrated as global -luckperms.usage.migration-powerfulperms.description=Migration command -luckperms.usage.migration-powerfulperms.argument.address=the address of the PP database -luckperms.usage.migration-powerfulperms.argument.database=the name of the PP database -luckperms.usage.migration-powerfulperms.argument.username=the username to log into the DB -luckperms.usage.migration-powerfulperms.argument.password=the password to log into the DB -luckperms.usage.migration-powerfulperms.argument.db-table=the name of the PP table where player data is stored diff --git a/common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.java b/common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.java index 38a6f1c04..232adc013 100644 --- a/common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/util/DurationParserTest.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.util; import org.junit.jupiter.api.Test; diff --git a/common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.java b/common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.java index 7965b1082..e3c517ce5 100644 --- a/common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/util/EnumNamerTest.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.util; import com.google.common.collect.ImmutableMap; diff --git a/common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.java b/common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.java index b387219a3..af30c6375 100644 --- a/common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/util/IteratorsTest.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.util; import com.google.common.collect.ImmutableList; diff --git a/common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.java b/common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.java index ab21e8fbd..8865db349 100644 --- a/common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/util/PaginatedTest.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.util; import com.google.common.collect.ImmutableList; @@ -37,7 +62,7 @@ void testSimple() { assertEquals("five", page3.get(0).value()); assertEquals(5, page3.get(0).position()); - assertThrows(IllegalArgumentException.class, () -> paginated.getPage(4, 2)); + assertThrows(IllegalStateException.class, () -> paginated.getPage(4, 2)); assertThrows(IllegalArgumentException.class, () -> paginated.getPage(0, 2)); assertThrows(IllegalArgumentException.class, () -> paginated.getPage(-1, 2)); } diff --git a/common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.java b/common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.java index 1dd5d35cb..904914c6e 100644 --- a/common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.java +++ b/common/src/test/java/me/lucko/luckperms/common/verbose/BooleanExpressionTest.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.verbose; import me.lucko.luckperms.common.verbose.expression.BooleanExpressionCompiler; From 8dfeef9575d855fba60251be10ef24e13f7ec773 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 23 Dec 2020 12:16:14 +0000 Subject: [PATCH 26/38] Some misc tidying up --- .../api/query/dataorder/DataTypeFilter.java | 4 +- .../bukkit/vault/LuckPermsVaultChat.java | 27 +- common/build.gradle | 4 +- .../common/actionlog/LogDispatcher.java | 2 +- .../api/implementation/ApiPlayerAdapter.java | 1 - .../luckperms/common/backup/Exporter.java | 2 +- .../luckperms/common/config/ConfigKeys.java | 8 +- .../adapter/ConfigurateConfigAdapter.java | 4 +- .../ContextSetConfigurateSerializer.java | 10 +- .../common/dependencies/Dependency.java | 33 -- .../luckperms/common/http/BytebinClient.java | 53 +-- .../common/locale/TranslationRepository.java | 5 - .../messaging/redis/RedisMessenger.java | 6 +- .../file/AbstractConfigurateStorage.java | 416 ++++++++---------- .../file/CombinedConfigurateStorage.java | 240 +++++----- .../implementation/file/FileIOException.java | 36 ++ .../implementation/file/FileUuidCache.java | 126 +++--- .../file/SeparatedConfigurateStorage.java | 161 ++++--- .../implementation/file/StorageLocation.java | 2 +- .../common/storage/misc/NodeEntry.java | 1 + .../luckperms/common/treeview/TreeView.java | 2 +- .../common/verbose/VerboseListener.java | 2 +- .../common/webeditor/WebEditorRequest.java | 2 +- .../dependencies/DependencyChecksumTest.java | 79 ++++ .../node/utils/ShorthandParserTest.java | 70 +++ .../listeners/NukkitAutoOpListener.java | 2 +- .../velocity/VelocityCommandExecutor.java | 54 +-- .../velocity/util/AdventureCompat.java | 33 +- 28 files changed, 703 insertions(+), 682 deletions(-) create mode 100644 common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileIOException.java create mode 100644 common/src/test/java/me/lucko/luckperms/common/dependencies/DependencyChecksumTest.java create mode 100644 common/src/test/java/me/lucko/luckperms/common/node/utils/ShorthandParserTest.java diff --git a/api/src/main/java/net/luckperms/api/query/dataorder/DataTypeFilter.java b/api/src/main/java/net/luckperms/api/query/dataorder/DataTypeFilter.java index 17f7d9e23..40d933495 100644 --- a/api/src/main/java/net/luckperms/api/query/dataorder/DataTypeFilter.java +++ b/api/src/main/java/net/luckperms/api/query/dataorder/DataTypeFilter.java @@ -93,8 +93,8 @@ public boolean test(DataType dataType) { }; private static final List ALL_LIST = Collections.unmodifiableList(Arrays.asList(DataType.NORMAL, DataType.TRANSIENT)); - private static final List NORMAL_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.NORMAL)); - private static final List TRANSIENT_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.TRANSIENT)); + private static final List NORMAL_ONLY_LIST = Collections.singletonList(DataType.NORMAL); + private static final List TRANSIENT_ONLY_LIST = Collections.singletonList(DataType.TRANSIENT); /** * Gets a {@link List} of all {@link DataType}s, filtered by the {@code predicate}. diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultChat.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultChat.java index 6519804de..0e539b8a1 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultChat.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultChat.java @@ -157,24 +157,20 @@ public void setUserMeta(String world, UUID uuid, String key, Object value) { @Override public String getGroupChatPrefix(String world, String name) { Objects.requireNonNull(name, "name"); - Group group = getGroup(name); - if (group == null) { + MetaCache metaData = getGroupMetaCache(name, world); + if (metaData == null) { return null; } - QueryOptions queryOptions = this.vaultPermission.getQueryOptions(null, world); - MetaCache metaData = group.getCachedData().getMetaData(queryOptions); return Strings.nullToEmpty(metaData.getPrefix(MetaCheckEvent.Origin.THIRD_PARTY_API)); } @Override public String getGroupChatSuffix(String world, String name) { Objects.requireNonNull(name, "name"); - Group group = getGroup(name); - if (group == null) { + MetaCache metaData = getGroupMetaCache(name, world); + if (metaData == null) { return null; } - QueryOptions queryOptions = this.vaultPermission.getQueryOptions(null, world); - MetaCache metaData = group.getCachedData().getMetaData(queryOptions); return Strings.nullToEmpty(metaData.getSuffix(MetaCheckEvent.Origin.THIRD_PARTY_API)); } @@ -202,12 +198,10 @@ public void setGroupChatSuffix(String world, String name, String suffix) { public String getGroupMeta(String world, String name, String key) { Objects.requireNonNull(name, "name"); Objects.requireNonNull(key, "key"); - Group group = getGroup(name); - if (group == null) { + MetaCache metaData = getGroupMetaCache(name, world); + if (metaData == null) { return null; } - QueryOptions queryOptions = this.vaultPermission.getQueryOptions(null, world); - MetaCache metaData = group.getCachedData().getMetaData(queryOptions); return metaData.getMetaValue(key, MetaCheckEvent.Origin.THIRD_PARTY_API); } @@ -228,6 +222,15 @@ private Group getGroup(String name) { return this.plugin.getGroupManager().getByDisplayName(name); } + private MetaCache getGroupMetaCache(String name, String world) { + Group group = getGroup(name); + if (group == null) { + return null; + } + QueryOptions queryOptions = this.vaultPermission.getQueryOptions(null, world); + return group.getCachedData().getMetaData(queryOptions); + } + private void setChatMeta(PermissionHolder holder, ChatMetaType type, String value, String world) { // remove all prefixes/suffixes directly set on the user/group holder.removeIf(DataType.NORMAL, null, type.nodeType()::matches, false); diff --git a/common/build.gradle b/common/build.gradle index 5d6929100..68fc40308 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,5 +1,7 @@ test { - useJUnitPlatform() + useJUnitPlatform { + excludeTags 'dependency_checksum' + } } dependencies { 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 a1df1622a..f167c8e21 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 @@ -100,7 +100,7 @@ public void dispatchFromApi(LoggedAction entry) { try { this.plugin.getStorage().logAction(entry).get(); } catch (Exception e) { - plugin.getLogger().warn("Error whilst storing action", e); + this.plugin.getLogger().warn("Error whilst storing action", e); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlayerAdapter.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlayerAdapter.java index a70b7d469..bb645f9a6 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlayerAdapter.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPlayerAdapter.java @@ -37,7 +37,6 @@ import java.util.Objects; -@SuppressWarnings({"unchecked", "rawtypes"}) public class ApiPlayerAdapter implements PlayerAdapter

{ private final UserManager userManager; private final ContextManager contextManager; diff --git a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java index 46d0184b1..b326b7aa9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java +++ b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java @@ -263,7 +263,7 @@ protected void processOutput(JsonObject json) { } try { - String pasteId = this.plugin.getBytebin().postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE, false).key(); + String pasteId = this.plugin.getBytebin().postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE).key(); this.log.getListeners().forEach(l -> Message.EXPORT_WEB_SUCCESS.send(l, pasteId, this.label)); } catch (UnsuccessfulRequestException e) { this.log.getListeners().forEach(l -> Message.HTTP_REQUEST_FAILURE.send(l, e.getResponse().code(), e.getResponse().message())); 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 742c0a2b5..cf849296e 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 @@ -124,7 +124,7 @@ private ConfigKeys() {} */ public static final ConfigKey CONTEXT_SATISFY_MODE = key(c -> { String value = c.getString("context-satisfy-mode", "at-least-one-value-per-key"); - if (value.toLowerCase().equals("all-values-per-key")) { + if (value.equalsIgnoreCase("all-values-per-key")) { return ContextSatisfyMode.ALL_VALUES_PER_KEY; } return ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY; @@ -199,11 +199,11 @@ private ConfigKeys() {} String option = PRIMARY_GROUP_CALCULATION_METHOD.get(c); switch (option) { case "stored": - return (Function) PrimaryGroupHolder.Stored::new; + return PrimaryGroupHolder.Stored::new; case "parents-by-weight": - return (Function) PrimaryGroupHolder.ParentsByWeight::new; + return PrimaryGroupHolder.ParentsByWeight::new; default: - return (Function) PrimaryGroupHolder.AllParentsByWeight::new; + return PrimaryGroupHolder.AllParentsByWeight::new; } })); diff --git a/common/src/main/java/me/lucko/luckperms/common/config/generic/adapter/ConfigurateConfigAdapter.java b/common/src/main/java/me/lucko/luckperms/common/config/generic/adapter/ConfigurateConfigAdapter.java index d43264e20..3a02cfce3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/config/generic/adapter/ConfigurateConfigAdapter.java +++ b/common/src/main/java/me/lucko/luckperms/common/config/generic/adapter/ConfigurateConfigAdapter.java @@ -88,7 +88,7 @@ public boolean getBoolean(String path, boolean def) { @Override public List getStringList(String path, List def) { ConfigurationNode node = resolvePath(path); - if (node.isVirtual() || !node.hasListChildren()) { + if (node.isVirtual() || !node.isList()) { return def; } @@ -98,7 +98,7 @@ public List getStringList(String path, List def) { @Override public List getKeys(String path, List def) { ConfigurationNode node = resolvePath(path); - if (node.isVirtual() || !node.hasMapChildren()) { + if (node.isVirtual() || !node.isMap()) { return def; } diff --git a/common/src/main/java/me/lucko/luckperms/common/context/ContextSetConfigurateSerializer.java b/common/src/main/java/me/lucko/luckperms/common/context/ContextSetConfigurateSerializer.java index 325346022..dffaf87b5 100644 --- a/common/src/main/java/me/lucko/luckperms/common/context/ContextSetConfigurateSerializer.java +++ b/common/src/main/java/me/lucko/luckperms/common/context/ContextSetConfigurateSerializer.java @@ -34,7 +34,6 @@ import net.luckperms.api.context.MutableContextSet; import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.SimpleConfigurationNode; import java.util.ArrayList; import java.util.List; @@ -45,7 +44,7 @@ public final class ContextSetConfigurateSerializer { private ContextSetConfigurateSerializer() {} public static ConfigurationNode serializeContextSet(ContextSet contextSet) { - ConfigurationNode data = SimpleConfigurationNode.root(); + ConfigurationNode data = ConfigurationNode.root(); Map> map = contextSet.toMap(); for (Map.Entry> entry : map.entrySet()) { @@ -63,7 +62,7 @@ public static ConfigurationNode serializeContextSet(ContextSet contextSet) { } public static ContextSet deserializeContextSet(ConfigurationNode data) { - Preconditions.checkArgument(data.hasMapChildren()); + Preconditions.checkArgument(data.isMap()); Map dataMap = data.getChildrenMap(); if (dataMap.isEmpty()) { @@ -75,9 +74,8 @@ public static ContextSet deserializeContextSet(ConfigurationNode data) { String k = e.getKey().toString(); ConfigurationNode v = e.getValue(); - if (v.hasListChildren()) { - List values = v.getChildrenList(); - for (ConfigurationNode value : values) { + if (v.isList()) { + for (ConfigurationNode value : v.getChildrenList()) { map.add(k, value.getString()); } } else { diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java index 3cd56d50e..359b8e9aa 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java @@ -340,37 +340,4 @@ public static MessageDigest createDigest() { } } - /* - public static void main(String[] args) { - Dependency[] dependencies = values(); - DependencyRepository[] repos = DependencyRepository.values(); - - java.util.concurrent.ExecutorService pool = java.util.concurrent.Executors.newCachedThreadPool(); - - for (Dependency dependency : dependencies) { - for (DependencyRepository repo : repos) { - pool.submit(() -> { - try { - byte[] hash = createDigest().digest(repo.downloadRaw(dependency)); - if (!dependency.checksumMatches(hash)) { - System.out.println("NO MATCH - " + repo.name() + " - " + dependency.name() + ": " + Base64.getEncoder().encodeToString(hash)); - } else { - System.out.println("OK - " + repo.name() + " - " + dependency.name()); - } - } catch (Exception e) { - e.printStackTrace(); - } - }); - } - } - - pool.shutdown(); - try { - pool.awaitTermination(1, java.util.concurrent.TimeUnit.HOURS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - */ - } diff --git a/common/src/main/java/me/lucko/luckperms/common/http/BytebinClient.java b/common/src/main/java/me/lucko/luckperms/common/http/BytebinClient.java index 619452ef4..94326efd4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/http/BytebinClient.java +++ b/common/src/main/java/me/lucko/luckperms/common/http/BytebinClient.java @@ -83,11 +83,10 @@ public Response makeHttpRequest(Request request) throws IOException, Unsuccessfu * * @param buf the compressed content * @param contentType the type of the content - * @param allowModification if the paste should be modifiable * @return the key of the resultant content * @throws IOException if an error occurs */ - public Content postContent(byte[] buf, MediaType contentType, boolean allowModification) throws IOException, UnsuccessfulRequestException { + public Content postContent(byte[] buf, MediaType contentType) throws IOException, UnsuccessfulRequestException { RequestBody body = RequestBody.create(contentType, buf); Request.Builder requestBuilder = new Request.Builder() @@ -95,52 +94,14 @@ public Content postContent(byte[] buf, MediaType contentType, boolean allowModif .header("User-Agent", this.userAgent) .header("Content-Encoding", "gzip"); - if (allowModification) { - requestBuilder.header("Allow-Modification", "true"); - } - Request request = requestBuilder.post(body).build(); try (Response response = makeHttpRequest(request)) { String key = response.header("Location"); if (key == null) { throw new IllegalStateException("Key not returned"); } - - if (allowModification) { - String modificationKey = response.header("Modification-Key"); - if (modificationKey == null) { - throw new IllegalStateException("Modification key not returned"); - } - return new Content(key, modificationKey); - } else { - return new Content(key); - } - } - } - - /** - * PUTs modified GZIP compressed content to bytebin in place of existing content. - * - * @param existingContent the existing content - * @param buf the compressed content to put - * @param contentType the type of the content - * @throws IOException if an error occurs - */ - public void modifyContent(Content existingContent, byte[] buf, MediaType contentType) throws IOException, UnsuccessfulRequestException { - if (!existingContent.modifiable) { - throw new IllegalArgumentException("Existing content is not modifiable"); + return new Content(key); } - - RequestBody body = RequestBody.create(contentType, buf); - - Request.Builder requestBuilder = new Request.Builder() - .url(this.url + existingContent.key()) - .header("User-Agent", this.userAgent) - .header("Content-Encoding", "gzip") - .header("Modification-Key", existingContent.modificationKey); - - Request request = requestBuilder.put(body).build(); - makeHttpRequest(request).close(); } /** @@ -173,19 +134,9 @@ public JsonElement getJsonContent(String id) throws IOException, UnsuccessfulReq public static final class Content { private final String key; - private final boolean modifiable; - private final String modificationKey; Content(String key) { this.key = key; - this.modifiable = false; - this.modificationKey = null; - } - - Content(String key, String modificationKey) { - this.key = key; - this.modifiable = true; - this.modificationKey = modificationKey; } public String key() { diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java b/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java index 3f320ca85..b71a98bc7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/TranslationRepository.java @@ -320,10 +320,5 @@ public int read(byte[] b, int off, int len) throws IOException { } return res; } - - @Override - public void close() throws IOException { - super.close(); - } } } diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java b/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java index 0e70054b3..3c3d226a0 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java @@ -64,7 +64,7 @@ public void init(String address, String password, boolean ssl) { this.jedisPool = new JedisPool(new JedisPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT, password, ssl); this.sub = new Subscription(this); - this.plugin.getBootstrap().getScheduler().executeAsync(sub); + this.plugin.getBootstrap().getScheduler().executeAsync(this.sub); } @Override @@ -95,13 +95,13 @@ public void run() { while (!Thread.interrupted() && !this.parent.jedisPool.isClosed()) { try (Jedis jedis = this.parent.jedisPool.getResource()) { if (wasBroken) { - parent.plugin.getLogger().info("Redis pubsub connection re-established"); + this.parent.plugin.getLogger().info("Redis pubsub connection re-established"); wasBroken = false; } jedis.subscribe(this, CHANNEL); } catch (Exception e) { wasBroken = true; - parent.plugin.getLogger().warn("Redis pubsub connection dropped, trying to re-open the connection: " + e.getMessage()); + this.parent.plugin.getLogger().warn("Redis pubsub connection dropped, trying to re-open the connection: " + e.getMessage()); try { unsubscribe(); } catch (Exception ignored) { diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java index c268fa031..a80515bb5 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/AbstractConfigurateStorage.java @@ -25,9 +25,7 @@ package me.lucko.luckperms.common.storage.implementation.file; -import com.google.common.base.Throwables; import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; import me.lucko.luckperms.common.actionlog.Log; import me.lucko.luckperms.common.bulkupdate.BulkUpdate; @@ -48,7 +46,6 @@ import me.lucko.luckperms.common.storage.implementation.file.loader.ConfigurateLoader; import me.lucko.luckperms.common.storage.implementation.file.loader.JsonLoader; import me.lucko.luckperms.common.storage.implementation.file.loader.YamlLoader; -import me.lucko.luckperms.common.util.ImmutableCollectors; import me.lucko.luckperms.common.util.MoreFiles; import net.luckperms.api.actionlog.Action; @@ -63,53 +60,51 @@ import net.luckperms.api.node.types.MetaNode; import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.SimpleConfigurationNode; import ninja.leaping.configurate.Types; import java.io.IOException; import java.nio.file.Path; import java.time.Instant; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.function.Function; /** - * Abstract implementation using configurate {@link ConfigurationNode}s to serialize and deserialize - * data. + * Abstract storage implementation using Configurate {@link ConfigurationNode}s to + * serialize and deserialize data. */ public abstract class AbstractConfigurateStorage implements StorageImplementation { - + /** The plugin instance */ protected final LuckPermsPlugin plugin; + + /** The name of this implementation */ private final String implementationName; - // the loader responsible for i/o + /** The Configurate loader used to read/write data */ protected final ConfigurateLoader loader; - // the name of the data directory - private final String dataDirectoryName; - // the data directory + /* The data directory */ protected Path dataDirectory; + private final String dataDirectoryName; - // the uuid cache instance - private final FileUuidCache uuidCache = new FileUuidCache(); - // the action logger instance - private final FileActionLogger actionLogger; + /* The UUID cache */ + private final FileUuidCache uuidCache; + private Path uuidCacheFile; - // the file used to store uuid data - private Path uuidDataFile; + /** The action logger */ + private final FileActionLogger actionLogger; protected AbstractConfigurateStorage(LuckPermsPlugin plugin, String implementationName, ConfigurateLoader loader, String dataDirectoryName) { this.plugin = plugin; this.implementationName = implementationName; this.loader = loader; this.dataDirectoryName = dataDirectoryName; + + this.uuidCache = new FileUuidCache(); this.actionLogger = new FileActionLogger(plugin); } @@ -143,27 +138,23 @@ public String getImplementationName() { */ protected abstract void saveFile(StorageLocation location, String name, ConfigurationNode node) throws IOException; - // used to report i/o exceptions which took place in a specific file - protected RuntimeException reportException(String file, Exception ex) throws RuntimeException { - this.plugin.getLogger().warn("Exception thrown whilst performing i/o: " + file, ex); - Throwables.throwIfUnchecked(ex); - throw new RuntimeException(ex); - } - @Override public void init() throws IOException { + // init the data directory and ensure it exists this.dataDirectory = this.plugin.getBootstrap().getDataDirectory().resolve(this.dataDirectoryName); MoreFiles.createDirectoriesIfNotExists(this.dataDirectory); - this.uuidDataFile = MoreFiles.createFileIfNotExists(this.dataDirectory.resolve("uuidcache.txt")); - this.uuidCache.load(this.uuidDataFile); + // setup the uuid cache + this.uuidCacheFile = MoreFiles.createFileIfNotExists(this.dataDirectory.resolve("uuidcache.txt")); + this.uuidCache.load(this.uuidCacheFile); + // setup the action logger this.actionLogger.init(this.dataDirectory.resolve("actions.txt"), this.dataDirectory.resolve("actions.json")); } @Override public void shutdown() { - this.uuidCache.save(this.uuidDataFile); + this.uuidCache.save(this.uuidCacheFile); this.actionLogger.flush(); } @@ -177,29 +168,19 @@ public Log getLog() throws IOException { return this.actionLogger.getLog(); } - protected boolean processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode node, HolderType holderType) { - Set nodes = readNodes(node); - Set results = bulkUpdate.apply(nodes, holderType); - - if (results == null) { - return false; - } - - writeNodes(node, results); - return true; - } - @Override - public User loadUser(UUID uniqueId, String username) { + public User loadUser(UUID uniqueId, String username) throws IOException { User user = this.plugin.getUserManager().getOrMake(uniqueId, username); try { - ConfigurationNode object = readFile(StorageLocation.USER, uniqueId.toString()); - if (object != null) { - String name = object.getNode("name").getString(); - user.getPrimaryGroup().setStoredValue(object.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").getString()); + ConfigurationNode file = readFile(StorageLocation.USERS, uniqueId.toString()); + if (file != null) { + String name = file.getNode("name").getString(); + String primaryGroup = file.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").getString(); + + user.getPrimaryGroup().setStoredValue(primaryGroup); user.setUsername(name, true); - user.loadNodesFromStorage(readNodes(object)); + user.loadNodesFromStorage(readNodes(file)); this.plugin.getUserManager().giveDefaultIfNeeded(user); boolean updatedUsername = user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name)); @@ -214,166 +195,159 @@ public User loadUser(UUID uniqueId, String username) { } } } catch (Exception e) { - throw reportException(uniqueId.toString(), e); + throw new FileIOException(uniqueId.toString(), e); } return user; } @Override - public void saveUser(User user) { + public void saveUser(User user) throws IOException { user.normalData().discardChanges(); try { if (!this.plugin.getUserManager().isNonDefaultUser(user)) { - saveFile(StorageLocation.USER, user.getUniqueId().toString(), null); + saveFile(StorageLocation.USERS, user.getUniqueId().toString(), null); } else { - ConfigurationNode data = SimpleConfigurationNode.root(); + ConfigurationNode file = ConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { - data.getNode("uuid").setValue(user.getUniqueId().toString()); + file.getNode("uuid").setValue(user.getUniqueId().toString()); } - data.getNode("name").setValue(user.getUsername().orElse("null")); - data.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").setValue(user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME)); - writeNodes(data, user.normalData().asList()); - saveFile(StorageLocation.USER, user.getUniqueId().toString(), data); + String name = user.getUsername().orElse("null"); + String primaryGroup = user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME); + + file.getNode("name").setValue(name); + file.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").setValue(primaryGroup); + + writeNodes(file, user.normalData().asList()); + saveFile(StorageLocation.USERS, user.getUniqueId().toString(), file); } } catch (Exception e) { - throw reportException(user.getUniqueId().toString(), e); + throw new FileIOException(user.getUniqueId().toString(), e); } } @Override - public Group createAndLoadGroup(String name) { + public Group createAndLoadGroup(String name) throws IOException { Group group = this.plugin.getGroupManager().getOrMake(name); try { - ConfigurationNode object = readFile(StorageLocation.GROUP, name); + ConfigurationNode file = readFile(StorageLocation.GROUPS, name); - if (object != null) { - group.loadNodesFromStorage(readNodes(object)); + if (file != null) { + group.loadNodesFromStorage(readNodes(file)); } else { - ConfigurationNode data = SimpleConfigurationNode.root(); + file = ConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { - data.getNode("name").setValue(group.getName()); + file.getNode("name").setValue(group.getName()); } - writeNodes(data, group.normalData().asList()); - saveFile(StorageLocation.GROUP, name, data); + writeNodes(file, group.normalData().asList()); + saveFile(StorageLocation.GROUPS, name, file); } } catch (Exception e) { - throw reportException(name, e); + throw new FileIOException(name, e); } return group; } @Override - public Optional loadGroup(String name) { + public Optional loadGroup(String name) throws IOException { try { - ConfigurationNode object = readFile(StorageLocation.GROUP, name); - - if (object == null) { + ConfigurationNode file = readFile(StorageLocation.GROUPS, name); + if (file == null) { return Optional.empty(); } Group group = this.plugin.getGroupManager().getOrMake(name); - group.loadNodesFromStorage(readNodes(object)); + group.loadNodesFromStorage(readNodes(file)); return Optional.of(group); } catch (Exception e) { - throw reportException(name, e); + throw new FileIOException(name, e); } } @Override - public void saveGroup(Group group) { + public void saveGroup(Group group) throws IOException { group.normalData().discardChanges(); try { - ConfigurationNode data = SimpleConfigurationNode.root(); + ConfigurationNode file = ConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { - data.getNode("name").setValue(group.getName()); + file.getNode("name").setValue(group.getName()); } - writeNodes(data, group.normalData().asList()); - saveFile(StorageLocation.GROUP, group.getName(), data); + writeNodes(file, group.normalData().asList()); + saveFile(StorageLocation.GROUPS, group.getName(), file); } catch (Exception e) { - throw reportException(group.getName(), e); + throw new FileIOException(group.getName(), e); } } @Override - public void deleteGroup(Group group) { + public void deleteGroup(Group group) throws IOException { try { - saveFile(StorageLocation.GROUP, group.getName(), null); + saveFile(StorageLocation.GROUPS, group.getName(), null); } catch (Exception e) { - throw reportException(group.getName(), e); + throw new FileIOException(group.getName(), e); } this.plugin.getGroupManager().unload(group.getName()); } @Override - public Track createAndLoadTrack(String name) { + public Track createAndLoadTrack(String name) throws IOException { Track track = this.plugin.getTrackManager().getOrMake(name); try { - ConfigurationNode object = readFile(StorageLocation.TRACK, name); - - if (object != null) { - List groups = object.getNode("groups").getChildrenList().stream() - .map(ConfigurationNode::getString) - .collect(ImmutableCollectors.toList()); - - track.setGroups(groups); + ConfigurationNode file = readFile(StorageLocation.TRACKS, name); + if (file != null) { + track.setGroups(file.getNode("groups").getList(Types::asString)); } else { - ConfigurationNode data = SimpleConfigurationNode.root(); + file = ConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { - data.getNode("name").setValue(name); + file.getNode("name").setValue(name); } - data.getNode("groups").setValue(track.getGroups()); - saveFile(StorageLocation.TRACK, name, data); + file.getNode("groups").setValue(track.getGroups()); + saveFile(StorageLocation.TRACKS, name, file); } - } catch (Exception e) { - throw reportException(name, e); + throw new FileIOException(name, e); } return track; } @Override - public Optional loadTrack(String name) { + public Optional loadTrack(String name) throws IOException { try { - ConfigurationNode object = readFile(StorageLocation.TRACK, name); - - if (object == null) { + ConfigurationNode file = readFile(StorageLocation.TRACKS, name); + if (file == null) { return Optional.empty(); } Track track = this.plugin.getTrackManager().getOrMake(name); - List groups = object.getNode("groups").getChildrenList().stream() - .map(ConfigurationNode::getString) - .collect(ImmutableCollectors.toList()); - track.setGroups(groups); + track.setGroups(file.getNode("groups").getList(Types::asString)); return Optional.of(track); } catch (Exception e) { - throw reportException(name, e); + throw new FileIOException(name, e); } } @Override - public void saveTrack(Track track) { + public void saveTrack(Track track) throws IOException { try { - ConfigurationNode data = SimpleConfigurationNode.root(); + ConfigurationNode file = ConfigurationNode.root(); if (this instanceof SeparatedConfigurateStorage) { - data.getNode("name").setValue(track.getName()); + file.getNode("name").setValue(track.getName()); } - data.getNode("groups").setValue(track.getGroups()); - saveFile(StorageLocation.TRACK, track.getName(), data); + file.getNode("groups").setValue(track.getGroups()); + saveFile(StorageLocation.TRACKS, track.getName(), file); } catch (Exception e) { - throw reportException(track.getName(), e); + throw new FileIOException(track.getName(), e); } } @Override - public void deleteTrack(Track track) { + public void deleteTrack(Track track) throws IOException { try { - saveFile(StorageLocation.TRACK, track.getName(), null); + saveFile(StorageLocation.TRACKS, track.getName(), null); } catch (Exception e) { - throw reportException(track.getName(), e); + throw new FileIOException(track.getName(), e); } this.plugin.getTrackManager().unload(track.getName()); } @@ -398,71 +372,54 @@ public String getPlayerName(UUID uniqueId) { return this.uuidCache.lookupUsername(uniqueId); } + protected boolean processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode node, HolderType holderType) { + Set nodes = readNodes(node); + Set results = bulkUpdate.apply(nodes, holderType); + + if (results == null) { + return false; + } + + writeNodes(node, results); + return true; + } + private static ImmutableContextSet readContexts(ConfigurationNode attributes) { ImmutableContextSet.Builder contextBuilder = new ImmutableContextSetImpl.BuilderImpl(); ConfigurationNode contextMap = attributes.getNode("context"); - if (!contextMap.isVirtual() && contextMap.hasMapChildren()) { + if (!contextMap.isVirtual() && contextMap.isMap()) { contextBuilder.addAll(ContextSetConfigurateSerializer.deserializeContextSet(contextMap)); } String server = attributes.getNode("server").getString("global"); - if (!server.equals("global")) { - contextBuilder.add(DefaultContextKeys.SERVER_KEY, server); - } + contextBuilder.add(DefaultContextKeys.SERVER_KEY, server); String world = attributes.getNode("world").getString("global"); - if (!world.equals("global")) { - contextBuilder.add(DefaultContextKeys.WORLD_KEY, world); - } + contextBuilder.add(DefaultContextKeys.WORLD_KEY, world); return contextBuilder.build(); } - private static Node readMetaAttributes(ConfigurationNode attributes, Function> permissionFunction) { + private static Node readAttributes(NodeBuilder builder, ConfigurationNode attributes) { long expiryVal = attributes.getNode("expiry").getLong(0L); Instant expiry = expiryVal == 0L ? null : Instant.ofEpochSecond(expiryVal); ImmutableContextSet context = readContexts(attributes); - return permissionFunction.apply(attributes) - .expiry(expiry) - .context(context) - .build(); + return builder.expiry(expiry).context(context).build(); } - private static Collection readAttributes(ConfigurationNode attributes, String permission) { - boolean value = attributes.getNode("value").getBoolean(true); - long expiryVal = attributes.getNode("expiry").getLong(0L); - Instant expiry = expiryVal == 0L ? null : Instant.ofEpochSecond(expiryVal); - ImmutableContextSet context = readContexts(attributes); + private static final class NodeEntry { + final String key; + final ConfigurationNode attributes; - ConfigurationNode batchAttribute = attributes.getNode("permissions"); - if (permission.startsWith("luckperms.batch") && !batchAttribute.isVirtual() && batchAttribute.hasListChildren()) { - List nodes = new ArrayList<>(); - for (ConfigurationNode element : batchAttribute.getChildrenList()) { - Node node = NodeBuilders.determineMostApplicable(element.getString()) - .value(value) - .expiry(expiry) - .context(context) - .build(); - nodes.add(node); - } - return nodes; - } else { - Node node = NodeBuilders.determineMostApplicable(permission) - .value(value) - .expiry(expiry) - .context(context) - .build(); - return Collections.singleton(node); + private NodeEntry(String key, ConfigurationNode attributes) { + this.key = key; + this.attributes = attributes; } } - private static Map.Entry parseEntry(ConfigurationNode appended, String keyFieldName) { - if (!appended.hasMapChildren()) { - return null; - } - - Map children = appended.getChildrenMap(); + private static NodeEntry parseNode(ConfigurationNode configNode, String keyFieldName) { + Map children = configNode.getChildrenMap(); if (children.isEmpty()) { return null; } @@ -476,88 +433,93 @@ private static Map.Entry parseEntry(ConfigurationNode ConfigurationNode attributes = entry.getValue(); if (!permission.equals(keyFieldName)) { - return Maps.immutableEntry(permission, attributes); + return new NodeEntry(permission, attributes); } } } - // assume 'appended' is the actual entry. + // assume 'configNode' is the actual entry. String permission = children.get(keyFieldName).getString(null); if (permission == null) { return null; } - return Maps.immutableEntry(permission, appended); + return new NodeEntry(permission, configNode); } protected static Set readNodes(ConfigurationNode data) { Set nodes = new HashSet<>(); - if (data.getNode("permissions").hasListChildren()) { - List children = data.getNode("permissions").getChildrenList(); - for (ConfigurationNode appended : children) { - String plainValue = appended.getValue(Types::strictAsString); - if (plainValue != null) { - nodes.add(NodeBuilders.determineMostApplicable(plainValue).build()); - continue; - } + for (ConfigurationNode appended : data.getNode("permissions").getChildrenList()) { + String plainValue = appended.getValue(Types::strictAsString); + if (plainValue != null) { + nodes.add(NodeBuilders.determineMostApplicable(plainValue).build()); + continue; + } - Map.Entry entry = parseEntry(appended, "permission"); - if (entry == null) { - continue; - } - nodes.addAll(readAttributes(entry.getValue(), entry.getKey())); + NodeEntry entry = parseNode(appended, "permission"); + if (entry == null) { + continue; } + + nodes.add(readAttributes( + NodeBuilders.determineMostApplicable(entry.key).value(entry.attributes.getNode("value").getBoolean(true)), + entry.attributes + )); } - if (data.getNode("parents").hasListChildren()) { - List children = data.getNode("parents").getChildrenList(); - for (ConfigurationNode appended : children) { - String plainValue = appended.getValue(Types::strictAsString); - if (plainValue != null) { - nodes.add(Inheritance.builder(plainValue).build()); - continue; - } + for (ConfigurationNode appended : data.getNode("parents").getChildrenList()) { + String plainValue = appended.getValue(Types::strictAsString); + if (plainValue != null) { + nodes.add(Inheritance.builder(plainValue).build()); + continue; + } - Map.Entry entry = parseEntry(appended, "group"); - if (entry == null) { - continue; - } - nodes.add(readMetaAttributes(entry.getValue(), c -> Inheritance.builder(entry.getKey()))); + NodeEntry entry = parseNode(appended, "group"); + if (entry == null) { + continue; } + + nodes.add(readAttributes( + Inheritance.builder(entry.key), + entry.attributes + )); } - if (data.getNode("prefixes").hasListChildren()) { - List children = data.getNode("prefixes").getChildrenList(); - for (ConfigurationNode appended : children) { - Map.Entry entry = parseEntry(appended, "prefix"); - if (entry == null) { - continue; - } - nodes.add(readMetaAttributes(entry.getValue(), c -> Prefix.builder(entry.getKey(), c.getNode("priority").getInt(0)))); + for (ConfigurationNode appended : data.getNode("prefixes").getChildrenList()) { + NodeEntry entry = parseNode(appended, "prefix"); + if (entry == null) { + continue; } + + nodes.add(readAttributes( + Prefix.builder(entry.key, entry.attributes.getNode("priority").getInt(0)), + entry.attributes + )); } - if (data.getNode("suffixes").hasListChildren()) { - List children = data.getNode("suffixes").getChildrenList(); - for (ConfigurationNode appended : children) { - Map.Entry entry = parseEntry(appended, "suffix"); - if (entry == null) { - continue; - } - nodes.add(readMetaAttributes(entry.getValue(), c -> Suffix.builder(entry.getKey(), c.getNode("priority").getInt(0)))); + for (ConfigurationNode appended : data.getNode("suffixes").getChildrenList()) { + NodeEntry entry = parseNode(appended, "suffix"); + if (entry == null) { + continue; } + + nodes.add(readAttributes( + Suffix.builder(entry.key, entry.attributes.getNode("priority").getInt(0)), + entry.attributes + )); } - if (data.getNode("meta").hasListChildren()) { - List children = data.getNode("meta").getChildrenList(); - for (ConfigurationNode appended : children) { - Map.Entry entry = parseEntry(appended, "key"); - if (entry == null) { - continue; - } - nodes.add(readMetaAttributes(entry.getValue(), c -> Meta.builder(entry.getKey(), c.getNode("value").getString("null")))); + for (ConfigurationNode appended : data.getNode("meta").getChildrenList()) { + NodeEntry entry = parseNode(appended, "key"); + if (entry == null) { + continue; } + + nodes.add(readAttributes( + Meta.builder(entry.key, entry.attributes.getNode("value").getString("null")), + entry.attributes + )); } return nodes; @@ -582,7 +544,7 @@ private static boolean isPlain(Node node) { } private void appendNode(ConfigurationNode base, String key, ConfigurationNode attributes, String keyFieldName) { - ConfigurationNode appended = base.getAppendedNode(); + ConfigurationNode appended = base.appendListNode(); if (this.loader instanceof YamlLoader) { // create a map node with a single entry of key --> attributes appended.getNode(key).setValue(attributes); @@ -594,7 +556,7 @@ private void appendNode(ConfigurationNode base, String key, ConfigurationNode at } private void writeNodes(ConfigurationNode to, Collection nodes) { - ConfigurationNode permissionsSection = SimpleConfigurationNode.root(); + ConfigurationNode permissionsSection = ConfigurationNode.root(); // ensure for CombinedConfigurateStorage that there's at least *something* // to save to the file even if it's just an empty list. @@ -602,20 +564,20 @@ private void writeNodes(ConfigurationNode to, Collection nodes) { permissionsSection.setValue(Collections.emptyList()); } - ConfigurationNode parentsSection = SimpleConfigurationNode.root(); - ConfigurationNode prefixesSection = SimpleConfigurationNode.root(); - ConfigurationNode suffixesSection = SimpleConfigurationNode.root(); - ConfigurationNode metaSection = SimpleConfigurationNode.root(); + ConfigurationNode parentsSection = ConfigurationNode.root(); + ConfigurationNode prefixesSection = ConfigurationNode.root(); + ConfigurationNode suffixesSection = ConfigurationNode.root(); + ConfigurationNode metaSection = ConfigurationNode.root(); for (Node n : nodes) { // just add a string to the list. if (this.loader instanceof YamlLoader && isPlain(n)) { if (n instanceof InheritanceNode) { - parentsSection.getAppendedNode().setValue(((InheritanceNode) n).getGroupName()); + parentsSection.appendListNode().setValue(((InheritanceNode) n).getGroupName()); continue; } if (!NodeType.META_OR_CHAT_META.matches(n)) { - permissionsSection.getAppendedNode().setValue(n.getKey()); + permissionsSection.appendListNode().setValue(n.getKey()); continue; } } @@ -624,7 +586,7 @@ private void writeNodes(ConfigurationNode to, Collection nodes) { // handle prefixes / suffixes ChatMetaNode chatMeta = (ChatMetaNode) n; - ConfigurationNode attributes = SimpleConfigurationNode.root(); + ConfigurationNode attributes = ConfigurationNode.root(); attributes.getNode("priority").setValue(chatMeta.getPriority()); writeAttributesTo(attributes, n, false); @@ -642,7 +604,7 @@ private void writeNodes(ConfigurationNode to, Collection nodes) { // handle meta nodes MetaNode meta = (MetaNode) n; - ConfigurationNode attributes = SimpleConfigurationNode.root(); + ConfigurationNode attributes = ConfigurationNode.root(); attributes.getNode("value").setValue(meta.getMetaValue()); writeAttributesTo(attributes, n, false); @@ -651,44 +613,44 @@ private void writeNodes(ConfigurationNode to, Collection nodes) { // handle group nodes InheritanceNode inheritance = (InheritanceNode) n; - ConfigurationNode attributes = SimpleConfigurationNode.root(); + ConfigurationNode attributes = ConfigurationNode.root(); writeAttributesTo(attributes, n, false); appendNode(parentsSection, inheritance.getGroupName(), attributes, "group"); } else { // handle regular permissions and negated meta+prefixes+suffixes - ConfigurationNode attributes = SimpleConfigurationNode.root(); + ConfigurationNode attributes = ConfigurationNode.root(); writeAttributesTo(attributes, n, true); appendNode(permissionsSection, n.getKey(), attributes, "permission"); } } - if (permissionsSection.hasListChildren() || this instanceof CombinedConfigurateStorage) { + if (permissionsSection.isList() || this instanceof CombinedConfigurateStorage) { to.getNode("permissions").setValue(permissionsSection); } else { to.removeChild("permissions"); } - if (parentsSection.hasListChildren()) { + if (parentsSection.isList()) { to.getNode("parents").setValue(parentsSection); } else { to.removeChild("parents"); } - if (prefixesSection.hasListChildren()) { + if (prefixesSection.isList()) { to.getNode("prefixes").setValue(prefixesSection); } else { to.removeChild("prefixes"); } - if (suffixesSection.hasListChildren()) { + if (suffixesSection.isList()) { to.getNode("suffixes").setValue(suffixesSection); } else { to.removeChild("suffixes"); } - if (metaSection.hasListChildren()) { + if (metaSection.isList()) { to.getNode("meta").setValue(metaSection); } else { to.removeChild("meta"); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/CombinedConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/CombinedConfigurateStorage.java index 89ad99df3..aaa58dc1d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/CombinedConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/CombinedConfigurateStorage.java @@ -52,106 +52,18 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +/** + * Flat-file storage using Configurate {@link ConfigurationNode}s. + * The data for users/groups/tracks is stored in a single shared file. + */ public class CombinedConfigurateStorage extends AbstractConfigurateStorage { private final String fileExtension; - private Path usersFile; - private Path groupsFile; - private Path tracksFile; - - private CachedLoader usersLoader; - private CachedLoader groupsLoader; - private CachedLoader tracksLoader; - - private final class CachedLoader { - private final Path path; - - private final ConfigurationLoader loader; - private ConfigurationNode node = null; - private final ReentrantLock lock = new ReentrantLock(); - - private CachedLoader(Path path) { - this.path = path; - this.loader = CombinedConfigurateStorage.super.loader.loader(path); - reload(); - } - - private void recordChange() { - if (CombinedConfigurateStorage.this.watcher != null) { - CombinedConfigurateStorage.this.watcher.recordChange(this.path.getFileName().toString()); - } - } - - public ConfigurationNode getNode() throws IOException { - this.lock.lock(); - try { - if (this.node == null) { - this.node = this.loader.load(); - } - - return this.node; - } finally { - this.lock.unlock(); - } - } - - public void apply(Consumer action) throws IOException { - apply(false, false, action); - } - - public void apply(boolean save, boolean reload, Consumer action) throws IOException { - this.lock.lock(); - try { - if (this.node == null || reload) { - reload(); - } - - action.accept(this.node); - - if (save) { - save(); - } - } finally { - this.lock.unlock(); - } - } - - public void save() throws IOException { - this.lock.lock(); - try { - recordChange(); - this.loader.save(this.node); - } finally { - this.lock.unlock(); - } - } - - public void reload() { - this.lock.lock(); - try { - this.node = null; - try { - recordChange(); - this.node = this.loader.load(); - } catch (IOException e) { - e.printStackTrace(); - } - } finally { - this.lock.unlock(); - } - } - } - + private CachedLoader users; + private CachedLoader groups; + private CachedLoader tracks; private FileWatcher.WatchedLocation watcher = null; - /** - * Creates a new configurate storage implementation - * - * @param plugin the plugin instance - * @param implementationName the name of this implementation - * @param fileExtension the file extension used by this instance, including a "." at the start - * @param dataFolderName the name of the folder used to store data - */ public CombinedConfigurateStorage(LuckPermsPlugin plugin, String implementationName, ConfigurateLoader loader, String fileExtension, String dataFolderName) { super(plugin, implementationName, loader, dataFolderName); this.fileExtension = fileExtension; @@ -159,24 +71,24 @@ public CombinedConfigurateStorage(LuckPermsPlugin plugin, String implementationN @Override protected ConfigurationNode readFile(StorageLocation location, String name) throws IOException { - ConfigurationNode root = getStorageLoader(location).getNode(); + ConfigurationNode root = getLoader(location).getNode(); ConfigurationNode node = root.getNode(name); return node.isVirtual() ? null : node; } @Override protected void saveFile(StorageLocation location, String name, ConfigurationNode node) throws IOException { - getStorageLoader(location).apply(true, false, root -> root.getNode(name).setValue(node)); + getLoader(location).apply(true, false, root -> root.getNode(name).setValue(node)); } - private CachedLoader getStorageLoader(StorageLocation location) { + private CachedLoader getLoader(StorageLocation location) { switch (location) { - case USER: - return this.usersLoader; - case GROUP: - return this.groupsLoader; - case TRACK: - return this.tracksLoader; + case USERS: + return this.users; + case GROUPS: + return this.groups; + case TRACKS: + return this.tracks; default: throw new RuntimeException(); } @@ -186,30 +98,26 @@ private CachedLoader getStorageLoader(StorageLocation location) { public void init() throws IOException { super.init(); - this.usersFile = super.dataDirectory.resolve("users" + this.fileExtension); - this.groupsFile = super.dataDirectory.resolve("groups" + this.fileExtension); - this.tracksFile = super.dataDirectory.resolve("tracks" + this.fileExtension); - - this.usersLoader = new CachedLoader(this.usersFile); - this.groupsLoader = new CachedLoader(this.groupsFile); - this.tracksLoader = new CachedLoader(this.tracksFile); + this.users = new CachedLoader(super.dataDirectory.resolve("users" + this.fileExtension)); + this.groups = new CachedLoader(super.dataDirectory.resolve("groups" + this.fileExtension)); + this.tracks = new CachedLoader(super.dataDirectory.resolve("tracks" + this.fileExtension)); // Listen for file changes. FileWatcher watcher = this.plugin.getFileWatcher().orElse(null); if (watcher != null) { this.watcher = watcher.getWatcher(super.dataDirectory); this.watcher.addListener(path -> { - if (path.getFileName().equals(this.usersFile.getFileName())) { + if (path.getFileName().equals(this.users.file.getFileName())) { this.plugin.getLogger().info("[FileWatcher] Detected change in users file - reloading..."); - this.usersLoader.reload(); + this.users.reload(); this.plugin.getSyncTaskBuffer().request(); - } else if (path.getFileName().equals(this.groupsFile.getFileName())) { + } else if (path.getFileName().equals(this.groups.file.getFileName())) { this.plugin.getLogger().info("[FileWatcher] Detected change in groups file - reloading..."); - this.groupsLoader.reload(); + this.groups.reload(); this.plugin.getSyncTaskBuffer().request(); - } else if (path.getFileName().equals(this.tracksFile.getFileName())) { + } else if (path.getFileName().equals(this.tracks.file.getFileName())) { this.plugin.getLogger().info("[FileWatcher] Detected change in tracks file - reloading..."); - this.tracksLoader.reload(); + this.tracks.reload(); this.plugin.getStorage().loadAllTracks(); } }); @@ -219,17 +127,17 @@ public void init() throws IOException { @Override public void shutdown() { try { - this.usersLoader.save(); + this.users.save(); } catch (IOException e) { e.printStackTrace(); } try { - this.groupsLoader.save(); + this.groups.save(); } catch (IOException e) { e.printStackTrace(); } try { - this.tracksLoader.save(); + this.tracks.save(); } catch (IOException e) { e.printStackTrace(); } @@ -239,7 +147,7 @@ public void shutdown() { @Override public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { if (bulkUpdate.getDataType().isIncludingUsers()) { - this.usersLoader.apply(true, true, root -> { + this.users.apply(true, true, root -> { for (Map.Entry entry : root.getChildrenMap().entrySet()) { processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.USER); } @@ -247,7 +155,7 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { } if (bulkUpdate.getDataType().isIncludingGroups()) { - this.groupsLoader.apply(true, true, root -> { + this.groups.apply(true, true, root -> { for (Map.Entry entry : root.getChildrenMap().entrySet()) { processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.GROUP); } @@ -257,7 +165,7 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { @Override public Set getUniqueUsers() throws IOException { - return this.usersLoader.getNode().getChildrenMap().keySet().stream() + return this.users.getNode().getChildrenMap().keySet().stream() .map(Object::toString) .map(Uuids::fromString) .filter(Objects::nonNull) @@ -267,7 +175,7 @@ public Set getUniqueUsers() throws IOException { @Override public List> searchUserNodes(ConstraintNodeMatcher constraint) throws Exception { List> held = new ArrayList<>(); - this.usersLoader.apply(false, true, root -> { + this.users.apply(false, true, root -> { for (Map.Entry entry : root.getChildrenMap().entrySet()) { try { UUID holder = UUID.fromString(entry.getKey().toString()); @@ -291,7 +199,7 @@ public List> searchUserNodes(ConstraintNodeM @Override public void loadAllGroups() throws IOException { List groups = new ArrayList<>(); - this.groupsLoader.apply(false, true, root -> { + this.groups.apply(false, true, root -> { groups.addAll(root.getChildrenMap().keySet().stream() .map(Object::toString) .collect(Collectors.toList())); @@ -307,7 +215,7 @@ public void loadAllGroups() throws IOException { @Override public List> searchGroupNodes(ConstraintNodeMatcher constraint) throws Exception { List> held = new ArrayList<>(); - this.groupsLoader.apply(false, true, root -> { + this.groups.apply(false, true, root -> { for (Map.Entry entry : root.getChildrenMap().entrySet()) { try { String holder = entry.getKey().toString(); @@ -331,7 +239,7 @@ public List> searchGroupNodes(ConstraintNo @Override public void loadAllTracks() throws IOException { List tracks = new ArrayList<>(); - this.tracksLoader.apply(false, true, root -> { + this.tracks.apply(false, true, root -> { tracks.addAll(root.getChildrenMap().keySet().stream() .map(Object::toString) .collect(Collectors.toList())); @@ -344,4 +252,82 @@ public void loadAllTracks() throws IOException { this.plugin.getTrackManager().retainAll(tracks); } + private final class CachedLoader { + private final Path file; + private final ConfigurationLoader loader; + private final ReentrantLock lock = new ReentrantLock(); + private ConfigurationNode node = null; + + private CachedLoader(Path file) { + this.file = file; + this.loader = CombinedConfigurateStorage.super.loader.loader(file); + reload(); + } + + private void recordChange() { + if (CombinedConfigurateStorage.this.watcher != null) { + CombinedConfigurateStorage.this.watcher.recordChange(this.file.getFileName().toString()); + } + } + + public ConfigurationNode getNode() throws IOException { + this.lock.lock(); + try { + if (this.node == null) { + this.node = this.loader.load(); + } + + return this.node; + } finally { + this.lock.unlock(); + } + } + + public void apply(Consumer action) throws IOException { + apply(false, false, action); + } + + public void apply(boolean save, boolean reload, Consumer action) throws IOException { + this.lock.lock(); + try { + if (this.node == null || reload) { + reload(); + } + + action.accept(this.node); + + if (save) { + save(); + } + } finally { + this.lock.unlock(); + } + } + + public void save() throws IOException { + this.lock.lock(); + try { + recordChange(); + this.loader.save(this.node); + } finally { + this.lock.unlock(); + } + } + + public void reload() { + this.lock.lock(); + try { + this.node = null; + try { + recordChange(); + this.node = this.loader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + } finally { + this.lock.unlock(); + } + } + } + } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileIOException.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileIOException.java new file mode 100644 index 000000000..7979a7ee9 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileIOException.java @@ -0,0 +1,36 @@ +/* + * 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.implementation.file; + +import java.io.IOException; + +public class FileIOException extends IOException { + + public FileIOException(String fileName, Throwable cause) { + super("Exception thrown whilst reading/writing file: " + fileName, cause); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileUuidCache.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileUuidCache.java index 8fb04665a..e05734753 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileUuidCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/FileUuidCache.java @@ -59,44 +59,6 @@ public class FileUuidCache { // the lookup map private final LookupMap lookupMap = new LookupMap(); - private static final class LookupMap extends ConcurrentHashMap { - private final SetMultimap reverse = Multimaps.newSetMultimap(new ConcurrentHashMap<>(), ConcurrentHashMap::newKeySet); - - @Override - public String put(@NonNull UUID key, @NonNull String value) { - String existing = super.put(key, value); - - // check if we need to remove a reverse entry which has been replaced - // existing might be null - if (!value.equalsIgnoreCase(existing)) { - if (existing != null) { - this.reverse.remove(existing.toLowerCase(), key); - } - } - - this.reverse.put(value.toLowerCase(), key); - return existing; - } - - @Override - public String remove(@NonNull Object k) { - UUID key = (UUID) k; - String username = super.remove(key); - if (username != null) { - this.reverse.remove(username.toLowerCase(), key); - } - return username; - } - - public String lookupUsername(UUID uuid) { - return super.get(uuid); - } - - public Set lookupUuid(String name) { - return this.reverse.get(name.toLowerCase()); - } - } - /** * Adds a mapping to the cache * @@ -154,6 +116,40 @@ public String lookupUsername(UUID uuid) { return this.lookupMap.lookupUsername(uuid); } + public void load(Path file) { + if (!Files.exists(file)) { + return; + } + + try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { + String entry; + while ((entry = reader.readLine()) != null) { + entry = entry.trim(); + if (entry.isEmpty() || entry.startsWith("#")) { + continue; + } + loadEntry(entry); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void save(Path file) { + try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { + writer.write("# LuckPerms UUID lookup cache"); + writer.newLine(); + for (Map.Entry ent : this.lookupMap.entrySet()) { + String out = ent.getKey() + ":" + ent.getValue(); + writer.write(out); + writer.newLine(); + } + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + private void loadEntry(String entry) { if (entry.contains(":")) { // new format @@ -193,37 +189,41 @@ private void loadEntry(String entry) { } } - public void load(Path file) { - if (!Files.exists(file)) { - return; - } + private static final class LookupMap extends ConcurrentHashMap { + private final SetMultimap reverse = Multimaps.newSetMultimap(new ConcurrentHashMap<>(), ConcurrentHashMap::newKeySet); - try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { - String entry; - while ((entry = reader.readLine()) != null) { - entry = entry.trim(); - if (entry.isEmpty() || entry.startsWith("#")) { - continue; + @Override + public String put(@NonNull UUID key, @NonNull String value) { + String existing = super.put(key, value); + + // check if we need to remove a reverse entry which has been replaced + // existing might be null + if (!value.equalsIgnoreCase(existing)) { + if (existing != null) { + this.reverse.remove(existing.toLowerCase(), key); } - loadEntry(entry); } - } catch (IOException e) { - e.printStackTrace(); + + this.reverse.put(value.toLowerCase(), key); + return existing; } - } - public void save(Path file) { - try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { - writer.write("# LuckPerms UUID lookup cache"); - writer.newLine(); - for (Map.Entry ent : this.lookupMap.entrySet()) { - String out = ent.getKey() + ":" + ent.getValue(); - writer.write(out); - writer.newLine(); + @Override + public String remove(@NonNull Object k) { + UUID key = (UUID) k; + String username = super.remove(key); + if (username != null) { + this.reverse.remove(username.toLowerCase(), key); } - writer.flush(); - } catch (IOException e) { - e.printStackTrace(); + return username; + } + + public String lookupUsername(UUID uuid) { + return super.get(uuid); + } + + public Set lookupUuid(String name) { + return this.reverse.get(name.toLowerCase()); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java index 7832f7ccf..56f797db1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/SeparatedConfigurateStorage.java @@ -25,6 +25,8 @@ package me.lucko.luckperms.common.storage.implementation.file; +import com.google.common.collect.ImmutableMap; + import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.model.HolderType; import me.lucko.luckperms.common.model.User; @@ -45,7 +47,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -53,30 +57,38 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +/** + * Flat-file storage using Configurate {@link ConfigurationNode}s. + * The data for each user/group/track is stored in a separate file. + */ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage { private final String fileExtension; private final Predicate fileExtensionFilter; - private Path usersDirectory; - private Path groupsDirectory; - private Path tracksDirectory; - - private FileWatcher.WatchedLocation userWatcher = null; - private FileWatcher.WatchedLocation groupWatcher = null; - private FileWatcher.WatchedLocation trackWatcher = null; - - /** - * Creates a new configurate storage implementation - * - * @param plugin the plugin instance - * @param implementationName the name of this implementation - * @param fileExtension the file extension used by this instance, including a "." at the start - * @param dataFolderName the name of the folder used to store data - */ + private final Map fileGroups; + private final FileGroup users; + private final FileGroup groups; + private final FileGroup tracks; + + private static final class FileGroup { + private Path directory; + private FileWatcher.WatchedLocation watcher; + } + public SeparatedConfigurateStorage(LuckPermsPlugin plugin, String implementationName, ConfigurateLoader loader, String fileExtension, String dataFolderName) { super(plugin, implementationName, loader, dataFolderName); this.fileExtension = fileExtension; this.fileExtensionFilter = path -> path.getFileName().toString().endsWith(this.fileExtension); + + this.users = new FileGroup(); + this.groups = new FileGroup(); + this.tracks = new FileGroup(); + + EnumMap fileGroups = new EnumMap<>(StorageLocation.class); + fileGroups.put(StorageLocation.USERS, this.users); + fileGroups.put(StorageLocation.GROUPS, this.groups); + fileGroups.put(StorageLocation.TRACKS, this.tracks); + this.fileGroups = ImmutableMap.copyOf(fileGroups); } @Override @@ -111,37 +123,13 @@ private void saveFile(Path file, ConfigurationNode node) throws IOException { } private Path getDirectory(StorageLocation location) { - switch (location) { - case USER: - return this.usersDirectory; - case GROUP: - return this.groupsDirectory; - case TRACK: - return this.tracksDirectory; - default: - throw new RuntimeException(); - } + return this.fileGroups.get(location).directory; } private void registerFileAction(StorageLocation type, Path file) { - switch (type) { - case USER: - if (this.userWatcher != null) { - this.userWatcher.recordChange(file.getFileName().toString()); - } - break; - case GROUP: - if (this.groupWatcher != null) { - this.groupWatcher.recordChange(file.getFileName().toString()); - } - break; - case TRACK: - if (this.trackWatcher != null) { - this.trackWatcher.recordChange(file.getFileName().toString()); - } - break; - default: - throw new RuntimeException(); + FileWatcher.WatchedLocation watcher = this.fileGroups.get(type).watcher; + if (watcher != null) { + watcher.recordChange(file.getFileName().toString()); } } @@ -149,22 +137,21 @@ private void registerFileAction(StorageLocation type, Path file) { public void init() throws IOException { super.init(); - this.usersDirectory = MoreFiles.createDirectoryIfNotExists(super.dataDirectory.resolve("users")); - this.groupsDirectory = MoreFiles.createDirectoryIfNotExists(super.dataDirectory.resolve("groups")); - this.tracksDirectory = MoreFiles.createDirectoryIfNotExists(super.dataDirectory.resolve("tracks")); + this.users.directory = MoreFiles.createDirectoryIfNotExists(super.dataDirectory.resolve("users")); + this.groups.directory = MoreFiles.createDirectoryIfNotExists(super.dataDirectory.resolve("groups")); + this.tracks.directory = MoreFiles.createDirectoryIfNotExists(super.dataDirectory.resolve("tracks")); // Listen for file changes. FileWatcher watcher = this.plugin.getFileWatcher().orElse(null); if (watcher != null) { - this.userWatcher = watcher.getWatcher(this.usersDirectory); - this.userWatcher.addListener(path -> { - String s = path.getFileName().toString(); - - if (!s.endsWith(this.fileExtension)) { + this.users.watcher = watcher.getWatcher(this.users.directory); + this.users.watcher.addListener(path -> { + String fileName = path.getFileName().toString(); + if (!fileName.endsWith(this.fileExtension)) { return; } - String user = s.substring(0, s.length() - this.fileExtension.length()); + String user = fileName.substring(0, fileName.length() - this.fileExtension.length()); UUID uuid = Uuids.parse(user); if (uuid == null) { return; @@ -177,28 +164,26 @@ public void init() throws IOException { } }); - this.groupWatcher = watcher.getWatcher(this.groupsDirectory); - this.groupWatcher.addListener(path -> { - String s = path.getFileName().toString(); - - if (!s.endsWith(this.fileExtension)) { + this.groups.watcher = watcher.getWatcher(this.groups.directory); + this.groups.watcher.addListener(path -> { + String fileName = path.getFileName().toString(); + if (!fileName.endsWith(this.fileExtension)) { return; } - String groupName = s.substring(0, s.length() - this.fileExtension.length()); + String groupName = fileName.substring(0, fileName.length() - this.fileExtension.length()); this.plugin.getLogger().info("[FileWatcher] Detected change in group file for " + groupName + " - reloading..."); this.plugin.getSyncTaskBuffer().request(); }); - this.trackWatcher = watcher.getWatcher(this.tracksDirectory); - this.trackWatcher.addListener(path -> { - String s = path.getFileName().toString(); - - if (!s.endsWith(this.fileExtension)) { + this.tracks.watcher = watcher.getWatcher(this.tracks.directory); + this.tracks.watcher.addListener(path -> { + String fileName = path.getFileName().toString(); + if (!fileName.endsWith(this.fileExtension)) { return; } - String trackName = s.substring(0, s.length() - this.fileExtension.length()); + String trackName = fileName.substring(0, fileName.length() - this.fileExtension.length()); this.plugin.getLogger().info("[FileWatcher] Detected change in track file for " + trackName + " - reloading..."); this.plugin.getStorage().loadAllTracks(); }); @@ -208,32 +193,38 @@ public void init() throws IOException { @Override public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { if (bulkUpdate.getDataType().isIncludingUsers()) { - try (Stream s = Files.list(getDirectory(StorageLocation.USER))) { + try (Stream s = Files.list(getDirectory(StorageLocation.USERS))) { s.filter(this.fileExtensionFilter).forEach(file -> { try { - registerFileAction(StorageLocation.USER, file); + registerFileAction(StorageLocation.USERS, file); ConfigurationNode object = readFile(file); if (processBulkUpdate(bulkUpdate, object, HolderType.USER)) { saveFile(file, object); } } catch (Exception e) { - throw reportException(file.getFileName().toString(), e); + this.plugin.getLogger().severe( + "Exception whilst performing bulkupdate", + new FileIOException(file.getFileName().toString(), e) + ); } }); } } if (bulkUpdate.getDataType().isIncludingGroups()) { - try (Stream s = Files.list(getDirectory(StorageLocation.GROUP))) { + try (Stream s = Files.list(getDirectory(StorageLocation.GROUPS))) { s.filter(this.fileExtensionFilter).forEach(file -> { try { - registerFileAction(StorageLocation.GROUP, file); + registerFileAction(StorageLocation.GROUPS, file); ConfigurationNode object = readFile(file); if (processBulkUpdate(bulkUpdate, object, HolderType.GROUP)) { saveFile(file, object); } } catch (Exception e) { - throw reportException(file.getFileName().toString(), e); + this.plugin.getLogger().severe( + "Exception whilst performing bulkupdate", + new FileIOException(file.getFileName().toString(), e) + ); } }); } @@ -242,7 +233,7 @@ public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { @Override public Set getUniqueUsers() throws IOException { - try (Stream stream = Files.list(this.usersDirectory)) { + try (Stream stream = Files.list(this.users.directory)) { return stream.filter(this.fileExtensionFilter) .map(p -> p.getFileName().toString()) .map(s -> s.substring(0, s.length() - this.fileExtension.length())) @@ -253,14 +244,14 @@ public Set getUniqueUsers() throws IOException { } @Override - public List> searchUserNodes(ConstraintNodeMatcher constraint) throws Exception { + public List> searchUserNodes(ConstraintNodeMatcher constraint) throws IOException { List> held = new ArrayList<>(); - try (Stream stream = Files.list(getDirectory(StorageLocation.USER))) { + try (Stream stream = Files.list(getDirectory(StorageLocation.USERS))) { stream.filter(this.fileExtensionFilter) .forEach(file -> { String fileName = file.getFileName().toString(); try { - registerFileAction(StorageLocation.USER, file); + registerFileAction(StorageLocation.USERS, file); ConfigurationNode object = readFile(file); UUID holder = UUID.fromString(fileName.substring(0, fileName.length() - this.fileExtension.length())); Set nodes = readNodes(object); @@ -271,7 +262,10 @@ public List> searchUserNodes(ConstraintNodeM } } } catch (Exception e) { - throw reportException(file.getFileName().toString(), e); + this.plugin.getLogger().severe( + "Exception whilst searching user nodes", + new FileIOException(file.getFileName().toString(), e) + ); } }); } @@ -281,7 +275,7 @@ public List> searchUserNodes(ConstraintNodeM @Override public void loadAllGroups() throws IOException { List groups; - try (Stream stream = Files.list(this.groupsDirectory)) { + try (Stream stream = Files.list(this.groups.directory)) { groups = stream.filter(this.fileExtensionFilter) .map(p -> p.getFileName().toString()) .map(s -> s.substring(0, s.length() - this.fileExtension.length())) @@ -296,14 +290,14 @@ public void loadAllGroups() throws IOException { } @Override - public List> searchGroupNodes(ConstraintNodeMatcher constraint) throws Exception { + public List> searchGroupNodes(ConstraintNodeMatcher constraint) throws IOException { List> held = new ArrayList<>(); - try (Stream stream = Files.list(getDirectory(StorageLocation.GROUP))) { + try (Stream stream = Files.list(getDirectory(StorageLocation.GROUPS))) { stream.filter(this.fileExtensionFilter) .forEach(file -> { String fileName = file.getFileName().toString(); try { - registerFileAction(StorageLocation.GROUP, file); + registerFileAction(StorageLocation.GROUPS, file); ConfigurationNode object = readFile(file); String holder = fileName.substring(0, fileName.length() - this.fileExtension.length()); Set nodes = readNodes(object); @@ -314,7 +308,10 @@ public List> searchGroupNodes(ConstraintNo } } } catch (Exception e) { - throw reportException(file.getFileName().toString(), e); + this.plugin.getLogger().severe( + "Exception whilst searching group nodes", + new FileIOException(file.getFileName().toString(), e) + ); } }); } @@ -324,7 +321,7 @@ public List> searchGroupNodes(ConstraintNo @Override public void loadAllTracks() throws IOException { List tracks; - try (Stream stream = Files.list(this.tracksDirectory)) { + try (Stream stream = Files.list(this.tracks.directory)) { tracks = stream.filter(this.fileExtensionFilter) .map(p -> p.getFileName().toString()) .map(s -> s.substring(0, s.length() - this.fileExtension.length())) diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/StorageLocation.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/StorageLocation.java index 5998ea9a2..51edf122f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/StorageLocation.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/file/StorageLocation.java @@ -27,6 +27,6 @@ public enum StorageLocation { - USER, GROUP, TRACK + USERS, GROUPS, TRACKS } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/misc/NodeEntry.java b/common/src/main/java/me/lucko/luckperms/common/storage/misc/NodeEntry.java index 07eb19977..12a97ca5f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/misc/NodeEntry.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/misc/NodeEntry.java @@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; +@SuppressWarnings("deprecation") public final class NodeEntry, N extends Node> implements HeldNode { public static , N extends Node> NodeEntry of(H holder, N node) { diff --git a/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java b/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java index 6391aa3aa..1d2fc9a35 100644 --- a/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java +++ b/common/src/main/java/me/lucko/luckperms/common/treeview/TreeView.java @@ -185,7 +185,7 @@ public String uploadPasteData(BytebinClient bytebin, Sender sender, User user, P e.printStackTrace(); } - return bytebin.postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE, false).key(); + return bytebin.postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE).key(); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java index 5ebd77971..fb4c03c8f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java +++ b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java @@ -319,7 +319,7 @@ public String uploadPasteData(BytebinClient bytebin) throws IOException, Unsucce e.printStackTrace(); } - return bytebin.postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE, false).key(); + return bytebin.postContent(bytesOut.toByteArray(), AbstractHttpClient.JSON_TYPE).key(); } public Sender getNotifiedSender() { diff --git a/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java index 2c9a15bbe..e7f2a6b26 100644 --- a/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java +++ b/common/src/main/java/me/lucko/luckperms/common/webeditor/WebEditorRequest.java @@ -155,7 +155,7 @@ public byte[] encode() { public CommandResult createSession(LuckPermsPlugin plugin, Sender sender) { String pasteId; try { - pasteId = plugin.getBytebin().postContent(encode(), AbstractHttpClient.JSON_TYPE, false).key(); + pasteId = plugin.getBytebin().postContent(encode(), AbstractHttpClient.JSON_TYPE).key(); } catch (UnsuccessfulRequestException e) { Message.EDITOR_HTTP_REQUEST_FAILURE.send(sender, e.getResponse().code(), e.getResponse().message()); return CommandResult.STATE_ERROR; diff --git a/common/src/test/java/me/lucko/luckperms/common/dependencies/DependencyChecksumTest.java b/common/src/test/java/me/lucko/luckperms/common/dependencies/DependencyChecksumTest.java new file mode 100644 index 000000000..a73380455 --- /dev/null +++ b/common/src/test/java/me/lucko/luckperms/common/dependencies/DependencyChecksumTest.java @@ -0,0 +1,79 @@ +/* + * 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.dependencies; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.Base64; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class DependencyChecksumTest { + + @Test + @Tag("dependency_checksum") + public void check() { + Dependency[] dependencies = Dependency.values(); + DependencyRepository[] repos = DependencyRepository.values(); + ExecutorService pool = Executors.newCachedThreadPool(); + + AtomicBoolean failed = new AtomicBoolean(false); + + for (Dependency dependency : dependencies) { + for (DependencyRepository repo : repos) { + pool.submit(() -> { + try { + byte[] hash = Dependency.createDigest().digest(repo.downloadRaw(dependency)); + if (!dependency.checksumMatches(hash)) { + System.out.println("NO MATCH - " + repo.name() + " - " + dependency.name() + ": " + Base64.getEncoder().encodeToString(hash)); + failed.set(true); + } else { + System.out.println("OK - " + repo.name() + " - " + dependency.name()); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } + + pool.shutdown(); + try { + pool.awaitTermination(1, TimeUnit.HOURS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (failed.get()) { + Assertions.fail("Some dependency checksums did not match"); + } + } + +} diff --git a/common/src/test/java/me/lucko/luckperms/common/node/utils/ShorthandParserTest.java b/common/src/test/java/me/lucko/luckperms/common/node/utils/ShorthandParserTest.java new file mode 100644 index 000000000..2cdbc5bc2 --- /dev/null +++ b/common/src/test/java/me/lucko/luckperms/common/node/utils/ShorthandParserTest.java @@ -0,0 +1,70 @@ +/* + * 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.node.utils; + +import com.google.common.collect.ImmutableSet; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ShorthandParserTest { + + private static void test(String shorthand, String... expected) { + assertEquals(ImmutableSet.copyOf(expected), ShorthandParser.expandShorthand(shorthand)); + } + + @Test + void testNumericRange() { + test("{2-4}", "2", "3", "4"); + } + + @Test + void testCharacterRange() { + test("{a-d}", "a", "b", "c", "d"); + test("{A-D}", "A", "B", "C", "D"); + } + + @Test + void testList() { + test("{aa,bb,cc}", "aa", "bb", "cc"); + test("{aa|bb|cc}", "aa", "bb", "cc"); + test("{aa,bb|cc}", "aa", "bb", "cc"); + } + + @Test + void testGroups() { + test("he{y|llo} {1-2}", "hey 1", "hey 2", "hello 1", "hello 2"); + test("my.permission.{test,hi}", "my.permission.test", "my.permission.hi"); + test("my.permission.{a-c}", "my.permission.a", "my.permission.b", "my.permission.c"); + + // use ( ) instead + test("he(y|llo) (1-2)", "hey 1", "hey 2", "hello 1", "hello 2"); + test("my.permission.(test,hi)", "my.permission.test", "my.permission.hi"); + test("my.permission.(a-c)", "my.permission.a", "my.permission.b", "my.permission.c"); + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitAutoOpListener.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitAutoOpListener.java index 50ee3ffce..499077fd2 100644 --- a/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitAutoOpListener.java +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitAutoOpListener.java @@ -67,7 +67,7 @@ private void onContextUpdate(ContextUpdateEvent e) { } private void refreshAutoOp(Player player) { - User user = plugin.getUserManager().getIfLoaded(player.getUniqueId()); + User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId()); boolean value; if (user != null) { diff --git a/velocity/src/main/java/me/lucko/luckperms/velocity/VelocityCommandExecutor.java b/velocity/src/main/java/me/lucko/luckperms/velocity/VelocityCommandExecutor.java index 1eb86d990..b9657cdeb 100644 --- a/velocity/src/main/java/me/lucko/luckperms/velocity/VelocityCommandExecutor.java +++ b/velocity/src/main/java/me/lucko/luckperms/velocity/VelocityCommandExecutor.java @@ -25,8 +25,7 @@ package me.lucko.luckperms.velocity; -import com.velocitypowered.api.command.Command; -import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.RawCommand; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.ProxyServer; @@ -34,16 +33,16 @@ import me.lucko.luckperms.common.command.utils.ArgumentTokenizer; import me.lucko.luckperms.common.sender.Sender; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.Arrays; import java.util.List; -public class VelocityCommandExecutor extends CommandManager implements Command { - /** The command aliases */ - private static final String[] ALIASES = {"luckpermsvelocity", "lpv", "vperm", "vperms", "vpermission", "vpermissions"}; +public class VelocityCommandExecutor extends CommandManager implements RawCommand { + /* The command aliases */ + private static final String PRIMARY_ALIAS = "luckpermsvelocity"; + private static final String[] ALIASES = {"lpv", "vperm", "vperms", "vpermission", "vpermissions"}; - /** The command aliases, prefixed with '/' */ + /* The command aliases, prefixed with '/' */ + private static final String SLASH_PRIMARY_ALIAS = "/luckpermsvelocity"; private static final String[] SLASH_ALIASES = Arrays.stream(ALIASES).map(s -> '/' + s).toArray(String[]::new); private final LPVelocityPlugin plugin; @@ -55,51 +54,46 @@ public VelocityCommandExecutor(LPVelocityPlugin plugin) { public void register() { ProxyServer proxy = this.plugin.getBootstrap().getProxy(); - proxy.getCommandManager().register(this, ALIASES); + proxy.getCommandManager().register(PRIMARY_ALIAS, this, ALIASES); // register slash aliases so the console can run '/lpv' in the same way as 'lpv'. - proxy.getCommandManager().register(new ForwardingCommand(this) { + proxy.getCommandManager().register(SLASH_PRIMARY_ALIAS, new ForwardingCommand(this) { @Override - public boolean hasPermission(CommandSource source, @NonNull String[] args) { - return source instanceof ConsoleCommandSource; + public boolean hasPermission(Invocation invocation) { + return invocation.source() instanceof ConsoleCommandSource; } }, SLASH_ALIASES); } @Override - public void execute(@NonNull CommandSource source, @NonNull String[] args) { - Sender wrapped = this.plugin.getSenderFactory().wrap(source); - List arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(args); + public void execute(Invocation invocation) { + Sender wrapped = this.plugin.getSenderFactory().wrap(invocation.source()); + List arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(invocation.arguments()); executeCommand(wrapped, "lpv", arguments); } @Override - public List suggest(@NonNull CommandSource source, @NonNull String[] args) { - Sender wrapped = this.plugin.getSenderFactory().wrap(source); - List arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(args); + public List suggest(Invocation invocation) { + Sender wrapped = this.plugin.getSenderFactory().wrap(invocation.source()); + List arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(invocation.arguments()); return tabCompleteCommand(wrapped, arguments); } - private static class ForwardingCommand implements Command { - private final Command delegate; + private static class ForwardingCommand implements RawCommand { + private final RawCommand delegate; - private ForwardingCommand(Command delegate) { + private ForwardingCommand(RawCommand delegate) { this.delegate = delegate; } @Override - public void execute(CommandSource source, @NonNull String[] args) { - this.delegate.execute(source, args); - } - - @Override - public List suggest(CommandSource source, @NonNull String[] currentArgs) { - return this.delegate.suggest(source, currentArgs); + public void execute(Invocation invocation) { + this.delegate.execute(invocation); } @Override - public boolean hasPermission(CommandSource source, @NonNull String[] args) { - return this.delegate.hasPermission(source, args); + public List suggest(Invocation invocation) { + return this.delegate.suggest(invocation); } } } diff --git a/velocity/src/main/java/me/lucko/luckperms/velocity/util/AdventureCompat.java b/velocity/src/main/java/me/lucko/luckperms/velocity/util/AdventureCompat.java index 4b9668039..1bda0c98c 100644 --- a/velocity/src/main/java/me/lucko/luckperms/velocity/util/AdventureCompat.java +++ b/velocity/src/main/java/me/lucko/luckperms/velocity/util/AdventureCompat.java @@ -51,24 +51,14 @@ private AdventureCompat() {} static { String adventurePkg = "net.kyo".concat("ri.adventure."); try { - if (classExists(adventurePkg + "audience.Audience")) { - Class audienceClass = Class.forName(adventurePkg + "audience.Audience"); - Class componentClass = Class.forName(adventurePkg + "text.Component"); - Class serializerClass = Class.forName(adventurePkg + "text.serializer.gson.GsonComponentSerializer"); + Class audienceClass = Class.forName(adventurePkg + "audience.Audience"); + Class componentClass = Class.forName(adventurePkg + "text.Component"); + Class serializerClass = Class.forName(adventurePkg + "text.serializer.gson.GsonComponentSerializer"); - PLATFORM_SERIALIZER_DESERIALIZE = serializerClass.getMethod("deserialize", Object.class); - PLATFORM_SEND_MESSAGE = audienceClass.getMethod("sendMessage", componentClass); - PLATFORM_COMPONENT_RESULT_DENIED = ComponentResult.class.getMethod("denied", componentClass); - PLATFORM_SERIALIZER_INSTANCE = serializerClass.getMethod("gson").invoke(null); - } else { - Class componentClass = Class.forName("net.kyori.text.Component"); - Class serializerClass = Class.forName("net.kyori.text.serializer.gson.GsonComponentSerializer"); - - PLATFORM_SERIALIZER_DESERIALIZE = serializerClass.getMethod("deserialize", String.class); - PLATFORM_SEND_MESSAGE = CommandSource.class.getMethod("sendMessage", componentClass); - PLATFORM_COMPONENT_RESULT_DENIED = ComponentResult.class.getMethod("denied", componentClass); - PLATFORM_SERIALIZER_INSTANCE = serializerClass.getField("INSTANCE").get(null); - } + PLATFORM_SERIALIZER_DESERIALIZE = serializerClass.getMethod("deserialize", Object.class); + PLATFORM_SEND_MESSAGE = audienceClass.getMethod("sendMessage", componentClass); + PLATFORM_COMPONENT_RESULT_DENIED = ComponentResult.class.getMethod("denied", componentClass); + PLATFORM_SERIALIZER_INSTANCE = serializerClass.getMethod("gson").invoke(null); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } @@ -99,13 +89,4 @@ public static ComponentResult deniedResult(Component message) { } } - private static boolean classExists(String className) { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - } From 8167fbf73f99debb0cd8c1a46412a0176afb88af Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 23 Dec 2020 14:36:31 +0000 Subject: [PATCH 27/38] Give false default permissions priority over wildcards I think this is a good compromise. It won't apply to registered permissions that are defaulted to 'op' (Bukkit) or 'undefined' (Sponge), only to those that are specifically set to false. The change is configurable, enabled by default for new installs of LP. Will hopefully go some way to resolving: - #2787 - https://v2.nucleuspowered.org/docs/nowildcard.html - NucleusPowered/Nucleus#1093 (and related) cc: @dualspiral @slipcor --- .../calculator/BukkitCalculatorFactory.java | 9 ++---- .../bukkit/calculator/DefaultsProcessor.java | 30 ++++++++++++++++-- .../bukkit/calculator/OpProcessor.java | 5 ++- bukkit/src/main/resources/config.yml | 11 +++++++ .../calculator/PermissionCalculator.java | 11 ++----- .../AbstractPermissionProcessor.java | 12 +++++++ .../processor/PermissionProcessor.java | 3 +- .../luckperms/common/config/ConfigKeys.java | 5 +++ .../nukkit/calculator/DefaultsProcessor.java | 28 +++++++++++++++-- .../calculator/NukkitCalculatorFactory.java | 9 ++---- .../nukkit/calculator/OpProcessor.java | 5 ++- nukkit/src/main/resources/config.yml | 11 +++++++ .../sponge/calculator/DefaultsProcessor.java | 31 +++++++++++++++++-- .../calculator/FixedDefaultsProcessor.java | 4 +-- .../calculator/GroupDefaultsProcessor.java | 4 +-- .../calculator/SpongeCalculatorFactory.java | 5 +-- .../calculator/UserDefaultsProcessor.java | 4 +-- .../CalculatedSubjectCachedDataManager.java | 2 +- sponge/src/main/resources/luckperms.conf | 11 +++++++ 19 files changed, 160 insertions(+), 40 deletions(-) diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/BukkitCalculatorFactory.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/BukkitCalculatorFactory.java index effe61b20..fe8ce0177 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/BukkitCalculatorFactory.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/BukkitCalculatorFactory.java @@ -45,14 +45,8 @@ public class BukkitCalculatorFactory implements CalculatorFactory { private final LPBukkitPlugin plugin; - private final DefaultsProcessor opDefaultsProcessor; - private final DefaultsProcessor nonOpDefaultsProcessor; - public BukkitCalculatorFactory(LPBukkitPlugin plugin) { this.plugin = plugin; - - this.opDefaultsProcessor = new DefaultsProcessor(this.plugin, true); - this.nonOpDefaultsProcessor = new DefaultsProcessor(this.plugin, false); } @Override @@ -79,7 +73,8 @@ public PermissionCalculator build(QueryOptions queryOptions, CacheMetadata metad boolean op = queryOptions.option(BukkitContextManager.OP_OPTION).orElse(false); if (metadata.getHolderType() == HolderType.USER && this.plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) { - processors.add(op ? this.opDefaultsProcessor : this.nonOpDefaultsProcessor); + boolean overrideWildcards = this.plugin.getConfiguration().get(ConfigKeys.APPLY_DEFAULT_NEGATIONS_BEFORE_WILDCARDS); + processors.add(new DefaultsProcessor(this.plugin, overrideWildcards, op)); } if (op) { diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/DefaultsProcessor.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/DefaultsProcessor.java index 64c6a0860..a395bdd35 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/DefaultsProcessor.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/DefaultsProcessor.java @@ -27,11 +27,14 @@ import me.lucko.luckperms.bukkit.LPBukkitPlugin; import me.lucko.luckperms.common.calculator.processor.PermissionProcessor; +import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor; +import me.lucko.luckperms.common.calculator.processor.WildcardProcessor; import me.lucko.luckperms.common.calculator.result.TristateResult; import net.luckperms.api.util.Tristate; import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; /** * Permission Processor for Bukkits "default" permission system. @@ -41,15 +44,38 @@ public class DefaultsProcessor implements PermissionProcessor { private static final TristateResult.Factory PERMISSION_MAP_RESULT_FACTORY = new TristateResult.Factory(DefaultsProcessor.class, "permission map"); private final LPBukkitPlugin plugin; + private final boolean overrideWildcards; private final boolean isOp; - public DefaultsProcessor(LPBukkitPlugin plugin, boolean isOp) { + public DefaultsProcessor(LPBukkitPlugin plugin, boolean overrideWildcards, boolean isOp) { this.plugin = plugin; + this.overrideWildcards = overrideWildcards; this.isOp = isOp; } + private boolean canOverrideWildcard(TristateResult prev) { + return this.overrideWildcards && + (prev.processorClass() == WildcardProcessor.class || prev.processorClass() == SpongeWildcardProcessor.class) && + prev.result() == Tristate.TRUE; + } + @Override - public TristateResult hasPermission(String permission) { + public TristateResult hasPermission(TristateResult prev, String permission) { + if (prev != TristateResult.UNDEFINED) { + // Check to see if the result should be overridden + if (canOverrideWildcard(prev)) { + Permission defPerm = this.plugin.getPermissionMap().get(permission); + if (defPerm != null) { + PermissionDefault def = defPerm.getDefault(); + if (def == PermissionDefault.FALSE || (this.isOp && def == PermissionDefault.NOT_OP)) { + return PERMISSION_MAP_RESULT_FACTORY.result(Tristate.FALSE, "permission map (overriding wildcard): " + prev.cause()); + } + } + } + + return prev; + } + Tristate t = this.plugin.getDefaultPermissionMap().lookupDefaultPermission(permission, this.isOp); if (t != Tristate.UNDEFINED) { return DEFAULT_PERMISSION_MAP_RESULT_FACTORY.result(t); diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/OpProcessor.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/OpProcessor.java index 7c22bab2f..a4b06a18d 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/OpProcessor.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/OpProcessor.java @@ -44,7 +44,10 @@ private OpProcessor() { } @Override - public TristateResult hasPermission(String permission) { + public TristateResult hasPermission(TristateResult prev, String permission) { + if (prev != TristateResult.UNDEFINED) { + return prev; + } return TRUE_RESULT; } } diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index d51a2893b..35deefc47 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -470,6 +470,17 @@ apply-wildcards: true # and so on. apply-sponge-implicit-wildcards: false +# If the plugin should apply negated Bukkit default permissions before it considers wildcard +# assignments. +# +# - Plugin authors can define permissions which explicitly should not be given automatically to OPs. +# This is usually used for so called "anti-permissions" - permissions which, when granted, apply +# something negative. +# - If this option is set to true, LuckPerms will consider any negated declarations made by +# plugins before it considers wildcards. (similar to the way the OP system works) +# - If this option is set to false, LuckPerms will consider any wildcard assignments first. +apply-default-negated-permissions-before-wildcards: true + # If the plugin should parse regex permissions. # # - If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the diff --git a/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java b/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java index facd17254..af84a7fc2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java +++ b/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java @@ -35,8 +35,6 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent; -import net.luckperms.api.util.Tristate; - import org.checkerframework.checker.nullness.qual.NonNull; import java.util.List; @@ -106,14 +104,11 @@ public TristateResult apply(@NonNull String permission) { // that this call is behind the cache. this.plugin.getPermissionRegistry().offer(permission); + TristateResult result = TristateResult.UNDEFINED; for (PermissionProcessor processor : this.processors) { - TristateResult result = processor.hasPermission(permission); - if (result.result() != Tristate.UNDEFINED) { - return result; - } + result = processor.hasPermission(result, permission); } - - return TristateResult.UNDEFINED; + return result; } /** diff --git a/common/src/main/java/me/lucko/luckperms/common/calculator/processor/AbstractPermissionProcessor.java b/common/src/main/java/me/lucko/luckperms/common/calculator/processor/AbstractPermissionProcessor.java index 9a3132590..198a7b69d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/calculator/processor/AbstractPermissionProcessor.java +++ b/common/src/main/java/me/lucko/luckperms/common/calculator/processor/AbstractPermissionProcessor.java @@ -25,6 +25,8 @@ package me.lucko.luckperms.common.calculator.processor; +import me.lucko.luckperms.common.calculator.result.TristateResult; + import java.util.Collections; import java.util.Map; @@ -35,4 +37,14 @@ public abstract class AbstractPermissionProcessor implements PermissionProcessor public void setSource(Map sourceMap) { this.sourceMap = sourceMap; } + + @Override + public TristateResult hasPermission(TristateResult prev, String permission) { + if (prev != TristateResult.UNDEFINED) { + return prev; + } + return hasPermission(permission); + } + + public abstract TristateResult hasPermission(String permission); } diff --git a/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java b/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java index 63cf79392..0bca9d5e4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java +++ b/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java @@ -41,10 +41,11 @@ public interface PermissionProcessor { /** * Returns the permission value determined by this calculator. * + * @param prev the result of the previous calculator in the chain * @param permission the permission * @return a tristate */ - TristateResult hasPermission(String permission); + TristateResult hasPermission(TristateResult prev, String permission); /** * Sets the source permissions which should be used by this processor 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 cf849296e..66110fbd0 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 @@ -237,6 +237,11 @@ private ConfigKeys() {} return c.getBoolean("apply-sponge-implicit-wildcards", def); })); + /** + * If default negated permissions should be applied before wildcards. + */ + public static final ConfigKey APPLY_DEFAULT_NEGATIONS_BEFORE_WILDCARDS = notReloadable(booleanKey("apply-default-negated-permissions-before-wildcards", false)); + /** * If regex permissions are being applied */ diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/DefaultsProcessor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/DefaultsProcessor.java index e3fcc0734..9266f2284 100644 --- a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/DefaultsProcessor.java +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/DefaultsProcessor.java @@ -26,6 +26,8 @@ package me.lucko.luckperms.nukkit.calculator; import me.lucko.luckperms.common.calculator.processor.PermissionProcessor; +import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor; +import me.lucko.luckperms.common.calculator.processor.WildcardProcessor; import me.lucko.luckperms.common.calculator.result.TristateResult; import me.lucko.luckperms.nukkit.LPNukkitPlugin; import me.lucko.luckperms.nukkit.inject.PermissionDefault; @@ -40,15 +42,37 @@ public class DefaultsProcessor implements PermissionProcessor { private static final TristateResult.Factory PERMISSION_MAP_RESULT_FACTORY = new TristateResult.Factory(DefaultsProcessor.class, "permission map"); private final LPNukkitPlugin plugin; + private final boolean overrideWildcards; private final boolean isOp; - public DefaultsProcessor(LPNukkitPlugin plugin, boolean isOp) { + public DefaultsProcessor(LPNukkitPlugin plugin, boolean overrideWildcards, boolean isOp) { this.plugin = plugin; + this.overrideWildcards = overrideWildcards; this.isOp = isOp; } + private boolean canOverrideWildcard(TristateResult prev) { + return this.overrideWildcards && + (prev.processorClass() == WildcardProcessor.class || prev.processorClass() == SpongeWildcardProcessor.class) && + prev.result() == Tristate.TRUE; + } + @Override - public TristateResult hasPermission(String permission) { + public TristateResult hasPermission(TristateResult prev, String permission) { + if (prev != TristateResult.UNDEFINED) { + // Check to see if the result should be overridden + if (canOverrideWildcard(prev)) { + PermissionDefault def = PermissionDefault.fromPermission(this.plugin.getPermissionMap().get(permission)); + if (def != null) { + if (def == PermissionDefault.FALSE || (this.isOp && def == PermissionDefault.NOT_OP)) { + return PERMISSION_MAP_RESULT_FACTORY.result(Tristate.FALSE, "permission map (overriding wildcard): " + prev.cause()); + } + } + } + + return prev; + } + Tristate t = this.plugin.getDefaultPermissionMap().lookupDefaultPermission(permission, this.isOp); if (t != Tristate.UNDEFINED) { return DEFAULT_PERMISSION_MAP_RESULT_FACTORY.result(t); diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/NukkitCalculatorFactory.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/NukkitCalculatorFactory.java index f37d56959..e74814d06 100644 --- a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/NukkitCalculatorFactory.java +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/NukkitCalculatorFactory.java @@ -45,14 +45,8 @@ public class NukkitCalculatorFactory implements CalculatorFactory { private final LPNukkitPlugin plugin; - private final DefaultsProcessor opDefaultsProcessor; - private final DefaultsProcessor nonOpDefaultsProcessor; - public NukkitCalculatorFactory(LPNukkitPlugin plugin) { this.plugin = plugin; - - this.opDefaultsProcessor = new DefaultsProcessor(this.plugin, true); - this.nonOpDefaultsProcessor = new DefaultsProcessor(this.plugin, false); } @Override @@ -79,7 +73,8 @@ public PermissionCalculator build(QueryOptions queryOptions, CacheMetadata metad boolean op = queryOptions.option(NukkitContextManager.OP_OPTION).orElse(false); if (metadata.getHolderType() == HolderType.USER && this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_DEFAULT_PERMISSIONS)) { - processors.add(op ? this.opDefaultsProcessor : this.nonOpDefaultsProcessor); + boolean overrideWildcards = this.plugin.getConfiguration().get(ConfigKeys.APPLY_DEFAULT_NEGATIONS_BEFORE_WILDCARDS); + processors.add(new DefaultsProcessor(this.plugin, overrideWildcards, op)); } if (op) { diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/OpProcessor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/OpProcessor.java index 31f125763..de22d9575 100644 --- a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/OpProcessor.java +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculator/OpProcessor.java @@ -44,7 +44,10 @@ private OpProcessor() { } @Override - public TristateResult hasPermission(String permission) { + public TristateResult hasPermission(TristateResult prev, String permission) { + if (prev != TristateResult.UNDEFINED) { + return prev; + } return TRUE_RESULT; } } diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml index 3809238fb..a062c1ea4 100644 --- a/nukkit/src/main/resources/config.yml +++ b/nukkit/src/main/resources/config.yml @@ -465,6 +465,17 @@ apply-wildcards: true # and so on. apply-sponge-implicit-wildcards: false +# If the plugin should apply negated Nukkit default permissions before it considers wildcard +# assignments. +# +# - Plugin authors can define permissions which explicitly should not be given automatically to OPs. +# This is usually used for so called "anti-permissions" - permissions which, when granted, apply +# something negative. +# - If this option is set to true, LuckPerms will consider any negated declarations made by +# plugins before it considers wildcards. (similar to the way the OP system works) +# - If this option is set to false, LuckPerms will consider any wildcard assignments first. +apply-default-negated-permissions-before-wildcards: true + # If the plugin should parse regex permissions. # # - If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/DefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/DefaultsProcessor.java index bc91a3668..0a5bd8f9a 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/DefaultsProcessor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/DefaultsProcessor.java @@ -26,6 +26,8 @@ package me.lucko.luckperms.sponge.calculator; import me.lucko.luckperms.common.calculator.processor.PermissionProcessor; +import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor; +import me.lucko.luckperms.common.calculator.processor.WildcardProcessor; import me.lucko.luckperms.common.calculator.result.TristateResult; import me.lucko.luckperms.sponge.service.model.LPPermissionService; import me.lucko.luckperms.sponge.service.model.LPSubject; @@ -39,16 +41,41 @@ public abstract class DefaultsProcessor implements PermissionProcessor { protected final LPPermissionService service; private final QueryOptions queryOptions; + private final boolean overrideWildcards; - public DefaultsProcessor(LPPermissionService service, QueryOptions queryOptions) { + public DefaultsProcessor(LPPermissionService service, QueryOptions queryOptions, boolean overrideWildcards) { this.service = service; this.queryOptions = queryOptions; + this.overrideWildcards = overrideWildcards; } protected abstract LPSubject getTypeDefaults(); + private boolean canOverrideWildcard(TristateResult prev) { + return this.overrideWildcards && + (prev.processorClass() == WildcardProcessor.class || prev.processorClass() == SpongeWildcardProcessor.class) && + prev.result() == Tristate.TRUE; + } + @Override - public TristateResult hasPermission(String permission) { + public TristateResult hasPermission(TristateResult prev, String permission) { + if (prev != TristateResult.UNDEFINED) { + // Check to see if the result should be overridden + if (canOverrideWildcard(prev)) { + Tristate t = getTypeDefaults().getPermissionValue(this.queryOptions, permission); + if (t == Tristate.FALSE) { + return TYPE_DEFAULTS_RESULT_FACTORY.result(Tristate.FALSE, "type defaults (overriding wildcard): " + prev.cause()); + } + + t = this.service.getRootDefaults().getPermissionValue(this.queryOptions, permission); + if (t == Tristate.FALSE) { + return ROOT_DEFAULTS_RESULT_FACTORY.result(Tristate.FALSE, "root defaults (overriding wildcard): " + prev.cause()); + } + } + + return prev; + } + Tristate t = getTypeDefaults().getPermissionValue(this.queryOptions, permission); if (t != Tristate.UNDEFINED) { return TYPE_DEFAULTS_RESULT_FACTORY.result(t); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/FixedDefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/FixedDefaultsProcessor.java index 5f7e8db28..7fb466d22 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/FixedDefaultsProcessor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/FixedDefaultsProcessor.java @@ -33,8 +33,8 @@ public class FixedDefaultsProcessor extends DefaultsProcessor { private final LPSubject defaultsSubject; - public FixedDefaultsProcessor(LPPermissionService service, QueryOptions queryOptions, LPSubject defaultsSubject) { - super(service, queryOptions); + public FixedDefaultsProcessor(LPPermissionService service, QueryOptions queryOptions, LPSubject defaultsSubject, boolean overrideWildcards) { + super(service, queryOptions, overrideWildcards); this.defaultsSubject = defaultsSubject; } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/GroupDefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/GroupDefaultsProcessor.java index d05c6e066..8a4fb8050 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/GroupDefaultsProcessor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/GroupDefaultsProcessor.java @@ -32,8 +32,8 @@ import net.luckperms.api.query.QueryOptions; public class GroupDefaultsProcessor extends DefaultsProcessor implements PermissionProcessor { - public GroupDefaultsProcessor(LPPermissionService service, QueryOptions queryOptions) { - super(service, queryOptions); + public GroupDefaultsProcessor(LPPermissionService service, QueryOptions queryOptions, boolean overrideWildcards) { + super(service, queryOptions, overrideWildcards); } @Override diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/SpongeCalculatorFactory.java b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/SpongeCalculatorFactory.java index 9b3c65767..3d494eb7c 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/SpongeCalculatorFactory.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/SpongeCalculatorFactory.java @@ -67,10 +67,11 @@ public PermissionCalculator build(QueryOptions queryOptions, CacheMetadata metad } if (this.plugin.getConfiguration().get(ConfigKeys.APPLY_SPONGE_DEFAULT_SUBJECTS)) { + boolean overrideWildcards = this.plugin.getConfiguration().get(ConfigKeys.APPLY_DEFAULT_NEGATIONS_BEFORE_WILDCARDS); if (metadata.getHolderType() == HolderType.USER) { - processors.add(new UserDefaultsProcessor(this.plugin.getService(), queryOptions)); + processors.add(new UserDefaultsProcessor(this.plugin.getService(), queryOptions, overrideWildcards)); } else if (metadata.getHolderType() == HolderType.GROUP) { - processors.add(new GroupDefaultsProcessor(this.plugin.getService(), queryOptions)); + processors.add(new GroupDefaultsProcessor(this.plugin.getService(), queryOptions, overrideWildcards)); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/UserDefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/UserDefaultsProcessor.java index e9d019a30..30e02cd4e 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/UserDefaultsProcessor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/calculator/UserDefaultsProcessor.java @@ -32,8 +32,8 @@ import net.luckperms.api.query.QueryOptions; public class UserDefaultsProcessor extends DefaultsProcessor implements PermissionProcessor { - public UserDefaultsProcessor(LPPermissionService service, QueryOptions queryOptions) { - super(service, queryOptions); + public UserDefaultsProcessor(LPPermissionService service, QueryOptions queryOptions, boolean overrideWildcards) { + super(service, queryOptions, overrideWildcards); } @Override diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectCachedDataManager.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectCachedDataManager.java index 4a25339b6..bead16df8 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectCachedDataManager.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectCachedDataManager.java @@ -103,7 +103,7 @@ public PermissionCalculator build(QueryOptions queryOptions, CacheMetadata metad processors.add(new WildcardProcessor()); if (!this.subject.getParentCollection().isDefaultsCollection()) { - processors.add(new FixedDefaultsProcessor(this.subject.getService(), queryOptions, this.subject.getDefaults())); + processors.add(new FixedDefaultsProcessor(this.subject.getService(), queryOptions, this.subject.getDefaults(), true)); } return new PermissionCalculator(getPlugin(), metadata, processors.build()); diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index a1a315c8c..7719f5294 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -481,6 +481,17 @@ apply-wildcards = true # and so on. apply-sponge-implicit-wildcards=true +# If the plugin should apply negated default permissions before it considers wildcard +# assignments. +# +# - Plugin authors can define permissions which explicitly should be false by default. +# This is usually used for so called "anti-permissions" - permissions which, when granted, apply +# something negative. +# - If this option is set to true, LuckPerms will consider any negated declarations made by +# plugins before it considers wildcards. +# - If this option is set to false, LuckPerms will consider any wildcard assignments first. +apply-default-negated-permissions-before-wildcards=true + # If the plugin should parse regex permissions. # # - If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the From d83559b94946138c52268c3c4c4d990499e10186 Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 1 Jan 2021 18:17:56 +0000 Subject: [PATCH 28/38] Change permission tree size limits to be less restrictive --- .../luckperms/common/treeview/TreeNode.java | 64 +++++++------------ 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/treeview/TreeNode.java b/common/src/main/java/me/lucko/luckperms/common/treeview/TreeNode.java index 5a9f869a7..be0b69ce4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/treeview/TreeNode.java +++ b/common/src/main/java/me/lucko/luckperms/common/treeview/TreeNode.java @@ -38,44 +38,39 @@ */ public class TreeNode { + /* + * We enforce a limit of the size of the node tree to ensure memory + * usage remains sane. some plugins check for "dynamic" permissions (cc: griefprevention) + * which means this tree can grow to very large sizes and use tons of memory + * + * The rules for limiting the tree size are designed to ensure the system is + * still useful, but that unnecessarily large amounts of data aren't stored + */ private static boolean allowInsert(TreeNode node) { - /* - We enforce a limit of the size of the node tree to ensure memory - usage remains sane. some plugins check for "dynamic" permissions (cc: griefprevention) - which means this tree can grow to very large sizes and use tons of memory - - the rules for limiting the tree size are designed to ensure the system is - still useful, but that unnecessarily large amounts of data aren't stored - - the rules are: - 1. there can be an unlimited number of root nodes e.g. (luckperms, minecraft) - 2. each root node can then have up to 500 child nodes - 3. *but*, each root node can have an unlimited number of 2nd level nodes (e.g. luckperms.user) - this takes priority over #2 - */ - - if (node.level == 2) { - // only allow up to a deep size of 500 - return node.parent.getDeepSize() < 500; + // level 0 => no limit + // level 1/2 => up to 500 + // level 3+ => up to 100 + + if (node.level == 0) { + return true; + } else if (node.level <= 2) { + return node.getChildrenSize() < 500; + } else { + return node.getChildrenSize() < 100; } - return true; } private Map children = null; private final int level; - private final TreeNode parent; - - private int cachedDeepSize = Integer.MIN_VALUE; public TreeNode() { this.level = 0; - this.parent = null; } + @SuppressWarnings("CopyConstructorMissesField") // it's not a copy constructor TreeNode(TreeNode parent) { this.level = parent.level + 1; - this.parent = parent; } // lazy init @@ -91,31 +86,18 @@ private synchronized Map getChildMap() { if (!allowInsert(this)) { return null; } - - return childMap.compute(s, (key, prev) -> { - if (prev != null) { - return prev; - } - - // dirty the cache & return a new node - this.cachedDeepSize = Integer.MIN_VALUE; - return new TreeNode(this); - }); + return childMap.computeIfAbsent(s, x -> new TreeNode(this)); } public Optional> getChildren() { return Optional.ofNullable(this.children); } - public int getDeepSize() { - if (this.cachedDeepSize != Integer.MIN_VALUE) { - return this.cachedDeepSize; - } - + public int getChildrenSize() { if (this.children == null) { - return (this.cachedDeepSize = 1); + return 0; } else { - return (this.cachedDeepSize = this.children.values().stream().mapToInt(TreeNode::getDeepSize).sum()); + return this.children.size(); } } From 657cf2a45e6a5228f07bff6323431ca65d1b7be6 Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 1 Jan 2021 19:38:55 +0000 Subject: [PATCH 29/38] Brigadier arguments in the same parse tree need unique names (#2798) --- bukkit/src/main/resources/luckperms.commodore | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/bukkit/src/main/resources/luckperms.commodore b/bukkit/src/main/resources/luckperms.commodore index e22f790e9..d823bf35e 100644 --- a/bukkit/src/main/resources/luckperms.commodore +++ b/bukkit/src/main/resources/luckperms.commodore @@ -321,7 +321,7 @@ luckperms { context brigadier:string greedy_phrase; } clone { - user brigadier:string single_word; + newname brigadier:string single_word; } } } @@ -379,36 +379,36 @@ luckperms { parent { info; set { - group brigadier:string single_word { + other_group brigadier:string single_word { context brigadier:string greedy_phrase; } } add { - group brigadier:string single_word { + other_group brigadier:string single_word { context brigadier:string greedy_phrase; } } remove { - group brigadier:string single_word { + other_group brigadier:string single_word { context brigadier:string greedy_phrase; } } settrack { track brigadier:string single_word { - group brigadier:string single_word { + other_group brigadier:string single_word { context brigadier:string greedy_phrase; } } } addtemp { - group brigadier:string quotable_phrase { + other_group brigadier:string quotable_phrase { duration brigadier:string single_word { context brigadier:string greedy_phrase; } } } removetemp { - group brigadier:string single_word { + other_group brigadier:string single_word { context brigadier:string greedy_phrase; duration brigadier:string single_word { context brigadier:string greedy_phrase; @@ -423,9 +423,6 @@ luckperms { context brigadier:string greedy_phrase; } } - switchprimarygroup { - group brigadier:string quotable_phrase; - } } meta { From 68167490d44316da84c7129b9f6baca2825379ba Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 1 Jan 2021 20:00:03 +0000 Subject: [PATCH 30/38] Fix permissible injection issue on Nukkit (#2791, #2799) --- .../nukkit/inject/permissible/PermissibleInjector.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/permissible/PermissibleInjector.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/permissible/PermissibleInjector.java index 40e05c299..549133939 100644 --- a/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/permissible/PermissibleInjector.java +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/permissible/PermissibleInjector.java @@ -76,13 +76,14 @@ private PermissibleInjector() {} * @throws Exception propagates any exceptions which were thrown during injection */ public static void inject(Player player, LuckPermsPermissible newPermissible) throws Exception { - // get the existing PermissibleBase held by the player PermissibleBase oldPermissible = (PermissibleBase) PLAYER_PERMISSIBLE_FIELD.get(player); - // seems we have already injected into this player. if (oldPermissible instanceof LuckPermsPermissible) { - throw new IllegalStateException("LPPermissible already injected into player " + player.toString()); + // Nukkit seems to re-use player instances (or perhaps calls the login event twice?) + // so, just uninject here instead of throwing an exception like we do on Bukkit + // See: https://github.com/lucko/LuckPerms/issues/2791 + uninject(player, false); } // Move attachments over from the old permissible From 8124a17adb378ce58b7e263af9b3d3ed4275fdd9 Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 1 Jan 2021 20:25:47 +0000 Subject: [PATCH 31/38] Conserve expiry time when promoting/demoting (#2794) --- .../src/main/java/me/lucko/luckperms/common/model/Track.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/model/Track.java b/common/src/main/java/me/lucko/luckperms/common/model/Track.java index 5683bcdc9..b06b5bd3c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/Track.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/Track.java @@ -316,7 +316,7 @@ public PromotionResult promote(User user, ContextSet context, Predicate } user.unsetNode(DataType.NORMAL, oldNode); - user.setNode(DataType.NORMAL, Inheritance.builder(nextGroup.getName()).withContext(context).build(), true); + user.setNode(DataType.NORMAL, oldNode.toBuilder().group(nextGroup.getName()).build(), true); if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) { user.getPrimaryGroup().setStoredValue(nextGroup.getName()); @@ -370,7 +370,7 @@ public DemotionResult demote(User user, ContextSet context, Predicate pr } user.unsetNode(DataType.NORMAL, oldNode); - user.setNode(DataType.NORMAL, Inheritance.builder(previousGroup.getName()).withContext(context).build(), true); + user.setNode(DataType.NORMAL, oldNode.toBuilder().group(previousGroup.getName()).build(), true); if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) { user.getPrimaryGroup().setStoredValue(previousGroup.getName()); From c3581c00d3e0774b2f3cef4ee99a26b7130b1d86 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 2 Jan 2021 00:50:18 +0000 Subject: [PATCH 32/38] Fix verbose command no checks message format --- .../src/main/java/me/lucko/luckperms/common/locale/Message.java | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java index f346483c6..b91b439aa 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java @@ -565,6 +565,7 @@ static TextComponent prefixed(ComponentLike component) { .color(GRAY) .append(translatable("luckperms.command.verbose.command.possibly-async")) .append(FULL_STOP) + .append(space()) .append(translatable("luckperms.command.verbose.command.try-again-manually")) .append(FULL_STOP)) ); From a8b1dc8c07326f713fba73a6b8295d2e70f77e30 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 2 Jan 2021 20:08:56 +0000 Subject: [PATCH 33/38] Only store ids of received messages for 1 hour to prevent high memory usage on instances with high uptime (#2807) --- .../common/messaging/LuckPermsMessagingService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 2020dcccc..af7465c54 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.UserUpdateMessageImpl; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.util.ExpiringSet; import me.lucko.luckperms.common.util.gson.GsonProvider; import me.lucko.luckperms.common.util.gson.JObject; @@ -51,8 +52,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Collections; -import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -73,7 +72,7 @@ public LuckPermsMessagingService(LuckPermsPlugin plugin, MessengerProvider messe this.messenger = messengerProvider.obtain(this); Objects.requireNonNull(this.messenger, "messenger"); - this.receivedMessages = Collections.synchronizedSet(new HashSet<>()); + this.receivedMessages = new ExpiringSet<>(1, TimeUnit.HOURS); this.updateBuffer = new PushUpdateBuffer(plugin); } From 0a99a9618835cb2b6b8a828fc61b4b4412e7673a Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 2 Jan 2021 21:16:45 +0000 Subject: [PATCH 34/38] Tidy up Node add/remove/clear API events --- .../api/event/node/NodeAddEvent.java | 13 +++- .../api/event/node/NodeClearEvent.java | 25 ++++++ .../api/event/node/NodeMutateEvent.java | 3 +- .../api/event/node/NodeRemoveEvent.java | 14 +++- .../common/event/EventDispatcher.java | 49 ++++++++++-- .../common/model/PermissionHolder.java | 77 +++++-------------- 6 files changed, 114 insertions(+), 67 deletions(-) diff --git a/api/src/main/java/net/luckperms/api/event/node/NodeAddEvent.java b/api/src/main/java/net/luckperms/api/event/node/NodeAddEvent.java index 0afb3dd73..8628b4e44 100644 --- a/api/src/main/java/net/luckperms/api/event/node/NodeAddEvent.java +++ b/api/src/main/java/net/luckperms/api/event/node/NodeAddEvent.java @@ -30,6 +30,10 @@ import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + /** * Called when a node is added to a holder */ @@ -40,7 +44,14 @@ public interface NodeAddEvent extends NodeMutateEvent { * * @return the node that was added */ - @Param(4) + @Param(3) @NonNull Node getNode(); + @Override + default @NonNull Set getDataBefore() { + // Get data after, then reverse the action + Set nodes = new HashSet<>(this.getDataAfter()); + nodes.remove(this.getNode()); + return Collections.unmodifiableSet(nodes); + } } diff --git a/api/src/main/java/net/luckperms/api/event/node/NodeClearEvent.java b/api/src/main/java/net/luckperms/api/event/node/NodeClearEvent.java index c44b59fb9..9768f6cff 100644 --- a/api/src/main/java/net/luckperms/api/event/node/NodeClearEvent.java +++ b/api/src/main/java/net/luckperms/api/event/node/NodeClearEvent.java @@ -25,9 +25,34 @@ package net.luckperms.api.event.node; +import net.luckperms.api.event.util.Param; +import net.luckperms.api.node.Node; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + /** * Called when a holder has their nodes cleared */ public interface NodeClearEvent extends NodeMutateEvent { + /** + * Gets the nodes that were cleared + * + * @return the nodes that were removed + * @since 5.3 + */ + @Param(3) + @NonNull Set getNodes(); + + @Override + default @NonNull Set getDataBefore() { + // Get data after, then reverse the action + Set nodes = new HashSet<>(this.getDataAfter()); + nodes.addAll(this.getNodes()); + return Collections.unmodifiableSet(nodes); + } } diff --git a/api/src/main/java/net/luckperms/api/event/node/NodeMutateEvent.java b/api/src/main/java/net/luckperms/api/event/node/NodeMutateEvent.java index aee6cc097..6a49f3d23 100644 --- a/api/src/main/java/net/luckperms/api/event/node/NodeMutateEvent.java +++ b/api/src/main/java/net/luckperms/api/event/node/NodeMutateEvent.java @@ -63,7 +63,6 @@ public interface NodeMutateEvent extends LuckPermsEvent { * * @return the data before the change */ - @Param(2) @NonNull Set getDataBefore(); /** @@ -71,7 +70,7 @@ public interface NodeMutateEvent extends LuckPermsEvent { * * @return the data after the change */ - @Param(3) + @Param(2) @NonNull Set getDataAfter(); /** diff --git a/api/src/main/java/net/luckperms/api/event/node/NodeRemoveEvent.java b/api/src/main/java/net/luckperms/api/event/node/NodeRemoveEvent.java index 846d7e32c..7ad5137d2 100644 --- a/api/src/main/java/net/luckperms/api/event/node/NodeRemoveEvent.java +++ b/api/src/main/java/net/luckperms/api/event/node/NodeRemoveEvent.java @@ -30,6 +30,10 @@ import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + /** * Called when a node is removed from a holder */ @@ -40,7 +44,15 @@ public interface NodeRemoveEvent extends NodeMutateEvent { * * @return the node that was removed */ - @Param(4) + @Param(3) @NonNull Node getNode(); + @Override + default @NonNull Set getDataBefore() { + // Get data after, then reverse the action + Set nodes = new HashSet<>(this.getDataAfter()); + nodes.add(this.getNode()); + return Collections.unmodifiableSet(nodes); + } + } diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java index a275b6c52..96f524eec 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java @@ -40,6 +40,7 @@ import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.model.nodemap.MutateResult; import me.lucko.luckperms.common.sender.Sender; import net.luckperms.api.actionlog.Action; @@ -61,6 +62,7 @@ import net.luckperms.api.event.log.LogReceiveEvent; import net.luckperms.api.event.node.NodeAddEvent; import net.luckperms.api.event.node.NodeClearEvent; +import net.luckperms.api.event.node.NodeMutateEvent; import net.luckperms.api.event.node.NodeRemoveEvent; import net.luckperms.api.event.player.PlayerDataSaveEvent; import net.luckperms.api.event.player.PlayerLoginProcessEvent; @@ -96,8 +98,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -223,16 +227,47 @@ public void dispatchLogReceive(UUID id, Action entry) { postAsync(LogReceiveEvent.class, id, entry); } - public void dispatchNodeAdd(Node node, PermissionHolder target, DataType dataType, Collection before, Collection after) { - postAsync(NodeAddEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after), node); - } + public void dispatchNodeChanges(PermissionHolder target, DataType dataType, MutateResult changes) { + if (!this.eventBus.shouldPost(NodeAddEvent.class) && !this.eventBus.shouldPost(NodeRemoveEvent.class)) { + return; + } - public void dispatchNodeClear(PermissionHolder target, DataType dataType, Collection before, Collection after) { - postAsync(NodeClearEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after)); + if (changes.isEmpty()) { + return; + } + + ApiPermissionHolder proxy = proxy(target); + ImmutableSet state = target.getData(dataType).asImmutableSet(); + + // call an event for each recorded change + for (MutateResult.Change change : changes.getChanges()) { + Class type = change.getType() == MutateResult.ChangeType.ADD ? + NodeAddEvent.class : NodeRemoveEvent.class; + + postAsync(type, proxy, dataType, state, change.getNode()); + } } - public void dispatchNodeRemove(Node node, PermissionHolder target, DataType dataType, Collection before, Collection after) { - postAsync(NodeRemoveEvent.class, proxy(target), dataType, ImmutableSet.copyOf(before), ImmutableSet.copyOf(after), node); + public void dispatchNodeClear(PermissionHolder target, DataType dataType, MutateResult changes) { + if (!this.eventBus.shouldPost(NodeClearEvent.class)) { + return; + } + + if (changes.isEmpty()) { + return; + } + + ApiPermissionHolder proxy = proxy(target); + ImmutableSet state = target.getData(dataType).asImmutableSet(); + + // call clear event + ImmutableSet nodes = ImmutableSet.copyOf(changes.getRemoved()); + postAsync(NodeClearEvent.class, proxy, dataType, state, nodes); + + // call add event if needed for any nodes that were added + for (Node added : changes.getAdded()) { + postAsync(NodeAddEvent.class, proxy, dataType, state, added); + } } public void dispatchConfigReload() { diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java index 0f0836cc8..0fe25a6da 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java @@ -426,17 +426,9 @@ public boolean auditTemporaryNodes() { } private boolean auditTemporaryNodes(DataType dataType) { - ImmutableSet before = getData(dataType).asImmutableSet(); - MutateResult result = getData(dataType).removeIf(Node::hasExpired); + this.plugin.getEventDispatcher().dispatchNodeChanges(this, dataType, result); if (!result.isEmpty()) { - // call event - ImmutableSet after = getData(dataType).asImmutableSet(); - for (Node r : result.getRemoved()) { - this.plugin.getEventDispatcher().dispatchNodeRemove(r, this, dataType, before, after); - } - - // invalidate invalidateCache(); } return !result.isEmpty(); @@ -468,14 +460,9 @@ public DataMutateResult setNode(DataType dataType, Node node, boolean callEvent) return DataMutateResult.FAIL_ALREADY_HAS; } - NodeMap data = getData(dataType); - - ImmutableSet before = data.asImmutableSet(); - data.add(node); - ImmutableSet after = data.asImmutableSet(); - + MutateResult changes = getData(dataType).add(node); if (callEvent) { - this.plugin.getEventDispatcher().dispatchNodeAdd(node, this, dataType, before, after); + this.plugin.getEventDispatcher().dispatchNodeChanges(this, dataType, changes); } invalidateCache(); @@ -511,11 +498,8 @@ public DataMutateResult.WithMergedNode setNode(DataType dataType, Node node, Tem if (newNode != null) { // Remove the old Node & add the new one. - ImmutableSet before = data.asImmutableSet(); - data.removeThenAdd(otherMatch, newNode); - ImmutableSet after = data.asImmutableSet(); - - this.plugin.getEventDispatcher().dispatchNodeAdd(newNode, this, dataType, before, after); + MutateResult changes = data.removeThenAdd(otherMatch, newNode); + this.plugin.getEventDispatcher().dispatchNodeChanges(this, dataType, changes); invalidateCache(); @@ -533,13 +517,8 @@ public DataMutateResult unsetNode(DataType dataType, Node node) { return DataMutateResult.FAIL_LACKS; } - NodeMap data = getData(dataType); - - ImmutableSet before = data.asImmutableSet(); - data.remove(node); - ImmutableSet after = data.asImmutableSet(); - - this.plugin.getEventDispatcher().dispatchNodeRemove(node, this, dataType, before, after); + MutateResult changes = getData(dataType).remove(node); + this.plugin.getEventDispatcher().dispatchNodeChanges(this, dataType, changes); invalidateCache(); @@ -561,12 +540,8 @@ public DataMutateResult.WithMergedNode unsetNode(DataType dataType, Node node, @ Node newNode = node.toBuilder().expiry(newExpiry).build(); // Remove the old Node & add the new one. - ImmutableSet before = data.asImmutableSet(); - data.removeThenAdd(otherMatch, newNode); - ImmutableSet after = data.asImmutableSet(); - - this.plugin.getEventDispatcher().dispatchNodeRemove(otherMatch, this, dataType, before, after); - this.plugin.getEventDispatcher().dispatchNodeAdd(newNode, this, dataType, before, after); + MutateResult changes = data.removeThenAdd(otherMatch, newNode); + this.plugin.getEventDispatcher().dispatchNodeChanges(this, dataType, changes); invalidateCache(); @@ -580,50 +555,40 @@ public DataMutateResult.WithMergedNode unsetNode(DataType dataType, Node node, @ } public boolean removeIf(DataType dataType, @Nullable ContextSet contextSet, Predicate predicate, boolean giveDefault) { - NodeMap data = getData(dataType); - ImmutableSet before = data.asImmutableSet(); - + MutateResult changes; if (contextSet == null) { - if (data.removeIf(predicate).isEmpty()) { - return false; - } + changes = getData(dataType).removeIf(predicate); } else { - if (data.removeIf(contextSet, predicate).isEmpty()) { - return false; - } + changes = getData(dataType).removeIf(contextSet, predicate); + } + + if (changes.isEmpty()) { + return false; } if (getType() == HolderType.USER && giveDefault) { getPlugin().getUserManager().giveDefaultIfNeeded((User) this); } - ImmutableSet after = data.asImmutableSet(); - this.plugin.getEventDispatcher().dispatchNodeClear(this, dataType, before, after); - + this.plugin.getEventDispatcher().dispatchNodeClear(this, dataType, changes); invalidateCache(); - return true; } public boolean clearNodes(DataType dataType, ContextSet contextSet, boolean giveDefault) { - NodeMap data = getData(dataType); - ImmutableSet before = data.asImmutableSet(); - + MutateResult changes; if (contextSet == null) { - data.clear(); + changes = getData(dataType).clear(); } else { - data.clear(contextSet); + changes = getData(dataType).clear(contextSet); } if (getType() == HolderType.USER && giveDefault) { getPlugin().getUserManager().giveDefaultIfNeeded((User) this); } - ImmutableSet after = data.asImmutableSet(); - this.plugin.getEventDispatcher().dispatchNodeClear(this, dataType, before, after); - + this.plugin.getEventDispatcher().dispatchNodeClear(this, dataType, changes); invalidateCache(); - return true; } From 21abb9479640582e89db099f482b0cf538272893 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Mon, 4 Jan 2021 09:23:54 -0600 Subject: [PATCH 35/38] Fabric (#2029) --- CONTRIBUTING.md | 2 +- .../net/luckperms/api/platform/Platform.java | 3 +- common/build.gradle | 5 +- .../luckperms/common/config/ConfigKeys.java | 5 + .../plugin/bootstrap/LuckPermsBootstrap.java | 1 + .../plugin/logging/Log4jPluginLogger.java | 61 ++ fabric/build.gradle | 94 +++ .../fabric/FabricCalculatorFactory.java | 76 +++ .../luckperms/fabric/FabricClassLoader.java | 49 ++ .../fabric/FabricCommandExecutor.java | 118 ++++ .../luckperms/fabric/FabricConfigAdapter.java | 46 ++ .../luckperms/fabric/FabricEventBus.java | 47 ++ .../fabric/FabricSchedulerAdapter.java | 43 ++ .../luckperms/fabric/FabricSenderFactory.java | 110 ++++ .../luckperms/fabric/LPFabricBootstrap.java | 280 +++++++++ .../luckperms/fabric/LPFabricPlugin.java | 248 ++++++++ .../calculator/ServerOwnerProcessor.java | 44 ++ .../fabric/context/FabricContextManager.java | 81 +++ .../context/FabricPlayerCalculator.java | 119 ++++ .../event/PlayerChangeWorldCallback.java | 42 ++ .../fabric/event/RespawnPlayerCallback.java | 42 ++ .../listeners/FabricConnectionListener.java | 133 +++++ .../listeners/PermissionCheckListener.java | 89 +++ .../ClientSettingsC2SPacketAccessor.java | 39 ++ .../fabric/mixin/PlayerManagerMixin.java | 49 ++ .../ServerLoginNetworkHandlerAccessor.java | 45 ++ .../fabric/mixin/ServerPlayerEntityMixin.java | 142 +++++ .../luckperms/fabric/model/MixinUser.java | 67 +++ .../main/resources/assets/luckperms/icon.png | Bin 0 -> 25938 bytes fabric/src/main/resources/fabric.mod.json | 43 ++ fabric/src/main/resources/luckperms.conf | 556 ++++++++++++++++++ .../src/main/resources/mixins.luckperms.json | 16 + gradle.properties | 2 + settings.gradle | 14 +- .../luckperms/sponge/LPSpongePlugin.java | 3 +- 35 files changed, 2709 insertions(+), 5 deletions(-) create mode 100644 common/src/main/java/me/lucko/luckperms/common/plugin/logging/Log4jPluginLogger.java create mode 100644 fabric/build.gradle create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/FabricCalculatorFactory.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/FabricClassLoader.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/FabricCommandExecutor.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/FabricConfigAdapter.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/FabricEventBus.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/FabricSchedulerAdapter.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/FabricSenderFactory.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricBootstrap.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/calculator/ServerOwnerProcessor.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricContextManager.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricPlayerCalculator.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/event/PlayerChangeWorldCallback.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/event/RespawnPlayerCallback.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/listeners/PermissionCheckListener.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ClientSettingsC2SPacketAccessor.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/mixin/PlayerManagerMixin.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerLoginNetworkHandlerAccessor.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerPlayerEntityMixin.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/model/MixinUser.java create mode 100644 fabric/src/main/resources/assets/luckperms/icon.png create mode 100644 fabric/src/main/resources/fabric.mod.json create mode 100644 fabric/src/main/resources/luckperms.conf create mode 100644 fabric/src/main/resources/mixins.luckperms.json create mode 100644 gradle.properties diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e0dfad55..30037813a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,4 +23,4 @@ The project is split up into a few separate modules. * **API** - The public, semantically versioned API used by other plugins wishing to integrate with and retrieve data from LuckPerms. This module (for the most part) does not contain any implementation itself, and is provided by the plugin. * **Common** - The common module contains most of the code which implements the respective LuckPerms plugins. This abstract module reduces duplicated code throughout the project. -* **Bukkit, BungeeCord, Sponge, Nukkit & Velocity** - Each use the common module to implement plugins on the respective server platforms. +* **Bukkit, BungeeCord, Sponge, Nukkit, Velocity & Fabric** - Each use the common module to implement plugins on the respective server platforms. diff --git a/api/src/main/java/net/luckperms/api/platform/Platform.java b/api/src/main/java/net/luckperms/api/platform/Platform.java index 271a6e6ca..316ee956f 100644 --- a/api/src/main/java/net/luckperms/api/platform/Platform.java +++ b/api/src/main/java/net/luckperms/api/platform/Platform.java @@ -73,7 +73,8 @@ enum Type { BUNGEECORD("BungeeCord"), SPONGE("Sponge"), NUKKIT("Nukkit"), - VELOCITY("Velocity"); + VELOCITY("Velocity"), + FABRIC("Fabric"); private final String friendlyName; diff --git a/common/build.gradle b/common/build.gradle index 68fc40308..3f66ed81f 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -9,7 +9,10 @@ dependencies { testCompile 'org.junit.jupiter:junit-jupiter-engine:5.7.0' compile project(':api') - compile 'org.checkerframework:checker-qual:2.5.5' + compile 'org.checkerframework:checker-qual:3.8.0' + + compileOnly 'org.slf4j:slf4j-api:1.7.30' + compileOnly 'org.apache.logging.log4j:log4j-api:2.14.0' // This is a special re-packaged version of 'net.kyori:adventure-*' for our own use. // Don't use it in other projects, you want the net.kyori version instead. 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 66110fbd0..b9ffc5caf 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 @@ -483,6 +483,11 @@ private ConfigKeys() {} */ public static final ConfigKey VAULT_IGNORE_WORLD = booleanKey("vault-ignore-world", false); + /** + * If the owner of an integrated server should automatically bypasses all permission checks. On fabric, this only applies on an Integrated Server. + */ + public static final ConfigKey FABRIC_INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS = booleanKey("integrated-server-owner-bypasses-checks", true); + /** * The world rewrites map */ diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java b/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java index 4d18a1528..77dc2d69a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java @@ -138,6 +138,7 @@ public interface LuckPermsBootstrap { *

Bukkit: /root/plugins/LuckPerms

*

Bungee: /root/plugins/LuckPerms

*

Sponge: /root/luckperms/

+ *

Fabric: /root/mods/LuckPerms

* * @return the platforms data folder */ diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/logging/Log4jPluginLogger.java b/common/src/main/java/me/lucko/luckperms/common/plugin/logging/Log4jPluginLogger.java new file mode 100644 index 000000000..ff3338a28 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/logging/Log4jPluginLogger.java @@ -0,0 +1,61 @@ +/* + * 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.plugin.logging; + +import org.apache.logging.log4j.Logger; + +public class Log4jPluginLogger implements PluginLogger { + private final Logger logger; + + public Log4jPluginLogger(Logger logger) { + this.logger = logger; + } + + @Override + public void info(String s) { + this.logger.info(s); + } + + @Override + public void warn(String s) { + this.logger.warn(s); + } + + @Override + public void warn(String s, Throwable t) { + this.logger.warn(s, t); + } + + @Override + public void severe(String s) { + this.logger.error(s); + } + + @Override + public void severe(String s, Throwable t) { + this.logger.error(s, t); + } +} diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 000000000..2d804e24f --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,94 @@ +import net.fabricmc.loom.task.RemapJarTask + +plugins { + id 'com.github.johnrengelman.shadow' version '4.0.1' + id 'fabric-loom' version '0.5-SNAPSHOT' +} + +repositories { + maven { url 'https://maven.fabricmc.net/' } + mavenLocal() +} + +def minecraftVersion = '1.16.4' +def yarnBuild = 7 +def loaderVersion = '0.10.8' +def fabricApiVersion = '0.28.4+1.16' + +dependencies { + // Fabric Stuff, We don't specifically target only a single version but yarn mappings require a version to be specified. + minecraft "com.mojang:minecraft:${minecraftVersion}" + mappings "net.fabricmc:yarn:${minecraftVersion}+build.${yarnBuild}:v2" + modImplementation "net.fabricmc:fabric-loader:${loaderVersion}" + + Set apiModules = [ + 'fabric-api-base', + 'fabric-command-api-v1', + 'fabric-lifecycle-events-v1', + 'fabric-networking-api-v1' + ] + + apiModules.forEach { + modImplementation(fabricApi.module(it, fabricApiVersion)) + } + + include(modImplementation('me.lucko:fabric-permissions-api:0.1-SNAPSHOT')) + + compile project(':common') +} + +processResources { + inputs.property 'version', project.ext.fullVersion + + from(sourceSets.main.resources.srcDirs) { + include 'fabric.mod.json' + expand 'version': project.ext.fullVersion + } + + from(sourceSets.main.resources.srcDirs) { + exclude 'fabric.mod.json' + } +} + +shadowJar { + archiveName = "LuckPerms-Fabric-${project.ext.fullVersion}-dev.jar" + + dependencies { + exclude('net.fabricmc:.*') + include(dependency('net.luckperms:.*')) + include(dependency('me.lucko.luckperms:.*')) + // We don't want to include the mappings in the jar do we? + exclude '/mappings/*' + } + + relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure' + relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus' + relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine' + relocate 'okio', 'me.lucko.luckperms.lib.okio' + relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3' + relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy' + relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore' + relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb' + relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql' + relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql' + relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari' + relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb' + relocate 'org.bson', 'me.lucko.luckperms.lib.bson' + relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis' + relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2' + relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate' +} + +task remappedShadowJar(type: RemapJarTask) { + dependsOn tasks.shadowJar + input = tasks.shadowJar.archivePath + addNestedDependencies = true + archiveName = "LuckPerms-Fabric-${project.ext.fullVersion}.jar" +} + +tasks.assemble.dependsOn tasks.remappedShadowJar + +artifacts { + archives remappedShadowJar + shadow shadowJar +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/FabricCalculatorFactory.java b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricCalculatorFactory.java new file mode 100644 index 000000000..47cf4c831 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricCalculatorFactory.java @@ -0,0 +1,76 @@ +/* + * 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.fabric; + +import com.google.common.collect.ImmutableList; + +import me.lucko.luckperms.common.cacheddata.CacheMetadata; +import me.lucko.luckperms.common.calculator.CalculatorFactory; +import me.lucko.luckperms.common.calculator.PermissionCalculator; +import me.lucko.luckperms.common.calculator.processor.MapProcessor; +import me.lucko.luckperms.common.calculator.processor.PermissionProcessor; +import me.lucko.luckperms.common.calculator.processor.RegexProcessor; +import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor; +import me.lucko.luckperms.common.calculator.processor.WildcardProcessor; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.fabric.calculator.ServerOwnerProcessor; +import me.lucko.luckperms.fabric.context.FabricContextManager; + +import net.luckperms.api.query.QueryOptions; + +public class FabricCalculatorFactory implements CalculatorFactory { + private final LPFabricPlugin plugin; + + public FabricCalculatorFactory(LPFabricPlugin plugin) { + this.plugin = plugin; + } + + @Override + public PermissionCalculator build(QueryOptions queryOptions, CacheMetadata metadata) { + ImmutableList.Builder processors = ImmutableList.builder(); + + processors.add(new MapProcessor()); + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) { + processors.add(new RegexProcessor()); + } + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS)) { + processors.add(new WildcardProcessor()); + } + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS_SPONGE)) { + processors.add(new SpongeWildcardProcessor()); + } + + boolean integratedOwner = queryOptions.option(FabricContextManager.INTEGRATED_SERVER_OWNER).orElse(false); + if (integratedOwner && this.plugin.getConfiguration().get(ConfigKeys.FABRIC_INTEGRATED_SERVER_OWNER_BYPASSES_CHECKS)) { + processors.add(new ServerOwnerProcessor()); + } + + return new PermissionCalculator(this.plugin, metadata, processors.build()); + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/FabricClassLoader.java b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricClassLoader.java new file mode 100644 index 000000000..819250dc3 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricClassLoader.java @@ -0,0 +1,49 @@ +/* + * 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.fabric; + +import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader; + +import net.fabricmc.loader.launch.common.FabricLauncherBase; + +import java.net.MalformedURLException; +import java.nio.file.Path; + +public class FabricClassLoader implements PluginClassLoader { + + @Override + public void addJarToClasspath(Path file) { + try { + // Fabric abstracts class loading away to the FabricLauncher. + // TODO(i509VCB): Work on API for Fabric Loader which does not touch internals. + // Player wants to use project jigsaw in the future. + FabricLauncherBase.getLauncher().propose(file.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/FabricCommandExecutor.java b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricCommandExecutor.java new file mode 100644 index 000000000..8086bc3f6 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricCommandExecutor.java @@ -0,0 +1,118 @@ +/* + * 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.fabric; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; + +import me.lucko.luckperms.common.command.CommandManager; +import me.lucko.luckperms.common.command.utils.ArgumentTokenizer; +import me.lucko.luckperms.common.sender.Sender; + +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +public class FabricCommandExecutor extends CommandManager implements Command, SuggestionProvider { + private static final String[] COMMAND_ALIASES = new String[] {"luckperms", "lp", "perm", "perms", "permission", "permissions"}; + + private final LPFabricPlugin plugin; + + public FabricCommandExecutor(LPFabricPlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + public void register() { + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { + for (String alias : COMMAND_ALIASES) { + LiteralCommandNode cmd = literal(alias) + .executes(this) + .build(); + + ArgumentCommandNode args = argument("args", greedyString()) + .suggests(this) + .executes(this) + .build(); + + cmd.addChild(args); + dispatcher.getRoot().addChild(cmd); + } + }); + } + + @Override + public int run(CommandContext ctx) { + Sender wrapped = this.plugin.getSenderFactory().wrap(ctx.getSource()); + + int start = ctx.getRange().getStart(); + List arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(ctx.getInput().substring(start)); + + String label = arguments.remove(0); + if (label.startsWith("/")) { + label = label.substring(1); + } + + executeCommand(wrapped, label, arguments); + return Command.SINGLE_SUCCESS; + } + + @Override + public CompletableFuture getSuggestions(CommandContext ctx, SuggestionsBuilder builder) { + Sender wrapped = this.plugin.getSenderFactory().wrap(ctx.getSource()); + + int idx = builder.getStart(); + + String buffer = ctx.getInput().substring(idx); + idx += buffer.length(); + + List arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(buffer); + if (!arguments.isEmpty()) { + idx -= arguments.get(arguments.size() - 1).length(); + } + + List completions = tabCompleteCommand(wrapped, arguments); + + // Offset the builder from the current string range so suggestions are placed in the right spot + builder = builder.createOffset(idx); + for (String completion : completions) { + builder.suggest(completion); + } + return builder.buildFuture(); + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/FabricConfigAdapter.java b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricConfigAdapter.java new file mode 100644 index 000000000..fbc07eb2c --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricConfigAdapter.java @@ -0,0 +1,46 @@ +/* + * 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.fabric; + +import me.lucko.luckperms.common.config.generic.adapter.ConfigurateConfigAdapter; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; + +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.loader.ConfigurationLoader; + +import java.nio.file.Path; + +public class FabricConfigAdapter extends ConfigurateConfigAdapter { + public FabricConfigAdapter(LuckPermsPlugin plugin, Path path) { + super(plugin, path); + } + + @Override + protected ConfigurationLoader createLoader(Path path) { + return HoconConfigurationLoader.builder().setPath(path).build(); + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/FabricEventBus.java b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricEventBus.java new file mode 100644 index 000000000..59a7d41b6 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricEventBus.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.fabric; + +import me.lucko.luckperms.common.api.LuckPermsApiProvider; +import me.lucko.luckperms.common.event.AbstractEventBus; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; + +import net.fabricmc.loader.api.ModContainer; + +public class FabricEventBus extends AbstractEventBus { + public FabricEventBus(LuckPermsPlugin plugin, LuckPermsApiProvider apiProvider) { + super(plugin, apiProvider); + } + + @Override + protected ModContainer checkPlugin(Object mod) throws IllegalArgumentException { + if (mod instanceof ModContainer) { + return (ModContainer) mod; + } + + throw new IllegalArgumentException("Object " + mod + " (" + mod.getClass().getName() + ") is not a ModContainer."); + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/FabricSchedulerAdapter.java b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricSchedulerAdapter.java new file mode 100644 index 000000000..30fc6315d --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricSchedulerAdapter.java @@ -0,0 +1,43 @@ +/* + * 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.fabric; + +import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler; + +import java.util.concurrent.Executor; + +public class FabricSchedulerAdapter extends AbstractJavaScheduler { + private final Executor sync; + + public FabricSchedulerAdapter(LPFabricBootstrap bootstrap) { + this.sync = r -> bootstrap.getServer().orElseThrow(() -> new IllegalStateException("Server not ready")).submitAndJoin(r); + } + + @Override + public Executor sync() { + return this.sync; + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/FabricSenderFactory.java b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricSenderFactory.java new file mode 100644 index 000000000..5bcc9aee5 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/FabricSenderFactory.java @@ -0,0 +1,110 @@ +/* + * 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.fabric; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.common.sender.SenderFactory; +import me.lucko.luckperms.fabric.model.MixinUser; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.luckperms.api.util.Tristate; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; + +import java.util.Locale; +import java.util.UUID; + +public class FabricSenderFactory extends SenderFactory { + private final LPFabricPlugin plugin; + + public FabricSenderFactory(LPFabricPlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + @Override + protected LPFabricPlugin getPlugin() { + return this.plugin; + } + + @Override + protected UUID getUniqueId(ServerCommandSource commandSource) { + if (commandSource.getEntity() != null) { + return commandSource.getEntity().getUuid(); + } + return Sender.CONSOLE_UUID; + } + + @Override + protected String getName(ServerCommandSource commandSource) { + String name = commandSource.getName(); + if (commandSource.getEntity() != null && name.equals("Server")) { + return Sender.CONSOLE_NAME; + } + return name; + } + + @Override + protected void sendMessage(ServerCommandSource sender, Component message) { + Locale locale = null; + if (sender.getEntity() instanceof ServerPlayerEntity) { + locale = ((MixinUser) sender.getEntity()).getCachedLocale(); + } + sender.sendFeedback(toNativeText(TranslationManager.render(message, locale)), false); + } + + @Override + protected Tristate getPermissionValue(ServerCommandSource commandSource, String node) { + switch (Permissions.getPermissionValue(commandSource, node)) { + case TRUE: + return Tristate.TRUE; + case FALSE: + return Tristate.FALSE; + case DEFAULT: + return Tristate.UNDEFINED; + default: + throw new AssertionError(); + } + } + + @Override + protected boolean hasPermission(ServerCommandSource commandSource, String node) { + return getPermissionValue(commandSource, node).asBoolean(); + } + + @Override + protected void performCommand(ServerCommandSource sender, String command) { + sender.getMinecraftServer().getCommandManager().execute(sender, command); + } + + public static Text toNativeText(Component component) { + return Text.Serializer.fromJson(GsonComponentSerializer.gson().serialize(component)); + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricBootstrap.java b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricBootstrap.java new file mode 100644 index 000000000..2a34f5433 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricBootstrap.java @@ -0,0 +1,280 @@ +/* + * 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.fabric; + +import com.mojang.authlib.GameProfile; + +import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader; +import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap; +import me.lucko.luckperms.common.plugin.logging.Log4jPluginLogger; +import me.lucko.luckperms.common.plugin.logging.PluginLogger; +import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter; + +import net.fabricmc.api.DedicatedServerModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.luckperms.api.platform.Platform; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; + +import org.apache.logging.log4j.LogManager; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; + +/** + * Bootstrap plugin for LuckPerms running on Fabric. + */ +public final class LPFabricBootstrap implements LuckPermsBootstrap, DedicatedServerModInitializer { + + private static final String MODID = "luckperms"; + private static final ModContainer MOD_CONTAINER = FabricLoader.getInstance().getModContainer(MODID) + .orElseThrow(() -> new RuntimeException("Could not get the LuckPerms mod container.")); + + /** + * The plugin logger + */ + private final PluginLogger logger; + + /** + * A scheduler adapter for the platform + */ + private final SchedulerAdapter schedulerAdapter; + + /** + * The plugin class loader. + */ + private final PluginClassLoader classLoader; + + /** + * The plugin instance + */ + private LPFabricPlugin plugin; + + /** + * The time when the plugin was enabled + */ + private Instant startTime; + + // load/enable latches + private final CountDownLatch loadLatch = new CountDownLatch(1); + private final CountDownLatch enableLatch = new CountDownLatch(1); + + /** + * The Minecraft server instance + */ + private MinecraftServer server; + + public LPFabricBootstrap() { + this.logger = new Log4jPluginLogger(LogManager.getLogger(MODID)); + this.schedulerAdapter = new FabricSchedulerAdapter(this); + this.classLoader = new FabricClassLoader(); + this.plugin = new LPFabricPlugin(this); + } + + // provide adapters + + @Override + public PluginLogger getPluginLogger() { + return this.logger; + } + + @Override + public SchedulerAdapter getScheduler() { + return this.schedulerAdapter; + } + + @Override + public PluginClassLoader getPluginClassLoader() { + return this.classLoader; + } + + // lifecycle + + @Override + public void onInitializeServer() { + this.plugin = new LPFabricPlugin(this); + try { + this.plugin.load(); + } finally { + this.loadLatch.countDown(); + } + + // Register the Server startup/shutdown events now + ServerLifecycleEvents.SERVER_STARTING.register(this::onServerStarting); + ServerLifecycleEvents.SERVER_STOPPING.register(this::onServerStopping); + this.plugin.registerFabricListeners(); + } + + private void onServerStarting(MinecraftServer server) { + this.server = server; + this.startTime = Instant.now(); + this.plugin.enable(); + } + + private void onServerStopping(MinecraftServer server) { + this.plugin.disable(); + this.server = null; + } + + @Override + public CountDownLatch getLoadLatch() { + return this.loadLatch; + } + + @Override + public CountDownLatch getEnableLatch() { + return this.enableLatch; + } + + // MinecraftServer singleton getter + + public Optional getServer() { + return Optional.ofNullable(this.server); + } + + // provide information about the plugin + + @Override + public String getVersion() { + return MOD_CONTAINER.getMetadata().getVersion().getFriendlyString(); + } + + @Override + public Instant getStartupTime() { + return this.startTime; + } + + // provide information about the platform + + @Override + public Platform.Type getType() { + return Platform.Type.FABRIC; + } + + @Override + public String getServerBrand() { + String fabricVersion = FabricLoader.getInstance().getModContainer("fabric") + .map(c -> c.getMetadata().getVersion().getFriendlyString()) + .orElse("unknown"); + + return "fabric@" + fabricVersion; + } + + @Override + public String getServerVersion() { + String fabricApiVersion = FabricLoader.getInstance().getModContainer("fabric-api-base") + .map(c -> c.getMetadata().getVersion().getFriendlyString()) + .orElse("unknown"); + + return getServer().map(MinecraftServer::getVersion).orElse("null") + " - fabric-api@" + fabricApiVersion; + } + + @Override + public Path getDataDirectory() { + return FabricLoader.getInstance().getGameDir().resolve("mods").resolve(MODID); + } + + @Override + public Path getConfigDirectory() { + return FabricLoader.getInstance().getConfigDir().resolve(MODID); + } + + @Override + public InputStream getResourceStream(String path) { + try { + return Files.newInputStream(LPFabricBootstrap.MOD_CONTAINER.getPath(path)); + } catch (IOException e) { + return null; + } + } + + @Override + public Optional getPlayer(UUID uniqueId) { + return getServer().map(MinecraftServer::getPlayerManager).map(s -> s.getPlayer(uniqueId)); + } + + @Override + public Optional lookupUniqueId(String username) { + return getServer().map(MinecraftServer::getUserCache).map(c -> c.findByName(username)).map(GameProfile::getId); + + } + + @Override + public Optional lookupUsername(UUID uniqueId) { + return getServer().map(MinecraftServer::getUserCache).map(c -> c.getByUuid(uniqueId)).map(GameProfile::getName); + } + + @Override + public int getPlayerCount() { + return getServer().map(MinecraftServer::getCurrentPlayerCount).orElse(0); + } + + @Override + public Collection getPlayerList() { + return getServer().map(MinecraftServer::getPlayerManager) + .map(server -> { + List players = server.getPlayerList(); + List list = new ArrayList<>(players.size()); + for (ServerPlayerEntity player : players) { + list.add(player.getGameProfile().getName()); + } + return list; + }) + .orElse(Collections.emptyList()); + } + + @Override + public Collection getOnlinePlayers() { + return getServer().map(MinecraftServer::getPlayerManager) + .map(server -> { + List players = server.getPlayerList(); + List list = new ArrayList<>(players.size()); + for (ServerPlayerEntity player : players) { + list.add(player.getGameProfile().getId()); + } + return list; + }) + .orElse(Collections.emptyList()); + } + + @Override + public boolean isPlayerOnline(UUID uniqueId) { + return getServer().map(MinecraftServer::getPlayerManager).map(s -> s.getPlayer(uniqueId) != null).orElse(false); + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java new file mode 100644 index 000000000..991d7078d --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java @@ -0,0 +1,248 @@ +/* + * 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.fabric; + +import me.lucko.luckperms.common.api.LuckPermsApiProvider; +import me.lucko.luckperms.common.calculator.CalculatorFactory; +import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter; +import me.lucko.luckperms.common.dependencies.Dependency; +import me.lucko.luckperms.common.event.AbstractEventBus; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.messaging.MessagingFactory; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.model.manager.group.StandardGroupManager; +import me.lucko.luckperms.common.model.manager.track.StandardTrackManager; +import me.lucko.luckperms.common.model.manager.user.StandardUserManager; +import me.lucko.luckperms.common.plugin.AbstractLuckPermsPlugin; +import me.lucko.luckperms.common.sender.DummySender; +import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.common.tasks.CacheHousekeepingTask; +import me.lucko.luckperms.common.tasks.ExpireTemporaryTask; +import me.lucko.luckperms.common.util.MoreFiles; +import me.lucko.luckperms.fabric.context.FabricContextManager; +import me.lucko.luckperms.fabric.context.FabricPlayerCalculator; +import me.lucko.luckperms.fabric.listeners.FabricConnectionListener; +import me.lucko.luckperms.fabric.listeners.PermissionCheckListener; + +import net.fabricmc.loader.api.ModContainer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.query.QueryOptions; +import net.minecraft.server.MinecraftServer; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +public class LPFabricPlugin extends AbstractLuckPermsPlugin { + private final LPFabricBootstrap bootstrap; + + private FabricConnectionListener connectionListener; + private FabricCommandExecutor commandManager; + private FabricSenderFactory senderFactory; + private FabricContextManager contextManager; + private StandardUserManager userManager; + private StandardGroupManager groupManager; + private StandardTrackManager trackManager; + + public LPFabricPlugin(LPFabricBootstrap bootstrap) { + this.bootstrap = bootstrap; + } + + @Override + public LPFabricBootstrap getBootstrap() { + return this.bootstrap; + } + + protected void registerFabricListeners() { + // Events are registered very early on, and persist between game states + this.connectionListener = new FabricConnectionListener(this); + this.connectionListener.registerListeners(); + + new PermissionCheckListener(this).registerListeners(); + + // Command registration also need to occur early, and will persist across game states as well. + this.commandManager = new FabricCommandExecutor(this); + this.commandManager.register(); + } + + @Override + protected void setupSenderFactory() { + this.senderFactory = new FabricSenderFactory(this); + } + + @Override + protected Set getGlobalDependencies() { + Set dependencies = super.getGlobalDependencies(); + dependencies.add(Dependency.CONFIGURATE_CORE); + dependencies.add(Dependency.CONFIGURATE_HOCON); + dependencies.add(Dependency.HOCON_CONFIG); + return dependencies; + } + + @Override + protected ConfigurationAdapter provideConfigurationAdapter() { + Path configPath = this.getBootstrap().getConfigDirectory().resolve("luckperms.conf"); + + if (!Files.exists(configPath)) { + try { + MoreFiles.createDirectoriesIfNotExists(this.bootstrap.getConfigDirectory()); + try (InputStream is = this.getBootstrap().getResourceStream("luckperms.conf")) { + Files.copy(is, configPath); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return new FabricConfigAdapter(this, configPath); + } + + @Override + protected void registerPlatformListeners() { + // Too late for Fabric, registered in #registerFabricListeners + } + + @Override + protected MessagingFactory provideMessagingFactory() { + return new MessagingFactory<>(this); + } + + @Override + protected void registerCommands() { + // Too late for Fabric, registered in #registerFabricListeners + } + + @Override + protected void setupManagers() { + this.userManager = new StandardUserManager(this); + this.groupManager = new StandardGroupManager(this); + this.trackManager = new StandardTrackManager(this); + } + + @Override + protected CalculatorFactory provideCalculatorFactory() { + return new FabricCalculatorFactory(this); + } + + @Override + protected void setupContextManager() { + this.contextManager = new FabricContextManager(this); + + FabricPlayerCalculator playerCalculator = new FabricPlayerCalculator(this); + playerCalculator.registerListeners(); + this.contextManager.registerCalculator(playerCalculator); + } + + @Override + protected void setupPlatformHooks() { + } + + @Override + protected AbstractEventBus provideEventBus(LuckPermsApiProvider provider) { + return new FabricEventBus(this, provider); + } + + @Override + protected void registerApiOnPlatform(LuckPerms api) { + } + + @Override + protected void registerHousekeepingTasks() { + this.bootstrap.getScheduler().asyncRepeating(new ExpireTemporaryTask(this), 3, TimeUnit.SECONDS); + this.bootstrap.getScheduler().asyncRepeating(new CacheHousekeepingTask(this), 2, TimeUnit.MINUTES); + } + + @Override + protected void performFinalSetup() { + } + + public FabricSenderFactory getSenderFactory() { + return this.senderFactory; + } + + @Override + public FabricConnectionListener getConnectionListener() { + return this.connectionListener; + } + + @Override + public FabricCommandExecutor getCommandManager() { + return this.commandManager; + } + + @Override + public FabricContextManager getContextManager() { + return this.contextManager; + } + + @Override + public StandardUserManager getUserManager() { + return this.userManager; + } + + @Override + public StandardGroupManager getGroupManager() { + return this.groupManager; + } + + @Override + public StandardTrackManager getTrackManager() { + return this.trackManager; + } + + @Override + public Optional getQueryOptionsForUser(User user) { + return this.bootstrap.getPlayer(user.getUniqueId()).map(player -> this.contextManager.getQueryOptions(player)); + } + + @Override + public Stream getOnlineSenders() { + return Stream.concat( + Stream.of(getConsoleSender()), + this.bootstrap.getServer().map(MinecraftServer::getPlayerManager).map(s -> s.getPlayerList().stream().map(p -> this.senderFactory.wrap(p.getCommandSource()))).orElseGet(Stream::empty) + ); + } + + @Override + public Sender getConsoleSender() { + return this.bootstrap.getServer() + .map(s -> this.senderFactory.wrap(s.getCommandSource())) + .orElseGet(() -> new DummySender(this, Sender.CONSOLE_UUID, Sender.CONSOLE_NAME) { + @Override + public void sendMessage(Component message) { + LPFabricPlugin.this.bootstrap.getPluginLogger().info(PlainComponentSerializer.plain().serialize(TranslationManager.render(message))); + } + }); + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/calculator/ServerOwnerProcessor.java b/fabric/src/main/java/me/lucko/luckperms/fabric/calculator/ServerOwnerProcessor.java new file mode 100644 index 000000000..61519020b --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/calculator/ServerOwnerProcessor.java @@ -0,0 +1,44 @@ +/* + * 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.fabric.calculator; + +import me.lucko.luckperms.common.calculator.processor.AbstractPermissionProcessor; +import me.lucko.luckperms.common.calculator.result.TristateResult; + +import net.luckperms.api.util.Tristate; + +/** + * Permission processor which is added to the owner of an Integrated server to + * simply return true if no other processors match. + */ +public class ServerOwnerProcessor extends AbstractPermissionProcessor { + private static final TristateResult TRUE_RESULT = new TristateResult.Factory(ServerOwnerProcessor.class).result(Tristate.TRUE); + + @Override + public TristateResult hasPermission(String permission) { + return TRUE_RESULT; + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricContextManager.java b/fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricContextManager.java new file mode 100644 index 000000000..eac3eddfc --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricContextManager.java @@ -0,0 +1,81 @@ +/* + * 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.fabric.context; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.context.ContextManager; +import me.lucko.luckperms.common.context.QueryOptionsCache; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.fabric.model.MixinUser; + +import net.luckperms.api.context.ImmutableContextSet; +import net.luckperms.api.query.OptionKey; +import net.luckperms.api.query.QueryOptions; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.UUID; + +public class FabricContextManager extends ContextManager { + public static final OptionKey INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class); + + public FabricContextManager(LuckPermsPlugin plugin) { + super(plugin, ServerPlayerEntity.class, ServerPlayerEntity.class); + } + + @Override + public UUID getUniqueId(ServerPlayerEntity player) { + return player.getUuid(); + } + + public QueryOptionsCache newQueryOptionsCache(ServerPlayerEntity player) { + return new QueryOptionsCache<>(player, this); + } + + @Override + public QueryOptionsCache getCacheFor(ServerPlayerEntity subject) { + if (subject == null) { + throw new NullPointerException("subject"); + } + + return ((MixinUser) subject).getQueryOptionsCache(this); + } + + @Override + public void invalidateCache(ServerPlayerEntity subject) { + getCacheFor(subject).invalidate(); + } + + @Override + public QueryOptions formQueryOptions(ServerPlayerEntity subject, ImmutableContextSet contextSet) { + QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder(); + if (subject.getServer().isHost(subject.getGameProfile())) { + queryOptions.option(INTEGRATED_SERVER_OWNER, true); + } + + return queryOptions.context(contextSet).build(); + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricPlayerCalculator.java b/fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricPlayerCalculator.java new file mode 100644 index 000000000..ac42b8be2 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/context/FabricPlayerCalculator.java @@ -0,0 +1,119 @@ +/* + * 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.fabric.context; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; +import me.lucko.luckperms.common.util.EnumNamer; +import me.lucko.luckperms.fabric.LPFabricPlugin; +import me.lucko.luckperms.fabric.event.PlayerChangeWorldCallback; +import me.lucko.luckperms.fabric.event.RespawnPlayerCallback; + +import net.luckperms.api.context.Context; +import net.luckperms.api.context.ContextCalculator; +import net.luckperms.api.context.ContextConsumer; +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.DefaultContextKeys; +import net.luckperms.api.context.ImmutableContextSet; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.world.GameMode; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Optional; + +public class FabricPlayerCalculator implements ContextCalculator { + private static final EnumNamer GAMEMODE_NAMER = new EnumNamer<>( + GameMode.class, + EnumNamer.LOWER_CASE_NAME + ); + + private final LPFabricPlugin plugin; + + public FabricPlayerCalculator(LPFabricPlugin plugin) { + this.plugin = plugin; + } + + public void registerListeners() { + PlayerChangeWorldCallback.EVENT.register(this::onWorldChange); + RespawnPlayerCallback.EVENT.register(this::onPlayerRespawn); + } + + @Override + public void calculate(@NonNull ServerPlayerEntity target, @NonNull ContextConsumer consumer) { + GameMode mode = target.interactionManager.getGameMode(); + if (mode != null && mode != GameMode.NOT_SET) { + consumer.accept(DefaultContextKeys.GAMEMODE_KEY, GAMEMODE_NAMER.name(mode)); + } + + // TODO: figure out dimension type context too + ServerWorld world = target.getServerWorld(); + this.plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).rewriteAndSubmit(getContextKey(world.getRegistryKey().getValue()), consumer); + } + + @Override + public ContextSet estimatePotentialContexts() { + ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl(); + + for (GameMode mode : GameMode.values()) { + builder.add(DefaultContextKeys.GAMEMODE_KEY, GAMEMODE_NAMER.name(mode)); + } + + // TODO: dimension type + + Optional server = this.plugin.getBootstrap().getServer(); + if (server.isPresent()) { + Iterable worlds = server.get().getWorlds(); + for (ServerWorld world : worlds) { + String worldName = getContextKey(world.getRegistryKey().getValue()); + if (Context.isValidValue(worldName)) { + builder.add(DefaultContextKeys.WORLD_KEY, worldName); + } + } + } + + return builder.build(); + } + + private static String getContextKey(Identifier key) { + if (key.getNamespace().equals("minecraft")) { + return key.getPath(); + } + return key.toString(); + } + + private void onWorldChange(ServerWorld origin, ServerWorld destination, ServerPlayerEntity player) { + this.plugin.getContextManager().invalidateCache(player); + } + + private void onPlayerRespawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity newPlayer, boolean alive) { + this.plugin.getContextManager().invalidateCache(oldPlayer); + this.plugin.getContextManager().invalidateCache(newPlayer); + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/event/PlayerChangeWorldCallback.java b/fabric/src/main/java/me/lucko/luckperms/fabric/event/PlayerChangeWorldCallback.java new file mode 100644 index 000000000..555dfb3a4 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/event/PlayerChangeWorldCallback.java @@ -0,0 +1,42 @@ +/* + * 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.fabric.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; + +// TODO: Use Fabric API alternative when merged. +public interface PlayerChangeWorldCallback { + Event EVENT = EventFactory.createArrayBacked(PlayerChangeWorldCallback.class, (callbacks) -> (originalWorld, destination, player) -> { + for (PlayerChangeWorldCallback callback : callbacks) { + callback.onChangeWorld(originalWorld, destination, player); + } + }); + + void onChangeWorld(ServerWorld originalWorld, ServerWorld destination, ServerPlayerEntity player); +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/event/RespawnPlayerCallback.java b/fabric/src/main/java/me/lucko/luckperms/fabric/event/RespawnPlayerCallback.java new file mode 100644 index 000000000..0309ea928 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/event/RespawnPlayerCallback.java @@ -0,0 +1,42 @@ +/* + * 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.fabric.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.network.ServerPlayerEntity; + +// TODO: Use Fabric API alternative when merged. +// https://github.com/FabricMC/fabric/pull/957 +public interface RespawnPlayerCallback { + Event EVENT = EventFactory.createArrayBacked(RespawnPlayerCallback.class, (callbacks) -> (newPlayer, oldPlayer, alive) -> { + for (RespawnPlayerCallback callback : callbacks) { + callback.onRespawn(newPlayer, oldPlayer, alive); + } + }); + + void onRespawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity newPlayer, boolean alive); +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java new file mode 100644 index 000000000..6353f1224 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java @@ -0,0 +1,133 @@ +/* + * 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.fabric.listeners; + +import com.mojang.authlib.GameProfile; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.locale.Message; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.plugin.util.AbstractConnectionListener; +import me.lucko.luckperms.fabric.FabricSenderFactory; +import me.lucko.luckperms.fabric.LPFabricPlugin; +import me.lucko.luckperms.fabric.mixin.ServerLoginNetworkHandlerAccessor; +import me.lucko.luckperms.fabric.model.MixinUser; + +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking.LoginSynchronizer; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.concurrent.CompletableFuture; + +public class FabricConnectionListener extends AbstractConnectionListener { + private final LPFabricPlugin plugin; + + public FabricConnectionListener(LPFabricPlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + public void registerListeners() { + ServerLoginConnectionEvents.QUERY_START.register(this::onPreLogin); + ServerPlayConnectionEvents.JOIN.register(this::onLogin); + ServerPlayConnectionEvents.DISCONNECT.register(this::onDisconnect); + } + + private void onPreLogin(ServerLoginNetworkHandler netHandler, MinecraftServer server, PacketSender packetSender, LoginSynchronizer sync) { + /* Called when the player first attempts a connection with the server. */ + + // Get their profile from the net handler - it should have been initialised by now. + GameProfile profile = ((ServerLoginNetworkHandlerAccessor) netHandler).getGameProfile(); + + + // Register with the LoginSynchronizer that we want to perform a task before the login proceeds. + sync.waitFor(CompletableFuture.runAsync(() -> onPreLoginAsync(netHandler, profile), this.plugin.getBootstrap().getScheduler().async())); + } + + private void onPreLoginAsync(ServerLoginNetworkHandler netHandler, GameProfile e) { + if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + this.plugin.getLogger().info("Processing pre-login for " + e.getId() + " - " + e.getName()); + } + + /* Actually process the login for the connection. + We do this here to delay the login until the data is ready. + If the login gets cancelled later on, then this will be cleaned up. + + This includes: + - loading uuid data + - loading permissions + - creating a user instance in the UserManager for this connection. + - setting up cached data. */ + try { + User user = loadUser(e.getId(), e.getName()); + recordConnection(e.getId()); + this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(e.getId(), e.getName(), user); + } catch (Exception ex) { + this.plugin.getLogger().severe("Exception occurred whilst loading data for " + e.getId() + " - " + e.getName(), ex); + + // deny the connection + Component reason = TranslationManager.render(Message.LOADING_DATABASE_ERROR.build()); + netHandler.disconnect(FabricSenderFactory.toNativeText(reason)); + this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(e.getId(), e.getName(), null); + } + } + + private void onLogin(ServerPlayNetworkHandler netHandler, PacketSender packetSender, MinecraftServer server) { + final ServerPlayerEntity player = netHandler.player; + + if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + this.plugin.getLogger().info("Processing login for " + player.getUuid() + " - " + player.getName()); + } + + final User user = this.plugin.getUserManager().getIfLoaded(player.getUuid()); + + /* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */ + if (user == null) { + this.plugin.getLogger().warn("User " + player.getUuid() + " - " + player.getName() + + " doesn't currently have data pre-loaded - denying login."); + Component reason = TranslationManager.render(Message.LOADING_STATE_ERROR.build()); + netHandler.disconnect(FabricSenderFactory.toNativeText(reason)); + return; + } + + // init permissions handler + ((MixinUser) player).initializePermissions(user); + + this.plugin.getContextManager().signalContextUpdate(player); + } + + private void onDisconnect(ServerPlayNetworkHandler netHandler, MinecraftServer server) { + handleDisconnect(netHandler.player.getUuid()); + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/PermissionCheckListener.java b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/PermissionCheckListener.java new file mode 100644 index 000000000..adaab827f --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/PermissionCheckListener.java @@ -0,0 +1,89 @@ +/* + * 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.fabric.listeners; + +import me.lucko.fabric.api.permissions.v0.PermissionCheckEvent; +import me.lucko.luckperms.common.calculator.result.TristateResult; +import me.lucko.luckperms.common.query.QueryOptionsImpl; +import me.lucko.luckperms.fabric.LPFabricPlugin; +import me.lucko.luckperms.fabric.model.MixinUser; + +import net.fabricmc.fabric.api.util.TriState; +import net.minecraft.command.CommandSource; +import net.minecraft.entity.Entity; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Listener to route permission checks made via fabric-permissions-api to LuckPerms. + */ +public class PermissionCheckListener { + private final LPFabricPlugin plugin; + + public PermissionCheckListener(LPFabricPlugin plugin) { + this.plugin = plugin; + } + + public void registerListeners() { + PermissionCheckEvent.EVENT.register(this::onPermissionCheck); + } + + private @NonNull TriState onPermissionCheck(CommandSource source, String permission) { + if (source instanceof ServerCommandSource) { + Entity entity = ((ServerCommandSource) source).getEntity(); + if (entity instanceof ServerPlayerEntity) { + return onPlayerPermissionCheck((ServerPlayerEntity) entity, permission); + } + } + return onOtherPermissionCheck(source, permission); + } + + private TriState onPlayerPermissionCheck(ServerPlayerEntity player, String permission) { + switch (((MixinUser) player).hasPermission(permission)) { + case TRUE: + return TriState.TRUE; + case FALSE: + return TriState.FALSE; + case UNDEFINED: + return TriState.DEFAULT; + default: + throw new AssertionError(); + } + } + + private TriState onOtherPermissionCheck(CommandSource source, String permission) { + if (source instanceof ServerCommandSource) { + String name = ((ServerCommandSource) source).getName(); + this.plugin.getVerboseHandler().offerPermissionCheckEvent(me.lucko.luckperms.common.verbose.event.PermissionCheckEvent.Origin.PLATFORM_PERMISSION_CHECK, name, QueryOptionsImpl.DEFAULT_CONTEXTUAL, permission, TristateResult.UNDEFINED); + this.plugin.getPermissionRegistry().offer(permission); + } + + return TriState.DEFAULT; + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ClientSettingsC2SPacketAccessor.java b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ClientSettingsC2SPacketAccessor.java new file mode 100644 index 000000000..929006fca --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ClientSettingsC2SPacketAccessor.java @@ -0,0 +1,39 @@ +/* + * 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.fabric.mixin; + +import net.minecraft.network.packet.c2s.play.ClientSettingsC2SPacket; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClientSettingsC2SPacket.class) +public interface ClientSettingsC2SPacketAccessor { + + @Accessor("language") + String getLanguage(); + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/PlayerManagerMixin.java b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/PlayerManagerMixin.java new file mode 100644 index 000000000..11b72ac2d --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/PlayerManagerMixin.java @@ -0,0 +1,49 @@ +/* + * 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.fabric.mixin; + +import me.lucko.luckperms.fabric.event.RespawnPlayerCallback; + +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(PlayerManager.class) +public abstract class PlayerManagerMixin { + + // Implement the callback for RespawnPlayerCallback + // We'll switch to Fabric's event when FabricMC/fabric#957 is merged. + @Inject(at = @At("TAIL"), method = "respawnPlayer", locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void luckperms_onRespawnPlayer(ServerPlayerEntity player, boolean alive, CallbackInfoReturnable cir) { + RespawnPlayerCallback.EVENT.invoker().onRespawn(player, cir.getReturnValue(), alive); // Transfer the old caches to the new player. + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerLoginNetworkHandlerAccessor.java b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerLoginNetworkHandlerAccessor.java new file mode 100644 index 000000000..9d0db0c74 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerLoginNetworkHandlerAccessor.java @@ -0,0 +1,45 @@ +/* + * 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.fabric.mixin; + +import com.mojang.authlib.GameProfile; + +import net.minecraft.server.network.ServerLoginNetworkHandler; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +/** + * Accessor mixin to provide access to the underlying {@link GameProfile} during the server + * login handling. + */ +@Mixin(ServerLoginNetworkHandler.class) +public interface ServerLoginNetworkHandlerAccessor { + + @Accessor("profile") + GameProfile getGameProfile(); + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerPlayerEntityMixin.java b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerPlayerEntityMixin.java new file mode 100644 index 000000000..9bf69edcd --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/ServerPlayerEntityMixin.java @@ -0,0 +1,142 @@ +/* + * 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.fabric.mixin; + +import me.lucko.luckperms.common.cacheddata.type.PermissionCache; +import me.lucko.luckperms.common.context.QueryOptionsCache; +import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent; +import me.lucko.luckperms.fabric.context.FabricContextManager; +import me.lucko.luckperms.fabric.event.PlayerChangeWorldCallback; +import me.lucko.luckperms.fabric.model.MixinUser; + +import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.util.Tristate; +import net.minecraft.network.packet.c2s.play.ClientSettingsC2SPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.Locale; + +/** + * Mixin into {@link ServerPlayerEntity} to store LP caches and implement {@link MixinUser}. + * + *

This mixin is also temporarily used to implement our internal PlayerChangeWorldCallback, + * until a similar event is added to Fabric itself.

+ */ +@Mixin(ServerPlayerEntity.class) +public abstract class ServerPlayerEntityMixin implements MixinUser { + + /** Cache a reference to the LP {@link User} instance loaded for this player */ + private User luckperms$user; + + /** + * Hold a QueryOptionsCache instance on the player itself, so we can just cast instead of + * having to maintain a map of Player->Cache. + */ + private QueryOptionsCache luckperms$queryOptions; + + // Cache player locale + private Locale luckperms$locale; + + // Used by PlayerChangeWorldCallback hook below. + @Shadow public abstract ServerWorld getServerWorld(); + + @Override + public QueryOptionsCache getQueryOptionsCache(FabricContextManager contextManager) { + if (this.luckperms$queryOptions == null) { + this.luckperms$queryOptions = contextManager.newQueryOptionsCache(((ServerPlayerEntity) (Object) this)); + } + return this.luckperms$queryOptions; + } + + @Override + public Locale getCachedLocale() { + return this.luckperms$locale; + } + + @Override + public void initializePermissions(User user) { + this.luckperms$user = user; + + // ensure query options cache is initialised too. + if (this.luckperms$queryOptions == null) { + this.getQueryOptionsCache((FabricContextManager) user.getPlugin().getContextManager()); + } + } + + @Override + public Tristate hasPermission(String permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + return hasPermission(permission, this.luckperms$queryOptions.getQueryOptions()); + } + + @Override + public Tristate hasPermission(String permission, QueryOptions queryOptions) { + if (permission == null) { + throw new NullPointerException("permission"); + } + if (queryOptions == null) { + throw new NullPointerException("queryOptions"); + } + + final User user = this.luckperms$user; + if (user == null) { + throw new IllegalStateException("Permissions have not been initialised for this player yet."); + } + + PermissionCache data = user.getCachedData().getPermissionData(queryOptions); + return data.checkPermission(permission, PermissionCheckEvent.Origin.PLATFORM_PERMISSION_CHECK).result(); + } + + @Inject( + at = @At("HEAD"), + method = "setClientSettings" + ) + private void luckperms_setClientSettings(ClientSettingsC2SPacket information, CallbackInfo ci) { + String language = ((ClientSettingsC2SPacketAccessor) information).getLanguage(); + this.luckperms$locale = TranslationManager.parseLocale(language); + } + + @Inject( + at = @At("TAIL"), + method = "worldChanged", + locals = LocalCapture.CAPTURE_FAILEXCEPTION + ) + private void luckperms_onChangeDimension(ServerWorld targetWorld, CallbackInfo ci) { + PlayerChangeWorldCallback.EVENT.invoker().onChangeWorld(this.getServerWorld(), targetWorld, (ServerPlayerEntity) (Object) this); + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/model/MixinUser.java b/fabric/src/main/java/me/lucko/luckperms/fabric/model/MixinUser.java new file mode 100644 index 000000000..801f300f0 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/model/MixinUser.java @@ -0,0 +1,67 @@ +/* + * 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.fabric.model; + +import me.lucko.luckperms.common.context.QueryOptionsCache; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.fabric.context.FabricContextManager; + +import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.util.Tristate; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.Locale; + +/** + * Mixin interface for {@link ServerPlayerEntity} implementing {@link User} related + * caches and functions. + */ +public interface MixinUser { + + /** + * Gets (or creates using the manager) the objects {@link QueryOptionsCache}. + * + * @param contextManager the contextManager + * @return the cache + */ + QueryOptionsCache getQueryOptionsCache(FabricContextManager contextManager); + + Locale getCachedLocale(); + + /** + * Initialises permissions for this player using the given {@link User}. + * + * @param user the user + */ + void initializePermissions(User user); + + // methods to perform permission checks using the User instance initialised on login + + Tristate hasPermission(String permission); + + Tristate hasPermission(String permission, QueryOptions queryOptions); + +} diff --git a/fabric/src/main/resources/assets/luckperms/icon.png b/fabric/src/main/resources/assets/luckperms/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ca72e468521344b8c53ace586e21d8c7a06dc831 GIT binary patch literal 25938 zcmXtfWmr^Q+xDKJdk~~kT2c_{8oH52KvIzIt|0}H?(XhRNu@)OZUv;fbH2Hs_xOIn zk2z*7*lV5FSvx{aMHUB>3=;qV9C`BXSe>|z>#9r2+ zUBm1PL_K*M8UIZMt3`8|ZFN6o6BWJe=;Hl_mUG1lr|K}zd2=V{I?g8NaR04h7x{qC zd9yH`>hP0ug#m-*@O$UnL*~_bZ>iL=_GtyyC{7IV&3yD8ny=xcV{C~yjmOnjBp%&R zs$`vQ)?wv+RxfF)Hp zU|2153K0>udoF*V-#2Y(7Y6!v5>OE!;cyx+??gb zJN!`q_cKm|mW?MB?q4$g@h)_I^dRNb){=lgW##s)f{rSzp@w5ubnGl0e$n3Fi;P|Cx&q zx%Ydtcl+zmD&!A{Wh|D>HY&-RfpgIdLd)&S(kOpKP=AhQ0)Ns|VZhjZPYvWAFSQi- z(zaOvw*zCie=9)Wj&y*Y+bhTH7zRFefI5A#xqwn0djN!lUzgrFBiIdqQWp>K>ze6} z7gS(9`N#exTgfOJ*Y#{W3CN9JAY+S$wuhhRoZS>~%M+1}iqi8N_+k>(husj|KxFlS zHd>6luwSp(8`VP9kPGRQu|-^Ct1GHI=91j%I+AB+4TYLtj$&Rv`8`~(VeJHDr{2mE z$vK~__=xR{xfs>l7+}%!luQcbOkSi)t9&dbRPuUj0@Z`tiH#Q*PtuDV$QMMIaE?t zw6`zQrdHJ?r?ROW$#%%S{;s7;`P7=+{)Z_g0=NnC>HGeN%Be=t!CuS%#1mCmaXsj* za7N`hWA{v@u`gf-$kk5Vec6qMcgg&uivP_iiIcG+sjR!C*R98t)a|sX?7K6HGZPmx z!sO1{+wg~UUTgt5l1$jgixfL2GvE2%;zy0jkbd?K&J2GtlmtoK@ft1TeblR|L98}w zV-rrTx7``(9ZY4u8GeIU82byB#OX8`?)VDSR{2D$zdpRIIV=oQQF^bpXn(l>%~xmH z!()n{o}?B67XRrUz#?*2AZriD<}Ih!8(*1LscQWmx^!(Dgy(R9{HgUS4Y}!WGZC|X z;+qjcZ#VhpO1&?d#!+TmA%eix`J|YGHQ^avrd5p53W`I8caU{yv>(}MMBJgEo)H>* zvzo@dn`ZFfwYhH~yt80Xg(mz$jQ2HnYf{Y7XQ^JZ>BR>bKb5|| zg@E$v4<1Ip&uv5dtVBFrH}K%FZrGZ%(l7oHS>Oi%`Bflv@`X&gFY*Vq;2W*+x%Y?U z)DD%=zPih`7;(81v2k2Jvhifxw7m6{;B%``@l;6?WVw)hA#06s_VfncgAIBQan$C^ zQM&V+F1^Wmj5q_hiXpPet5%5H_&aJUCS#Opr$bmRG?`x|l(5NZVcUTp*+`^ys)s!4@8bYJ3`t=hNQ z(*cK@_Fy@^al|O;?URIuowB38i}^Z8PHk{^f(O8EavP6PT_mD0J)9 zYV(GTWA8gT(F|?g5 z)2p7-u<+AC_?b5zGn5AcQLl0w&SEl?zBN{Rw*AEqYDPPwEsXKM3x{?7jVS^0AP{Lk z#y#9CR-A1Qw30vLSlqRluyE#F%D~1xe@P#|wU5INygYAl^9Z@hNKX$PzHOwB-Zj?@ ze4JIT5{fI0#+aOCLbh`~@^~?2QO0>H(4r$I9?OLuyD=`tunQuo+OgYU#T|JbFWqkjr_5(dzQ zX*j|DF`G(JlBp=-6G(9ljwe{<(eR$W>}{`yFJif8xOEA1)U&VWUk*_xkj+T<=jPh)=>bF#u~-AbYM92I{4fx{%Bfp&i^@{*Rn~`H(D1r`dgWPh8TyI-viub)02kWq};y+W= z^7-F3x|}j?QM?PNc@ObGelAa|aRMX#2IQTcoeiI!+@dfs>58vmM;vHAP>$YWrc0=? z#Z8YFM$fv`<`1GiX!x7#9fmy_c)JWr;GYBy$F9i8SoUAIB#q>_rdZKn_#E9)oUl^WOe<1GWEnt^K0ex{<_m zn_V6EOTWdS-Bp}lSv0t!PA4M#4r4_;M_ky;)@mVHzQb;t#47P4Y_RB;39gu&*v~)| zHag~~o8iVatzT}dp=5))rO0xx*?e1;j{EWW8bxY;=Q3$S$nYc>moF;uaD{{3%)B}3 z#}j)Q_q;yMtk}CqpPwwiOz;A&*|$57Otk3BQ3r^rQTKyLlo?nPOwV;`Wzi5I_I5mF zZTiJm3yN@z2nSyv$j+{X)}Pe6jBL;d9jn)po1446z1nABAev;vS!?*3KKp{;)wkdh zn+m<joCR%=2gXT^O0=DT0OCik~}qL z#m!qJ6WZGl`^>xab-kU@oP>xWv-B$xt(QWWSdoVAq2@wpL=9EV!0RFRi z!u{vBW9xZV9R5-=blMK1sv4Qe=4qo1)#?M>ot1tR7;?4LTDi<36amVe5^7^#AXwtD z#yOz9W_IxNzxHF#D9KhJoOpgW%u~C-79{ZWuwFeHm;i}HKqp5dlC3`St$EM_{{HB7kSt9O`R!*Sy5$fabi%u^xCT%s$)akDIrMGvRnU3rFSN!6r z0JOHB@4DRn9Xoi!THBhU?`_X#v#3S~ZTI32u962p_7wpXMEm3Ja-!+AhH>No?bHY0 z*OWsCd)BAZh3p~Hf5s%n=!<7i@LN%~bH=ml≦~@PVl)n-cbaB0TS!b!0`$e}ZBm z`GY}A+bY=~hsX%>xT7|)u)*}67cC6GPL>`>cW+K0P{jBG=iuqPgXe5A<^JFTi?)(q znA6vlDLqs6{D{9moS%f;W_R0GZ5J_gi=5CsfIhzqBfI`&vF4ldgB4kH6Bx}__xISB zi=Yd{1ArJS>sfW?ufwpLo+a$>64C>2Awxrb4phs1HmYw!(c zny^I_!mEq>+Ae= zgj|y+j-->}ntBe)Yumeo1tSvKmaf5<-DG|kwLF)3*7K+MZPVvWPDw}Fom_IoxV@$4 z1Z`LCFJia#mSVKBSIdHok7@c#WqAY|`TrKP6rRc%ORaHT4ST~fyQLw!-(m#4-z;cfk;gMKMl?8R|(PzD~But4*kYj_`dqwr%${-3XAvt+Yi7y=Q8}E5qLM3i?^>|=J z=@s5vX-Lldy4PrH6=ueI3H_GVlKMZ)qhLIwq@)pKOhX$lG`a5P&xB9(G|10pnkdu4 zv~Vw)caD^~xg9#-zuq3n_liqsY$_I~xN~_iZQN~bZMBmzm2SL{yWC-|K3Mxy-a5G= zBEK6Fk|5Se`7Qd_#BoBzDwgB{tmpj<7HJ2;sofZgO0DQ)s`W7v*1DDlpX4B2;W^Hj zCsePRsLI;(vq&uOy2{(d51y<^08+?7yUV!dwW2H`had&5++E}FDCN{P>VsD7xGJv{ z_SdrbUIqgIIoNl6UCE{p3CpM4u;#Qf(f4dXtM15}m>}8xP3uQ=`a-lQ1zvUbUl*Nu zGoadxkL~#ZW*;^~q*x1zSZRIMOYw5h8gW?l+&o-d4!#%@qm>OaHh*9Lw9(H7JPeJD zSXZ##iYeJ7hW_@UX(6YKgqZsoON2L@uL!({S;2cdQw<5qW7zyw@4ab$18$-I z{f#o{9=r<0@h*#J>PY5+xqiq&C}_$zO*FD%)kOoG`_{(gO&&aE0GqhZKkx-OPq3n3 z2jdaq{4?piMf2%hOOQ|AottcL?%$*=B?IKKO>mEIa87snr|o47%6}`kQs}CwfMD33ocI@b&U5;+wW6&GaJ)R~Q9qj?FB#0w$3JZc|yUSm(r z#yD|qTW36527R>@&8p0{MgG|l_%F~F``hTK4O7aWKYym|ef1eB4AWMhJBDV3K|J>g zqL9f*(43rX(HfeYc;Tl>!C|euzCx7VgR^Wi9*tC+%cH=taM3CM4FplLu&}_K$yBn^ z+$450=~zk@}dZP*SZ4)B;-nf1{q!((R=FK_h5Hp9 z^3GBoTS|ht_*QgD^^vCv8&Xxsg)FJd5YXfsnD~48q!o z*saS^@pM`qw7202L*$9}3aeEGxZ)J(2z0!EaQGXX=*;c4rbCcQJwDS7@RZZ1t)Zo} zuRD}_?~wiRz4P##;6kNo-g(AgEPSNhu)I3^Va?|aeJ3)-ypcdNJTVG8h2Ye>{tF}K z&YuvCHnB!rLFKcX?)%RNWW1hG1j3ctiiJ!M2I`O)IF?4S=z;j0&}iO$<14zDYpf&q zn!mLjDfmDS&V<}_8>U2>^6XcHzX7I@5(0#to)q_RKz;OsrRTENL-$|OANBQLnIrV$ z5FwhkiLkrB4#ECDv7k^D`){Bigk$5&_CaDvxugmD6C}vB7ORN`>il3?s;r92mr7#x z`jHg7v|_Lo34$U#neqDj`2$g5nV-X(96n)^r*#LpCFx}%+V2nM<-z|0bp?Y8t0v!S+H@zB{-;=zY*mBeu>R7E?kwS2 zPgFN=6IC3j(3fX%G9XU~qj+CwHNAnxl6kE5_ZlM86t(vaN^o~8sj50uITZ#+db$R# z(RTKk$f!@rS)H}rl}#GXF|r*6U5wVQt{FcFdTB8=-419cUDwQUI~XnRC*%Bi1?=Sq zQkoG_+SoxwM8+tA05BZ&;G`j*)o1$aL}1SCXtv_7srI9S_NY}4$Mkf6Lo}`X&t?bi z=wYgR?+fZ_zF{!?h_>$M|D1PyVI<2{_M1OG&esL~(Iyw`oM1Q!+hz*$VM@IIIk1?E zH^Qq8k7P>mgu782ZN0`!8kRs@KGn=ft82SyARN0n5r^ldwt;*m5hyl3G$c2}uih)o zmc2t6+l9IAR_||IBv{j0-+_$dW{W}goesvc>X@AcQ3{gq0|lBpAH!TRkr$bf3$Zx$ zFI0QEyFDLp%*Bn)MpQ;FeiRvzbub8esNVh)3o#ZFE)|MBIJS9h$Z>0w-t`Ss3c9I zrzM)sp+w^+M9j-7wr^DQeh;ViLRi8EMR`kn(PE+&;F(-Jz+OY(xGKy|X=m)(J!iyn3b#rhMX0eX%v)twF2@jX&5? z&E9b{B@&+%e$7`!r@Qm~tN06VSsBvbAKO_`r{+CVq12O#L!IB{1Fxq~g{Q7usLcS5 ztP3%N+c{@r!Qb`z`q0&1w;-p!c#ixSB*(<9`Gyx@P?aH5a0MQhnWWF2EQrn%`Se8m zxFwn+7w^ZvhnSSKntlFbUjK~0YY2OCm``FexSquJ4LU~2#rpd|#t_%Ubf|jA>Q^ZL zsWHHqIde{u@?|0wx*1(}PF!r=MF;9y(SItOp#w#f{4*sm?(-Jwg+to-3=3hqJ|f6et9T!aGB-hIj;(q%~{tn)Mfp2e8h5B@HFuZ*u>7@HhD zaDfkQ$n4<+u5-#O1~0MeL_zwD$r`-jl7Wa~?T&nWmA6!vIrMkk&6o=!Dtk@}8S@eevmAMuXZlXn;9bvv+dpQGQ z^TCWds(kS9af03%R1$JQKkLzd(YYOn4px$vw<@H*wR|=EH@dO8i-3^*&^yqQ2_b}q zv!f#`gG(h49%0(G0ucpag~z$eSQHsk^2Q4d;mKu=I&he>LRn)>)5}8MkNMcQ#f_Uq zw?B}2>bO3biig;G=R+UA=GH`vw>uen6pZTuzaAa(s4s8+HXA{!|1;*U7)g;ZIkyi~ zxww9es(#O97Ic#w;`PYt=#U=qPo>d%*lPJe&NTIbmS*zeI{aJWs(nD=q1FQfTKd$v zSKssm8@yCJd2RZ<^IGQDWau&2w!s;VhKZ@9?MDmTUvheecJwDVZl}J^Cmq(?P|v(k zEd*gQbLbsY0$%ViDLJ3I+RY*`P_7_X_Za*`{oEs^r!Ne*>nh6cIy(LKApzm&lm&}b zn@Rej(#r9C7tf#@b+BV;nrQw|gcOTl|JV+2@TBjbLHoPq0Lr8+9gUZ4+*Z;%sq+cM zrfmr|ZdchIUizMRaNN-DOQ}Q;NVK|Lcy|0fTkViC@3{PqEs2s@G%E95`7NtPH%KYPbIIkjem9 zn0N{}(zmn+Qxna=qVWaIWz<#hV@+dFRtqNyDrsuxD^AY(U0tPSHCoA|hwlXHO#>Kd z4H{eT_beU9XZfeVDW!kfkD*qfO97wL1s?;Ss1|)b)|_lE5*Z@V&XY%aALwtK#Np6B zsJ_Q+6};XZddAlc_$f00^la(|1kiKVvXYeRM%sTqGHZ)7`N_Fd?<@}s6e zRrU`7Jug|82)EnzI4ofDf^k^!K@sO0&)iEL_nWpy^dH{XUy*2#p_aU#0~)b8NK#(E zr>yOEQh?nL7{zHLrT~@<7j`1NGj{#MJpGK&eK!7s>g?6gZdk07jz^Wm1D(x@a-wbw z9%&O21T(Z=>gzM|)=_}l%;PqO^Hq#5;VMZyEy1G~7OnZkjdi&j)NzHWl4^2aps8fw zjNHIicKuX@j3MHH${t#PWkkd9={+xYkU$dkjeKe!nal&-{Mx$a-#0zVG87=s?36-> zTZec|haZhpfz~|;Z!6q6ZJO)z=H?U+_R^{DX81{5?6hp3T|uFvH=Y$1th?}k_L_fC zWX*2IYT5hvh11mySLEA8%TKcUGeOvFhh?}qWt8kFgL04L=n@b|WV_fz(P?_fc~9%D zOz7$4WE&2VSZobdMm@uCMIoswsz@SNoQE!S4fu$^+!-|%Pnmz1fcEAs3P%^>2`1HJ zV(T~cVg~8KjVraQe_Z4f>(^iY(>7Tppylz^OMQ|06C)8Vn^7B6cYR|t9@A#5)Bc>C zj0txWVQ4+KMxR`@KJbo!6VeleXppRWM4KeJ%cU|i?=N1&0Fb8~mMR`wuXd9SGFy4Q zZ*)T#29Nn>c*5nDQOCjm;r1UFp+dvjM!|`(F{OEae^?|ZjdGv_*O}%1hYuqIo6LYL zr~mx+H=LsiKfDma@tA>VCek;`1~NVP@2y~`>~nnALgmjtQLKlta^bZf_c!*IoQ7o? z3nyWh%xTXEg-cX|d#nF6oke4TCcgg5R~{v#>bF@AYC|0n`!}6S{xt8ZnqD2ps!DAI zCfbUw*>4GMi9H6~pA-4rCKgRi0fTuFmNLg=|L>oS$yveFn>yFRnBvd1Iad*HwUgP~ z2oYw-Un$B@$tyD>BgWzeuRuZo%dgkT>RevI(|?XA7Q!$u@O)7Ds8HDW7s9p`INFx= zrq=S&do!mkjoaS_z_wa_K)d9NOe6Jf4(hq&`#-k7fohrY$b&f8eq%xF5oD8+b3ozr z%h=+q_6PC>{CP^)o zhm5Etpu_%sX-w?FcTz-};d6w>OD<2Ov{DZ6mI{9EF?-0kbTDX4MBWBv- zuf5ntO|_wK`r`0&8=Z!Mp(yV8xgjx##OI|nR}X@uU|OZpmpFti-Dq#MEe@Sr3nw$( z3Gbft_!!Bl5?FkA!WR@KW1?`xz

Qe5-(IqdG+Joc9?et#c%Yuqx=)2v}oB%0z#}B@S%Sv7(6vJa^v4xKk889?N8wL zrd^bGaF$=ner~_M$;{0BM^;k2p>8HTsm~$rXLm!qY-)a?frEl;>gywPYM&*i5ep_m z6Gt#@cH2x^#K0wfiM{K!Hv487^sD=nfdx<0+x=YNrKAG&Ha<*Bdkw!$*(Q|A3x+_A zqmXD`^R3F`cp_kOz#J5tm9;7Z?+cLkI-MPyTqUDpwQpef^lcDm^tJ%@>Lv>jpGG*; z3{5Eke?N=v+r#dO*^|u!y|xNmg5g7lS@P$oJYdSR@X$c*wJBl#PXj0g_MGv9@4)P+ zCeMJ5v>_Uxj{~J5D)-pY!Xy=ZBfXm|(=1GxCVZMbe}I;oIduEe5PR7A6%SuaN+IU0 zxn>f8Z^g`f4;S7RtG^i^LN*J3v`s=xLp=i-WczGcfx8UwKN@+n<8>Zx?iai&ZoNfI z|GDG}vMiZv!8pH44V=`uIA+m|U@FPUcr5Ab9Vir7B>}Fbmf-(J&QUV$csv{qTEySIW)=|YWnS*uipT}!^3V@H`Ilt z6-Q!PRu7607K6sz3J?;dp0Am?z-n!W%tK3w(opFOm1k6>ZajIvbG+0F#MXL>KI~Ep zs~6C7ITN|S_khob#?*Joo96mp5&1ujRQso-D$m6l{Dz5)J!Cjr_)tAl4h#nA_~eOlAdxtBzg6msg(APBV74 z9LVAh+7W(1;Pz+##%^gc#;tn~Davub{R1!!A?ByA7|ChTbRO!YJMiURZk9@mo z^Do(*O%W;Wt->pewzpDNC9_tnK=_7{4h( zSvA%2L5a4sIg{B=!arLWaDje5lEcH{?8v8-TN220_avA0aAj-KdfK~n?GebE;)dQWEox*^Y(o|<4vun z8hzUn&#PqLtoKWSpI9i^)>78V5-S;k+KFbK&!70g3!DXXhW37!@QD<1P1JZ~;6E)H zcLWhxOWkHptvpn+)5#u+c%ued%74^#zpn#4_51p_MU*L;3j7|XQk{{5`4zieeM+$vy%4bZRso!mQ}4k0{h{f;+}L(f950y=#oB)U4M0h4 zGWK#|I3`#1>&P$Cm&4$V+}NYHwh8(>tEz8^xe*CN95oW*DTz1`z8)d|FKxlKFj7EK zu49G#8r*ZyNiFXg9x*DYPt44HirIBXV3f&D%h6v?n0u^4dn|=M-BQ#zuG9Yk*!uVH z-P}4n@f5;p-@^2ScfYQ5Mz`a9H8qFoAP^s4Phb8ZAG!yF%l${#oj8y=h@sz}IMrWo zwAd>4r?xk5rew#&N))KS36yfihV?Y?uLyZKY=AvJ`ayyO6x9VcY8|5iclp-4Jc1*6 zcn4xm5c|p?vI)B}TOfGoIH7EA)x(Jr4T|~Iyb!6qTEF;zYe>O=QV;!_RH>I?5_C|z zM?vT(48?Y-QmSEFm~LE3PI0s7@6-4z2|4*RlK$!v>qewpF{9b_?gYmRIhXppH%hwR zXSoolv1{nYui6-psKnMA;?ya|0N2;k9s<4i2}Il7KQoUtZwN3$xr?yBzJ#)z=edwa z#I|+N5p=xnD%K!8i7|Ln&>?tA_4`roir=^R-9IkcFwKvgQ7)pbnRxD?e~PVEQlrbi zATmm3Gh$`wnO&s#%3KHohNQ@cPVPYP{QdYx8FUHkj97kH8*c>YHY_9~yR9he499pC z19znGer%P%eGJQe6!}h`Ydj{Fl>Fys*A?|65(NKTOLF{e(`DC{p#8_S>uDbQ7)<=eEY zYm=OZw?y}Q&O_~s%0*|6ROS8aB11T(OUHl!vB3mlFblk}_cS(9MQFR+O8y>-i`wl# zkIos`Clb(Ifj3kyE+NGs7DQ+6*=25F5%zYXZNr&E7hp>~N#dZ+9Q?O+Cj5>KJ;bli z&c`HGDQ?Y=J1!JdX0P(WAG$$6%RpfK(XAe^Pj+lTAc|G<{<`6xif8;6`$y%gOynU< zeBYy;f6xBJ+DnO=&B}LQksF%M(3iJ5;nQS7KXt?4>)8B>T)_JMvxyFFEjJzcS2N{lOYvaPDRG*){#C{zUB@|;1VKon_ulzl{2 z=_qf&YCM>fe5nEjZMml#xpxd7y&iw;QyUUDedCjufjT}Sfu(vOxVyVJETeN9Hux9^ zqc?y2TPqBX?Hf23ih;5ZHT~(3k&Be2$M}9%7H0h?Zq9mT)==xCf4f8E`ya{1^XAvl z^i9vb#rqHsymB0(EGQV%Vel3m@x^B^?49Sd8&F{X4s8$g^&rRKL*KH24nVEi>`v=H z{=~>;!<3T6$)^_SV5{})o|Bb=uOVc9NHVsTR_Jz;VZ3wk>PF7O-kwt$-|_NiBH)Bw zE77O?7yI|t$N+Ab(I*MHvypVmA7f%Y(#(wh0g5Tw7_&$3^vIqnavqcH&c!{lVWx5&48xVO`K|^bReEz47g5&GJMgSPlP90ECiR z=q4>6FGLoEI7=Uonp4FXXZOz$9&|N=Qe#F^n*Hua1mh-cU|L%gLCJvXVHM&CIiAV|%qaq4L!>WM^NN!{xeI^_!YCdEfYJ*%_n zo8uh(;ne}P7|lPce2;tY!ySJoSVY|REMQFklfLLEtIQm!@(ZFYH+<<(=y zac9_vw)wYW5pJ^R53+ zXS_;C*aXm6YARwA>PMB-1*Q4!?sdoqmcfj($o3CyfG%8IgXNR3!lq_1z>(yrv0%;P zA8DyiqAwi6wZQpL2sS8MRrGFu=m`NWScKcO7034Wt6<~7NDSzY)K2ODTkQJ_bwAr* z5ncD6ZQMUXt(lJ-HbWQqVNh8hOaeiXUK}sU-($D{+KgFqe4QN30^3mr(95O%Y+bXz5e#)Q}|qYRA_JNf5{~ zDW)d&SAnAY{&`Yb4AzB!@9uFeF1xC*1~6ktSW~~#$wS+ol{WeH^z@pp#4@99aM;`0 zig;gk?vfyZFxpb7JE^Vw5w1_okygU#g>E${x3Dsc_o6>OL+Uf=kyT8rn6GabgQBYBfcs|zT_Ir2QH>Je;#V%wcT zy=c(Xxu)$09L~MwFEnKXn&~%H7pSO}9uW4WN)84VAsPJ;%2ts8{uK@HR(p59*O{2z01co<(~M>*>o?yd!cg zB^E5>@5i#A1De+S_+v_X=V(xLGr3BB4l2R-QjP~W(OncVH&NfRmvKT(Sz{TaIy2E} zLukIP-3pjo9-3o(x?}$V2$Ka+!&J060m$NtCCwy11}(ihLL&D0iov#DJpxqD8<>QM z{YLX){hUfaB?tAfhoskpn={ zX;Gxh1}2eQZo+keOc}^Vz`%nH5ru6q6azCh2)lnE5%Eue^<@uCf-dkHlhl&MoG!O_ z&bs0%Vwz?0kiR~a?3V}C86eLfL(%ty_{VS00{;A&dogle65aJExW-q%RjkdN*i#2T zYFBG6m(at9qJO7XqJAJ=Ur>PRJx@=itRq-S^7_(?&=+#XK7v-#Zq85)+K{d7jBcdb z%L8_X#BB!(zdM3Z%D|Diyh#atWxQDBj}mo(-jhos?yRIr&s>48;q?}x8e0`8 z@4v7`WxbNmQEu*|?xlJ1uY{}|;e~5_5{XWmkJ=`o3fb^Xlz=^-S{8%IDCoHU1F9s- z;bnRh>3h5bt>bZ+CzjF}q93A-D9%dJ6el0pYGl`R+8qB2=Xg=enXor`=tCzLv)Xy~kX5;okkh$gruq>X_ag4d>t1r(`^2 zf)qFHi)%&`A?1^BdQJQ8NDFVPFI+hpkf1DqfCu^_m=&t|o~iMlKkpXo2gpm}^!*&4 z{tNoj+uB6j1!{3(_&x{6*u5YD@tp_!ed2f}z0;}`C2zvuc;r=Ojf z?k%$A20})&7=;oU{lPiFaJlsJ5$px63732a`NBEgNz8G&>#4Csa6qLN;v3Uf0>#fc0vG6!PR=L;0L{ zU~))93umC9WTLRHXCW`Q0FC|o?3*rx?%h#3HlMv4^K7Y|FSk7r z`$m+C2XvTe)zCv2B@p%VoO@JS% z=tI6`CfRPM(n?g0yR8=J0*7C3qI$L`ekjmq-p*%#E)P>W?cgE!pS5aO^S0Lc5GK+J zLlGw>0uKIuobn>I(4zT64i_0`K9od9g7@WnFcrqaq^+1HlxP+IM-9sSep_C6Z zYiG#*j8GzgpcaWssc$?{gmb6zOYDC|O|5lnZF^RV{c!_-s3~ZAv-Y>e#e({EKQd{U zO*S7g7T~Mdxp=yIJ0kp|_|ya+b8i#j)Ra})s@#YCcu~5UNBu!`uIG!il$WK2MX?ZK zUfZFpVAE>|;K!wA_uF~rzi*?RZk0CNP$2o@u8KZ9GCWhB#t!DWHnw~}1kO$RuSKN@ zAMO6@$Ae(;q)lL^Z%bc(a)EDYdNEcb)QHwk7ko;Th&rJ+;9JSGf*M;ZiS zG5ru@{gR5-XRWjZk@cy+WIrr-zCSzq$nA$(oI*~gtUE33MKwydOH*KhXrPG47$ID! zST*8xF%OWVYNtc#dTe7P5ecn+j#a6Jom~m$fO^PaVxa$VHoe~>xog-gj|qg%7ByOnXrTEqst3US@UZF6lsz&BSrS^rXrIB(LJ%Z%A5-v z6bnrVAX2KsX2!y6`ICBWV#gw6A~dlI(tVW|c<9>(ViEN?6?sd+R4jW$>Cf^FT1gVxZ1x)VdS|)^-zhKU;27 zXkI@S6u9$#uPNT<#Nl<(M;Yy7z!-l+zLf>py)F*a9Iz(f$zW1uAd{ZVBTFnI07dF+6nps^qa&JSG;U`BehHC^>nrc<{+c5`~Kh2e5aM!G3;7osqZ>7>}f4J zV}IYlA1Q%CB=?9*G#&!jhA?UN!^iODCsXu(Hy4C#tZiIFyxPI}_nj*ut4=~_HD+o7up^OOi?h#jq)mdBMgF#CVWRT(!Es7fY8tV zb@0%L1kNK&iNJp;0xat<^D$x*4X5VcsxY)>j<(RhO|_e3BvC6vINS&2Wi~=&gl)>P zVS20gS#^1Sy4{=ULKzQp1Bk}qXU{Z(TTx`c%9Zn{F!lY-YejZIVbNI54ct__YIGL% z8G;O@=UrIDP<{MljRz;B6XO!0f_=+c=RGz1SFisw)u`(G4A?j`BqOseG_F9M#itsi z?87z(;?D`H`;(<_@710cW5*E~KZTl*loio4As&;VV{gq_Br0R*3fgG5$7`33!*beH zvSS-#{%7`VY*65BgeaVa@1A%)ERh);{}-+xiTFonCb2OfSlNKe- zhlLa{a^pDbRq3gCK{j^1L;j=f?@%`1zLs@ALFB@n^Z5N-?cY;uCB!*&X~wu>z(x_p z8#=};XHJjx_I$r-@ER2u1o-4z7ZD=)n_1DDA%|h*s@GAh;h$_hE1i_0LG@R;0UN7= zUCqiAl4*L!TkT&mke!7M5qJI7$U0&GlI<8c3J|k_iR4-r5na{4YI?{HUqXMAZdvl2 zT81Y@3_l+DC0)yKrRB}_n*U2S7DQ4pSkoH;8`~%Sm%Do_wl0D~HDkXr@S9}?pWq9E%!61eutc^=QY2$K}oS4a@Vm5ybBQk)6$lwqpNvIzk44$Fy|*$&Q$aW4{I~r z%*5i@2{way(63zXZ$`YYrpdmdo6Gm$SieU?KJ>R)(l-80*+unOs=40a?cN#Nlc;&I zsak0{yK3rQp<(?Q!>ydU-c76D{=(ZTY&Z<=^+TaX)k&dFb7bX2y!pu7}ZCf_)>porTZdimrURI0hKLqO_5H+5l$cQRGm0Qn|F4sbjl9S zEI%P;ybXuS~IKT72L|oh;cZ!AWU>3#=!W5cNp#o zf>A)>4r8iDPTa4)4Ot>eTEx$FONB49{PY)2Jl_a*0LH%)qBOPN1%r#Y%*=d>;XgS5 z$JhZ3A#}*r&d&UAvBODjzcGwYYVyLFi^d20M(GYarD%iNx}*vGNVF^&gPU;;@kNPO z)CmZTu+CHrR7~2XzD6J%K-_D;L&)DtWWDf7VRwviV*#TBSk7^P%=$YYJt?;l(7+I- z8&fx$W}3yT6Ke`FQ=sPqEgp)UkILiD+g7!cR6c&F<-0}^&`1cc*;BA`D|&cCp-{SpcD zL@0TCvliCsLyqFgtIVu?r*d$LbzZ=(B{Uk8ilf%MyBL?nhmYuw9i1+(Qv|pi(dLk4 zRHhBQ&WMDi@*v)!=_r72+rk6#ogRh3Jt{qKXz|dBYI;8sKh(+Ws$fdUy3}U(H7(=v2AiA{sav@f`g+r5AXqX%u77@ z_r!t56WQ%3mfEpZ<(hyO0*G`Qs{`kivlv;Rseacwm&FtMrkn)+OC*$5)%3$B&f(x~ zj}Lv_(F0|;t(xRd{nUxmxkNu{b2PQPtbCDF&@bx$coRjH-sj)d%|3{m--nk?`3BQn zTrVDM*PqJS$bWjGjM6Z4EW>Ch5@wz`0Sn&s-(<#(Nrep0#bT5VApT-WOKYCecV7=Z zX?y3+NIJ+(M;6^yS^uw3pvjN*q&(8EEf@a(YC7w`rr);>k5MC}kroC>N+TgLKq(0+ z6_EyM=@1YYAtBw;h;(;LD2!CPkw#Dul&-Po=JS0$f57$wWAFRE&g(dj;gK{qpKJSv zs2ef7usrE`mGvruN)fUfj}EvaTs}MhCtz4UVerD~zkW?o>xvFrelau%bdPYSPnTm4 z*ueg`KWOjgUPSisFA!p&{Js8&bgtXOjvn7*id=ooQc})5Au;yf#{~YuQKH$PA^Bc+ z^Kdxg?;nS$rgX)zOEyV=zg_2@exn~S^GD@RF<%#hB~?aGFpSZZ4Q!CO?Cd22Ol`np z@9n8mQKCht>Cf8%&$2>}n5TL38ezvBHcQNlS8G!vl`UnG1&wT$?|at})V9?+XUgCJ zAd1$dbS@FPNR<)T>{d?3c|uRi?Z7PE&pmL+46eyI)n=4-H79Nva+$t&A z$D;FnDqe$8h7xw&Wi)r4oj+QE3VDSw{uG&}U2FDNFCk>m zuy&RC_2i5^=0u&3L}brz_{&>43f;L9;gS(hs6Msk2c0+i>mk_ut9#Jlq{)@(EEVEm zsHsm$05(6!;A6PrZvQk?n4m^){l#xsect7%`_6(lM#F(AVCJoqww`xerIg}-FNXvm zanM7U{=>rAU^ddu!RRrdU8)<64m6&mJdQMODis?9;Gx_ka;Cs!0Vx)zrX@kjFZRvlMT<$ z_Y3gyelL3bXcN^O_JSZg0=0wana9_OU+Y7Ap2L=BS^pOH4|-bE_V-QK*8f>4&wOk` zQVSwQzL749)F2u4^&V+F2o(*DNnz&=ty@OVaOlPnR6& zIVqeYX#GS4A|Hgc&XJvRV~LfA9Pz0f-euuWv9;1NPqZTw@qSwE{@^C*LBDC+Mm-xJ zM2FFH>VVgc9R5`0$qamcRDW@g@E(|?1zsrvnr%3IVSV1$B7;H39%ef)`Jb#;+o4z5!QDuT+8m4lD3;$uh2vMNt*!*b0_TATfhvESYyEFSi*3Z zT4(j>&b0V2=!*Gq^D2<7GOE6mb}#fC-uJ%nVCp&5&gUbc9UB_V?fmv6*&e+R^XPo* zE4}hCIT#nKQu9sav_9iKM%XEMb})M5cp3Zq3|B;*qKmVS{0T|1ELHVi!Hq6d)>+iA zo%5)kUsvuF+A<=#dc}{veda33RT@!kY;|E>fngcOr%>Lc6tw3V;~PmImQO*+AVGbMvlQ!B;1LC&DygnNuQK>huMy`hQHBX0#*n4_< zGDNR)HlM|G%(WHV|9MPm;OAM8Be2_@N(MX6Wg-}b!eWE+^gMP&9tL|O$}4V`?n)9c zS+*Oj;71h--e(f-AKWGDlYt`d#D)lqh&&u8bl&bsdXM$RAUFQbvpKTbi)<_MrcMy( zI>$4AoVnU=`KLy6q_FutE}0G;sdmY)=*fd z$X`=OE?Q23I}i7ZW5@n`s~hS@9Mdr>&NSp#wlH0+4ts$?BHAnz57*1yh#$|BOdoIk zxcJ8C82Q|Aj!8kLr*(k%`AI{=`Qs}1%yPrj&_nv-w zyiF-*F7V<0=lX|l*=8tE<3mid;N-gBY4pemS$9 zO^^oGj^>?LRgYJ5Yy#Ev#zUAsF#z9<~di%{(+9gE>TNZkjfp$MDIO1V#E$;d6~|vn{Ud6J8kvMdQkk8?zi6B zP%3?OhP}!bXpPAb)vWKnJI``EH^RSoS%r54&}Z0nPhk~a{Lubh#^bB{;uC2)J5N|j z+vI18LbN=KrfJSqMPUglLaez5XXLO^jqxfa0GocT`vs2W>G=UGGA^h_2JK+2v!+Q# z9Wdt0ZuyIkPw_-~(cTjRc_s?=!V=V%{~{_FeaGjI%Daso<-9s0{;dig0fYvV zMTb;NwZr{+<=o|KEdCnHqAqBdgwjf$F)vd;EX_Zwb4?Q565o4Dvg7Gj=RFjvo28B+{96dYumS9NY{qru?GTX9zFhsl0L+r;d-<*f@Q-n)cCEaV}zh7I7*Y8Fs%X3vi;?%DG< zE^;Y1hOjhg2VnHfaqmela>Qlj9mS(cmq(2=iLnP`Xs&*sgO>3w@5lFF?et#cUlFHE z{#P<_JNjBlrH@569QaSkG}s(uNwmRK{Hzt<9-4IIG3npM(<4|jk~ohCfqq;j;pa!| z`rvDw>r~!}NYp2bF8zIXJslf`t4w?$zMg zB2@psJ>$~t@uNitzZb-hI5m4hkp>gU2vQy@VpGdpp)9YN9KC-F`|^EfM@y@$gBnG zK}D{Yk$!2C?^?9hd$0}HLV;L;mng_e-nHAj*wUB63+EoT(=Ys`=tnqe9kgV(g8BiI z$=-=39hdkAO>NF2T7{P+*PTdhbuh&T@6TwfIocJ3QWxn9VkMX*q9rh?0(g1@8&9)l zW3f>nLrJ(?@e94}eIHbK(;_Tx zi2YS?kTmUj@rp$jE{9H%N~KoTUIVv1fr~b`6&eWj?i5!@cQvyTIu>YS4yG}l(UeZk5mIDwqwfwnb$IQ^ZdKTip7nQ6 zG)!^)sf$+Z295-=*esIV3U3t?&9@PYPehl5+Kw+`<;-i3FO#>5PQffCED9wo_Q z`o4wC=W*D|?OcBKLchC;F4#e2AAV62H)(C+_eA7`+y1_~-44^EvGtxZkf)O{X2w(>`-$mfCGT=bN$6-4a`3MUAuGD(4V;&yc3K?+qQ5dB zBaxG^mOzFhn$)gzCr*X0vco>?fYqZ*^hvN2#MS;_L4{j;Yg^hhDy986AQiO=U7>iB zJ)=r9om244d@0wx6o*if0`_VQB@rd!jAM(+k0C5NC3{K2z})nW0a;q$eQuj@xx{QZ z^STdL?zs8c0m}m)NAb_Sbo)wAZZFTahI%L$Rz_!WT!U%9H|3$~3#rHpD0ZlFGP~No zFYo;rQD<^xe}8{IG`jGQ_GJ7+rI8ZVN`Z)1Vne%3s^&uVu#nquK@oGfHsC9NIcSo~(rkaR4kJGNN^DzA7=zO|r!kc{_A z?}~`huqPok^|=YnT3-S$tz=Qyh!!(THYkKY%@e_)_*N71cKM$|P{N%d3#$yyu{?DN9t~|SX8yjJ!Ub&td;oS)2PhV_Uro4j&}@zEV+AmIiv~mk3LKa za&3((&Hniy-sCNyEnK!)EWzNK^_+QQ{&KyoR|Zr<2R5ZJ2|xDKxPY&q{TQvURm`csDBwJNIjjfHuZLG)8sBoUygqX^iXF%AYVMiAKf; zMnwmif}_KT(PyDbUv+QwrcQ<1Mq$SdUhOQTk8e4SN&t9a3_)lEZbNjxqM;PYGmCSX zOp+)tA-u}v40yD}jMhhyPj)o@ju5EQa!U1qKwp3_-{!lhZMPyJPFu!_uHHx0lw{+$ z81d*gHSrox#(hLnx=LJT^OH;+(We7?QYMed?#?G9CvtZzK4vy!M6!#?j=?O72>kY{ z=-)E#L3+S!GMypFbxIdk>G@e<1gRf%@O@y27)jL18MHyuv8d?#VycilW2Z$(Y*Ykw>$5^1W}Jy2{PUpLgmx!``t zN$=);zcE{wW(c3iPcd4E;ZTU>R0%Q!JXtaC&|1>#M@9~n#5=#(|?B6X)Zyr&)|7JP|yVJsK2rQap;4Nl*Puz zDp`6Vx|}(%eAw>gfoBkes=+cW2dC(r)&zh1D7~FL{v_fUzBfaWU3t$^-rQ`- zu@n%w)(P=5JigM@pP$S3Kh4nMN-B48z@jPpEA4d8l1{@`57f`cncyi*pl<(3mP5;e z6T@x)fZi)438B;xueRK6yUQ#z8HO}$l}GP6Oz{V(V<)ZAH>-V8H&ZekZ=$8C1`i3I zqAH;_EV9YK!H*G}w=7uM2Hz~l(!yvnhRN#8T@7b`6uLA+1RpWW1t4s7urnGenD9d- zRHw{oP(&@Y6IWL5Gf?hZ#F~BT!gErspvr6xRoWb^a$cc`z=;_-_G<#t)M zuE~m&eAIZk9N35D0zGJo% ze)vDF2Ol;4w*aw{ZViL6w7@_Y9H%+j{53yOq+Bn6L-j3m>Hz7XbcDbuP%I**{VV=T zgL9%0jmcXdtQC!^STVn!z4cAhzBfX?SS?c*hoC3@+2WGIjjQclt+}T(ZAGm9qWR#jmAOd!n&_%K5a<0(}j z!TM=0v)*rZwn25@$s~d2(ky6dQ{NQHt>u}cj){30e&?%uHs(tuO5+-I`X?kB!F#cD za?VU8UzA2%?BPFgBWKZ`wF0|{%`ZaWYT5fzYH_G^laK%b+eR1yGRFX8Iw42*$jmn@ zp{skfdP$MtX@Y}!>7Stoq2)2C8YV-i{LRP*WGU0MfQmw@hGH>gGoDvBwi-X#t`_0* z<(E&ES%zSuCWXrz z^mms8H3D?makxD>j+*X@H<&)ws^0Ebk+BVbcngR`A$Fouq!(U!F$S~c^z;n?G%n8plHU4w@r5K=7=vFi4&Jy z>YwcnjKlistGaL9e`RX)t{Y^JC9>EP8ktukKH1`G>&{qYWGsJd@<`r3Yb*oNU`sqU z4qV0MzFAYW$5WAK%uaYEc1bxA|}T)4C^8*!o4ocp^SXUP)d|X0AV+31a80r}l&NV3T9P z4iQG>5>2V+Odza}BRY0#RYsRV=)i$AON><`j!eBgfg0=M7LrPH`svi;G%{Wh?}GpY z8ylPUIu4y7G}e(g>ptRsJ^Z0z=-OSB9B6>Ymvl&Wmdzh3Jw=D-MiIV`Qa>z9IzuBy z4jeJzCc3>Vfs&+!)Mlb8CdGtrzSD1t~4zo0~;xS3fZfw`56R z51Qi$rALNuVYZUdgzTy`Yy--s{)R~o$}?~5T(FYR7lspK5p)T9R9 zR1;(O@&;hc6lnm%N?d70w_hSyotBHDgp(1rd>^pr!wj`x;c~=Ku{b<&SDQ*VXEyV| zA0CdNKbImz9-Y0QA@F9Q6kyfnjOs}vm%NKt>OV(7ziC<`pGRCyI1k8;^zfn|Kb11x zXyhPQouwRNUKIo5 z(uSN;Pt8>ZpWW_6Cy3R4(@q#$pjt1}OEdlH3ZGnKj-;JRQE*~tIeF{9B%A93?ZO~A z&dh*2mr2<4@n5fXk4MGI!fjjHrKcRedeJS?tHtnEdR+9P00_mV``#< z?#{-onAeUOkAvp_-I@Zoxj0`;fm{awD{kr&J)8~6M0JFu0lXZTUJ%MPLs)q454sDE zn!k+2&U!>^XuPP2myg07%y1VrUYEAJ7wrO4S?$~ zFmOk75U;j&=v^=^iwYEmjb2$xO*YasW1hco#geP6944v8DlYtUNse6!BY=)-(TWH_ z*_hL?0)UWjm0Yg;16+-h#Z@4%;Z#SY%L?|wPg?t!F3xdc1M_6YW6Or|3rm;22px|u z{VO6s>d&S6E5ep9RwI$Jj+6n1(oPAe2pVaqsd^XJx19Tfw{x#?*JCYm(q#XVf2q4p z_Nv!v@%(%o+-dh-RI?rE-i&bYf9)^ZCuvY=?pJ!YPj+<$#*)?USo-yx<_avJm}N#7 zN?r21m>_#NmiGPy+G6p^i6uYxRCSa~x*dr_93#X3?*HJNKn-BT(>v(HoblzTZftvC z7XIY$$<(lP9zYR{rUc}~^)di)tZ`WjYhA|JN=F0NCs)Go@GyUCP4+a`!e52T^p*mq z&ocz%D6sS^PRm&M6q{Ey-ybV8vXk!ui-kN&jE|2mX4oYa9aJLI3gJUpU`D^WpnR^CRf9l6A`WO zRGaHU?ZzO7z=T@Q-z&dtnu#eCR#qaSGRqy#$I!B{tKmrru`XIj&`MX(d#c;7M9`u6 zzeEOp%F+{)e{s8}Zfer{`ayDQ5ei-t_>Rn?wytiWxzbmdg~`A-+6^wJqQQNm0Fe#Qdh=pG zp>bS--Lsl(P3~rp0Xcf^D*ijOMBkOyp}|t{RMQE{XqN71SxR@_5uqe5;6r;!8xp#;~czRprjLqR8`gG9E0vgdqgxZK1Zjxt?n6Jdik$T(gfl z^z@jR%~A?pKTND}fdxabHE!6ek;4v}E_ujSrk3!hQqx>hJw(jcYKBl*GP&$gsoZb> z{d?n5-EuWLHTWS39s%+eH%cQSwwbiVkmR@bTeMY|PRd{iZ24Y;CKIko(kxPyTT5A;6a4_L4 zjTGnA6nnI|AlrC;Nm>!NbGH+)b)~2Xh=Kq_=i{9-cKl=kYtnn43kn__z=QGD@q#aM zT_Z$0+%nI@cUnhm=4~07S)#C5sHea1iUv#-kH96-!js@h^mNWGikhXyY)g$`Q%c(p zFPmmk`MPp$kSoCdEaE$UAM{uD*5}LptXSflaqN;1?Ew~+qZQzwvcLcKK=J5&z;?z% zCYn+aNEcO%jg9@#emLF@G<#9RjQ291)zH4-ZSRG^@s}!IW;dCLGGqDe^)4_rUG1Ca zG6zE}NU?F0ZLX_)n35v{0i5n?UIx{?@F+$6O3c}Dv57)OV22Pv5|Wdro_X~sm+qD0 zU|)can%#K?A1@7d$abUF6TXSp&}*v9qcZe#K!A^rkA1WSf5zzz_WmEWr-F&^ zEYwMVAX?f{Miiptw2=Ef)7JE*;BIINx~o_BAyP5fTN>jMA-2dzB(vl;?+%;Oy&T5d zo^Uk!4e1h{ws$vUF>Jnxuf_KmSiGilIK01g4|O~JY?L@0hR^Gc!h59D-8}jJM<5C` zq3v#cK*5cyXXnxyDzbRE-jBWwdke4_Ibi9;mfHxPMRHK5K{1oX{ieH+7@}NpB%}tA z1Y7L9e;Ju?x9QiLy+lkdgw?%0AZFeM|3biMRPgI zRdp6T^vVD_6sQ=O@_utnK40!P-Kz*;#kssF?P={YwFBjEgKKMRL!vQ1$Zjhjk+R~! zI2OL{g4NYDK}2R$!7QS)3~o&;U*zfuwoYcB%2(f6+D%KLk@{|@d6T8A>l2z0Lx8HL zdT)2%mMTJK&YR8-jF1OxAunDolz_XU&JMFpDe%{s9#>*z-x>+GzTvGlM`DDLHM*L{ zj53D<4C(&#VK_v$6$Itq%2g1$eO$Jqihl^kmCX)3%2cmsbzbadfm$8DTEMyW;f{Rj z9ZmN<4$no(y<4Z@zP&iHzB$e1BH6ej>{aCh7@ONTlkH*P8y=XRdI!f*NZ=*X(*92n zz|fLAB*i2q_b&fLzV z&f3oCk`Yd=*K=n~XGVb9T=RN}=x(q5^kKy0=#=F5DWxGyKG&4zJLLYL0Svx8h5ihL zs3GtE-VNXKet69CuTkj*HhhEc{ka}NFoZy!FbU$?!pMjA;ma8r84LRBWUk!$u&L1e z{Cv?h1teJuWq^{BQXU`3r!Xwp4I-tE=(D zb`59rq{ONmIHSyA&}cGS;$`RP_aq^4?~#iYnP(9jxLuf;B^l7hQgoqRZg{;QV_ord zL1}WS*pp$>#SZ1+yUtn+e%v8={v>M-on!fYq~oGvBlQ%OEotZrlmBKobDX_*-E0Y& z7;bYNE-kBk5XNq5@PWUIO0bS^5v@cQFRciX9(B+wO0r70jtDOEd;@}b1 znNelQj*o?^oJlMckJVgAa_$ErRr)q0XQ1R8)F`1)+%&X9C|8}vVc@eck9R-T$iQlb zW9u)&1S_gsdnelB9iQlMiJ2dr3DS>{`}s!P$;gkH*bvcAp+C@F;cr4dL1|4t9cVq; zt!lISnW6_-#Di^%cDueBT)|HW#QLlKD6?(JXQzUvOmg%8E#uvKGSo5@{->Kuu>mTt2Zz94AtF6XO=>*-QulQT=v6x!KSx;tqD-R$4eg(-1=?IDzo3|kskz!@t z6X9 z<9i{V?lEmg5{$)f4gOB5z!*1>R%1a-jYSqzHN8&THl+sq2$q$4j4n>~&}|PzTn#9Y z5&ZbXa^D!q?K9@&_m)ypH%JF$a0B`y$z9T}Wwa*PXC8BZW1rw)Wo0$elgLP`pd`Oc z{W=gk5St+M@QQ0BS-Y)odzr7bF^Rv+wF|;({>xRSTWCC`uh5PI%Rg~ulV0+v!MD)o zh~a5YR@U^Aa_8oXHfDPAtrRmwfd>ru(fgZD4NOs|GSy}T+OK&F( z4xfaFe_BM}6KPY}b31*$DMA;&Xsxs%;@xnrKe&2P$GX=4)1o4&O@OOJ3i4MK;$tW> z=lu1SxM8i1hmjKvxo@h>CgrAji%Mkz!-lbUeI{E#oqFBnsrfH1Nf~Jh^nIIqe=zS Gp#KA9M(QU3 literal 0 HcmV?d00001 diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 000000000..81913b107 --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,43 @@ +{ + "schemaVersion": 1, + "id": "luckperms", + "version": "${version}", + "name": "LuckPerms", + "icon": "assets/luckperms/icon.png", + "description": "A permissions plugin (mod)", + "authors": [ + { + "name": "Luck", + "contact": { + "sources": "https://github.com/lucko", + "homepage": "https://lucko.me/" + } + } + ], + "license": "MIT", + "contact": { + "homepage": "https://luckperms.net", + "source": "https://github.com/lucko/LuckPerms", + "issues": "https://github.com/lucko/LuckPerms/issues" + }, + "environment": "server", + "entrypoints": { + "server": [ + "me.lucko.luckperms.fabric.LPFabricBootstrap" + ] + }, + "mixins": [ + "mixins.luckperms.json" + ], + "depends": { + "fabricloader": ">=0.9.0", + "fabric-api-base": "*", + "fabric-command-api-v1": "*", + "fabric-lifecycle-events-v1": "*", + "fabric-networking-v0": "*", + "fabric-permissions-api-v0": "*" + }, + "custom": { + "modmenu:api": true + } +} diff --git a/fabric/src/main/resources/luckperms.conf b/fabric/src/main/resources/luckperms.conf new file mode 100644 index 000000000..19e9ab882 --- /dev/null +++ b/fabric/src/main/resources/luckperms.conf @@ -0,0 +1,556 @@ +#################################################################################################### +# +----------------------------------------------------------------------------------------------+ # +# | __ __ ___ __ __ | # +# | | | | / ` |__/ |__) |__ |__) |\/| /__` | # +# | |___ \__/ \__, | \ | |___ | \ | | .__/ | # +# | | # +# | https://luckperms.net | # +# | | # +# | WIKI: https://luckperms.net/wiki | # +# | DISCORD: https://discord.gg/luckperms | # +# | BUG REPORTS: https://github.com/lucko/LuckPerms/issues | # +# | | # +# | Each option in this file is documented and explained here: | # +# | ==> https://luckperms.net/wiki/Configuration | # +# | | # +# | New options are not added to this file automatically. Default values are used if an | # +# | option cannot be found. The latest config versions can be obtained at the link above. | # +# +----------------------------------------------------------------------------------------------+ # +#################################################################################################### + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | ESSENTIAL SETTINGS | # +# | | # +# | Important settings that control how LuckPerms functions. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# The name of the server, used for server specific permissions. +# +# - When set to "global" this setting is effectively ignored. +# - In all other cases, the value here is added to all players in a "server" context. +# - See: https://luckperms.net/wiki/Context +server = "global" + +# If the servers own UUID cache/lookup facility should be used when there is no record for a player +# already in LuckPerms. +# +# - When this is set to 'false', commands using a player's username will not work unless the player +# has joined since LuckPerms was first installed. +# - To get around this, you can use a player's uuid directly in the command, or enable this option. +# - When this is set to 'true', the server facility is used. This may use a number of methods, +# including checking the servers local cache, or making a request to the Mojang API. +use-server-uuid-cache = false + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | STORAGE SETTINGS | # +# | | # +# | Controls which storage method LuckPerms will use to store data. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# How the plugin should store data +# +# - The various options are explained in more detail on the wiki: +# https://luckperms.net/wiki/Storage-types +# +# - Possible options: +# +# | Remote databases - require connection information to be configured below +# |=> MySQL +# |=> MariaDB (preferred over MySQL) +# |=> PostgreSQL +# |=> MongoDB +# +# | Flatfile/local database - don't require any extra configuration +# |=> H2 (preferred over SQLite) +# |=> SQLite +# +# | Readable & editable text files - don't require any extra configuration +# |=> YAML (.yml files) +# |=> JSON (.json files) +# |=> HOCON (.conf files) +# |=> TOML (.toml files) +# | +# | By default, user, group and track data is separated into different files. Data can be combined +# | and all stored in the same file by switching to a combined storage variant. +# | Just add '-combined' to the end of the storage-method, e.g. 'yaml-combined' +# +# - A H2 database is the default option. +# - If you want to edit data manually in "traditional" storage files, we suggest using YAML. +storage-method = "h2" + +# The following block defines the settings for remote database storage methods. +# +# - You don't need to touch any of the settings here if you're using a local storage method! +# - The connection detail options are shared between all remote storage types. +data { + + # Define the address and port for the database. + # - The standard DB engine port is used by default + # (MySQL = 3306, PostgreSQL = 5432, MongoDB = 27017) + # - Specify as "host:port" if differs + address = "localhost" + + # The name of the database to store LuckPerms data in. + # - This must be created already. Don't worry about this setting if you're using MongoDB. + database = "minecraft" + + # Credentials for the database. + username = "root" + password = "" + + # These settings apply to the MySQL connection pool. + # - The default values will be suitable for the majority of users. + # - Do not change these settings unless you know what you're doing! + pool-settings { + + # Sets the maximum size of the MySQL connection pool. + # - Basically this value will determine the maximum number of actual + # connections to the database backend. + # - More information about determining the size of connection pools can be found here: + # https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing + maximum-pool-size = 10 + + # Sets the minimum number of idle connections that the pool will try to maintain. + # - For maximum performance and responsiveness to spike demands, it is recommended to not set + # this value and instead allow the pool to act as a fixed size connection pool. + # (set this value to the same as 'maximum-pool-size') + minimum-idle = 10 + + # This setting controls the maximum lifetime of a connection in the pool in milliseconds. + # - The value should be at least 30 seconds less than any database or infrastructure imposed + # connection time limit. + maximum-lifetime = 1800000 # 30 minutes + + # This setting controls the maximum number of milliseconds that the plugin will wait for a + # connection from the pool, before timing out. + connection-timeout = 5000 # 5 seconds + + # This setting allows you to define extra properties for connections. + # + # By default, the following options are set to enable utf8 encoding. (you may need to remove + # these if you are using PostgreSQL) + # useUnicode = true + # characterEncoding = "utf8" + # + # You can also use this section to disable SSL connections, by uncommenting the 'useSSL' and + # 'verifyServerCertificate' options below. + properties { + useUnicode = true + characterEncoding = "utf8" + #useSSL: false + #verifyServerCertificate: false + } + } + + # The prefix for all LuckPerms SQL tables. + # - Change this if you want to use different tables for different servers. + table-prefix = "luckperms_" + + # The prefix to use for all LuckPerms collections. Change this if you want to use different + # collections for different servers. The default is no prefix. + mongodb-collection-prefix = "" + + # MongoDB ClientConnectionURI for use with replica sets and custom connection options + # - See https://docs.mongodb.com/manual/reference/connection-string/ + mongodb-connection-uri = "" +} + +# Define settings for a "split" storage setup. +# +# - This allows you to define a storage method for each type of data. +# - The connection options above still have to be correct for each type here. +split-storage { + # Don't touch this if you don't want to use split storage! + enabled = false + methods { + # These options don't need to be modified if split storage isn't enabled. + user = "h2" + group = "h2" + track = "h2" + uuid = "h2" + log = "h2" + } +} + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | UPDATE PROPAGATION & MESSAGING SERVICE | # +# | | # +# | Controls the ways in which LuckPerms will sync data & notify other servers of changes. | # +# | These options are documented on greater detail on the wiki under "Instant Updates". | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# This option controls how frequently LuckPerms will perform a sync task. +# +# - A sync task will refresh all data from the storage, and ensure that the most up-to-date data is +# being used by the plugin. +# - This is disabled by default, as most users will not need it. However, if you're using a remote +# storage type without a messaging service setup, you may wish to set this to something like 3. +# - Set to -1 to disable the task completely. +sync-minutes = -1 + +# If the file watcher should be enabled. +# +# - When using a file-based storage type, LuckPerms can monitor the data files for changes, and +# automatically update when changes are detected. +# - If you don't want this feature to be active, set this option to false. +watch-files = true + +# Define which messaging service should be used by the plugin. +# +# - If enabled and configured, LuckPerms will use the messaging service to inform other connected +# servers of changes. +# - Use the command "/lp networksync" to manually push changes. +# - Data is NOT stored using this service. It is only used as a messaging platform. +# +# - If you decide to enable this feature, you should set "sync-minutes" to -1, as there is no need +# for LuckPerms to poll the database for changes. +# +# - Possible options: +# => sql Uses the SQL database to form a queue system for communication. Will only work when +# 'storage-method' is set to MySQL or MariaDB. This is chosen by default if the +# option is set to 'auto' and SQL storage is in use. Set to 'notsql' to disable this. +# => pluginmsg Uses the plugin messaging channels to communicate with the proxy. +# LuckPerms must be installed on your proxy & all connected servers backend servers. +# Won't work if you have more than one proxy. +# => redis Uses Redis pub-sub to push changes. Your server connection info must be configured +# below. +# => auto Attempts to automatically setup a messaging service using redis or sql. +messaging-service = "auto" + +# If LuckPerms should automatically push updates after a change has been made with a command. +auto-push-updates = true + +# If LuckPerms should push logging entries to connected servers via the messaging service. +push-log-entries = true + +# If LuckPerms should broadcast received logging entries to players on this platform. +# +# - If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you +# should set this option to false on either your backends or your proxies, to avoid players being +# messaged twice about log entries. +broadcast-received-log-entries = true + +# Settings for Redis. +# Port 6379 is used by default; set address to "host:port" if differs +redis { + enabled = false + address = "localhost" + password = "" +} + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | CUSTOMIZATION SETTINGS | # +# | | # +# | Settings that allow admins to customize the way LuckPerms operates. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# Controls how temporary permissions/parents/meta should be accumulated. +# +# - The default behaviour is "deny". +# - This behaviour can also be specified when the command is executed. See the command usage +# documentation for more info. +# +# - Possible options: +# => accumulate durations will be added to the existing expiry time +# => replace durations will be replaced if the new duration is later than the current +# expiration +# => deny the command will just fail if you try to add another node with the same expiry +temporary-add-behaviour = "deny" + +# Controls how LuckPerms will determine a users "primary" group. +# +# - The meaning and influence of "primary groups" are explained in detail on the wiki. +# - The preferred approach is to let LuckPerms automatically determine a users primary group +# based on the relative weight of their parent groups. +# +# - Possible options: +# => stored use the value stored against the users record in the file/database +# => parents-by-weight just use the users most highly weighted parent +# => all-parents-by-weight same as above, but calculates based upon all parents inherited from +# both directly and indirectly +primary-group-calculation = "parents-by-weight" + +# If the plugin should check for "extra" permissions with users run LP commands. +# +# - These extra permissions allow finer control over what users can do with each command, and who +# they have access to edit. +# - The nature of the checks are documented on the wiki under "Argument based command permissions". +# - Argument based permissions are *not* static, unlike the 'base' permissions, and will depend upon +# the arguments given within the command. +argument-based-command-permissions = false + +# If the plugin should check whether senders are a member of a given group before they're able to +# edit the groups data or add/remove other users to/from it. +# Note: these limitations do not apply to the web editor! +require-sender-group-membership-to-modify = false + +# If the plugin should send log notifications to users whenever permissions are modified. +# +# - Notifications are only sent to those with the appropriate permission to receive them +# - They can also be temporarily enabled/disabled on a per-user basis using +# '/lp log notify ' +log-notify = true + +# Defines a list of log entries which should not be sent as notifications to users. +# +# - Each entry in the list is a RegEx expression which is matched against the log entry description. +log-notify-filtered-descriptions = [ +# "parent add example" +] + +# If LuckPerms should automatically install translation bundles and periodically update them. +auto-install-translations = true + +# Defines the options for prefix and suffix stacking. +# +# - The feature allows you to display multiple prefixes or suffixes alongside a players username in +# chat. +# - It is explained and documented in more detail on the wiki under "Prefix & Suffix Stacking". +# +# - The options are divided into separate sections for prefixes and suffixes. +# - The 'duplicates' setting refers to how duplicate elements are handled. Can be 'retain-all', +# 'first-only' or 'last-only'. +# - The value of 'start-spacer' is included at the start of the resultant prefix/suffix. +# - The value of 'end-spacer' is included at the end of the resultant prefix/suffix. +# - The value of 'middle-spacer' is included between each element in the resultant prefix/suffix. +# +# - Possible format options: +# => highest Selects the value with the highest weight, from all values +# held by or inherited by the player. +# +# => lowest Same as above, except takes the one with the lowest weight. +# +# => highest_own Selects the value with the highest weight, but will not +# accept any inherited values. +# +# => lowest_own Same as above, except takes the value with the lowest weight. +# +# => highest_inherited Selects the value with the highest weight, but will only +# accept inherited values. +# +# => lowest_inherited Same as above, except takes the value with the lowest weight. +# +# => highest_on_track_ Selects the value with the highest weight, but only if the +# value was inherited from a group on the given track. +# +# => lowest_on_track_ Same as above, except takes the value with the lowest weight. +# +# => highest_not_on_track_ Selects the value with the highest weight, but only if the +# value was inherited from a group not on the given track. +# +# => lowest_not_on_track_ Same as above, except takes the value with the lowest weight. +# +# => highest_from_group_ Selects the value with the highest weight, but only if the +# value was inherited from the given group. +# +# => lowest_from_group_ Same as above, except takes the value with the lowest weight. +# +# => highest_not_from_group_ Selects the value with the highest weight, but only if the +# value was not inherited from the given group. +# +# => lowest_not_from_group_ Same as above, except takes the value with the lowest weight. +meta-formatting { + prefix { + format = [ + "highest" + ] + duplicates = "first-only" + start-spacer = "" + middle-spacer = " " + end-spacer = "" + } + suffix { + format = [ + "highest" + ] + duplicates = "first-only" + start-spacer = "" + middle-spacer = " " + end-spacer = "" + } +} + + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | PERMISSION CALCULATION AND INHERITANCE | # +# | | # +# | Modify the way permission checks, meta lookups and inheritance resolutions are handled. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# The algorithm LuckPerms should use when traversing the "inheritance tree". +# +# - Possible options: +# => breadth-first See: https://en.wikipedia.org/wiki/Breadth-first_search +# => depth-first-pre-order See: https://en.wikipedia.org/wiki/Depth-first_search +# => depth-first-post-order See: https://en.wikipedia.org/wiki/Depth-first_search +inheritance-traversal-algorithm = "depth-first-pre-order" + +# If a final sort according to "inheritance rules" should be performed after the traversal algorithm +# has resolved the inheritance tree. +# +# "Inheritance rules" refers to things such as group weightings, primary group status, and the +# natural contextual ordering of the group nodes. +# +# Setting this to 'true' will allow for the inheritance rules to take priority over the structure of +# the inheritance tree. +# +# Effectively when this setting is 'true': the tree is flattened, and rules applied afterwards, +# and when this setting is 'false':, the rules are just applied during each step of the traversal. +post-traversal-inheritance-sort = false + +# Defines the mode used to determine whether a set of contexts are satisfied. +# +# - Possible options: +# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the +# key-value entries per key in A are also in B. +# => all-values-per-key Set A will be satisfied by another set B, if all key-value +# entries in A are also in B. +context-satisfy-mode = "at-least-one-value-per-key" + +# +----------------------------------------------------------------------------------------------+ # +# | Permission resolution settings | # +# +----------------------------------------------------------------------------------------------+ # + +# If users on this server should have their global permissions applied. +# When set to false, only server specific permissions will apply for users on this server +include-global = true + +# If users on this server should have their global world permissions applied. +# When set to false, only world specific permissions will apply for users on this server +include-global-world = true + +# If users on this server should have global (non-server specific) groups applied +apply-global-groups = true + +# If users on this server should have global (non-world specific) groups applied +apply-global-world-groups = true + +# +----------------------------------------------------------------------------------------------+ # +# | Meta lookup settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Defines how meta values should be selected. +# +# - Possible options: +# => inheritance Selects the meta value that was inherited first +# => highest-number Selects the highest numerical meta value +# => lowest-number Selects the lowest numerical meta value +meta-value-selection-default = "inheritance" + +# Defines how meta values should be selected per key. +meta-value-selection { + #max-homes = "highest-number" +} + +# +----------------------------------------------------------------------------------------------+ # +# | Inheritance settings | # +# +----------------------------------------------------------------------------------------------+ # + +# If the plugin should apply wildcard permissions. +# +# - If set to true, LuckPerms will detect wildcard permissions, and resolve & apply all registered +# permissions matching the wildcard. +apply-wildcards = true + +# If LuckPerms should resolve and apply permissions according to the Sponge style implicit wildcard +# inheritance system. +# +# - That being: If a user has been granted "example", then the player should have also be +# automatically granted "example.function", "example.another", "example.deeper.nesting", +# and so on. +apply-sponge-implicit-wildcards=true + +# If the plugin should parse regex permissions. +# +# - If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the +# node, and resolve & apply all registered permissions matching the regex. +apply-regex = true + +# If the plugin should complete and apply shorthand permissions. +# +# - If set to true, LuckPerms will detect and expand shorthand node patterns. +apply-shorthand = true + +# If the owner of an integrated server should bypass permission checks. +# +# - This setting only applies when LuckPerms is active on a single-player world. +# - The owner of an integrated server is the player whose client instance is running the server. +integrated-server-owner-bypasses-checks = true + +# +----------------------------------------------------------------------------------------------+ # +# | Extra settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Allows you to set "aliases" for the worlds sent forward for context calculation. +# +# - These aliases are provided in addition to the real world name. Applied recursively. +# - Remove the comment characters for the default aliases to apply. +world-rewrite { + #world_nether = "world" + #world_the_end = "world" +} + +# Define special group weights for this server. +# +# - Group weights can also be applied directly to group data, using the setweight command. +# - This section allows weights to be set on a per-server basis. +group-weight { + #admin = 10 +} + + + +# +----------------------------------------------------------------------------------------------+ # +# | | # +# | FINE TUNING OPTIONS | # +# | | # +# | A number of more niche settings for tweaking and changing behaviour. The section also | # +# | contains toggles for some more specialised features. It is only necessary to make changes to | # +# | these options if you want to fine-tune LuckPerms behaviour. | # +# | | # +# +----------------------------------------------------------------------------------------------+ # + +# +----------------------------------------------------------------------------------------------+ # +# | Miscellaneous (and rarely used) settings | # +# +----------------------------------------------------------------------------------------------+ # + +# If LuckPerms should produce extra logging output when it handles logins. +# +# - Useful if you're having issues with UUID forwarding or data not being loaded. +debug-logins = false + +# If LuckPerms should allow usernames with non alphanumeric characters. +# +# - Note that due to the design of the storage implementation, usernames must still be 16 characters +# or less. +allow-invalid-usernames = false + +# If LuckPerms should allow a users primary group to be removed with the 'parent remove' command. +# +# - When this happens, the plugin will set their primary group back to default. +prevent-primary-group-removal = false + +# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands. +# See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors +resolve-command-selectors = false diff --git a/fabric/src/main/resources/mixins.luckperms.json b/fabric/src/main/resources/mixins.luckperms.json new file mode 100644 index 000000000..a2d619141 --- /dev/null +++ b/fabric/src/main/resources/mixins.luckperms.json @@ -0,0 +1,16 @@ +{ + "required": true, + "package": "me.lucko.luckperms.fabric.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "ClientSettingsC2SPacketAccessor", + "PlayerManagerMixin", + "ServerLoginNetworkHandlerAccessor", + "ServerPlayerEntityMixin" + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..f12c912d6 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +# Fabric requires some more ram. +org.gradle.jvmargs=-Xmx1G \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e5de4f935..01e87906c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,14 @@ +// Fabric Needs this +pluginManagement { + repositories { + jcenter() + maven { + url 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} + rootProject.name = 'luckperms' include ( 'api', @@ -5,7 +16,8 @@ include ( 'bukkit', 'bukkit-legacy', 'bungee', - 'sponge', 'sponge:sponge-service', 'sponge:sponge-service-api6', 'sponge:sponge-service-api7', + 'fabric', 'nukkit', + 'sponge', 'sponge:sponge-service', 'sponge:sponge-service-api6', 'sponge:sponge-service-api7', 'velocity' ) diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java index 38df1acf1..9fe5bd141 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java @@ -32,6 +32,7 @@ import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter; import me.lucko.luckperms.common.dependencies.Dependency; import me.lucko.luckperms.common.event.AbstractEventBus; +import me.lucko.luckperms.common.locale.TranslationManager; import me.lucko.luckperms.common.messaging.MessagingFactory; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.manager.track.StandardTrackManager; @@ -275,7 +276,7 @@ public Sender getConsoleSender() { return new DummySender(this, Sender.CONSOLE_UUID, Sender.CONSOLE_NAME) { @Override public void sendMessage(Component message) { - LPSpongePlugin.this.bootstrap.getPluginLogger().info(LegacyComponentSerializer.legacySection().serialize(message)); + LPSpongePlugin.this.bootstrap.getPluginLogger().info(LegacyComponentSerializer.legacySection().serialize(TranslationManager.render(message))); } }; } From 848c36928f6221c75e91062e621b85a4dc639e8f Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 5 Jan 2021 10:30:28 +0000 Subject: [PATCH 36/38] Fix logging in on Fabric using offline mode (#2810) --- .../common/event/EventDispatcher.java | 4 ++-- fabric/build.gradle | 2 +- .../listeners/FabricConnectionListener.java | 21 +++++++++++-------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java index 96f524eec..e5584e7b1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java @@ -332,8 +332,8 @@ public void dispatchUserFirstLogin(UUID uniqueId, String username) { postAsync(UserFirstLoginEvent.class, uniqueId, username); } - public void dispatchPlayerLoginProcess(UUID uniqueId, String username, User user) { - postSync(PlayerLoginProcessEvent.class, uniqueId, username, user.getApiProxy()); + public void dispatchPlayerLoginProcess(UUID uniqueId, String username, @Nullable User user) { + postSync(PlayerLoginProcessEvent.class, uniqueId, username, user == null ? null : user.getApiProxy()); } public void dispatchPlayerDataSave(UUID uniqueId, String username, PlayerSaveResult result) { diff --git a/fabric/build.gradle b/fabric/build.gradle index 2d804e24f..fd0f0431d 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -51,7 +51,7 @@ processResources { } shadowJar { - archiveName = "LuckPerms-Fabric-${project.ext.fullVersion}-dev.jar" + archiveName = "luckpermsfabric-${project.ext.fullVersion}-dev.jar" dependencies { exclude('net.fabricmc:.*') diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java index 6353f1224..0a509008c 100644 --- a/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricConnectionListener.java @@ -42,11 +42,13 @@ import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking.LoginSynchronizer; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.kyori.adventure.text.Component; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerLoginNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; +import java.util.UUID; import java.util.concurrent.CompletableFuture; public class FabricConnectionListener extends AbstractConnectionListener { @@ -68,15 +70,16 @@ private void onPreLogin(ServerLoginNetworkHandler netHandler, MinecraftServer se // Get their profile from the net handler - it should have been initialised by now. GameProfile profile = ((ServerLoginNetworkHandlerAccessor) netHandler).getGameProfile(); - + UUID uniqueId = PlayerEntity.getUuidFromProfile(profile); + String username = profile.getName(); // Register with the LoginSynchronizer that we want to perform a task before the login proceeds. - sync.waitFor(CompletableFuture.runAsync(() -> onPreLoginAsync(netHandler, profile), this.plugin.getBootstrap().getScheduler().async())); + sync.waitFor(CompletableFuture.runAsync(() -> onPreLoginAsync(netHandler, uniqueId, username), this.plugin.getBootstrap().getScheduler().async())); } - private void onPreLoginAsync(ServerLoginNetworkHandler netHandler, GameProfile e) { + private void onPreLoginAsync(ServerLoginNetworkHandler netHandler, UUID uniqueId, String username) { if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { - this.plugin.getLogger().info("Processing pre-login for " + e.getId() + " - " + e.getName()); + this.plugin.getLogger().info("Processing pre-login for " + uniqueId + " - " + username); } /* Actually process the login for the connection. @@ -89,16 +92,16 @@ private void onPreLoginAsync(ServerLoginNetworkHandler netHandler, GameProfile e - creating a user instance in the UserManager for this connection. - setting up cached data. */ try { - User user = loadUser(e.getId(), e.getName()); - recordConnection(e.getId()); - this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(e.getId(), e.getName(), user); + User user = loadUser(uniqueId, username); + recordConnection(uniqueId); + this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(uniqueId, username, user); } catch (Exception ex) { - this.plugin.getLogger().severe("Exception occurred whilst loading data for " + e.getId() + " - " + e.getName(), ex); + this.plugin.getLogger().severe("Exception occurred whilst loading data for " + uniqueId + " - " + username, ex); // deny the connection Component reason = TranslationManager.render(Message.LOADING_DATABASE_ERROR.build()); netHandler.disconnect(FabricSenderFactory.toNativeText(reason)); - this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(e.getId(), e.getName(), null); + this.plugin.getEventDispatcher().dispatchPlayerLoginProcess(uniqueId, username, null); } } From 443ea510bbce46fb89a27c88cb6cd1465ed5e9b5 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 6 Jan 2021 13:14:41 +0000 Subject: [PATCH 37/38] Implement plugin message messenger service on Fabric --- README.md | 2 +- .../luckperms/fabric/LPFabricPlugin.java | 5 +- .../messaging/FabricMessagingFactory.java | 72 ++++++++++++ .../messaging/PluginMessageMessenger.java | 103 ++++++++++++++++++ 4 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/messaging/FabricMessagingFactory.java create mode 100644 fabric/src/main/java/me/lucko/luckperms/fabric/messaging/PluginMessageMessenger.java diff --git a/README.md b/README.md index bf118ef18..30cf41542 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The project is split up into a few separate modules. * **API** - The public, semantically versioned API used by other plugins wishing to integrate with and retrieve data from LuckPerms. This module (for the most part) does not contain any implementation itself, and is provided by the plugin. * **Common** - The common module contains most of the code which implements the respective LuckPerms plugins. This abstract module reduces duplicated code throughout the project. -* **Bukkit, BungeeCord, Sponge, Nukkit & Velocity** - Each use the common module to implement plugins on the respective server platforms. +* **Bukkit, BungeeCord, Sponge, Fabric, Nukkit & Velocity** - Each use the common module to implement plugins on the respective server platforms. ## License LuckPerms is licensed under the permissive MIT license. Please see [`LICENSE.txt`](https://github.com/lucko/LuckPerms/blob/master/LICENSE.txt) for more info. diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java index 991d7078d..bca58808d 100644 --- a/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java @@ -46,6 +46,7 @@ import me.lucko.luckperms.fabric.context.FabricPlayerCalculator; import me.lucko.luckperms.fabric.listeners.FabricConnectionListener; import me.lucko.luckperms.fabric.listeners.PermissionCheckListener; +import me.lucko.luckperms.fabric.messaging.FabricMessagingFactory; import net.fabricmc.loader.api.ModContainer; import net.kyori.adventure.text.Component; @@ -133,8 +134,8 @@ protected void registerPlatformListeners() { } @Override - protected MessagingFactory provideMessagingFactory() { - return new MessagingFactory<>(this); + protected MessagingFactory provideMessagingFactory() { + return new FabricMessagingFactory(this); } @Override diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/messaging/FabricMessagingFactory.java b/fabric/src/main/java/me/lucko/luckperms/fabric/messaging/FabricMessagingFactory.java new file mode 100644 index 000000000..9b207bc88 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/messaging/FabricMessagingFactory.java @@ -0,0 +1,72 @@ +/* + * 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.fabric.messaging; + +import me.lucko.luckperms.common.messaging.InternalMessagingService; +import me.lucko.luckperms.common.messaging.LuckPermsMessagingService; +import me.lucko.luckperms.common.messaging.MessagingFactory; +import me.lucko.luckperms.fabric.LPFabricPlugin; + +import net.luckperms.api.messenger.IncomingMessageConsumer; +import net.luckperms.api.messenger.Messenger; +import net.luckperms.api.messenger.MessengerProvider; + +import org.checkerframework.checker.nullness.qual.NonNull; + +public class FabricMessagingFactory extends MessagingFactory { + public FabricMessagingFactory(LPFabricPlugin plugin) { + super(plugin); + } + + @Override + protected InternalMessagingService getServiceFor(String messagingType) { + if (messagingType.equals("pluginmsg") || messagingType.equals("bungee") || messagingType.equals("velocity")) { + try { + return new LuckPermsMessagingService(getPlugin(), new PluginMessageMessengerProvider()); + } catch (Exception e) { + getPlugin().getLogger().severe("Exception occurred whilst enabling messaging", e); + } + } + + return super.getServiceFor(messagingType); + } + + private class PluginMessageMessengerProvider implements MessengerProvider { + + @Override + public @NonNull String getName() { + return "PluginMessage"; + } + + @Override + public @NonNull Messenger obtain(@NonNull IncomingMessageConsumer incomingMessageConsumer) { + PluginMessageMessenger messenger = new PluginMessageMessenger(getPlugin(), incomingMessageConsumer); + messenger.init(); + return messenger; + } + } + +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/messaging/PluginMessageMessenger.java b/fabric/src/main/java/me/lucko/luckperms/fabric/messaging/PluginMessageMessenger.java new file mode 100644 index 000000000..733c8a41d --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/messaging/PluginMessageMessenger.java @@ -0,0 +1,103 @@ +/* + * 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.fabric.messaging; + +import com.google.common.collect.Iterables; + +import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask; +import me.lucko.luckperms.fabric.LPFabricPlugin; + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.luckperms.api.messenger.IncomingMessageConsumer; +import net.luckperms.api.messenger.Messenger; +import net.luckperms.api.messenger.message.OutgoingMessage; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class PluginMessageMessenger implements Messenger, ServerPlayNetworking.PlayChannelHandler { + private static final Identifier CHANNEL = new Identifier("luckperms", "update"); + + private final LPFabricPlugin plugin; + private final IncomingMessageConsumer consumer; + + public PluginMessageMessenger(LPFabricPlugin plugin, IncomingMessageConsumer consumer) { + this.plugin = plugin; + this.consumer = consumer; + } + + public void init() { + ServerPlayNetworking.registerGlobalReceiver(CHANNEL, this); + } + + @Override + public void close() { + ServerPlayNetworking.unregisterGlobalReceiver(CHANNEL); + } + + @Override + public void sendOutgoingMessage(@NonNull OutgoingMessage outgoingMessage) { + AtomicReference taskRef = new AtomicReference<>(); + SchedulerTask task = this.plugin.getBootstrap().getScheduler().asyncRepeating(() -> { + MinecraftServer server = this.plugin.getBootstrap().getServer().orElse(null); + if (server == null) { + return; + } + + Collection players = server.getPlayerManager().getPlayerList(); + ServerPlayerEntity p = Iterables.getFirst(players, null); + if (p == null) { + return; + } + + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeString(outgoingMessage.asEncodedString()); + ServerPlayNetworking.send(p, CHANNEL, buf); + + SchedulerTask t = taskRef.getAndSet(null); + if (t != null) { + t.cancel(); + } + }, 10, TimeUnit.SECONDS); + taskRef.set(task); + } + + @Override + public void receive(MinecraftServer server, ServerPlayerEntity entity, ServerPlayNetworkHandler netHandler, PacketByteBuf buf, PacketSender packetSender) { + String msg = buf.readString(); + this.consumer.consumeIncomingMessageAsString(msg); + } +} From 505c073c8e9b9a841e7267b34f3e42a84d0469d3 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 9 Jan 2021 20:36:08 +0000 Subject: [PATCH 38/38] Add config to control whether display names are returned by the Vault hook --- .../vault/LuckPermsVaultPermission.java | 21 ++++++++++--------- bukkit/src/main/resources/config.yml | 6 ++++++ .../luckperms/common/config/ConfigKeys.java | 5 +++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java index 4cb5e78b5..ad6276314 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java @@ -176,7 +176,7 @@ public PermissionHolder lookupUser(UUID uuid) { @Override public String[] getGroups() { return this.plugin.getGroupManager().getAll().values().stream() - .map(Group::getPlainDisplayName) + .map(this::groupName) .toArray(String[]::new); } @@ -252,10 +252,7 @@ public String[] userGetGroups(String world, UUID uuid) { return user.getOwnNodes(NodeType.INHERITANCE, queryOptions).stream() .map(n -> { Group group = this.plugin.getGroupManager().getIfLoaded(n.getGroupName()); - if (group != null) { - return group.getPlainDisplayName(); - } - return n.getGroupName(); + return group != null ? groupName(group) : n.getGroupName(); }) .toArray(String[]::new); } @@ -274,11 +271,7 @@ public String userGetPrimaryGroup(String world, UUID uuid) { String value = metaData.getPrimaryGroup(MetaCheckEvent.Origin.THIRD_PARTY_API); Group group = getGroup(value); - if (group != null) { - return group.getPlainDisplayName(); - } - - return value; + return group != null ? groupName(group) : value; } @Override @@ -328,6 +321,14 @@ private Group getGroup(String name) { return this.plugin.getGroupManager().getByDisplayName(name); } + private String groupName(Group group) { + if (this.plugin.getConfiguration().get(ConfigKeys.VAULT_GROUP_USE_DISPLAYNAMES)) { + return group.getPlainDisplayName(); + } else { + return group.getName(); + } + } + private boolean checkGroupExists(String group) { return this.plugin.getGroupManager().getByDisplayName(group) != null; } diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index 35deefc47..564fb016d 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -587,6 +587,12 @@ commands-allow-op: true # option to 'true. vault-unsafe-lookups: false +# If LuckPerms should use the 'display name' of a group when returning groups in Vault API calls. +# +# - When this option is set to true, the display name of the group is returned. +# - When this option is set to false, the standard name/id of the group is returned. +vault-group-use-displaynames: true + # Controls which group LuckPerms should use for NPC players when handling Vault requests. # # - As NPCs aren't actually real players, LuckPerms does not load any user data for them. This 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 b9ffc5caf..915c4d25c 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 @@ -446,6 +446,11 @@ private ConfigKeys() {} */ public static final ConfigKey VAULT_UNSAFE_LOOKUPS = booleanKey("vault-unsafe-lookups", false); + /** + * If LuckPerms should use the 'display name' of a group when returning groups in Vault API calls. + */ + public static final ConfigKey VAULT_GROUP_USE_DISPLAYNAMES = booleanKey("vault-group-use-displaynames", true); + /** * Controls which group LuckPerms should use for NPC players when handling Vault requests */