Some misc tidying up

This commit is contained in:
Luck 2020-12-23 12:16:14 +00:00
parent 5c44333892
commit 8dfeef9575
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
28 changed files with 792 additions and 771 deletions

View File

@ -93,8 +93,8 @@ public enum DataTypeFilter implements Predicate<DataType> {
};
private static final List<DataType> ALL_LIST = Collections.unmodifiableList(Arrays.asList(DataType.NORMAL, DataType.TRANSIENT));
private static final List<DataType> NORMAL_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.NORMAL));
private static final List<DataType> TRANSIENT_ONLY_LIST = Collections.unmodifiableList(Collections.singletonList(DataType.TRANSIENT));
private static final List<DataType> NORMAL_ONLY_LIST = Collections.singletonList(DataType.NORMAL);
private static final List<DataType> TRANSIENT_ONLY_LIST = Collections.singletonList(DataType.TRANSIENT);
/**
* Gets a {@link List} of all {@link DataType}s, filtered by the {@code predicate}.

View File

@ -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);

View File

@ -1,5 +1,7 @@
test {
useJUnitPlatform()
useJUnitPlatform {
excludeTags 'dependency_checksum'
}
}
dependencies {

View File

@ -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);
}
}

View File

@ -37,7 +37,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Objects;
@SuppressWarnings({"unchecked", "rawtypes"})
public class ApiPlayerAdapter<S, P extends S> implements PlayerAdapter<P> {
private final UserManager<?> userManager;
private final ContextManager<S, P> contextManager;

View File

@ -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()));

View File

@ -124,7 +124,7 @@ public final class ConfigKeys {
*/
public static final ConfigKey<ContextSatisfyMode> 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<User, PrimaryGroupHolder>) PrimaryGroupHolder.Stored::new;
return PrimaryGroupHolder.Stored::new;
case "parents-by-weight":
return (Function<User, PrimaryGroupHolder>) PrimaryGroupHolder.ParentsByWeight::new;
return PrimaryGroupHolder.ParentsByWeight::new;
default:
return (Function<User, PrimaryGroupHolder>) PrimaryGroupHolder.AllParentsByWeight::new;
return PrimaryGroupHolder.AllParentsByWeight::new;
}
}));

View File

