Implement the option to combine yaml/json/hocon storage files into one

This commit is contained in:
Luck 2018-04-20 21:59:32 +01:00
parent 4e87489dc1
commit 328353d053
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
37 changed files with 1248 additions and 703 deletions

View File

@ -36,15 +36,13 @@ import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* Bootstrap plugin for LuckPerms running on Bukkit.
*/
@ -197,8 +195,8 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap
}
@Override
public File getDataDirectory() {
return getDataFolder();
public Path getDataDirectory() {
return getDataFolder().toPath().toAbsolutePath();
}
@Override

View File

@ -35,15 +35,13 @@ import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* Bootstrap plugin for LuckPerms running on BungeeCord.
*/
@ -157,8 +155,8 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap {
}
@Override
public File getDataDirectory() {
return getDataFolder();
public Path getDataDirectory() {
return getDataFolder().toPath().toAbsolutePath();
}
@Override

View File

@ -36,7 +36,6 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.utils.Predicates;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -57,16 +56,14 @@ public class ExportCommand extends SingleCommand {
return CommandResult.STATE_ERROR;
}
File f = new File(plugin.getBootstrap().getDataDirectory(), args.get(0));
if (f.exists()) {
Message.LOG_EXPORT_ALREADY_EXISTS.send(sender, f.getAbsolutePath());
Path path = plugin.getBootstrap().getDataDirectory().resolve(args.get(0));
if (Files.exists(path)) {
Message.LOG_EXPORT_ALREADY_EXISTS.send(sender, path.toString());
return CommandResult.INVALID_ARGS;
}
Path path = f.toPath();
try {
f.createNewFile();
Files.createFile(path);
} catch (IOException e) {
Message.LOG_EXPORT_FAILURE.send(sender);
e.printStackTrace();
@ -74,7 +71,7 @@ public class ExportCommand extends SingleCommand {
}
if (!Files.isWritable(path)) {
Message.LOG_EXPORT_NOT_WRITABLE.send(sender, f.getAbsolutePath());
Message.LOG_EXPORT_NOT_WRITABLE.send(sender, path.toString());
return CommandResult.FAILURE;
}

View File

@ -36,7 +36,6 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.utils.Predicates;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -58,16 +57,14 @@ public class ImportCommand extends SingleCommand {
return CommandResult.STATE_ERROR;
}
File f = new File(plugin.getBootstrap().getDataDirectory(), args.get(0));
if (!f.exists()) {
Message.IMPORT_LOG_DOESNT_EXIST.send(sender, f.getAbsolutePath());
Path path = plugin.getBootstrap().getDataDirectory().resolve(args.get(0));
if (!Files.exists(path)) {
Message.IMPORT_LOG_DOESNT_EXIST.send(sender, path.toString());
return CommandResult.INVALID_ARGS;
}
Path path = f.toPath();
if (!Files.isReadable(path)) {
Message.IMPORT_LOG_NOT_READABLE.send(sender, f.getAbsolutePath());
Message.IMPORT_LOG_NOT_READABLE.send(sender, path.toString());
return CommandResult.FAILURE;
}

View File

@ -34,10 +34,10 @@ import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* A wrapper for the 'contexts.json' file.
@ -53,19 +53,23 @@ public class ContextsFile {
}
public void load() {
File file = new File(this.configuration.getPlugin().getBootstrap().getDataDirectory(), "contexts.json");
File oldFile = new File(this.configuration.getPlugin().getBootstrap().getDataDirectory(), "static-contexts.json");
if (oldFile.exists()) {
oldFile.renameTo(file);
Path file = this.configuration.getPlugin().getBootstrap().getDataDirectory().resolve("contexts.json");
Path oldFile = this.configuration.getPlugin().getBootstrap().getDataDirectory().resolve("static-contexts.json");
if (Files.exists(oldFile)) {
try {
Files.move(oldFile, file);
} catch (IOException e) {
e.printStackTrace();
}
}
if (!file.exists()) {
if (!Files.exists(file)) {
save();
return;
}
boolean save = false;
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
JsonObject data = new Gson().fromJson(reader, JsonObject.class);
if (data.has("context")) {
@ -91,10 +95,9 @@ public class ContextsFile {
}
public void save() {
File file = new File(this.configuration.getPlugin().getBootstrap().getDataDirectory(), "contexts.json");
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
Path file = this.configuration.getPlugin().getBootstrap().getDataDirectory().resolve("contexts.json");
try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
JsonObject data = new JsonObject();
data.add("static-contexts", ContextSetJsonSerializer.serializeContextSet(this.staticContexts));
data.add("default-contexts", ContextSetJsonSerializer.serializeContextSet(this.defaultContexts));

View File

@ -34,11 +34,12 @@ import me.lucko.luckperms.common.dependencies.relocation.RelocationHandler;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageType;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@ -57,7 +58,7 @@ public class DependencyManager {
private final LuckPermsPlugin plugin;
private final MessageDigest digest;
private final DependencyRegistry registry;
private final EnumMap<Dependency, File> loaded = new EnumMap<>(Dependency.class);
private final EnumMap<Dependency, Path> loaded = new EnumMap<>(Dependency.class);
private final Map<ImmutableSet<Dependency>, IsolatedClassLoader> loaders = new HashMap<>();
private RelocationHandler relocationHandler = null;
@ -78,12 +79,13 @@ public class DependencyManager {
return this.relocationHandler;
}
private File getSaveDirectory() {
File saveDirectory = new File(this.plugin.getBootstrap().getDataDirectory(), "lib");
if (!(saveDirectory.exists() || saveDirectory.mkdirs())) {
throw new RuntimeException("Unable to create lib dir - " + saveDirectory.getPath());
private Path getSaveDirectory() {
Path saveDirectory = this.plugin.getBootstrap().getDataDirectory().resolve("lib");
try {
Files.createDirectories(saveDirectory);
} catch (IOException e) {
throw new RuntimeException("Unable to create lib directory", e);
}
return saveDirectory;
}
@ -106,7 +108,7 @@ public class DependencyManager {
.map(this.loaded::get)
.map(file -> {
try {
return file.toURI().toURL();
return file.toUri().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
@ -124,7 +126,7 @@ public class DependencyManager {
}
public void loadDependencies(Set<Dependency> dependencies) {
File saveDirectory = getSaveDirectory();
Path saveDirectory = getSaveDirectory();
// create a list of file sources
List<Source> sources = new ArrayList<>();
@ -136,7 +138,7 @@ public class DependencyManager {
}
try {
File file = downloadDependency(saveDirectory, dependency);
Path file = downloadDependency(saveDirectory, dependency);
sources.add(new Source(dependency, file));
} catch (Throwable e) {
this.plugin.getLogger().severe("Exception whilst downloading dependency " + dependency.name());
@ -156,11 +158,11 @@ public class DependencyManager {
continue;
}
File input = source.file;
File output = new File(input.getParentFile(), "remapped-" + input.getName());
Path input = source.file;
Path output = input.getParent().resolve("remapped-" + input.getFileName().toString());
// if the remapped file exists already, just use that.
if (output.exists()) {
if (Files.exists(output)) {
remappedJars.add(new Source(source.dependency, output));
continue;
}
@ -169,7 +171,7 @@ public class DependencyManager {
RelocationHandler relocationHandler = getRelocationHandler();
// attempt to remap the jar.
this.plugin.getLogger().info("Attempting to apply relocations to " + input.getName() + "...");
this.plugin.getLogger().info("Attempting to apply relocations to " + input.getFileName().toString() + "...");
relocationHandler.remap(input, output, relocations);
remappedJars.add(new Source(source.dependency, output));
@ -190,18 +192,18 @@ public class DependencyManager {
this.plugin.getBootstrap().getPluginClassLoader().loadJar(source.file);
this.loaded.put(source.dependency, source.file);
} catch (Throwable e) {
this.plugin.getLogger().severe("Failed to load dependency jar '" + source.file.getName() + "'.");
this.plugin.getLogger().severe("Failed to load dependency jar '" + source.file.getFileName().toString() + "'.");
e.printStackTrace();
}
}
}
private File downloadDependency(File saveDirectory, Dependency dependency) throws Exception {
private Path downloadDependency(Path saveDirectory, Dependency dependency) throws Exception {
String fileName = dependency.name().toLowerCase() + "-" + dependency.getVersion() + ".jar";
File file = new File(saveDirectory, fileName);
Path file = saveDirectory.resolve(fileName);
// if the file already exists, don't attempt to re-download it.
if (file.exists()) {
if (Files.exists(file)) {
return file;
}
@ -226,11 +228,11 @@ public class DependencyManager {
}
// if the checksum matches, save the content to disk
Files.write(file.toPath(), bytes);
Files.write(file, bytes);
}
// ensure the file saved correctly
if (!file.exists()) {
if (!Files.exists(file)) {
throw new IllegalStateException("File not present. - " + file.toString());
} else {
return file;
@ -239,9 +241,9 @@ public class DependencyManager {
private static final class Source {
private final Dependency dependency;
private final File file;
private final Path file;
private Source(Dependency dependency, File file) {
private Source(Dependency dependency, Path file) {
this.dependency = dependency;
this.file = file;
}

View File

@ -47,9 +47,12 @@ public class DependencyRegistry {
));
private static final Map<StorageType, List<Dependency>> STORAGE_DEPENDENCIES = ImmutableMap.<StorageType, List<Dependency>>builder()
.put(StorageType.JSON, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_GSON))
.put(StorageType.YAML, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_YAML))
.put(StorageType.JSON, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_GSON))
.put(StorageType.HOCON, ImmutableList.of(Dependency.HOCON_CONFIG, Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_HOCON))
.put(StorageType.YAML_COMBINED, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_YAML))
.put(StorageType.JSON_COMBINED, ImmutableList.of(Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_GSON))
.put(StorageType.HOCON_COMBINED, ImmutableList.of(Dependency.HOCON_CONFIG, Dependency.CONFIGURATE_CORE, Dependency.CONFIGURATE_HOCON))
.put(StorageType.MONGODB, ImmutableList.of(Dependency.MONGODB_DRIVER))
.put(StorageType.MARIADB, ImmutableList.of(Dependency.MARIADB_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))
.put(StorageType.MYSQL, ImmutableList.of(Dependency.MYSQL_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, Dependency.HIKARI))

View File

@ -25,8 +25,8 @@
package me.lucko.luckperms.common.dependencies.classloader;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
/**
* Represents the plugins classloader
@ -35,6 +35,6 @@ public interface PluginClassLoader {
void loadJar(URL url);
void loadJar(File file);
void loadJar(Path file);
}

View File

@ -25,12 +25,12 @@
package me.lucko.luckperms.common.dependencies.classloader;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
public class ReflectionClassLoader implements PluginClassLoader {
private static final Method ADD_URL_METHOD;
@ -65,9 +65,9 @@ public class ReflectionClassLoader implements PluginClassLoader {
}
@Override
public void loadJar(File file) {
public void loadJar(Path file) {
try {
loadJar(file.toURI().toURL());
loadJar(file.toUri().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}

View File

@ -32,6 +32,7 @@ import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
@ -70,14 +71,14 @@ public class RelocationHandler {
}
}
public void remap(File input, File output, List<Relocation> relocations) throws Exception {
public void remap(Path input, Path output, List<Relocation> relocations) throws Exception {
Map<String, String> mappings = new HashMap<>();
for (Relocation relocation : relocations) {
mappings.put(relocation.getPattern(), relocation.getRelocatedPattern());
}
// create and invoke a new relocator
Object relocator = this.jarRelocatorConstructor.newInstance(input, output, mappings);
Object relocator = this.jarRelocatorConstructor.newInstance(input.toFile(), output.toFile(), mappings);
this.jarRelocatorRunMethod.invoke(relocator);
}
}

View File

@ -30,7 +30,7 @@ import me.lucko.luckperms.common.locale.command.CommandSpecData;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.io.File;
import java.nio.file.Path;
/**
* Manages translations
@ -43,7 +43,7 @@ public interface LocaleManager {
* @param plugin the plugin to log to
* @param file the file to load from
*/
void tryLoad(LuckPermsPlugin plugin, File file);
void tryLoad(LuckPermsPlugin plugin, Path file);
/**
* Loads a locale file
@ -51,7 +51,7 @@ public interface LocaleManager {
* @param file the file to load from
* @throws Exception if the process fails
*/
void loadFromFile(File file) throws Exception;
void loadFromFile(Path file) throws Exception;
/**
* Gets the size of loaded translations

View File

@ -35,9 +35,9 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.yaml.snakeyaml.Yaml;
import java.io.BufferedReader;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
public class SimpleLocaleManager implements LocaleManager {
@ -46,8 +46,8 @@ public class SimpleLocaleManager implements LocaleManager {
private Map<CommandSpec, CommandSpecData> commands = ImmutableMap.of();
@Override
public void tryLoad(LuckPermsPlugin plugin, File file) {
if (file.exists()) {
public void tryLoad(LuckPermsPlugin plugin, Path file) {
if (Files.exists(file)) {
plugin.getLogger().info("Found lang.yml - loading messages...");
try {
loadFromFile(file);
@ -59,8 +59,8 @@ public class SimpleLocaleManager implements LocaleManager {
@Override
@SuppressWarnings("unchecked")
public void loadFromFile(File file) throws Exception {
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
public void loadFromFile(Path file) throws Exception {
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
ImmutableMap.Builder<Message, String> messages = ImmutableMap.builder();
ImmutableMap.Builder<CommandSpec, CommandSpecData> commands = ImmutableMap.builder();

View File

@ -59,7 +59,7 @@ import me.lucko.luckperms.common.tasks.UpdateTask;
import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.Set;
@ -114,7 +114,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// load locale
this.localeManager = new SimpleLocaleManager();
this.localeManager.tryLoad(this, new File(getBootstrap().getConfigDirectory(), "lang.yml"));
this.localeManager.tryLoad(this, getBootstrap().getConfigDirectory().resolve("lang.yml"));
// now the configuration is loaded, we can create a storage factory and load initial dependencies
StorageFactory storageFactory = new StorageFactory(this);
@ -127,8 +127,11 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// initialise the storage
// first, setup the file watcher, if enabled
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
this.fileWatcher = new FileWatcher(this);
getBootstrap().getScheduler().asyncRepeating(this.fileWatcher, 30L);
try {
this.fileWatcher = new FileWatcher(this, getBootstrap().getDataDirectory());
} catch (IOException e) {
e.printStackTrace();
}
}
// initialise storage

View File

@ -29,8 +29,8 @@ import me.lucko.luckperms.api.platform.PlatformType;
import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
import me.lucko.luckperms.common.plugin.SchedulerAdapter;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@ -132,14 +132,14 @@ public interface LuckPermsBootstrap {
*
* @return the platforms data folder
*/
File getDataDirectory();
Path getDataDirectory();
/**
* Gets the plugins configuration directory
*
* @return the config directory
*/
default File getConfigDirectory() {
default Path getConfigDirectory() {
return getDataDirectory();
}

View File

@ -32,9 +32,11 @@ import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.dao.AbstractDao;
import me.lucko.luckperms.common.storage.dao.SplitStorageDao;
import me.lucko.luckperms.common.storage.dao.file.HoconDao;
import me.lucko.luckperms.common.storage.dao.file.JsonDao;
import me.lucko.luckperms.common.storage.dao.file.YamlDao;
import me.lucko.luckperms.common.storage.dao.file.CombinedConfigurateDao;
import me.lucko.luckperms.common.storage.dao.file.SeparatedConfigurateDao;
import me.lucko.luckperms.common.storage.dao.file.loader.HoconLoader;
import me.lucko.luckperms.common.storage.dao.file.loader.JsonLoader;
import me.lucko.luckperms.common.storage.dao.file.loader.YamlLoader;
import me.lucko.luckperms.common.storage.dao.mongodb.MongoDao;
import me.lucko.luckperms.common.storage.dao.sql.SqlDao;
import me.lucko.luckperms.common.storage.dao.sql.connection.file.H2ConnectionFactory;
@ -45,7 +47,6 @@ import me.lucko.luckperms.common.storage.dao.sql.connection.hikari.PostgreConnec
import me.lucko.luckperms.common.storage.provider.StorageProviders;
import me.lucko.luckperms.common.utils.ImmutableCollectors;
import java.io.File;
import java.util.Map;
import java.util.Set;
@ -139,13 +140,13 @@ public class StorageFactory {
case SQLITE:
return new SqlDao(
this.plugin,
new SQLiteConnectionFactory(this.plugin, new File(this.plugin.getBootstrap().getDataDirectory(), "luckperms-sqlite.db")),
new SQLiteConnectionFactory(this.plugin, this.plugin.getBootstrap().getDataDirectory().resolve("luckperms-sqlite.db")),
this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX)
);
case H2:
return new SqlDao(
this.plugin,
new H2ConnectionFactory(this.plugin, new File(this.plugin.getBootstrap().getDataDirectory(), "luckperms-h2")),
new H2ConnectionFactory(this.plugin, this.plugin.getBootstrap().getDataDirectory().resolve("luckperms-h2")),
this.plugin.getConfiguration().get(ConfigKeys.SQL_TABLE_PREFIX)
);
case POSTGRESQL:
@ -162,11 +163,19 @@ public class StorageFactory {
this.plugin.getConfiguration().get(ConfigKeys.MONGODB_CONNECTION_URI)
);
case YAML:
return new YamlDao(this.plugin, "yaml-storage");
return new SeparatedConfigurateDao(this.plugin, new YamlLoader(), "YAML", ".yml", "yaml-storage");
case JSON:
return new SeparatedConfigurateDao(this.plugin, new JsonLoader(), "JSON", ".json", "json-storage");
case HOCON:
return new HoconDao(this.plugin, "hocon-storage");
return new SeparatedConfigurateDao(this.plugin, new HoconLoader(), "HOCON", ".conf", "hocon-storage");
case YAML_COMBINED:
return new CombinedConfigurateDao(this.plugin, new YamlLoader(), "YAML Combined", ".yml", "yaml-storage");
case JSON_COMBINED:
return new CombinedConfigurateDao(this.plugin, new JsonLoader(), "JSON Combined", ".json", "json-storage");
case HOCON_COMBINED:
return new CombinedConfigurateDao(this.plugin, new HoconLoader(), "HOCON Combined", ".conf", "hocon-storage");
default:
return new JsonDao(this.plugin, "json-storage");
throw new RuntimeException("Unknown method: " + method);
}
}
}

View File

@ -31,15 +31,25 @@ import java.util.List;
public enum StorageType {
JSON("JSON", "json", "flatfile"),
// Config file based
YAML("YAML", "yaml", "yml"),
JSON("JSON", "json", "flatfile"),
HOCON("HOCON", "hocon"),
YAML_COMBINED("YAML Combined", "yaml-combined"),
JSON_COMBINED("JSON Combined", "json-combined"),
HOCON_COMBINED("HOCON Combined", "hocon-combined"),
// Remote databases
MONGODB("MongoDB", "mongodb"),
MARIADB("MariaDB", "mariadb"),
MYSQL("MySQL", "mysql"),
POSTGRESQL("PostgreSQL", "postgresql"),
// Local databases
SQLITE("SQLite", "sqlite"),
H2("H2", "h2"),
// Custom
CUSTOM("Custom", "custom");
private final String name;

View File

@ -62,7 +62,7 @@ public abstract class AbstractDao {
return this.name;
}
public abstract void init();
public abstract void init() throws Exception;
public abstract void shutdown();

View File

@ -68,7 +68,7 @@ public class SplitStorageDao extends AbstractDao {
}
}
if (failed) {
throw new RuntimeException("One of the backing failed to init");
throw new RuntimeException("One of the backings failed to init");
}
}

View File

@ -25,44 +25,40 @@
package me.lucko.luckperms.common.storage.dao.file;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.contexts.ContextSetConfigurateSerializer;
import me.lucko.luckperms.common.managers.group.GroupManager;
import me.lucko.luckperms.common.managers.track.TrackManager;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.MetaType;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.node.NodeHeldPermission;
import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier;
import me.lucko.luckperms.common.storage.PlayerSaveResult;
import me.lucko.luckperms.common.storage.dao.AbstractDao;
import me.lucko.luckperms.common.storage.dao.file.loader.ConfigurateLoader;
import me.lucko.luckperms.common.storage.dao.file.loader.JsonLoader;
import me.lucko.luckperms.common.utils.ImmutableCollectors;
import me.lucko.luckperms.common.utils.Uuids;
import me.lucko.luckperms.common.utils.MoreFiles;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.SimpleConfigurationNode;
import ninja.leaping.configurate.Types;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -76,143 +72,70 @@ import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
public abstract class ConfigurateDao extends AbstractDao {
/**
* Abstract implementation using configurate {@link ConfigurationNode}s to serialize and deserialize
* data.
*/
public abstract class AbstractConfigurateDao extends AbstractDao {
// the loader responsible for i/o
protected final ConfigurateLoader loader;
// the name of the data directory
private final String dataDirectoryName;
// the data directory
protected Path dataDirectory;
// the uuid cache instance
private final FileUuidCache uuidCache = new FileUuidCache();
// the action logger instance
private final FileActionLogger actionLogger = new FileActionLogger();
private final String fileExtension;
private final String dataFolderName;
// the file used to store uuid data
private Path uuidDataFile;
// the file used to store logged actions
private Path actionLogFile;
private File uuidDataFile;
private File actionLogFile;
private File usersDirectory;
private File groupsDirectory;
private File tracksDirectory;
protected ConfigurateDao(LuckPermsPlugin plugin, String name, String fileExtension, String dataFolderName) {
protected AbstractConfigurateDao(LuckPermsPlugin plugin, ConfigurateLoader loader, String name, String dataDirectoryName) {
super(plugin, name);
this.fileExtension = fileExtension;
this.dataFolderName = dataFolderName;
this.loader = loader;
this.dataDirectoryName = dataDirectoryName;
}
public String getFileExtension() {
return this.fileExtension;
}
/**
* Reads a configuration node from the given location
*
* @param location the location
* @param name the name of the object
* @return the node
* @throws IOException if an io error occurs
*/
protected abstract ConfigurationNode readFile(StorageLocation location, String name) throws IOException;
protected abstract ConfigurationLoader<? extends ConfigurationNode> loader(Path path);
/**
* Saves a configuration node to the given location
*
* @param location the location
* @param name the name of the object
* @param node the node
* @throws IOException if an io error occurs
*/
protected abstract void saveFile(StorageLocation location, String name, ConfigurationNode node) throws IOException;
private ConfigurationNode readFile(StorageLocation location, String name) throws IOException {
File file = new File(getDirectory(location), name + this.fileExtension);
registerFileAction(location, file);
return readFile(file);
}
private ConfigurationNode readFile(File file) throws IOException {
if (!file.exists()) {
return null;
}
return loader(file.toPath()).load();
}
private void saveFile(StorageLocation location, String name, ConfigurationNode node) throws IOException {
File file = new File(getDirectory(location), name + this.fileExtension);
registerFileAction(location, file);
saveFile(file, node);
}
private void saveFile(File file, ConfigurationNode node) throws IOException {
if (node == null) {
if (file.exists()) {
file.delete();
}
return;
}
loader(file.toPath()).save(node);
}
private File getDirectory(StorageLocation location) {
switch (location) {
case USER:
return this.usersDirectory;
case GROUP:
return this.groupsDirectory;
case TRACK:
return this.tracksDirectory;
default:
throw new RuntimeException();
}
}
private FilenameFilter getFileTypeFilter() {
return (dir, name) -> name.endsWith(this.fileExtension);
}
private Exception reportException(String file, Exception ex) throws Exception {
// 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.printStackTrace();
throw ex;
}
private void registerFileAction(StorageLocation type, File file) {
this.plugin.getFileWatcher().ifPresent(fileWatcher -> fileWatcher.registerChange(type, file.getName()));
throw Throwables.propagate(ex);
}
@Override
public void init() {
try {
File data = FileUtils.mkdirs(new File(this.plugin.getBootstrap().getDataDirectory(), this.dataFolderName));
public void init() throws IOException {
this.dataDirectory = this.plugin.getBootstrap().getDataDirectory().resolve(this.dataDirectoryName);
Files.createDirectories(this.dataDirectory);
this.usersDirectory = FileUtils.mkdir(new File(data, "users"));
this.groupsDirectory = FileUtils.mkdir(new File(data, "groups"));
this.tracksDirectory = FileUtils.mkdir(new File(data, "tracks"));
this.uuidDataFile = FileUtils.createNewFile(new File(data, "uuidcache.txt"));
this.actionLogFile = FileUtils.createNewFile(new File(data, "actions.log"));
// Listen for file changes.
this.plugin.getFileWatcher().ifPresent(watcher -> {
watcher.subscribe("user", this.usersDirectory.toPath(), s -> {
if (!s.endsWith(this.fileExtension)) {
return;
}
String user = s.substring(0, s.length() - this.fileExtension.length());
UUID uuid = Uuids.parseNullable(user);
if (uuid == null) {
return;
}
User u = this.plugin.getUserManager().getIfLoaded(uuid);
if (u != null) {
this.plugin.getLogger().info("[FileWatcher] Refreshing user " + u.getFriendlyName());
this.plugin.getStorage().loadUser(uuid, null);
}
});
watcher.subscribe("group", this.groupsDirectory.toPath(), s -> {
if (!s.endsWith(this.fileExtension)) {
return;
}
String groupName = s.substring(0, s.length() - this.fileExtension.length());
this.plugin.getLogger().info("[FileWatcher] Refreshing group " + groupName);
this.plugin.getUpdateTaskBuffer().request();
});
watcher.subscribe("track", this.tracksDirectory.toPath(), s -> {
if (!s.endsWith(this.fileExtension)) {
return;
}
String trackName = s.substring(0, s.length() - this.fileExtension.length());
this.plugin.getLogger().info("[FileWatcher] Refreshing track " + trackName);
this.plugin.getStorage().loadAllTracks();
});
});
} catch (IOException e) {
e.printStackTrace();
return;
}
this.uuidDataFile = MoreFiles.createFileIfNotExists(this.dataDirectory.resolve("uuidcache.txt"));
this.actionLogFile = MoreFiles.createFileIfNotExists(this.dataDirectory.resolve("actions.log"));
this.uuidCache.load(this.uuidDataFile);
this.actionLogger.init(this.actionLogFile);
@ -235,70 +158,30 @@ public abstract class ConfigurateDao extends AbstractDao {
return Log.empty();
}
@Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception {
if (bulkUpdate.getDataType().isIncludingUsers()) {
File[] files = getDirectory(StorageLocation.USER).listFiles(getFileTypeFilter());
if (files == null) {
throw new IllegalStateException("Users directory matched no files.");
}
protected ConfigurationNode processBulkUpdate(BulkUpdate bulkUpdate, ConfigurationNode node) {
Set<NodeModel> nodes = readNodes(node);
Set<NodeModel> results = nodes.stream()
.map(bulkUpdate::apply)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (File file : files) {
try {
registerFileAction(StorageLocation.USER, file);
ConfigurationNode object = readFile(file);
Set<NodeModel> nodes = readNodes(object);
Set<NodeModel> results = nodes.stream()
.map(bulkUpdate::apply)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (!nodes.equals(results)) {
writeNodes(object, results);
saveFile(file, object);
}
} catch (Exception e) {
throw reportException(file.getName(), e);
}
}
if (nodes.equals(results)) {
return null;
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
File[] files = getDirectory(StorageLocation.GROUP).listFiles(getFileTypeFilter());
if (files == null) {
throw new IllegalStateException("Groups directory matched no files.");
}
for (File file : files) {
try {
registerFileAction(StorageLocation.GROUP, file);
ConfigurationNode object = readFile(file);
Set<NodeModel> nodes = readNodes(object);
Set<NodeModel> results = nodes.stream()
.map(bulkUpdate::apply)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (!nodes.equals(results)) {
writeNodes(object, results);
saveFile(file, object);
}
} catch (Exception e) {
throw reportException(file.getName(), e);
}
}
}
writeNodes(node, results);
return node;
}
@Override
public User loadUser(UUID uuid, String username) throws Exception {
public User loadUser(UUID uuid, String username) {
User user = this.plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username));
user.getIoLock().lock();
try {
ConfigurationNode object = readFile(StorageLocation.USER, uuid.toString());
if (object != null) {
String name = object.getNode("name").getString();
user.getPrimaryGroup().setStoredValue(object.getNode(this instanceof JsonDao ? "primaryGroup" : "primary-group").getString());
user.getPrimaryGroup().setStoredValue(object.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").getString());
Set<Node> nodes = readNodes(object).stream().map(NodeModel::toNode).collect(Collectors.toSet());
user.setEnduringNodes(nodes);
@ -329,16 +212,18 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public void saveUser(User user) throws Exception {
public void saveUser(User user) {
user.getIoLock().lock();
try {
if (!this.plugin.getUserManager().shouldSave(user)) {
saveFile(StorageLocation.USER, user.getUuid().toString(), null);
} else {
ConfigurationNode data = SimpleConfigurationNode.root();
data.getNode("uuid").setValue(user.getUuid().toString());
if (this instanceof SeparatedConfigurateDao) {
data.getNode("uuid").setValue(user.getUuid().toString());
}
data.getNode("name").setValue(user.getName().orElse("null"));
data.getNode(this instanceof JsonDao ? "primaryGroup" : "primary-group").setValue(user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME));
data.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").setValue(user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME));
Set<NodeModel> nodes = user.getEnduringNodes().values().stream().map(NodeModel::fromNode).collect(Collectors.toCollection(LinkedHashSet::new));
writeNodes(data, nodes);
@ -353,44 +238,7 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public Set<UUID> getUniqueUsers() {
String[] fileNames = this.usersDirectory.list(getFileTypeFilter());
if (fileNames == null) return null;
return Arrays.stream(fileNames)
.map(s -> s.substring(0, s.length() - this.fileExtension.length()))
.map(UUID::fromString)
.collect(Collectors.toSet());
}
@Override
public List<HeldPermission<UUID>> getUsersWithPermission(String permission) throws Exception {
List<HeldPermission<UUID>> held = new ArrayList<>();
File[] files = getDirectory(StorageLocation.USER).listFiles(getFileTypeFilter());
if (files == null) {
throw new IllegalStateException("Users directory matched no files.");
}
for (File file : files) {
try {
registerFileAction(StorageLocation.USER, file);
ConfigurationNode object = readFile(file);
UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - this.fileExtension.length()));
Set<NodeModel> nodes = readNodes(object);
for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) {
continue;
}
held.add(NodeHeldPermission.of(holder, e));
}
} catch (Exception e) {
throw reportException(file.getName(), e);
}
}
return held;
}
@Override
public Group createAndLoadGroup(String name) throws Exception {
public Group createAndLoadGroup(String name) {
Group group = this.plugin.getGroupManager().getOrMake(name);
group.getIoLock().lock();
try {
@ -401,7 +249,9 @@ public abstract class ConfigurateDao extends AbstractDao {
group.setEnduringNodes(nodes);
} else {
ConfigurationNode data = SimpleConfigurationNode.root();
data.getNode("name").setValue(group.getName());
if (this instanceof SeparatedConfigurateDao) {
data.getNode("name").setValue(group.getName());
}
Set<NodeModel> nodes = group.getEnduringNodes().values().stream().map(NodeModel::fromNode).collect(Collectors.toCollection(LinkedHashSet::new));
writeNodes(data, nodes);
@ -418,7 +268,7 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public Optional<Group> loadGroup(String name) throws Exception {
public Optional<Group> loadGroup(String name) {
Group group = this.plugin.getGroupManager().getIfLoaded(name);
if (group != null) {
group.getIoLock().lock();
@ -452,41 +302,13 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public void loadAllGroups() throws IOException {
String[] fileNames = this.groupsDirectory.list(getFileTypeFilter());
if (fileNames == null) {
throw new IOException("Not a directory");
}
List<String> groups = Arrays.stream(fileNames)
.map(s -> s.substring(0, s.length() - this.fileExtension.length()))
.collect(Collectors.toList());
boolean success = true;
for (String g : groups) {
try {
loadGroup(g);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
}
if (!success) {
throw new RuntimeException("Exception occurred whilst loading a group");
}
GroupManager<?> gm = this.plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
}
@Override
public void saveGroup(Group group) throws Exception {
public void saveGroup(Group group) {
group.getIoLock().lock();
try {
ConfigurationNode data = SimpleConfigurationNode.root();
data.getNode("name").setValue(group.getName());
if (this instanceof SeparatedConfigurateDao) {
data.getNode("name").setValue(group.getName());
}
Set<NodeModel> nodes = group.getEnduringNodes().values().stream().map(NodeModel::fromNode).collect(Collectors.toCollection(LinkedHashSet::new));
writeNodes(data, nodes);
@ -500,15 +322,10 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public void deleteGroup(Group group) throws Exception {
public void deleteGroup(Group group) {
group.getIoLock().lock();
try {
File groupFile = new File(this.groupsDirectory, group.getName() + this.fileExtension);
registerFileAction(StorageLocation.GROUP, groupFile);
if (groupFile.exists()) {
groupFile.delete();
}
saveFile(StorageLocation.GROUP, group.getName(), null);
} catch (Exception e) {
throw reportException(group.getName(), e);
} finally {
@ -518,34 +335,7 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public List<HeldPermission<String>> getGroupsWithPermission(String permission) throws Exception {
List<HeldPermission<String>> held = new ArrayList<>();
File[] files = getDirectory(StorageLocation.GROUP).listFiles(getFileTypeFilter());
if (files == null) {
throw new IllegalStateException("Groups directory matched no files.");
}
for (File file : files) {
try {
registerFileAction(StorageLocation.GROUP, file);
ConfigurationNode object = readFile(file);
String holder = file.getName().substring(0, file.getName().length() - this.fileExtension.length());
Set<NodeModel> nodes = readNodes(object);
for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) {
continue;
}
held.add(NodeHeldPermission.of(holder, e));
}
} catch (Exception e) {
throw reportException(file.getName(), e);
}
}
return held;
}
@Override
public Track createAndLoadTrack(String name) throws Exception {
public Track createAndLoadTrack(String name) {
Track track = this.plugin.getTrackManager().getOrMake(name);
track.getIoLock().lock();
try {
@ -559,7 +349,9 @@ public abstract class ConfigurateDao extends AbstractDao {
track.setGroups(groups);
} else {
ConfigurationNode data = SimpleConfigurationNode.root();
data.getNode("name").setValue(name);
if (this instanceof SeparatedConfigurateDao) {
data.getNode("name").setValue(name);
}
data.getNode("groups").setValue(track.getGroups());
saveFile(StorageLocation.TRACK, name, data);
}
@ -573,7 +365,7 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public Optional<Track> loadTrack(String name) throws Exception {
public Optional<Track> loadTrack(String name) {
Track track = this.plugin.getTrackManager().getIfLoaded(name);
if (track != null) {
track.getIoLock().lock();
@ -608,41 +400,13 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public void loadAllTracks() throws IOException {
String[] fileNames = this.tracksDirectory.list(getFileTypeFilter());
if (fileNames == null) {
throw new IOException("Not a directory");
}
List<String> tracks = Arrays.stream(fileNames)
.map(s -> s.substring(0, s.length() - this.fileExtension.length()))
.collect(Collectors.toList());
boolean success = true;
for (String t : tracks) {
try {
loadTrack(t);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
}
if (!success) {
throw new RuntimeException("Exception occurred whilst loading a track");
}
TrackManager<?> tm = this.plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
}
@Override
public void saveTrack(Track track) throws Exception {
public void saveTrack(Track track) {
track.getIoLock().lock();
try {
ConfigurationNode data = SimpleConfigurationNode.root();
data.getNode("name").setValue(track.getName());
if (this instanceof SeparatedConfigurateDao) {
data.getNode("name").setValue(track.getName());
}
data.getNode("groups").setValue(track.getGroups());
saveFile(StorageLocation.TRACK, track.getName(), data);
} catch (Exception e) {
@ -653,15 +417,10 @@ public abstract class ConfigurateDao extends AbstractDao {
}
@Override
public void deleteTrack(Track track) throws Exception {
public void deleteTrack(Track track) {
track.getIoLock().lock();
try {
File trackFile = new File(this.tracksDirectory, track.getName() + this.fileExtension);
registerFileAction(StorageLocation.TRACK, trackFile);
if (trackFile.exists()) {
trackFile.delete();
}
saveFile(StorageLocation.TRACK, track.getName(), null);
} catch (Exception e) {
throw reportException(track.getName(), e);
} finally {
@ -736,7 +495,7 @@ public abstract class ConfigurateDao extends AbstractDao {
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue());
}
private static Set<NodeModel> readNodes(ConfigurationNode data) {
protected static Set<NodeModel> readNodes(ConfigurationNode data) {
Set<NodeModel> nodes = new HashSet<>();
if (data.getNode("permissions").hasListChildren()) {
@ -924,5 +683,4 @@ public abstract class ConfigurateDao extends AbstractDao {
to.getNode("meta").setValue(metaSection);
}
}
}

View File

@ -0,0 +1,372 @@
/*
* 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.dao.file;
import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.managers.group.GroupManager;
import me.lucko.luckperms.common.managers.track.TrackManager;
import me.lucko.luckperms.common.node.NodeHeldPermission;
import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.dao.file.loader.ConfigurateLoader;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class CombinedConfigurateDao extends AbstractConfigurateDao {
private final String fileExtension;
private Path usersFile;
private Path groupsFile;
private Path tracksFile;
private CachedLoader usersLoader;
private CachedLoader groupsLoader;
private CachedLoader tracksLoader;
private final class CachedLoader {
private final Path path;
private final ConfigurationLoader<? extends ConfigurationNode> loader;
private ConfigurationNode node = null;
private final ReentrantLock lock = new ReentrantLock();
private CachedLoader(Path path) {
this.path = path;
this.loader = CombinedConfigurateDao.super.loader.loader(path);
reload();
}
private void recordChange() {
if (CombinedConfigurateDao.this.watcher != null) {
CombinedConfigurateDao.this.watcher.recordChange(this.path.getFileName().toString());
}
}
public ConfigurationNode getNode() throws IOException {
this.lock.lock();
try {
if (this.node == null) {
this.node = this.loader.load();
}
return this.node;
} finally {
this.lock.unlock();
}
}
public void apply(Consumer<ConfigurationNode> action) throws IOException {
apply(false, false, action);
}
public void apply(boolean save, boolean reload, Consumer<ConfigurationNode> action) throws IOException {
this.lock.lock();
try {
if (this.node == null || reload) {
recordChange();
this.node = this.loader.load();
}
action.accept(this.node);
if (save) {
recordChange();
this.loader.save(this.node);
}
} finally {
this.lock.unlock();
}
}
public void save() throws IOException {
this.lock.lock();
try {
recordChange();
this.loader.save(this.node);
} finally {
this.lock.unlock();
}
}
public void reload() {
this.lock.lock();
try {
this.node = null;
try {
recordChange();
this.node = this.loader.load();
} catch (IOException e) {
e.printStackTrace();
}
} finally {
this.lock.unlock();
}
}
}
private FileWatcher.WatchedLocation watcher = null;
/**
* Creates a new configurate dao
*
* @param plugin the plugin instance
* @param name the name of this dao
* @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 CombinedConfigurateDao(LuckPermsPlugin plugin, ConfigurateLoader loader, String name, String fileExtension, String dataFolderName) {
super(plugin, loader, name, dataFolderName);
this.fileExtension = fileExtension;
}
@Override
protected ConfigurationNode readFile(StorageLocation location, String name) throws IOException {
ConfigurationNode root = getStorageLoader(location).getNode();
ConfigurationNode ret = root.getNode(name);
return ret.isVirtual() ? null : ret;
}
@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.getUpdateTaskBuffer().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.getUpdateTaskBuffer().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());
}
});
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
this.groupsLoader.apply(true, true, root -> {
for (Map.Entry<Object, ? extends ConfigurationNode> entry : root.getChildrenMap().entrySet()) {
processBulkUpdate(bulkUpdate, entry.getValue());
}
});
}
}
@Override
public Set<UUID> getUniqueUsers() throws IOException {
return this.usersLoader.getNode().getChildrenMap().keySet().stream()
.map(Object::toString)
.map(UUID::fromString)
.collect(Collectors.toSet());
}
@Override
public List<HeldPermission<UUID>> getUsersWithPermission(String permission) throws Exception {
List<HeldPermission<UUID>> 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<NodeModel> nodes = readNodes(object);
for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) {
continue;
}
held.add(NodeHeldPermission.of(holder, e));
}
} 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()));
});
boolean success = true;
for (String g : groups) {
try {
loadGroup(g);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
}
if (!success) {
throw new RuntimeException("Exception occurred whilst loading a group");
}
GroupManager<?> gm = this.plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
}
@Override
public List<HeldPermission<String>> getGroupsWithPermission(String permission) throws Exception {
List<HeldPermission<String>> 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<NodeModel> nodes = readNodes(object);
for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) {
continue;
}
held.add(NodeHeldPermission.of(holder, e));
}
} 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()));
});
boolean success = true;
for (String t : tracks) {
try {
loadTrack(t);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
}
if (!success) {
throw new RuntimeException("Exception occurred whilst loading a track");
}
TrackManager<?> tm = this.plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
}
}

View File

@ -28,7 +28,7 @@ package me.lucko.luckperms.common.storage.dao.file;
import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.common.command.CommandManager;
import java.io.File;
import java.nio.file.Path;
import java.util.Date;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
@ -40,9 +40,9 @@ public class FileActionLogger {
private static final String LOG_FORMAT = "%s(%s): [%s] %s(%s) --> %s";
private final Logger actionLogger = Logger.getLogger("luckperms_actions");
public void init(File file) {
public void init(Path file) {
try {
FileHandler fh = new FileHandler(file.getAbsolutePath(), 0, 1, true);
FileHandler fh = new FileHandler(file.toString(), 0, 1, true);
fh.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {

View File

@ -35,10 +35,10 @@ import me.lucko.luckperms.common.utils.Uuids;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
@ -182,12 +182,12 @@ public class FileUuidCache {
}
}
public void load(File file) {
if (!file.exists()) {
public void load(Path file) {
if (!Files.exists(file)) {
return;
}
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
String entry;
while ((entry = reader.readLine()) != null) {
entry = entry.trim();
@ -201,8 +201,8 @@ public class FileUuidCache {
}
}
public void save(File file) {
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
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()) {

View File

@ -26,6 +26,7 @@
package me.lucko.luckperms.common.storage.dao.file;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Iterators;
import java.io.IOException;
import java.nio.file.Path;
@ -38,54 +39,35 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class FileWatcher implements Runnable {
public class FileWatcher {
private static final WatchEvent.Kind[] KINDS = new WatchEvent.Kind[]{
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY
};
private final LuckPermsPlugin plugin;
private final Path basePath;
private final Map<Path, WatchedLocation> watchedLocations;
private final Map<String, WatchedLocation> keyMap;
private final Map<String, Long> internalChanges;
private WatchService watchService = null;
// the watchservice instance
private final WatchService watchService;
public FileWatcher(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.keyMap = Collections.synchronizedMap(new HashMap<>());
this.internalChanges = Collections.synchronizedMap(new HashMap<>());
try {
this.watchService = plugin.getBootstrap().getDataDirectory().toPath().getFileSystem().newWatchService();
} catch (IOException e) {
e.printStackTrace();
}
public FileWatcher(LuckPermsPlugin plugin, Path basePath) throws IOException {
this.watchedLocations = Collections.synchronizedMap(new HashMap<>());
this.basePath = basePath;
this.watchService = basePath.getFileSystem().newWatchService();
plugin.getBootstrap().getScheduler().asyncLater(this::initLocations, 25L);
plugin.getBootstrap().getScheduler().asyncRepeating(this::tick, 10L);
}
public void subscribe(String id, Path path, Consumer<String> consumer) {
if (this.watchService == null) {
return;
}
// Register with a delay to ignore changes made at startup
this.plugin.getBootstrap().getScheduler().asyncLater(() -> {
this.keyMap.computeIfAbsent(id, s -> {
WatchKey key;
try {
key = path.register(this.watchService, KINDS);
} catch (IOException e) {
throw new RuntimeException(e);
}
return new WatchedLocation(path, key, consumer);
});
}, 40L);
}
public void registerChange(StorageLocation location, String fileName) {
this.internalChanges.put(location.name().toLowerCase() + "/" + fileName, System.currentTimeMillis());
public WatchedLocation getWatcher(Path path) {
Path relativePath = this.basePath.relativize(path);
return this.watchedLocations.computeIfAbsent(relativePath, p -> new WatchedLocation(this, p));
}
public void close() {
@ -100,21 +82,78 @@ public class FileWatcher implements Runnable {
}
}
@Override
public void run() {
long expireTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(4);
// was either processed last time, or recently modified by the system.
this.internalChanges.values().removeIf(lastChange -> lastChange < expireTime);
private void initLocations() {
for (WatchedLocation loc : this.watchedLocations.values()) {
try {
loc.setup();
} catch (IOException e) {
e.printStackTrace();
}
}
}
List<String> expired = new ArrayList<>();
private void tick() {
List<Path> expired = new ArrayList<>();
for (Map.Entry<Path, WatchedLocation> ent : this.watchedLocations.entrySet()) {
boolean valid = ent.getValue().tick();
if (!valid) {
new RuntimeException("WatchKey no longer valid: " + ent.getKey().toString()).printStackTrace();
expired.add(ent.getKey());
}
}
expired.forEach(this.watchedLocations::remove);
}
for (Map.Entry<String, WatchedLocation> ent : this.keyMap.entrySet()) {
String id = ent.getKey();
Path path = ent.getValue().getPath();
WatchKey key = ent.getValue().getKey();
/**
* Encapsulates a "watcher" in a specific directory.
*/
public static final class WatchedLocation {
// the parent watcher
private final FileWatcher watcher;
List<WatchEvent<?>> watchEvents = key.pollEvents();
// the relative path to the directory being watched
private final Path relativePath;
// the absolute path to the directory being watched
private final Path absolutePath;
// the times of recent changes
private final Map<String, Long> lastChange = Collections.synchronizedMap(new HashMap<>());
// if the key is registered
private boolean ready = false;
// the watch key
private WatchKey key = null;
// the callback functions
private final List<Consumer<Path>> callbacks = new CopyOnWriteArrayList<>();
private WatchedLocation(FileWatcher watcher, Path relativePath) {
this.watcher = watcher;
this.relativePath = relativePath;
this.absolutePath = this.watcher.basePath.resolve(this.relativePath);
}
private synchronized void setup() throws IOException {
if (this.ready) {
return;
}
this.key = this.absolutePath.register(this.watcher.watchService, KINDS);
this.ready = true;
}
private boolean tick() {
if (!this.ready) {
return true;
}
// remove old change entries.
long expireTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(4);
this.lastChange.values().removeIf(lastChange -> lastChange < expireTime);
List<WatchEvent<?>> watchEvents = this.key.pollEvents();
for (WatchEvent<?> event : watchEvents) {
Path context = (Path) event.context();
@ -122,7 +161,6 @@ public class FileWatcher implements Runnable {
continue;
}
Path file = path.resolve(context);
String fileName = context.toString();
// ignore temporary changes
@ -130,50 +168,26 @@ public class FileWatcher implements Runnable {
continue;
}
if (this.internalChanges.containsKey(id + "/" + fileName)) {
// This file was modified by the system.
// ignore changes already registered to the system
if (this.lastChange.containsKey(fileName)) {
continue;
}
this.lastChange.put(fileName, System.currentTimeMillis());
this.internalChanges.put(id + "/" + fileName, System.currentTimeMillis());
this.plugin.getLogger().info("[FileWatcher] Detected change in file: " + file.toString());
// Process the change
ent.getValue().getFileConsumer().accept(fileName);
// process the change
Iterators.iterate(this.callbacks, cb -> cb.accept(context));
}
boolean valid = key.reset();
if (!valid) {
new RuntimeException("WatchKey no longer valid: " + key.toString()).printStackTrace();
expired.add(id);
}
// reset the watch key.
return this.key.reset();
}
expired.forEach(this.keyMap::remove);
}
private static class WatchedLocation {
private final Path path;
private final WatchKey key;
private final Consumer<String> fileConsumer;
public WatchedLocation(Path path, WatchKey key, Consumer<String> fileConsumer) {
this.path = path;
this.key = key;
this.fileConsumer = fileConsumer;
public void recordChange(String fileName) {
this.lastChange.put(fileName, System.currentTimeMillis());
}
public Path getPath() {
return this.path;
}
public WatchKey getKey() {
return this.key;
}
public Consumer<String> getFileConsumer() {
return this.fileConsumer;
public void addListener(Consumer<Path> updateConsumer) {
this.callbacks.add(updateConsumer);
}
}

View File

@ -0,0 +1,366 @@
/*
* 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.dao.file;
import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.managers.group.GroupManager;
import me.lucko.luckperms.common.managers.track.TrackManager;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeHeldPermission;
import me.lucko.luckperms.common.node.NodeModel;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.dao.file.loader.ConfigurateLoader;
import me.lucko.luckperms.common.utils.Uuids;
import ninja.leaping.configurate.ConfigurationNode;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SeparatedConfigurateDao extends AbstractConfigurateDao {
private final String fileExtension;
private Path usersDirectory;
private Path groupsDirectory;
private Path tracksDirectory;
private FileWatcher.WatchedLocation userWatcher = null;
private FileWatcher.WatchedLocation groupWatcher = null;
private FileWatcher.WatchedLocation trackWatcher = null;
/**
* Creates a new configurate dao
*
* @param plugin the plugin instance
* @param name the name of this dao
* @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 SeparatedConfigurateDao(LuckPermsPlugin plugin, ConfigurateLoader loader, String name, String fileExtension, String dataFolderName) {
super(plugin, loader, name, dataFolderName);
this.fileExtension = fileExtension;
}
@Override
protected ConfigurationNode readFile(StorageLocation location, String name) throws IOException {
Path file = getDirectory(location).resolve(name + this.fileExtension);
registerFileAction(location, file);
return readFile(file);
}
private ConfigurationNode readFile(Path file) throws IOException {
if (!Files.exists(file)) {
return null;
}
return this.loader.loader(file).load();
}
@Override
protected void saveFile(StorageLocation location, String name, ConfigurationNode node) throws IOException {
Path file = getDirectory(location).resolve(name + this.fileExtension);
registerFileAction(location, file);
saveFile(file, node);
}
private void saveFile(Path file, ConfigurationNode node) throws IOException {
if (node == null) {
Files.deleteIfExists(file);
return;
}
this.loader.loader(file).save(node);
}
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();
}
}
private Predicate<Path> getFileTypeFilter() {
return path -> path.getFileName().toString().endsWith(this.fileExtension);
}
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();
}
}
@Override
public void init() throws IOException {
super.init();
this.usersDirectory = Files.createDirectory(super.dataDirectory.resolve("users"));
this.groupsDirectory = Files.createDirectory(super.dataDirectory.resolve("groups"));
this.tracksDirectory = Files.createDirectory(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)) {
return;
}
String user = s.substring(0, s.length() - this.fileExtension.length());
UUID uuid = Uuids.parseNullable(user);
if (uuid == null) {
return;
}
User u = this.plugin.getUserManager().getIfLoaded(uuid);
if (u != null) {
this.plugin.getLogger().info("[FileWatcher] Detected change in user file for " + u.getFriendlyName() + " - reloading...");
this.plugin.getStorage().loadUser(uuid, null);
}
});
this.groupWatcher = watcher.getWatcher(this.groupsDirectory);
this.groupWatcher.addListener(path -> {
String s = path.getFileName().toString();
if (!s.endsWith(this.fileExtension)) {
return;
}
String groupName = s.substring(0, s.length() - this.fileExtension.length());
this.plugin.getLogger().info("[FileWatcher] Detected change in group file for " + groupName + " - reloading...");
this.plugin.getUpdateTaskBuffer().request();
});
this.trackWatcher = watcher.getWatcher(this.tracksDirectory);
this.trackWatcher.addListener(path -> {
String s = path.getFileName().toString();
if (!s.endsWith(this.fileExtension)) {
return;
}
String trackName = s.substring(0, s.length() - this.fileExtension.length());
this.plugin.getLogger().info("[FileWatcher] Detected change in track file for " + trackName + " - reloading...");
this.plugin.getStorage().loadAllTracks();
});
}
}
@Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) throws Exception {
if (bulkUpdate.getDataType().isIncludingUsers()) {
try (Stream<Path> s = Files.list(getDirectory(StorageLocation.USER))) {
s.filter(getFileTypeFilter()).forEach(file -> {
try {
registerFileAction(StorageLocation.USER, file);
ConfigurationNode object = readFile(file);
ConfigurationNode results = processBulkUpdate(bulkUpdate, object);
if (results != null) {
saveFile(file, object);
}
} catch (Exception e) {
throw reportException(file.getFileName().toString(), e);
}
});
}
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
try (Stream<Path> s = Files.list(getDirectory(StorageLocation.GROUP))) {
s.filter(getFileTypeFilter()).forEach(file -> {
try {
registerFileAction(StorageLocation.GROUP, file);
ConfigurationNode object = readFile(file);
ConfigurationNode results = processBulkUpdate(bulkUpdate, object);
if (results != null) {
saveFile(file, object);
}
} catch (Exception e) {
throw reportException(file.getFileName().toString(), e);
}
});
}
}
}
@Override
public Set<UUID> getUniqueUsers() throws IOException {
try (Stream<Path> stream = Files.list(this.usersDirectory)) {
return stream.filter(getFileTypeFilter())
.map(p -> p.getFileName().toString())
.map(s -> s.substring(0, s.length() - this.fileExtension.length()))
.map(UUID::fromString)
.collect(Collectors.toSet());
}
}
@Override
public List<HeldPermission<UUID>> getUsersWithPermission(String permission) throws Exception {
List<HeldPermission<UUID>> held = new ArrayList<>();
try (Stream<Path> stream = Files.list(getDirectory(StorageLocation.USER))) {
stream.filter(getFileTypeFilter())
.forEach(file -> {
String fileName = file.getFileName().toString();
try {
registerFileAction(StorageLocation.USER, file);
ConfigurationNode object = readFile(file);
UUID holder = UUID.fromString(fileName.substring(0, fileName.length() - this.fileExtension.length()));
Set<NodeModel> nodes = readNodes(object);
for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) {
continue;
}
held.add(NodeHeldPermission.of(holder, e));
}
} catch (Exception e) {
throw reportException(file.getFileName().toString(), e);
}
});
}
return held;
}
@Override
public void loadAllGroups() throws IOException {
List<String> groups;
try (Stream<Path> stream = Files.list(this.groupsDirectory)) {
groups = stream.filter(getFileTypeFilter())
.map(p -> p.getFileName().toString())
.map(s -> s.substring(0, s.length() - this.fileExtension.length()))
.collect(Collectors.toList());
}
boolean success = true;
for (String g : groups) {
try {
loadGroup(g);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
}
if (!success) {
throw new RuntimeException("Exception occurred whilst loading a group");
}
GroupManager<?> gm = this.plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
}
@Override
public List<HeldPermission<String>> getGroupsWithPermission(String permission) throws Exception {
List<HeldPermission<String>> held = new ArrayList<>();
try (Stream<Path> stream = Files.list(getDirectory(StorageLocation.USER))) {
stream.filter(getFileTypeFilter())
.forEach(file -> {
String fileName = file.getFileName().toString();
try {
registerFileAction(StorageLocation.GROUP, file);
ConfigurationNode object = readFile(file);
String holder = fileName.substring(0, fileName.length() - this.fileExtension.length());
Set<NodeModel> nodes = readNodes(object);
for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) {
continue;
}
held.add(NodeHeldPermission.of(holder, e));
}
} catch (Exception e) {
throw reportException(file.getFileName().toString(), e);
}
});
}
return held;
}
@Override
public void loadAllTracks() throws IOException {
List<String> tracks;
try (Stream<Path> stream = Files.list(this.tracksDirectory)) {
tracks = stream.filter(getFileTypeFilter())
.map(p -> p.getFileName().toString())
.map(s -> s.substring(0, s.length() - this.fileExtension.length()))
.collect(Collectors.toList());
}
boolean success = true;
for (String t : tracks) {
try {
loadTrack(t);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
}
if (!success) {
throw new RuntimeException("Exception occurred whilst loading a track");
}
TrackManager<?> tm = this.plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.dao.file.loader;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import java.nio.file.Path;
/**
* Wraps an object which can produce configurate {@link ConfigurationLoader}s.
*/
public interface ConfigurateLoader {
ConfigurationLoader<? extends ConfigurationNode> loader(Path path);
}

View File

@ -23,9 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.dao.file;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
package me.lucko.luckperms.common.storage.dao.file.loader;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
@ -35,13 +33,10 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class HoconDao extends ConfigurateDao {
public HoconDao(LuckPermsPlugin plugin, String dataFolderName) {
super(plugin, "HOCON", ".conf", dataFolderName);
}
public class HoconLoader implements ConfigurateLoader {
@Override
protected ConfigurationLoader<? extends ConfigurationNode> loader(Path path) {
public ConfigurationLoader<? extends ConfigurationNode> loader(Path path) {
return HoconConfigurationLoader.builder()
.setSource(() -> Files.newBufferedReader(path, StandardCharsets.UTF_8))
.setSink(() -> Files.newBufferedWriter(path, StandardCharsets.UTF_8))

View File

@ -23,9 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.dao.file;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
package me.lucko.luckperms.common.storage.dao.file.loader;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
@ -35,13 +33,10 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class JsonDao extends ConfigurateDao {
public JsonDao(LuckPermsPlugin plugin, String dataFolderName) {
super(plugin, "JSON", ".json", dataFolderName);
}
public class JsonLoader implements ConfigurateLoader {
@Override
protected ConfigurationLoader<? extends ConfigurationNode> loader(Path path) {
public ConfigurationLoader<? extends ConfigurationNode> loader(Path path) {
return GsonConfigurationLoader.builder()
.setIndent(2)
.setSource(() -> Files.newBufferedReader(path, StandardCharsets.UTF_8))

View File

@ -23,9 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.dao.file;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
package me.lucko.luckperms.common.storage.dao.file.loader;
import org.yaml.snakeyaml.DumperOptions;
@ -37,13 +35,10 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class YamlDao extends ConfigurateDao {
public YamlDao(LuckPermsPlugin plugin, String dataFolderName) {
super(plugin, "YAML", ".yml", dataFolderName);
}
public class YamlLoader implements ConfigurateLoader {
@Override
protected ConfigurationLoader<? extends ConfigurationNode> loader(Path path) {
public ConfigurationLoader<? extends ConfigurationNode> loader(Path path) {
return YAMLConfigurationLoader.builder()
.setFlowStyle(DumperOptions.FlowStyle.BLOCK)
.setIndent(2)

View File

@ -151,63 +151,56 @@ public class SqlDao extends AbstractDao {
}
@Override
public void init() {
try {
this.provider.init();
public void init() throws Exception {
this.provider.init();
// Init tables
if (!tableExists(this.prefix.apply("{prefix}user_permissions"))) {
String schemaFileName = "me/lucko/luckperms/schema/" + this.provider.getName().toLowerCase() + ".sql";
try (InputStream is = this.plugin.getBootstrap().getResourceStream(schemaFileName)) {
if (is == null) {
throw new Exception("Couldn't locate schema file for " + this.provider.getName());
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
try (Connection connection = this.provider.getConnection()) {
try (Statement s = connection.createStatement()) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("--") || line.startsWith("#")) continue;
sb.append(line);
// check for end of declaration
if (line.endsWith(";")) {
sb.deleteCharAt(sb.length() - 1);
String result = this.prefix.apply(sb.toString().trim());
if (!result.isEmpty()) s.addBatch(result);
// reset
sb = new StringBuilder();
}
}
s.executeBatch();
}
}
}
// Init tables
if (!tableExists(this.prefix.apply("{prefix}user_permissions"))) {
String schemaFileName = "me/lucko/luckperms/schema/" + this.provider.getName().toLowerCase() + ".sql";
try (InputStream is = this.plugin.getBootstrap().getResourceStream(schemaFileName)) {
if (is == null) {
throw new Exception("Couldn't locate schema file for " + this.provider.getName());
}
}
// migrations
try {
if (!(this.provider instanceof SQLiteConnectionFactory) && !(this.provider instanceof PostgreConnectionFactory)) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
try (Connection connection = this.provider.getConnection()) {
try (Statement s = connection.createStatement()) {
s.execute(this.prefix.apply("ALTER TABLE {prefix}actions MODIFY COLUMN actor_name VARCHAR(100)"));
s.execute(this.prefix.apply("ALTER TABLE {prefix}actions MODIFY COLUMN action VARCHAR(300)"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("--") || line.startsWith("#")) continue;
sb.append(line);
// check for end of declaration
if (line.endsWith(";")) {
sb.deleteCharAt(sb.length() - 1);
String result = this.prefix.apply(sb.toString().trim());
if (!result.isEmpty()) s.addBatch(result);
// reset
sb = new StringBuilder();
}
}
s.executeBatch();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// migrations
try {
if (!(this.provider instanceof SQLiteConnectionFactory) && !(this.provider instanceof PostgreConnectionFactory)) {
try (Connection connection = this.provider.getConnection()) {
try (Statement s = connection.createStatement()) {
s.execute(this.prefix.apply("ALTER TABLE {prefix}actions MODIFY COLUMN actor_name VARCHAR(100)"));
s.execute(this.prefix.apply("ALTER TABLE {prefix}actions MODIFY COLUMN action VARCHAR(300)"));
}
}
}
} catch (Exception e) {
this.plugin.getLogger().severe("Error occurred whilst initialising the database.");
e.printStackTrace();
}
}

View File

@ -27,7 +27,9 @@ package me.lucko.luckperms.common.storage.dao.sql.connection.file;
import me.lucko.luckperms.common.storage.dao.sql.connection.AbstractConnectionFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.util.LinkedHashMap;
import java.util.Map;
@ -35,9 +37,9 @@ import java.util.Map;
abstract class FlatfileConnectionFactory extends AbstractConnectionFactory {
protected static final DecimalFormat DF = new DecimalFormat("#.##");
protected final File file;
protected final Path file;
FlatfileConnectionFactory(String name, File file) {
FlatfileConnectionFactory(String name, Path file) {
super(name);
this.file = file;
}
@ -47,7 +49,7 @@ abstract class FlatfileConnectionFactory extends AbstractConnectionFactory {
}
protected File getWriteFile() {
protected Path getWriteFile() {
return this.file;
}
@ -55,9 +57,16 @@ abstract class FlatfileConnectionFactory extends AbstractConnectionFactory {
public Map<String, String> getMeta() {
Map<String, String> ret = new LinkedHashMap<>();
File databaseFile = getWriteFile();
if (databaseFile.exists()) {
double size = databaseFile.length() / 1048576D;
Path databaseFile = getWriteFile();
if (Files.exists(databaseFile)) {
long length;
try {
length = Files.size(databaseFile);
} catch (IOException e) {
length = 0;
}
double size = length / 1048576D;
ret.put("File Size", DF.format(size) + "MB");
} else {
ret.put("File Size", "0MB");

View File

@ -29,9 +29,11 @@ import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
@ -45,13 +47,17 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory {
// the active connection
private NonClosableConnection connection;
public H2ConnectionFactory(LuckPermsPlugin plugin, File file) {
public H2ConnectionFactory(LuckPermsPlugin plugin, Path file) {
super("H2", file);
// backwards compat
File data = new File(file.getParent(), "luckperms.db.mv.db");
if (data.exists()) {
data.renameTo(new File(file.getParent(), file.getName() + ".mv.db"));
Path data = file.getParent().resolve("luckperms.db.mv.db");
if (Files.exists(data)) {
try {
Files.move(data, getWriteFile());
} catch (IOException e) {
e.printStackTrace();
}
}
// setup the classloader
@ -68,7 +74,7 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory {
@Override
public synchronized Connection getConnection() throws SQLException {
if (this.connection == null || this.connection.isClosed()) {
Connection connection = this.driver.connect("jdbc:h2:" + this.file.getAbsolutePath(), new Properties());
Connection connection = this.driver.connect("jdbc:h2:" + this.file.toString(), new Properties());
if (connection != null) {
this.connection = NonClosableConnection.wrap(connection);
}
@ -89,8 +95,8 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory {
}
@Override
protected File getWriteFile() {
protected Path getWriteFile() {
// h2 appends this to the end of the database file
return new File(super.file.getParent(), super.file.getName() + ".mv.db");
return super.file.getParent().resolve(super.file.getFileName().toString() + ".mv.db");
}
}

View File

@ -29,9 +29,11 @@ import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.EnumSet;
@ -44,13 +46,17 @@ public class SQLiteConnectionFactory extends FlatfileConnectionFactory {
// the active connection
private NonClosableConnection connection;
public SQLiteConnectionFactory(LuckPermsPlugin plugin, File file) {
public SQLiteConnectionFactory(LuckPermsPlugin plugin, Path file) {
super("SQLite", file);
// backwards compat
File data = new File(file.getParent(), "luckperms.sqlite");
if (data.exists()) {
data.renameTo(file);
Path data = file.getParent().resolve("luckperms.sqlite");
if (Files.exists(data)) {
try {
Files.move(data, file);
} catch (IOException e) {
e.printStackTrace();
}
}
// setup the classloader
@ -79,7 +85,7 @@ public class SQLiteConnectionFactory extends FlatfileConnectionFactory {
@Override
public synchronized Connection getConnection() throws SQLException {
if (this.connection == null || this.connection.isClosed()) {
Connection connection = createConnection("jdbc:sqlite:" + this.file.getAbsolutePath());
Connection connection = createConnection("jdbc:sqlite:" + this.file.toString());
if (connection != null) {
this.connection = NonClosableConnection.wrap(connection);
}

View File

@ -23,43 +23,20 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.dao.file;
package me.lucko.luckperms.common.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class FileUtils {
public final class MoreFiles {
public static File mkdir(File file) throws IOException {
if (file.exists()) {
return file;
public static Path createFileIfNotExists(Path path) throws IOException {
if (!Files.exists(path)) {
Files.createFile(path);
}
if (!file.mkdir()) {
throw new IOException("Unable to create directory - " + file.getPath());
}
return file;
return path;
}
public static File mkdirs(File file) throws IOException {
if (file.exists()) {
return file;
}
if (!file.mkdirs()) {
throw new IOException("Unable to create directory - " + file.getPath());
}
return file;
}
public static File createNewFile(File file) throws IOException {
if (file.exists()) {
return file;
}
if (!file.createNewFile()) {
throw new IOException("Unable to create file - " + file.getPath());
}
return file;
}
private FileUtils() {}
private MoreFiles() {}
}

View File

@ -33,15 +33,13 @@ import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
import cn.nukkit.Player;
import cn.nukkit.plugin.PluginBase;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* Bootstrap plugin for LuckPerms running on Nukkit.
*/
@ -160,8 +158,8 @@ public class LPNukkitBootstrap extends PluginBase implements LuckPermsBootstrap
}
@Override
public File getDataDirectory() {
return getDataFolder();
public Path getDataDirectory() {
return getDataFolder().toPath().toAbsolutePath();
}
@Override

View File

@ -51,7 +51,6 @@ import org.spongepowered.api.scheduler.Scheduler;
import org.spongepowered.api.scheduler.SpongeExecutorService;
import org.spongepowered.api.scheduler.SynchronousExecutor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@ -61,8 +60,6 @@ import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* Bootstrap plugin for LuckPerms running on Sponge.
*/
@ -242,19 +239,19 @@ public class LPSpongeBootstrap implements LuckPermsBootstrap {
}
@Override
public File getDataDirectory() {
public Path getDataDirectory() {
Path dataDirectory = this.game.getGameDirectory().resolve("luckperms");
try {
Files.createDirectories(dataDirectory);
} catch (IOException e) {
e.printStackTrace();
}
return dataDirectory.toFile();
return dataDirectory.toAbsolutePath();
}
@Override
public File getConfigDirectory() {
return this.configDirectory.toFile();
public Path getConfigDirectory() {
return this.configDirectory.toAbsolutePath();
}
@Override

View File

@ -54,7 +54,6 @@ import org.spongepowered.api.service.permission.PermissionService;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.text.Text;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@ -110,7 +109,7 @@ public class LuckPermsService implements LPPermissionService {
this.permissionDescriptions = new ConcurrentHashMap<>();
// init subject storage
this.storage = new SubjectStorage(this, new File(plugin.getBootstrap().getDataDirectory(), "sponge-data"));
this.storage = new SubjectStorage(this, plugin.getBootstrap().getDataDirectory().resolve("sponge-data"));
// load defaults collection
this.defaultSubjects = new DefaultsCollection(this);

View File

@ -34,15 +34,16 @@ import me.lucko.luckperms.sponge.service.model.LPPermissionService;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Handles persisted Subject I/O and (de)serialization
@ -62,9 +63,9 @@ public class SubjectStorage {
/**
* The root directory used to store files
*/
private final File container;
private final Path container;
public SubjectStorage(LPPermissionService service, File container) {
public SubjectStorage(LPPermissionService service, Path container) {
this.service = service;
this.gson = new GsonBuilder().setPrettyPrinting().create();
this.container = container;
@ -72,7 +73,11 @@ public class SubjectStorage {
}
private void checkContainer() {
this.container.getParentFile().mkdirs();
try {
Files.createDirectories(this.container.getParent());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
@ -83,12 +88,14 @@ public class SubjectStorage {
public Set<String> getSavedCollections() {
checkContainer();
File[] dirs = this.container.listFiles(File::isDirectory);
if (dirs == null) {
return Collections.emptySet();
try (Stream<Path> s = Files.list(this.container)) {
return s.filter(p -> Files.isDirectory(p))
.map(p -> p.getFileName().toString())
.collect(Collectors.toSet());
} catch (IOException e) {
e.printStackTrace();
return ImmutableSet.of();
}
return ImmutableSet.copyOf(dirs).stream().map(File::getName).collect(Collectors.toSet());
}
/**
@ -98,14 +105,16 @@ public class SubjectStorage {
* @param subjectIdentifier the identifier of the subject
* @return a file
*/
private File resolveFile(String collectionIdentifier, String subjectIdentifier) {
private Path resolveFile(String collectionIdentifier, String subjectIdentifier) {
checkContainer();
File collection = new File(this.container, collectionIdentifier);
if (!collection.exists()) {
collection.mkdirs();
Path collection = this.container.resolve(collectionIdentifier);
try {
Files.createDirectories(collection);
} catch (IOException e) {
e.printStackTrace();
}
return new File(collection, subjectIdentifier + ".json");
return collection.resolve(subjectIdentifier + ".json");
}
/**
@ -115,7 +124,7 @@ public class SubjectStorage {
* @throws IOException if the write fails
*/
public void saveToFile(PersistedSubject subject) throws IOException {
File subjectFile = resolveFile(subject.getParentCollection().getIdentifier(), subject.getIdentifier());
Path subjectFile = resolveFile(subject.getParentCollection().getIdentifier(), subject.getIdentifier());
saveToFile(SubjectDataContainer.copyOf(subject.getSubjectData()), subjectFile);
}
@ -126,14 +135,9 @@ public class SubjectStorage {
* @param file the file
* @throws IOException if the write fails
*/
public void saveToFile(SubjectDataContainer container, File file) throws IOException {
file.getParentFile().mkdirs();
if (file.exists()) {
file.delete();
}
file.createNewFile();
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
public void saveToFile(SubjectDataContainer container, Path file) throws IOException {
Files.createDirectories(file.getParent());
try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
this.gson.toJson(container.serialize(), writer);
writer.flush();
}
@ -147,28 +151,27 @@ public class SubjectStorage {
*/
public Map<String, SubjectDataContainer> loadAllFromFile(String collectionIdentifier) {
checkContainer();
File collection = new File(this.container, collectionIdentifier);
if (!collection.exists()) {
Path collection = this.container.resolve(collectionIdentifier);
if (!Files.exists(collection)) {
return Collections.emptyMap();
}
String[] fileNames = collection.list((dir, name) -> name.endsWith(".json"));
if (fileNames == null) return Collections.emptyMap();
Map<String, SubjectDataContainer> holders = new HashMap<>();
for (String name : fileNames) {
File subjectFile = new File(collection, name);
try {
LoadedSubject s = loadFromFile(subjectFile);
if (s != null) {
holders.put(s.identifier, s.data);
}
} catch (IOException e) {
e.printStackTrace();
}
try (Stream<Path> s = Files.list(collection)){
s.filter(p -> p.getFileName().toString().endsWith(".json"))
.forEach(subjectFile -> {
try {
LoadedSubject sub = loadFromFile(subjectFile);
if (sub != null) {
holders.put(sub.identifier, sub.data);
}
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
return holders;
}
@ -182,12 +185,12 @@ public class SubjectStorage {
*/
public LoadedSubject loadFromFile(String collectionIdentifier, String subjectIdentifier) throws IOException {
checkContainer();
File collection = new File(this.container, collectionIdentifier);
if (!collection.exists()) {
Path collection = this.container.resolve(collectionIdentifier);
if (!Files.exists(collection)) {
return null;
}
File subject = new File(collection, subjectIdentifier + ".json");
Path subject = collection.resolve(subjectIdentifier + ".json");
return new LoadedSubject(subjectIdentifier, loadFromFile(subject).data);
}
@ -198,14 +201,15 @@ public class SubjectStorage {
* @return a loaded subject
* @throws IOException if the read fails
*/
public LoadedSubject loadFromFile(File file) throws IOException {
if (!file.exists()) {
public LoadedSubject loadFromFile(Path file) throws IOException {
if (!Files.exists(file)) {
return null;
}
String subjectName = file.getName().substring(0, file.getName().length() - ".json".length());
String fileName = file.getFileName().toString();
String subjectName = fileName.substring(0, fileName.length() - ".json".length());
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
JsonObject data = this.gson.fromJson(reader, JsonObject.class);
SubjectDataContainer model = SubjectDataContainer.derserialize(this.service, data);
return new LoadedSubject(subjectName, model);