mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2024-12-28 20:17:55 +01:00
Listen for changes in data files and automatically update
This commit is contained in:
parent
9dc2278083
commit
b9fc5c39ae
@ -71,6 +71,7 @@ import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
|
||||
import me.lucko.luckperms.common.tasks.UpdateTask;
|
||||
import me.lucko.luckperms.common.treeview.PermissionVault;
|
||||
import me.lucko.luckperms.common.utils.BufferedRequest;
|
||||
import me.lucko.luckperms.common.utils.FileWatcher;
|
||||
import me.lucko.luckperms.common.utils.LoggerImpl;
|
||||
|
||||
import org.bukkit.World;
|
||||
@ -106,6 +107,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
||||
private GroupManager groupManager;
|
||||
private TrackManager trackManager;
|
||||
private Storage storage;
|
||||
private FileWatcher fileWatcher = null;
|
||||
private InternalMessagingService messagingService = null;
|
||||
private UuidCache uuidCache;
|
||||
private BukkitListener listener;
|
||||
@ -168,6 +170,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
||||
listener = new BukkitListener(this);
|
||||
pm.registerEvents(listener, this);
|
||||
|
||||
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
|
||||
fileWatcher = new FileWatcher(this);
|
||||
getScheduler().doAsyncRepeating(fileWatcher, 30L);
|
||||
}
|
||||
|
||||
// initialise datastore
|
||||
storage = StorageFactory.getInstance(this, StorageType.H2);
|
||||
|
||||
@ -336,6 +343,10 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
||||
getLog().info("Closing datastore...");
|
||||
storage.shutdown();
|
||||
|
||||
if (fileWatcher != null) {
|
||||
fileWatcher.close();
|
||||
}
|
||||
|
||||
if (messagingService != null) {
|
||||
getLog().info("Closing messaging service...");
|
||||
messagingService.close();
|
||||
@ -363,6 +374,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
|
||||
groupManager = null;
|
||||
trackManager = null;
|
||||
storage = null;
|
||||
fileWatcher = null;
|
||||
messagingService = null;
|
||||
uuidCache = null;
|
||||
listener = null;
|
||||
|
@ -192,6 +192,12 @@ group-name-rewrite:
|
||||
# Fill out connection info below if you're using MySQL, PostgreSQL or MongoDB
|
||||
storage-method: h2
|
||||
|
||||
# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then schedule automatic
|
||||
# updates when changes are detected.
|
||||
#
|
||||
# If you don't want this to happen, set this option to false.
|
||||
watch-files: true
|
||||
|
||||
# This block enables support for split datastores.
|
||||
split-storage:
|
||||
enabled: false
|
||||
@ -217,9 +223,14 @@ data:
|
||||
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
|
||||
table_prefix: 'luckperms_'
|
||||
|
||||
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
|
||||
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
|
||||
sync-minutes: 3
|
||||
# This option controls how frequently LuckPerms will perform a sync task.
|
||||
# A sync task will refresh all data from the storage, and ensure that the most up-to-date data is being used by the plugin.
|
||||
#
|
||||
# This is disabled by default, as most users will not need it. However, if you're using a remote storage type
|
||||
# without a messaging service setup, you may wish to set this value to something like 3.
|
||||
#
|
||||
# Set to -1 to disable the task completely.
|
||||
sync-minutes: -1
|
||||
|
||||
# Settings for the messaging service
|
||||
#
|
||||
|
@ -64,6 +64,7 @@ import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
|
||||
import me.lucko.luckperms.common.tasks.UpdateTask;
|
||||
import me.lucko.luckperms.common.treeview.PermissionVault;
|
||||
import me.lucko.luckperms.common.utils.BufferedRequest;
|
||||
import me.lucko.luckperms.common.utils.FileWatcher;
|
||||
import me.lucko.luckperms.common.utils.LoggerImpl;
|
||||
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
@ -88,6 +89,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
|
||||
private GroupManager groupManager;
|
||||
private TrackManager trackManager;
|
||||
private Storage storage;
|
||||
private FileWatcher fileWatcher = null;
|
||||
private InternalMessagingService messagingService = null;
|
||||
private UuidCache uuidCache;
|
||||
private ApiProvider apiProvider;
|
||||
@ -122,6 +124,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
|
||||
// register events
|
||||
getProxy().getPluginManager().registerListener(this, new BungeeListener(this));
|
||||
|
||||
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
|
||||
fileWatcher = new FileWatcher(this);
|
||||
getScheduler().doAsyncRepeating(fileWatcher, 30L);
|
||||
}
|
||||
|
||||
// initialise datastore
|
||||
storage = StorageFactory.getInstance(this, StorageType.H2);
|
||||
|
||||
@ -226,6 +233,10 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
|
||||
getLog().info("Closing datastore...");
|
||||
storage.shutdown();
|
||||
|
||||
if (fileWatcher != null) {
|
||||
fileWatcher.close();
|
||||
}
|
||||
|
||||
if (messagingService != null) {
|
||||
getLog().info("Closing messaging service...");
|
||||
messagingService.close();
|
||||
|
@ -134,6 +134,12 @@ meta-formatting:
|
||||
# Fill out connection info below if you're using MySQL, PostgreSQL or MongoDB
|
||||
storage-method: h2
|
||||
|
||||
# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then schedule automatic
|
||||
# updates when changes are detected.
|
||||
#
|
||||
# If you don't want this to happen, set this option to false.
|
||||
watch-files: true
|
||||
|
||||
# This block enables support for split datastores.
|
||||
split-storage:
|
||||
enabled: false
|
||||
@ -159,9 +165,14 @@ data:
|
||||
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
|
||||
table_prefix: 'luckperms_'
|
||||
|
||||
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
|
||||
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
|
||||
sync-minutes: 3
|
||||
# This option controls how frequently LuckPerms will perform a sync task.
|
||||
# A sync task will refresh all data from the storage, and ensure that the most up-to-date data is being used by the plugin.
|
||||
#
|
||||
# This is disabled by default, as most users will not need it. However, if you're using a remote storage type
|
||||
# without a messaging service setup, you may wish to set this value to something like 3.
|
||||
#
|
||||
# Set to -1 to disable the task completely.
|
||||
sync-minutes: -1
|
||||
|
||||
# Settings for the messaging service
|
||||
#
|
||||
|
@ -51,7 +51,7 @@ import java.util.Map;
|
||||
public class ConfigKeys {
|
||||
|
||||
public static final ConfigKey<String> SERVER = StringKey.of("server", "global");
|
||||
public static final ConfigKey<Integer> SYNC_TIME = EnduringKey.wrap(IntegerKey.of("data.sync-minutes", 3));
|
||||
public static final ConfigKey<Integer> SYNC_TIME = EnduringKey.wrap(IntegerKey.of("data.sync-minutes", -1));
|
||||
public static final ConfigKey<String> DEFAULT_GROUP_NODE = StaticKey.of("group.default"); // constant since 2.6
|
||||
public static final ConfigKey<String> DEFAULT_GROUP_NAME = StaticKey.of("default"); // constant since 2.6
|
||||
public static final ConfigKey<Boolean> INCLUDING_GLOBAL_PERMS = BooleanKey.of("include-global", true);
|
||||
@ -140,6 +140,7 @@ public class ConfigKeys {
|
||||
}));
|
||||
public static final ConfigKey<String> SQL_TABLE_PREFIX = EnduringKey.wrap(StringKey.of("data.table_prefix", "luckperms_"));
|
||||
public static final ConfigKey<String> STORAGE_METHOD = EnduringKey.wrap(StringKey.of("storage-method", "h2"));
|
||||
public static final ConfigKey<Boolean> WATCH_FILES = BooleanKey.of("watch-files", true);
|
||||
public static final ConfigKey<Boolean> SPLIT_STORAGE = EnduringKey.wrap(BooleanKey.of("split-storage.enabled", false));
|
||||
public static final ConfigKey<Map<String, String>> SPLIT_STORAGE_OPTIONS = EnduringKey.wrap(AbstractKey.of(c -> {
|
||||
return ImmutableMap.<String, String>builder()
|
||||
|
@ -46,6 +46,7 @@ import me.lucko.luckperms.common.messaging.InternalMessagingService;
|
||||
import me.lucko.luckperms.common.storage.Storage;
|
||||
import me.lucko.luckperms.common.treeview.PermissionVault;
|
||||
import me.lucko.luckperms.common.utils.BufferedRequest;
|
||||
import me.lucko.luckperms.common.utils.FileWatcher;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@ -54,6 +55,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Main internal interface for LuckPerms plugins, providing the base for abstraction throughout the project.
|
||||
@ -216,6 +218,20 @@ public interface LuckPermsPlugin {
|
||||
*/
|
||||
String getServerVersion();
|
||||
|
||||
/**
|
||||
* Gets the file watcher running on the platform, or null if it's not enabled.
|
||||
*
|
||||
* @return the file watcher, or null
|
||||
*/
|
||||
FileWatcher getFileWatcher();
|
||||
|
||||
default void applyToFileWatcher(Consumer<FileWatcher> consumer) {
|
||||
FileWatcher fw = getFileWatcher();
|
||||
if (fw != null) {
|
||||
consumer.accept(fw);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugins main data storage directory
|
||||
*
|
||||
|
@ -23,7 +23,9 @@
|
||||
package me.lucko.luckperms.common.storage.backing;
|
||||
|
||||
import me.lucko.luckperms.api.LogEntry;
|
||||
import me.lucko.luckperms.common.commands.utils.Util;
|
||||
import me.lucko.luckperms.common.constants.Constants;
|
||||
import me.lucko.luckperms.common.core.model.User;
|
||||
import me.lucko.luckperms.common.data.Log;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
|
||||
@ -49,23 +51,27 @@ abstract class FlatfileBacking extends AbstractBacking {
|
||||
private static final String LOG_FORMAT = "%s(%s): [%s] %s(%s) --> %s";
|
||||
|
||||
private final Logger actionLogger = Logger.getLogger("lp_actions");
|
||||
private Map<String, String> uuidCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final File pluginDir;
|
||||
private final String fileExtension;
|
||||
|
||||
private File uuidData;
|
||||
private File actionLog;
|
||||
File usersDir;
|
||||
File groupsDir;
|
||||
File tracksDir;
|
||||
private Map<String, String> uuidCache = new ConcurrentHashMap<>();
|
||||
private File uuidData;
|
||||
private File actionLog;
|
||||
|
||||
FlatfileBacking(LuckPermsPlugin plugin, String name, File pluginDir) {
|
||||
FlatfileBacking(LuckPermsPlugin plugin, String name, File pluginDir, String fileExtension) {
|
||||
super(plugin, name);
|
||||
this.pluginDir = pluginDir;
|
||||
this.fileExtension = fileExtension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
makeFiles();
|
||||
setupFiles();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
@ -93,7 +99,7 @@ abstract class FlatfileBacking extends AbstractBacking {
|
||||
setAcceptingLogins(true);
|
||||
}
|
||||
|
||||
private void makeFiles() throws IOException {
|
||||
private void setupFiles() throws IOException {
|
||||
File data = new File(pluginDir, "data");
|
||||
data.mkdirs();
|
||||
|
||||
@ -111,6 +117,45 @@ abstract class FlatfileBacking extends AbstractBacking {
|
||||
|
||||
actionLog = new File(data, "actions.log");
|
||||
actionLog.createNewFile();
|
||||
|
||||
// Listen for file changes.
|
||||
plugin.applyToFileWatcher(watcher -> {
|
||||
watcher.subscribe("users", usersDir.toPath(), s -> {
|
||||
if (!s.endsWith(fileExtension)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String user = s.substring(0, s.length() - fileExtension.length());
|
||||
UUID uuid = Util.parseUuid(user);
|
||||
if (uuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
User u = plugin.getUserManager().get(uuid);
|
||||
if (u != null) {
|
||||
plugin.getLog().info("[FileWatcher] Refreshing user " + u.getName());
|
||||
plugin.getStorage().loadUser(uuid, "null");
|
||||
}
|
||||
});
|
||||
watcher.subscribe("groups", groupsDir.toPath(), s -> {
|
||||
if (!s.endsWith(fileExtension)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String groupName = s.substring(0, s.length() - fileExtension.length());
|
||||
plugin.getLog().info("[FileWatcher] Refreshing group " + groupName);
|
||||
plugin.getUpdateTaskBuffer().request();
|
||||
});
|
||||
watcher.subscribe("tracks", tracksDir.toPath(), s -> {
|
||||
if (!s.endsWith(fileExtension)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String trackName = s.substring(0, s.length() - fileExtension.length());
|
||||
plugin.getLog().info("[FileWatcher] Refreshing track " + trackName);
|
||||
plugin.getStorage().loadAllTracks();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,6 +163,10 @@ abstract class FlatfileBacking extends AbstractBacking {
|
||||
saveUUIDCache(uuidCache);
|
||||
}
|
||||
|
||||
protected void registerFileAction(String type, File file) {
|
||||
plugin.applyToFileWatcher(fileWatcher -> fileWatcher.registerChange(type, file.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logAction(LogEntry entry) {
|
||||
actionLogger.info(String.format(LOG_FORMAT,
|
||||
|
@ -70,7 +70,7 @@ public class JSONBacking extends FlatfileBacking {
|
||||
}
|
||||
|
||||
public JSONBacking(LuckPermsPlugin plugin, File pluginDir) {
|
||||
super(plugin, "JSON", pluginDir);
|
||||
super(plugin, "JSON", pluginDir, ".json");
|
||||
}
|
||||
|
||||
private boolean fileToWriter(File file, ThrowingFunction<JsonWriter, Boolean> writeOperation) {
|
||||
@ -108,6 +108,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File userFile = new File(usersDir, uuid.toString() + ".json");
|
||||
registerFileAction("users", userFile);
|
||||
|
||||
if (userFile.exists()) {
|
||||
return fileToReader(userFile, reader -> {
|
||||
reader.beginObject();
|
||||
@ -178,6 +180,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File userFile = new File(usersDir, user.getUuid().toString() + ".json");
|
||||
registerFileAction("users", userFile);
|
||||
|
||||
if (!GenericUserManager.shouldSave(user)) {
|
||||
if (userFile.exists()) {
|
||||
userFile.delete();
|
||||
@ -221,6 +225,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
if (files == null) return false;
|
||||
|
||||
for (File file : files) {
|
||||
registerFileAction("users", file);
|
||||
|
||||
Map<String, Boolean> nodes = new HashMap<>();
|
||||
fileToReader(file, reader -> {
|
||||
reader.beginObject();
|
||||
@ -277,6 +283,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
if (files == null) return false;
|
||||
|
||||
for (File file : files) {
|
||||
registerFileAction("users", file);
|
||||
|
||||
UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 5));
|
||||
Map<String, Boolean> nodes = new HashMap<>();
|
||||
fileToReader(file, reader -> {
|
||||
@ -321,6 +329,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, name + ".json");
|
||||
registerFileAction("groups", groupFile);
|
||||
|
||||
if (groupFile.exists()) {
|
||||
return fileToReader(groupFile, reader -> {
|
||||
reader.beginObject();
|
||||
@ -374,6 +384,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, name + ".json");
|
||||
registerFileAction("groups", groupFile);
|
||||
|
||||
return groupFile.exists() && fileToReader(groupFile, reader -> {
|
||||
reader.beginObject();
|
||||
reader.nextName(); // name record
|
||||
@ -420,6 +432,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, group.getName() + ".json");
|
||||
registerFileAction("groups", groupFile);
|
||||
|
||||
if (!groupFile.exists()) {
|
||||
try {
|
||||
groupFile.createNewFile();
|
||||
@ -453,6 +467,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, group.getName() + ".json");
|
||||
registerFileAction("groups", groupFile);
|
||||
|
||||
if (groupFile.exists()) {
|
||||
groupFile.delete();
|
||||
}
|
||||
@ -471,6 +487,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
if (files == null) return false;
|
||||
|
||||
for (File file : files) {
|
||||
registerFileAction("groups", file);
|
||||
|
||||
String holder = file.getName().substring(0, file.getName().length() - 5);
|
||||
Map<String, Boolean> nodes = new HashMap<>();
|
||||
fileToReader(file, reader -> {
|
||||
@ -511,6 +529,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, name + ".json");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
if (trackFile.exists()) {
|
||||
return fileToReader(trackFile, reader -> {
|
||||
reader.beginObject();
|
||||
@ -561,6 +581,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, name + ".json");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
return trackFile.exists() && fileToReader(trackFile, reader -> {
|
||||
reader.beginObject();
|
||||
reader.nextName(); // name record
|
||||
@ -606,6 +628,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, track.getName() + ".json");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
if (!trackFile.exists()) {
|
||||
try {
|
||||
trackFile.createNewFile();
|
||||
@ -639,6 +663,8 @@ public class JSONBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, track.getName() + ".json");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
if (trackFile.exists()) {
|
||||
trackFile.delete();
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
}
|
||||
|
||||
public YAMLBacking(LuckPermsPlugin plugin, File pluginDir) {
|
||||
super(plugin, "YAML", pluginDir);
|
||||
super(plugin, "YAML", pluginDir, ".yml");
|
||||
}
|
||||
|
||||
private boolean writeMapToFile(File file, Map<String, Object> values) {
|
||||
@ -110,6 +110,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File userFile = new File(usersDir, uuid.toString() + ".yml");
|
||||
registerFileAction("users", userFile);
|
||||
if (userFile.exists()) {
|
||||
return readMapFromFile(userFile, values -> {
|
||||
// User exists, let's load.
|
||||
@ -159,6 +160,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File userFile = new File(usersDir, user.getUuid().toString() + ".yml");
|
||||
registerFileAction("users", userFile);
|
||||
if (!GenericUserManager.shouldSave(user)) {
|
||||
if (userFile.exists()) {
|
||||
userFile.delete();
|
||||
@ -194,6 +196,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
if (files == null) return false;
|
||||
|
||||
for (File file : files) {
|
||||
registerFileAction("users", file);
|
||||
Map<String, Boolean> nodes = new HashMap<>();
|
||||
readMapFromFile(file, values -> {
|
||||
Map<String, Boolean> perms = (Map<String, Boolean>) values.get("perms");
|
||||
@ -235,6 +238,8 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
if (files == null) return false;
|
||||
|
||||
for (File file : files) {
|
||||
registerFileAction("users", file);
|
||||
|
||||
UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 4));
|
||||
Map<String, Boolean> nodes = new HashMap<>();
|
||||
readMapFromFile(file, values -> {
|
||||
@ -264,6 +269,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, name + ".yml");
|
||||
registerFileAction("groups", groupFile);
|
||||
if (groupFile.exists()) {
|
||||
return readMapFromFile(groupFile, values -> {
|
||||
Map<String, Boolean> perms = (Map<String, Boolean>) values.get("perms");
|
||||
@ -296,6 +302,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, name + ".yml");
|
||||
registerFileAction("groups", groupFile);
|
||||
return groupFile.exists() && readMapFromFile(groupFile, values -> {
|
||||
Map<String, Boolean> perms = (Map<String, Boolean>) values.get("perms");
|
||||
group.setNodes(perms);
|
||||
@ -331,6 +338,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, group.getName() + ".yml");
|
||||
registerFileAction("groups", groupFile);
|
||||
if (!groupFile.exists()) {
|
||||
try {
|
||||
groupFile.createNewFile();
|
||||
@ -356,6 +364,7 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File groupFile = new File(groupsDir, group.getName() + ".yml");
|
||||
registerFileAction("groups", groupFile);
|
||||
if (groupFile.exists()) {
|
||||
groupFile.delete();
|
||||
}
|
||||
@ -374,6 +383,8 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
if (files == null) return false;
|
||||
|
||||
for (File file : files) {
|
||||
registerFileAction("groups", file);
|
||||
|
||||
String holder = file.getName().substring(0, file.getName().length() - 4);
|
||||
Map<String, Boolean> nodes = new HashMap<>();
|
||||
readMapFromFile(file, values -> {
|
||||
@ -403,6 +414,8 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, name + ".yml");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
if (trackFile.exists()) {
|
||||
return readMapFromFile(trackFile, values -> {
|
||||
track.setGroups((List<String>) values.get("groups"));
|
||||
@ -435,6 +448,8 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, name + ".yml");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
return trackFile.exists() && readMapFromFile(trackFile, values -> {
|
||||
track.setGroups((List<String>) values.get("groups"));
|
||||
return true;
|
||||
@ -468,6 +483,8 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, track.getName() + ".yml");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
if (!trackFile.exists()) {
|
||||
try {
|
||||
trackFile.createNewFile();
|
||||
@ -493,6 +510,8 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
try {
|
||||
return call(() -> {
|
||||
File trackFile = new File(tracksDir, track.getName() + ".yml");
|
||||
registerFileAction("tracks", trackFile);
|
||||
|
||||
if (trackFile.exists()) {
|
||||
trackFile.delete();
|
||||
}
|
||||
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
|
||||
*
|
||||
* 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.utils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FileWatcher implements Runnable {
|
||||
private final LuckPermsPlugin plugin;
|
||||
|
||||
private final Map<String, WatchedLocation> keyMap;
|
||||
private final Map<String, Long> internalChanges;
|
||||
private WatchService watchService = null;
|
||||
|
||||
public FileWatcher(LuckPermsPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.keyMap = Collections.synchronizedMap(new HashMap<>());
|
||||
this.internalChanges = Collections.synchronizedMap(new HashMap<>());
|
||||
try {
|
||||
this.watchService = plugin.getDataDirectory().toPath().getFileSystem().newWatchService();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void subscribe(String id, Path path, Consumer<String> consumer) {
|
||||
if (watchService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register with a delay to ignore changes made at startup
|
||||
plugin.getScheduler().doAsyncLater(() -> {
|
||||
try {
|
||||
// doesn't need to be atomic
|
||||
if (keyMap.containsKey(id)) {
|
||||
throw new IllegalArgumentException("id already registered");
|
||||
}
|
||||
|
||||
WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
|
||||
keyMap.put(id, new WatchedLocation(path, key, consumer));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, 40L);
|
||||
}
|
||||
|
||||
public void registerChange(String id, String filename) {
|
||||
internalChanges.put(id + "/" + filename, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (watchService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
watchService.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long expireTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(4);
|
||||
// was either processed last time, or recently modified by the system.
|
||||
internalChanges.values().removeIf(lastChange -> lastChange < expireTime);
|
||||
|
||||
List<String> expired = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, WatchedLocation> ent : keyMap.entrySet()) {
|
||||
String id = ent.getKey();
|
||||
Path path = ent.getValue().getPath();
|
||||
WatchKey key = ent.getValue().getKey();
|
||||
|
||||
List<WatchEvent<?>> watchEvents = key.pollEvents();
|
||||
|
||||
for (WatchEvent<?> event : watchEvents) {
|
||||
Path name = (Path) event.context();
|
||||
Path file = path.resolve(name);
|
||||
|
||||
String fileName = name.toString();
|
||||
|
||||
if (internalChanges.containsKey(id + "/" + fileName)) {
|
||||
// This file was modified by the system.
|
||||
continue;
|
||||
}
|
||||
|
||||
registerChange(id, fileName);
|
||||
|
||||
plugin.getLog().info("[FileWatcher] Detected change in file: " + file.toString());
|
||||
|
||||
// Process the change
|
||||
ent.getValue().getFileConsumer().accept(fileName);
|
||||
}
|
||||
|
||||
boolean valid = key.reset();
|
||||
if (!valid) {
|
||||
new RuntimeException("WatchKey no longer valid: " + key.toString()).printStackTrace();
|
||||
expired.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
expired.forEach(keyMap::remove);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
private static class WatchedLocation {
|
||||
private final Path path;
|
||||
private final WatchKey key;
|
||||
private final Consumer<String> fileConsumer;
|
||||
}
|
||||
|
||||
}
|
@ -62,6 +62,7 @@ import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
|
||||
import me.lucko.luckperms.common.tasks.UpdateTask;
|
||||
import me.lucko.luckperms.common.treeview.PermissionVault;
|
||||
import me.lucko.luckperms.common.utils.BufferedRequest;
|
||||
import me.lucko.luckperms.common.utils.FileWatcher;
|
||||
import me.lucko.luckperms.common.utils.LoggerImpl;
|
||||
import me.lucko.luckperms.sponge.commands.SpongeMainCommand;
|
||||
import me.lucko.luckperms.sponge.contexts.WorldCalculator;
|
||||
@ -146,6 +147,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
|
||||
private SpongeGroupManager groupManager;
|
||||
private TrackManager trackManager;
|
||||
private Storage storage;
|
||||
private FileWatcher fileWatcher = null;
|
||||
private InternalMessagingService messagingService = null;
|
||||
private UuidCache uuidCache;
|
||||
private ApiProvider apiProvider;
|
||||
@ -183,6 +185,11 @@ public class LPSpongePlugin implements LuckPermsPlugin {
|
||||
// register events
|
||||
game.getEventManager().registerListeners(this, new SpongeListener(this));
|
||||
|
||||
if (getConfiguration().get(ConfigKeys.WATCH_FILES)) {
|
||||
fileWatcher = new FileWatcher(this);
|
||||
getScheduler().doAsyncRepeating(fileWatcher, 30L);
|
||||
}
|
||||
|
||||
// initialise datastore
|
||||
storage = StorageFactory.getInstance(this, StorageType.H2);
|
||||
|
||||
@ -309,6 +316,10 @@ public class LPSpongePlugin implements LuckPermsPlugin {
|
||||
getLog().info("Closing datastore...");
|
||||
storage.shutdown();
|
||||
|
||||
if (fileWatcher != null) {
|
||||
fileWatcher.close();
|
||||
}
|
||||
|
||||
if (messagingService != null) {
|
||||
getLog().info("Closing messaging service...");
|
||||
messagingService.close();
|
||||
|
@ -138,6 +138,12 @@ meta-formatting {
|
||||
# Fill out connection info below if you're using MySQL, PostgreSQL or MongoDB
|
||||
storage-method="h2"
|
||||
|
||||
# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then schedule automatic
|
||||
# updates when changes are detected.
|
||||
#
|
||||
# If you don't want this to happen, set this option to false.
|
||||
watch-files=true
|
||||
|
||||
# This block enables support for split datastores.
|
||||
split-storage {
|
||||
enabled=false
|
||||
@ -165,9 +171,14 @@ data {
|
||||
# This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with this database.
|
||||
table_prefix="luckperms_"
|
||||
|
||||
# Set to -1 to disable. If this is the only instance accessing the datastore, you can disable syncing.
|
||||
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
|
||||
sync-minutes=3
|
||||
# This option controls how frequently LuckPerms will perform a sync task.
|
||||
# A sync task will refresh all data from the storage, and ensure that the most up-to-date data is being used by the plugin.
|
||||
#
|
||||
# This is disabled by default, as most users will not need it. However, if you're using a remote storage type
|
||||
# without a messaging service setup, you may wish to set this value to something like 3.
|
||||
#
|
||||
# Set to -1 to disable the task completely.
|
||||
sync-minutes=-1
|
||||
}
|
||||
|
||||
# Settings for the messaging service
|
||||
|
Loading…
Reference in New Issue
Block a user