Listen for changes in data files and automatically update

This commit is contained in:
Luck 2017-03-11 23:05:03 +00:00
parent 9dc2278083
commit b9fc5c39ae
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
12 changed files with 347 additions and 18 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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