@ -88,7 +88,7 @@ public abstract class ConfigurateConfigAdapter implements ConfigurationAdapter {
@Override
public List<String> getStringList(String path, List<String> 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<String> getKeys(String path, List<String> def) {
ConfigurationNode node = resolvePath(path);
if (node.isVirtual() || !node.hasMapChildren()) {
if (node.isVirtual() || !node.isMap()) {
return def;
}

View File

@ -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<String, Set<String>> map = contextSet.toMap();
for (Map.Entry<String, Set<String>> 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<Object, ? extends ConfigurationNode> 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<? extends ConfigurationNode> values = v.getChildrenList();
for (ConfigurationNode value : values) {
if (v.isList()) {
for (ConfigurationNode value : v.getChildrenList()) {
map.add(k, value.getString());
}
} else {

View File

@ -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();
}
}
*/
}

View File

@ -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() {

View File

@ -320,10 +320,5 @@ public class TranslationRepository {
}
return res;
}
@Override
public void close() throws IOException {
super.close();
}
}
}

View File

@ -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) {

View File

@ -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<Node> nodes = readNodes(node);
Set<Node> 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<Group> loadGroup(String name) {
public Optional<Group> 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<String> 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<Track> loadTrack(String name) {
public Optional<Track> 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<String> 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<Node> nodes = readNodes(node);
Set<Node> 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<ConfigurationNode, NodeBuilder<?, ?>> 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<Node> 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<Node> 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<String, ConfigurationNode> parseEntry(ConfigurationNode appended, String keyFieldName) {
if (!appended.hasMapChildren()) {
return null;
}
Map<Object, ? extends ConfigurationNode> children = appended.getChildrenMap();
private static NodeEntry parseNode(ConfigurationNode configNode, String keyFieldName) {
Map<Object, ? extends ConfigurationNode> 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<Node> readNodes(ConfigurationNode data) {
Set<Node> nodes = new HashSet<>();
if (data.getNode("permissions").hasListChildren()) {
List<? extends ConfigurationNode> 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<String, ConfigurationNode> 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<? extends ConfigurationNode> 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<String, ConfigurationNode> 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<? extends ConfigurationNode> children = data.getNode("prefixes").getChildrenList();
for (ConfigurationNode appended : children) {
Map.Entry<String, ConfigurationNode> 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<? extends ConfigurationNode> children = data.getNode("suffixes").getChildrenList();
for (ConfigurationNode appended : children) {
Map.Entry<String, ConfigurationNode> 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<? extends ConfigurationNode> children = data.getNode("meta").getChildrenList();
for (ConfigurationNode appended : children) {
Map.Entry<String, ConfigurationNode> 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<Node> 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");

View File

@ -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<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.USER);
}
});
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
this.groups.apply(true, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.GROUP);
}
});
}
}
@Override
public Set<UUID> getUniqueUsers() throws IOException {
return this.users.getNode().getChildrenMap().keySet().stream()
.map(Object::toString)
.map(Uuids::fromString)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@Override
public <N extends Node> List<NodeEntry<UUID, N>> searchUserNodes(ConstraintNodeMatcher<N> constraint) throws Exception {
List<NodeEntry<UUID, N>> held = new ArrayList<>();
this.users.apply(false, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
try {
UUID holder = UUID.fromString(entry.getKey().toString());
ConfigurationNode object = entry.getValue();
Set<Node> 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<String> 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 <N extends Node> List<NodeEntry<String, N>> searchGroupNodes(ConstraintNodeMatcher<N> constraint) throws Exception {
List<NodeEntry<String, N>> held = new ArrayList<>();
this.groups.apply(false, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
try {
String holder = entry.getKey().toString();
ConfigurationNode object = entry.getValue();
Set<Node> 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<String> 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<? extends ConfigurationNode> 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<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.USER);
}
});
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
this.groupsLoader.apply(true, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
processBulkUpdate(bulkUpdate, entry.getValue(), HolderType.GROUP);
}
});
}
}
@Override
public Set<UUID> getUniqueUsers() throws IOException {
return this.usersLoader.getNode().getChildrenMap().keySet().stream()
.map(Object::toString)
.map(Uuids::fromString)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@Override
public <N extends Node> List<NodeEntry<UUID, N>> searchUserNodes(ConstraintNodeMatcher<N> constraint) throws Exception {
List<NodeEntry<UUID, N>> held = new ArrayList<>();
this.usersLoader.apply(false, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
try {
UUID holder = UUID.fromString(entry.getKey().toString());
ConfigurationNode object = entry.getValue();
Set<Node> 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<String> 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 <N extends Node> List<NodeEntry<String, N>> searchGroupNodes(ConstraintNodeMatcher<N> constraint) throws Exception {
List<NodeEntry<String, N>> held = new ArrayList<>();
this.groupsLoader.apply(false, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
try {
String holder = entry.getKey().toString();
ConfigurationNode object = entry.getValue();
Set<Node> 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<String> 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);
}
}

View File

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

View File

@ -59,44 +59,6 @@ public class FileUuidCache {
// the lookup map
private final LookupMap lookupMap = new LookupMap();
private static final class LookupMap extends ConcurrentHashMap<UUID, String> {
private final SetMultimap<String, UUID> 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<UUID> 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<UUID, String> 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<UUID, String> {
private final SetMultimap<String, UUID> 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<UUID, String> 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<UUID> lookupUuid(String name) {
return this.reverse.get(name.toLowerCase());
}
}

View File

@ -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<Path> fileExtensionFilter;
private Path usersDirectory;
private Path groupsDirectory;
private Path tracksDirectory;
private final Map<StorageLocation, FileGroup> 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<StorageLocation, FileGroup> 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<Path> s = Files.list(getDirectory(StorageLocation.USER))) {
try (Stream<Path> 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<Path> s = Files.list(getDirectory(StorageLocation.GROUP))) {
try (Stream<Path> 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<UUID> getUniqueUsers() throws IOException {
try (Stream<Path> stream = Files.list(this.usersDirectory)) {
try (Stream<Path> 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 <N extends Node> List<NodeEntry<UUID, N>> searchUserNodes(ConstraintNodeMatcher<N> constraint) throws Exception {
public <N extends Node> List<NodeEntry<UUID, N>> searchUserNodes(ConstraintNodeMatcher<N> constraint) throws IOException {
List<NodeEntry<UUID, N>> held = new ArrayList<>();
try (Stream<Path> stream = Files.list(getDirectory(StorageLocation.USER))) {
try (Stream<Path> 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<Node> 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<String> groups;
try (Stream<Path> stream = Files.list(this.groupsDirectory)) {
try (Stream<Path> 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 <N extends Node> List<NodeEntry<String, N>> searchGroupNodes(ConstraintNodeMatcher<N> constraint) throws Exception {
public <N extends Node> List<NodeEntry<String, N>> searchGroupNodes(ConstraintNodeMatcher<N> constraint) throws IOException {
List<NodeEntry<String, N>> held = new ArrayList<>();
try (Stream<Path> stream = Files.list(getDirectory(StorageLocation.GROUP))) {
try (Stream<Path> 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<Node> 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<String> tracks;
try (Stream<Path> stream = Files.list(this.tracksDirectory)) {
try (Stream<Path> 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()))

View File

@ -27,6 +27,6 @@ package me.lucko.luckperms.common.storage.implementation.file;
public enum StorageLocation {
USER, GROUP, TRACK
USERS, GROUPS, TRACKS
}

View File

@ -30,6 +30,7 @@ import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.NonNull;
@SuppressWarnings("deprecation")
public final class NodeEntry<H extends Comparable<H>, N extends Node> implements HeldNode<H> {
public static <H extends Comparable<H>, N extends Node> NodeEntry<H, N> of(H holder, N node) {

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -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;

View File

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

View File

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

View File

@ -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) {

View File

@ -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<String> arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(args);
public void execute(Invocation invocation) {
Sender wrapped = this.plugin.getSenderFactory().wrap(invocation.source());
List<String> arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(invocation.arguments());
executeCommand(wrapped, "lpv", arguments);
}
@Override
public List<String> suggest(@NonNull CommandSource source, @NonNull String[] args) {
Sender wrapped = this.plugin.getSenderFactory().wrap(source);
List<String> arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(args);
public List<String> suggest(Invocation invocation) {
Sender wrapped = this.plugin.getSenderFactory().wrap(invocation.source());
List<String> 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<String> 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<String> suggest(Invocation invocation) {
return this.delegate.suggest(invocation);
}
}
}

View File

@ -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;
}
}
}