From 8dfeef9575d855fba60251be10ef24e13f7ec773 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 23 Dec 2020 12:16:14 +0000 Subject: [PATCH] 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 | 420 ++++++++---------- .../file/CombinedConfigurateStorage.java | 416 +++++++++-------- .../implementation/file/FileIOException.java | 36 ++ .../implementation/file/FileUuidCache.java | 128 +++--- .../file/SeparatedConfigurateStorage.java | 157 ++++--- .../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, 792 insertions(+), 771 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 enum DataTypeFilter implements Predicate { }; 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 class LuckPermsVaultChat extends AbstractVaultChat { @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 class LuckPermsVaultChat extends AbstractVaultChat { 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 @@ public class LuckPermsVaultChat extends AbstractVaultChat { 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 class LogDispatcher { 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 org.checkerframework.checker.nullness.qual.NonNull; 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 @@ public abstract class Exporter implements Runnable { } 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 @@ public final class 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 @@ public final class 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 abstract class ConfigurateConfigAdapter implements ConfigurationAdapter { @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 abstract class ConfigurateConfigAdapter implements ConfigurationAdapter { @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.ContextSet; 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 final class ContextSetConfigurateSerializer { } 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 final class ContextSetConfigurateSerializer { 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 enum Dependency { } } - /* - 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 class BytebinClient extends AbstractHttpClient { * * @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,54 +94,16 @@ public class BytebinClient extends AbstractHttpClient { .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); - } + 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"); - } - - 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(); - } - /** * GETs json content from bytebin * @@ -173,19 +134,9 @@ public class BytebinClient extends AbstractHttpClient { 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 class TranslationRepository { } 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 class RedisMessenger implements Messenger { 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 class RedisMessenger implements Messenger { 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.StorageImplementation; 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.InheritanceNode; 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 + /* The UUID cache */ + private final FileUuidCache uuidCache; + private Path uuidCacheFile; + + /** The action logger */ private final FileActionLogger actionLogger; - // the file used to store uuid data - private Path uuidDataFile; - 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 abstract class AbstractConfigurateStorage implements StorageImplementatio */ 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 abstract class AbstractConfigurateStorage implements StorageImplementatio 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 abstract class AbstractConfigurateStorage implements StorageImplementatio } } } 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 abstract class AbstractConfigurateStorage implements StorageImplementatio 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 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio 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; - } - - Map.Entry entry = parseEntry(appended, "permission"); - if (entry == null) { - continue; - } - nodes.addAll(readAttributes(entry.getValue(), entry.getKey())); + for (ConfigurationNode appended : data.getNode("permissions").getChildrenList()) { + String plainValue = appended.getValue(Types::strictAsString); + if (plainValue != null) { + nodes.add(NodeBuilders.determineMostApplicable(plainValue).build()); + continue; } + + 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; - } - - Map.Entry entry = parseEntry(appended, "group"); - if (entry == null) { - continue; - } - nodes.add(readMetaAttributes(entry.getValue(), c -> Inheritance.builder(entry.getKey()))); + for (ConfigurationNode appended : data.getNode("parents").getChildrenList()) { + String plainValue = appended.getValue(Types::strictAsString); + if (plainValue != null) { + nodes.add(Inheritance.builder(plainValue).build()); + continue; } + + 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 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio } 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 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio } 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 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio 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 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio // 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 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio // 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 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio // 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,33 +52,221 @@ import java.util.concurrent.locks.ReentrantLock; 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 users; + private CachedLoader groups; + private CachedLoader tracks; + private FileWatcher.WatchedLocation watcher = null; - private CachedLoader usersLoader; - private CachedLoader groupsLoader; - private CachedLoader tracksLoader; + public CombinedConfigurateStorage(LuckPermsPlugin plugin, String implementationName, ConfigurateLoader loader, String fileExtension, String dataFolderName) { + super(plugin, implementationName, loader, dataFolderName); + this.fileExtension = fileExtension; + } + + @Override + protected ConfigurationNode readFile(StorageLocation location, String name) throws IOException { + 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 { + getLoader(location).apply(true, false, root -> root.getNode(name).setValue(node)); + } + + private CachedLoader getLoader(StorageLocation location) { + switch (location) { + case USERS: + return this.users; + case GROUPS: + return this.groups; + case TRACKS: + return this.tracks; + default: + throw new RuntimeException(); + } + } + + @Override + public void init() throws IOException { + super.init(); + + 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.users.file.getFileName())) { + this.plugin.getLogger().info("[FileWatcher] Detected change in users file - reloading..."); + this.users.reload(); + this.plugin.getSyncTaskBuffer().request(); + } else if (path.getFileName().equals(this.groups.file.getFileName())) { + this.plugin.getLogger().info("[FileWatcher] Detected change in groups file - reloading..."); + this.groups.reload(); + this.plugin.getSyncTaskBuffer().request(); + } else if (path.getFileName().equals(this.tracks.file.getFileName())) { + this.plugin.getLogger().info("[FileWatcher] Detected change in tracks file - reloading..."); + this.tracks.reload(); + this.plugin.getStorage().loadAllTracks(); + } + }); + } + } + + @Override + public void shutdown() { + try { + this.users.save(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + this.groups.save(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + this.tracks.save(); + } catch (IOException e) { + e.printStackTrace(); + } + super.shutdown(); + } + + @Override + public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { + if (bulkUpdate.getDataType().isIncludingUsers()) { + this.users.apply(true, true, root -> { + for (Map.Entry entry : root.getChildrenMap().entrySet()) { + processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.USER); + } + }); + } + + if (bulkUpdate.getDataType().isIncludingGroups()) { + this.groups.apply(true, true, root -> { + for (Map.Entry entry : root.getChildrenMap().entrySet()) { + processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.GROUP); + } + }); + } + } + + @Override + public Set getUniqueUsers() throws IOException { + return this.users.getNode().getChildrenMap().keySet().stream() + .map(Object::toString) + .map(Uuids::fromString) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + @Override + public List> searchUserNodes(ConstraintNodeMatcher constraint) throws Exception { + List> held = new ArrayList<>(); + this.users.apply(false, true, root -> { + for (Map.Entry entry : root.getChildrenMap().entrySet()) { + try { + UUID holder = UUID.fromString(entry.getKey().toString()); + ConfigurationNode object = entry.getValue(); + + Set nodes = readNodes(object); + for (Node e : nodes) { + N match = constraint.match(e); + if (match != null) { + held.add(NodeEntry.of(holder, match)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + return held; + } + + @Override + public void loadAllGroups() throws IOException { + List groups = new ArrayList<>(); + this.groups.apply(false, true, root -> { + groups.addAll(root.getChildrenMap().keySet().stream() + .map(Object::toString) + .collect(Collectors.toList())); + }); + + if (!Iterators.tryIterate(groups, this::loadGroup)) { + throw new RuntimeException("Exception occurred whilst loading a group"); + } + + this.plugin.getGroupManager().retainAll(groups); + } + + @Override + public List> searchGroupNodes(ConstraintNodeMatcher constraint) throws Exception { + List> held = new ArrayList<>(); + this.groups.apply(false, true, root -> { + for (Map.Entry entry : root.getChildrenMap().entrySet()) { + try { + String holder = entry.getKey().toString(); + ConfigurationNode object = entry.getValue(); + + Set nodes = readNodes(object); + for (Node e : nodes) { + N match = constraint.match(e); + if (match != null) { + held.add(NodeEntry.of(holder, match)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + return held; + } + + @Override + public void loadAllTracks() throws IOException { + List tracks = new ArrayList<>(); + this.tracks.apply(false, true, root -> { + tracks.addAll(root.getChildrenMap().keySet().stream() + .map(Object::toString) + .collect(Collectors.toList())); + }); + + if (!Iterators.tryIterate(tracks, this::loadTrack)) { + throw new RuntimeException("Exception occurred whilst loading a track"); + } + + this.plugin.getTrackManager().retainAll(tracks); + } private final class CachedLoader { - private final Path path; - + private final Path file; private final ConfigurationLoader loader; - private ConfigurationNode node = null; private final ReentrantLock lock = new ReentrantLock(); + private ConfigurationNode node = null; - private CachedLoader(Path path) { - this.path = path; - this.loader = CombinedConfigurateStorage.super.loader.loader(path); + 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.path.getFileName().toString()); + CombinedConfigurateStorage.this.watcher.recordChange(this.file.getFileName().toString()); } } @@ -142,206 +330,4 @@ public class CombinedConfigurateStorage extends AbstractConfigurateStorage { } } - 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; - } - - @Override - protected ConfigurationNode readFile(StorageLocation location, String name) throws IOException { - ConfigurationNode root = getStorageLoader(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)); - } - - private CachedLoader getStorageLoader(StorageLocation location) { - switch (location) { - case USER: - return this.usersLoader; - case GROUP: - return this.groupsLoader; - case TRACK: - return this.tracksLoader; - default: - throw new RuntimeException(); - } - } - - @Override - 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); - - // 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())) { - this.plugin.getLogger().info("[FileWatcher] Detected change in users file - reloading..."); - this.usersLoader.reload(); - this.plugin.getSyncTaskBuffer().request(); - } else if (path.getFileName().equals(this.groupsFile.getFileName())) { - this.plugin.getLogger().info("[FileWatcher] Detected change in groups file - reloading..."); - this.groupsLoader.reload(); - this.plugin.getSyncTaskBuffer().request(); - } else if (path.getFileName().equals(this.tracksFile.getFileName())) { - this.plugin.getLogger().info("[FileWatcher] Detected change in tracks file - reloading..."); - this.tracksLoader.reload(); - this.plugin.getStorage().loadAllTracks(); - } - }); - } - } - - @Override - public void shutdown() { - try { - this.usersLoader.save(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - this.groupsLoader.save(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - this.tracksLoader.save(); - } catch (IOException e) { - e.printStackTrace(); - } - super.shutdown(); - } - - @Override - public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception { - if (bulkUpdate.getDataType().isIncludingUsers()) { - this.usersLoader.apply(true, true, root -> { - for (Map.Entry entry : root.getChildrenMap().entrySet()) { - processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.USER); - } - }); - } - - if (bulkUpdate.getDataType().isIncludingGroups()) { - this.groupsLoader.apply(true, true, root -> { - for (Map.Entry entry : root.getChildrenMap().entrySet()) { - processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.GROUP); - } - }); - } - } - - @Override - public Set getUniqueUsers() throws IOException { - return this.usersLoader.getNode().getChildrenMap().keySet().stream() - .map(Object::toString) - .map(Uuids::fromString) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - - @Override - public List> searchUserNodes(ConstraintNodeMatcher constraint) throws Exception { - List> held = new ArrayList<>(); - this.usersLoader.apply(false, true, root -> { - for (Map.Entry entry : root.getChildrenMap().entrySet()) { - try { - UUID holder = UUID.fromString(entry.getKey().toString()); - ConfigurationNode object = entry.getValue(); - - Set nodes = readNodes(object); - for (Node e : nodes) { - N match = constraint.match(e); - if (match != null) { - held.add(NodeEntry.of(holder, match)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - return held; - } - - @Override - public void loadAllGroups() throws IOException { - List groups = new ArrayList<>(); - this.groupsLoader.apply(false, true, root -> { - groups.addAll(root.getChildrenMap().keySet().stream() - .map(Object::toString) - .collect(Collectors.toList())); - }); - - if (!Iterators.tryIterate(groups, this::loadGroup)) { - throw new RuntimeException("Exception occurred whilst loading a group"); - } - - this.plugin.getGroupManager().retainAll(groups); - } - - @Override - public List> searchGroupNodes(ConstraintNodeMatcher constraint) throws Exception { - List> held = new ArrayList<>(); - this.groupsLoader.apply(false, true, root -> { - for (Map.Entry entry : root.getChildrenMap().entrySet()) { - try { - String holder = entry.getKey().toString(); - ConfigurationNode object = entry.getValue(); - - Set nodes = readNodes(object); - for (Node e : nodes) { - N match = constraint.match(e); - if (match != null) { - held.add(NodeEntry.of(holder, match)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - return held; - } - - @Override - public void loadAllTracks() throws IOException { - List tracks = new ArrayList<>(); - this.tracksLoader.apply(false, true, root -> { - tracks.addAll(root.getChildrenMap().keySet().stream() - .map(Object::toString) - .collect(Collectors.toList())); - }); - - if (!Iterators.tryIterate(tracks, this::loadTrack)) { - throw new RuntimeException("Exception occurred whilst loading a track"); - } - - this.plugin.getTrackManager().retainAll(tracks); - } - } 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 class FileUuidCache { 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 @@ public class FileUuidCache { } } - 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(); - } - } - 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(); + 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); } - 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.io.IOException; 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.function.Predicate; 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 final Map fileGroups; + private final FileGroup users; + private final FileGroup groups; + private final FileGroup tracks; - private FileWatcher.WatchedLocation userWatcher = null; - private FileWatcher.WatchedLocation groupWatcher = null; - private FileWatcher.WatchedLocation trackWatcher = null; + private static final class FileGroup { + private Path directory; + private FileWatcher.WatchedLocation watcher; + } - /** - * 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 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 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage { } 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 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage { 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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { } }); - 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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { @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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { @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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { } @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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { } } } 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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { @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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { } @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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { } } } 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 class SeparatedConfigurateStorage extends AbstractConfigurateStorage { @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 @@ package me.lucko.luckperms.common.storage.implementation.file; 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 net.luckperms.api.node.Node; 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 class TreeView { 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 class VerboseListener { 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 class WebEditorRequest { 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 @@ public class NukkitAutoOpListener implements LuckPermsEventListener { } 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.CommandManager; 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 class VelocityCommandExecutor extends CommandManager implements Command { 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); + public void execute(Invocation invocation) { + this.delegate.execute(invocation); } @Override - public List suggest(CommandSource source, @NonNull String[] currentArgs) { - return this.delegate.suggest(source, currentArgs); - } - - @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 @@ public final class 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 final class AdventureCompat { } } - private static boolean classExists(String className) { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - }