diff --git a/bukkit-legacy/pom.xml b/bukkit-legacy/pom.xml
index f2f03178..6bc6e8c9 100644
--- a/bukkit-legacy/pom.xml
+++ b/bukkit-legacy/pom.xml
@@ -75,6 +75,18 @@
com.google.gson
me.lucko.luckperms.lib.gson
+
+ redis.clients.jedis
+ me.lucko.luckperms.lib.jedis.jedis
+
+
+ redis.clients.util
+ me.lucko.luckperms.lib.jedis.util
+
+
+ org.apache.commons.pool2
+ me.lucko.luckperms.lib.jedis.pool2
+
diff --git a/bukkit/pom.xml b/bukkit/pom.xml
index ca19cd05..14a8cbc4 100644
--- a/bukkit/pom.xml
+++ b/bukkit/pom.xml
@@ -70,6 +70,18 @@
org.h2
me.lucko.luckperms.lib.h2
+
+ redis.clients.jedis
+ me.lucko.luckperms.lib.jedis.jedis
+
+
+ redis.clients.util
+ me.lucko.luckperms.lib.jedis.util
+
+
+ org.apache.commons.pool2
+ me.lucko.luckperms.lib.jedis.pool2
+
diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java
index 51243fbf..7a08e5bc 100644
--- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java
+++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java
@@ -45,6 +45,7 @@ import me.lucko.luckperms.common.contexts.ServerCalculator;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.groups.GroupManager;
+import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.runnables.ExpireTemporaryTask;
import me.lucko.luckperms.common.runnables.UpdateTask;
import me.lucko.luckperms.common.storage.Datastore;
@@ -77,6 +78,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private GroupManager groupManager;
private TrackManager trackManager;
private Datastore datastore;
+ private RedisMessaging redisMessaging = null;
private UuidCache uuidCache;
private ApiProvider apiProvider;
private Logger log;
@@ -97,6 +99,42 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
getLog().info("Loading configuration...");
configuration = new BukkitConfig(this);
+ // setup the Bukkit defaults hook
+ defaultsProvider = new DefaultsProvider();
+ // give all plugins a chance to load their defaults, then refresh.
+ getServer().getScheduler().runTaskLater(this, () -> defaultsProvider.refresh(), 1L);
+
+ // register events
+ PluginManager pm = getServer().getPluginManager();
+ pm.registerEvents(new BukkitListener(this), this);
+
+ // initialise datastore
+ datastore = StorageFactory.getDatastore(this, "h2");
+
+ // initialise redis
+ if (getConfiguration().isRedisEnabled()) {
+ getLog().info("Loading redis...");
+ redisMessaging = new RedisMessaging(this);
+ try {
+ redisMessaging.init(getConfiguration().getRedisAddress(), getConfiguration().getRedisPassword());
+ getLog().info("Loaded redis successfully...");
+ } catch (Exception e) {
+ getLog().info("Couldn't load redis...");
+ e.printStackTrace();
+ }
+ }
+
+ // setup the update task buffer
+ final LPBukkitPlugin i = this;
+ updateTaskBuffer = new BufferedRequest(1000L, this::doAsync) {
+ @Override
+ protected Void perform() {
+ getServer().getScheduler().runTaskAsynchronously(i, new UpdateTask(i));
+ return null;
+ }
+ };
+
+ // load locale
localeManager = new LocaleManager();
File locale = new File(getDataFolder(), "lang.yml");
if (locale.exists()) {
@@ -108,13 +146,6 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
}
}
- defaultsProvider = new DefaultsProvider();
- getServer().getScheduler().runTaskLater(this, () -> defaultsProvider.refresh(), 1L);
-
- // register events
- PluginManager pm = getServer().getPluginManager();
- pm.registerEvents(new BukkitListener(this), this);
-
// register commands
getLog().info("Registering commands...");
BukkitCommand commandManager = new BukkitCommand(this);
@@ -123,8 +154,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
main.setTabCompleter(commandManager);
main.setAliases(Arrays.asList("perms", "lp", "permissions", "p", "perm"));
- datastore = StorageFactory.getDatastore(this, "h2");
-
+ // load internal managers
getLog().info("Loading internal permission managers...");
uuidCache = new UuidCache(getConfiguration().isOnlineMode());
userManager = new UserManager(this);
@@ -140,29 +170,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
contextManager.registerCalculator(worldCalculator);
contextManager.registerCalculator(new ServerCalculator<>(getConfiguration().getServer()));
+ // handle server operators
if (getConfiguration().isAutoOp()) {
contextManager.registerListener(new AutoOPListener());
}
- final LPBukkitPlugin i = this;
- updateTaskBuffer = new BufferedRequest(1000L, this::doAsync) {
- @Override
- protected Void perform() {
- getServer().getScheduler().runTaskAsynchronously(i, new UpdateTask(i));
- return null;
- }
- };
-
- int mins = getConfiguration().getSyncTime();
- if (mins > 0) {
- long ticks = mins * 60 * 20;
- getServer().getScheduler().runTaskTimerAsynchronously(this, () -> updateTaskBuffer.request(), ticks, ticks);
- }
-
- getServer().getScheduler().runTaskTimer(this, BukkitSenderFactory.get(this), 1L, 1L);
- getServer().getScheduler().runTaskTimerAsynchronously(this, new ExpireTemporaryTask(this), 60L, 60L);
- getServer().getScheduler().runTaskTimerAsynchronously(this, consecutiveExecutor, 20L, 20L);
-
// Provide vault support
getLog().info("Attempting to hook into Vault...");
try {
@@ -178,15 +190,29 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
e.printStackTrace();
}
+ // register with the LP API
getLog().info("Registering API...");
apiProvider = new ApiProvider(this);
ApiHandler.registerProvider(apiProvider);
getServer().getServicesManager().register(LuckPermsApi.class, apiProvider, this, ServicePriority.Normal);
- // Run update task to refresh any online users
- getLog().info("Scheduling Update Task to refresh any online users.");
- updateTaskBuffer.request();
+ // schedule update tasks
+ int mins = getConfiguration().getSyncTime();
+ if (mins > 0) {
+ long ticks = mins * 60 * 20;
+ getServer().getScheduler().runTaskTimerAsynchronously(this, () -> updateTaskBuffer.request(), 20L, ticks);
+ } else {
+ // Update online users
+ updateTaskBuffer.request();
+ }
+
+ // register tasks
+ getServer().getScheduler().runTaskTimer(this, BukkitSenderFactory.get(this), 1L, 1L);
+ getServer().getScheduler().runTaskTimerAsynchronously(this, new ExpireTemporaryTask(this), 60L, 60L);
+ getServer().getScheduler().runTaskTimerAsynchronously(this, consecutiveExecutor, 20L, 20L);
+
+ // register permissions
registerPermissions(getConfiguration().isCommandsAllowOp() ? PermissionDefault.OP : PermissionDefault.FALSE);
if (!getConfiguration().isOpsEnabled()) {
getServer().getOperators().forEach(o -> o.setOp(false));
@@ -202,6 +228,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
getLog().info("Closing datastore...");
datastore.shutdown();
+ if (redisMessaging != null) {
+ getLog().info("Closing redis...");
+ redisMessaging.shutdown();
+ }
+
getLog().info("Unregistering API...");
ApiHandler.unregisterProvider();
getServer().getServicesManager().unregisterAll(this);
diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml
index e5a144cb..7d2aedf5 100644
--- a/bukkit/src/main/resources/config.yml
+++ b/bukkit/src/main/resources/config.yml
@@ -155,6 +155,19 @@ data:
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3
+# Settings for Redis.
+#
+# If enabled and configured, LuckPerms will use the Redis PubSub system to inform other
+# connected servers of changes. Use the command "/luckperms networksync" to push changes.
+# Data is NOT stored on redis. It is only used as a messaging platform.
+#
+# If you decide to enable this feature, you should set "sync-minutes" to -1, as there is no need for LuckPerms
+# to poll the database for changes.
+redis:
+ enabled: false
+ address: localhost:6379
+ password: ''
+
diff --git a/bungee/pom.xml b/bungee/pom.xml
index fdd8d1f2..1ab60cfc 100644
--- a/bungee/pom.xml
+++ b/bungee/pom.xml
@@ -69,6 +69,18 @@
org.sqlite
me.lucko.luckperms.lib.sqlite
+
+ redis.clients.jedis
+ me.lucko.luckperms.lib.jedis.jedis
+
+
+ redis.clients.util
+ me.lucko.luckperms.lib.jedis.util
+
+
+ org.apache.commons.pool2
+ me.lucko.luckperms.lib.jedis.pool2
+
diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java
index e818ec4f..c8be2556 100644
--- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java
+++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java
@@ -42,6 +42,7 @@ import me.lucko.luckperms.common.contexts.ServerCalculator;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.groups.GroupManager;
+import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.runnables.ExpireTemporaryTask;
import me.lucko.luckperms.common.runnables.UpdateTask;
import me.lucko.luckperms.common.storage.Datastore;
@@ -69,6 +70,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private GroupManager groupManager;
private TrackManager trackManager;
private Datastore datastore;
+ private RedisMessaging redisMessaging = null;
private UuidCache uuidCache;
private ApiProvider apiProvider;
private Logger log;
@@ -86,6 +88,36 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
getLog().info("Loading configuration...");
configuration = new BungeeConfig(this);
+ // register events
+ getProxy().getPluginManager().registerListener(this, new BungeeListener(this));
+
+ // initialise datastore
+ datastore = StorageFactory.getDatastore(this, "h2");
+
+ // initialise redis
+ if (getConfiguration().isRedisEnabled()) {
+ getLog().info("Loading redis...");
+ redisMessaging = new RedisMessaging(this);
+ try {
+ redisMessaging.init(getConfiguration().getRedisAddress(), getConfiguration().getRedisPassword());
+ getLog().info("Loaded redis successfully...");
+ } catch (Exception e) {
+ getLog().info("Couldn't load redis...");
+ e.printStackTrace();
+ }
+ }
+
+ // setup the update task buffer
+ final LPBungeePlugin i = this;
+ updateTaskBuffer = new BufferedRequest(1000L, this::doAsync) {
+ @Override
+ protected Void perform() {
+ doAsync(new UpdateTask(i));
+ return null;
+ }
+ };
+
+ // load locale
localeManager = new LocaleManager();
File locale = new File(getDataFolder(), "lang.yml");
if (locale.exists()) {
@@ -97,9 +129,6 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
}
}
- // register events
- getProxy().getPluginManager().registerListener(this, new BungeeListener(this));
-
// register commands
getLog().info("Registering commands...");
CommandManager commandManager = new CommandManager(this);
@@ -108,8 +137,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// disable the default Bungee /perms command so it gets handled by the Bukkit plugin
getProxy().getDisabledCommands().add("perms");
- datastore = StorageFactory.getDatastore(this, "h2");
-
+ // load internal managers
getLog().info("Loading internal permission managers...");
uuidCache = new UuidCache(getConfiguration().isOnlineMode());
userManager = new UserManager(this);
@@ -125,32 +153,24 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
contextManager.registerCalculator(serverCalculator);
contextManager.registerCalculator(new ServerCalculator<>(getConfiguration().getServer()));
- final LPBungeePlugin i = this;
- updateTaskBuffer = new BufferedRequest(1000L, this::doAsync) {
- @Override
- protected Void perform() {
- doAsync(new UpdateTask(i));
- return null;
- }
- };
-
- int mins = getConfiguration().getSyncTime();
- if (mins > 0) {
- getProxy().getScheduler().schedule(this, new UpdateTask(this), mins, mins, TimeUnit.MINUTES);
- }
-
- // 20 times per second (once per "tick")
- getProxy().getScheduler().schedule(this, BungeeSenderFactory.get(this), 50L, 50L, TimeUnit.MILLISECONDS);
- getProxy().getScheduler().schedule(this, new ExpireTemporaryTask(this), 3L, 3L, TimeUnit.SECONDS);
- getProxy().getScheduler().schedule(this, consecutiveExecutor, 1L, 1L, TimeUnit.SECONDS);
-
+ // register with the LP API
getLog().info("Registering API...");
apiProvider = new ApiProvider(this);
ApiHandler.registerProvider(apiProvider);
- // Run update task to refresh any online users
- getLog().info("Scheduling Update Task to refresh any online users.");
- updateTaskBuffer.request();
+ // schedule update tasks
+ int mins = getConfiguration().getSyncTime();
+ if (mins > 0) {
+ getProxy().getScheduler().schedule(this, new UpdateTask(this), mins, mins, TimeUnit.MINUTES);
+ } else {
+ // Update online users
+ updateTaskBuffer.request();
+ }
+
+ // register tasks
+ getProxy().getScheduler().schedule(this, BungeeSenderFactory.get(this), 50L, 50L, TimeUnit.MILLISECONDS); // 20 times per second (once per "tick")
+ getProxy().getScheduler().schedule(this, new ExpireTemporaryTask(this), 3L, 3L, TimeUnit.SECONDS);
+ getProxy().getScheduler().schedule(this, consecutiveExecutor, 1L, 1L, TimeUnit.SECONDS);
getLog().info("Successfully loaded.");
}
@@ -160,6 +180,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
getLog().info("Closing datastore...");
datastore.shutdown();
+ if (redisMessaging != null) {
+ getLog().info("Closing redis...");
+ redisMessaging.shutdown();
+ }
+
getLog().info("Unregistering API...");
ApiHandler.unregisterProvider();
}
diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml
index 0a71488f..100e36d2 100644
--- a/bungee/src/main/resources/config.yml
+++ b/bungee/src/main/resources/config.yml
@@ -113,6 +113,19 @@ data:
# e.g. if you're using sqlite or flatfile, this can be set to -1 to save resources.
sync-minutes: 3
+# Settings for Redis.
+#
+# If enabled and configured, LuckPerms will use the Redis PubSub system to inform other
+# connected servers of changes. Use the command "/luckpermsbungee networksync" to push changes.
+# Data is NOT stored on redis. It is only used as a messaging platform.
+#
+# If you decide to enable this feature, you should set "sync-minutes" to -1, as there is no need for LuckPerms
+# to poll the database for changes.
+redis:
+ enabled: false
+ address: localhost:6379
+ password: ''
+
diff --git a/common/pom.xml b/common/pom.xml
index 8b52afd5..4a7b41ba 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -39,6 +39,13 @@
2.5.1
compile
+
+
+ redis.clients
+ jedis
+ 2.8.1
+ compile
+
org.xerial
diff --git a/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java
index 22a5bdc7..1859e364 100644
--- a/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java
+++ b/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java
@@ -35,6 +35,7 @@ import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.groups.GroupManager;
+import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.storage.Datastore;
import me.lucko.luckperms.common.tracks.TrackManager;
import me.lucko.luckperms.common.users.UserManager;
@@ -60,6 +61,7 @@ public interface LuckPermsPlugin {
TrackManager getTrackManager();
LPConfiguration getConfiguration();
Datastore getDatastore();
+ RedisMessaging getRedisMessaging();
Logger getLog();
UuidCache getUuidCache();
ApiProvider getApiProvider();
diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java b/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java
index b87aa3a0..12a61df9 100644
--- a/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java
+++ b/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java
@@ -59,6 +59,7 @@ public class CommandManager {
.add(new TrackMainCommand())
.add(new LogMainCommand())
.add(new SyncCommand())
+ .add(new NetworkSyncCommand())
.add(new InfoCommand())
.add(new DebugCommand())
.add(new ImportCommand())
diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/NetworkSyncCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/NetworkSyncCommand.java
new file mode 100644
index 00000000..580ea9c6
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/NetworkSyncCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2016 Lucko (Luck)
+ *
+ * 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.commands.misc;
+
+import me.lucko.luckperms.common.LuckPermsPlugin;
+import me.lucko.luckperms.common.commands.CommandResult;
+import me.lucko.luckperms.common.commands.Sender;
+import me.lucko.luckperms.common.commands.SingleMainCommand;
+import me.lucko.luckperms.common.constants.Message;
+import me.lucko.luckperms.common.constants.Permission;
+
+import java.util.List;
+
+public class NetworkSyncCommand extends SingleMainCommand {
+ public NetworkSyncCommand() {
+ super("NetworkSync", "/%s networksync", 0, Permission.SYNC);
+ }
+
+ @Override
+ protected CommandResult execute(LuckPermsPlugin plugin, Sender sender, List args, String label) {
+ Message.UPDATE_TASK_REQUEST.send(sender);
+ plugin.getUpdateTaskBuffer().request().getUnchecked();
+ Message.UPDATE_TASK_COMPLETE_NETWORK.send(sender);
+
+ if (plugin.getRedisMessaging() != null) {
+ plugin.getRedisMessaging().pushUpdate();
+ Message.UPDATE_TASK_PUSH_SUCCESS.send(sender);
+ } else {
+ Message.UPDATE_TASK_PUSH_FAILURE.send(sender);
+ }
+
+ return CommandResult.SUCCESS;
+ }
+}
diff --git a/common/src/main/java/me/lucko/luckperms/common/config/AbstractConfiguration.java b/common/src/main/java/me/lucko/luckperms/common/config/AbstractConfiguration.java
index e37434b7..e05f3eda 100644
--- a/common/src/main/java/me/lucko/luckperms/common/config/AbstractConfiguration.java
+++ b/common/src/main/java/me/lucko/luckperms/common/config/AbstractConfiguration.java
@@ -74,6 +74,9 @@ public abstract class AbstractConfiguration implement
private String storageMethod;
private boolean splitStorage;
private Map splitStorageOptions;
+ private boolean redisEnabled;
+ private String redisAddress;
+ private String redisPassword;
public AbstractConfiguration(T plugin, String defaultServerName, boolean defaultIncludeGlobal, String defaultStorage) {
this.plugin = plugin;
@@ -142,6 +145,10 @@ public abstract class AbstractConfiguration implement
.put("uuid", getString("split-storage.methods.uuid", defaultStorage))
.put("log", getString("split-storage.methods.log", defaultStorage))
.build();
+
+ redisEnabled = getBoolean("redis.enabled", false);
+ redisAddress = getString("redis.address", null);
+ redisPassword = getString("redis.password", "");
if (Patterns.NON_ALPHA_NUMERIC.matcher(getServer()).find()) {
plugin.getLog().severe("Server name defined in config.yml contains invalid characters. Server names can " +
diff --git a/common/src/main/java/me/lucko/luckperms/common/config/LPConfiguration.java b/common/src/main/java/me/lucko/luckperms/common/config/LPConfiguration.java
index cae17528..ffcec226 100644
--- a/common/src/main/java/me/lucko/luckperms/common/config/LPConfiguration.java
+++ b/common/src/main/java/me/lucko/luckperms/common/config/LPConfiguration.java
@@ -92,4 +92,10 @@ public interface LPConfiguration {
Map getSplitStorageOptions();
+ boolean isRedisEnabled();
+
+ String getRedisAddress();
+
+ String getRedisPassword();
+
}
diff --git a/common/src/main/java/me/lucko/luckperms/common/constants/Message.java b/common/src/main/java/me/lucko/luckperms/common/constants/Message.java
index 3f4ed47f..61e1d42f 100644
--- a/common/src/main/java/me/lucko/luckperms/common/constants/Message.java
+++ b/common/src/main/java/me/lucko/luckperms/common/constants/Message.java
@@ -110,6 +110,9 @@ public enum Message {
UPDATE_TASK_REQUEST("&bUpdate task scheduled.", true),
UPDATE_TASK_COMPLETE("&aUpdate task finished.", true),
+ UPDATE_TASK_COMPLETE_NETWORK("&aUpdate task finished. Now attempting to push to other servers.", true),
+ UPDATE_TASK_PUSH_SUCCESS("&aOther servers were notified successfully.", true),
+ UPDATE_TASK_PUSH_FAILURE("&cError whilst pushing changes to other servers. Is Redis enabled?", true),
INFO(
"{PREFIX}&2Running &bLuckPerms v{0}&2 by &bLuck&2." + "\n" +
"{PREFIX}&f-> &3Platform: &f{1}" + "\n" +
diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/RedisMessaging.java b/common/src/main/java/me/lucko/luckperms/common/messaging/RedisMessaging.java
new file mode 100644
index 00000000..69ec818e
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/messaging/RedisMessaging.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2016 Lucko (Luck)
+ *
+ * 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.messaging;
+
+import lombok.RequiredArgsConstructor;
+import me.lucko.luckperms.common.LuckPermsPlugin;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+import redis.clients.jedis.JedisPubSub;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Uses Redis to push/receive changes to/from other servers
+ */
+@RequiredArgsConstructor
+public class RedisMessaging {
+ private static final String CHANNEL = "luckperms";
+
+ private final LuckPermsPlugin plugin;
+ private JedisPool jedisPool;
+ private LPSub sub;
+
+ public void init(String address, String password) {
+ String host = address.substring(0, address.indexOf(':'));
+ int port = Integer.parseInt(address.substring(address.indexOf(":") + 1));
+
+ if (password.equals("")) {
+ jedisPool = new JedisPool(new JedisPoolConfig(), host, port);
+ } else {
+ jedisPool = new JedisPool(new JedisPoolConfig(), host, port, 0, password);
+ }
+
+ plugin.doAsync(() -> {
+ sub = new LPSub(plugin);
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.subscribe(sub, CHANNEL);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ public void shutdown() {
+ sub.unsubscribe();
+ jedisPool.destroy();
+ }
+
+ public void pushUpdate() {
+ plugin.doAsync(() -> {
+ UUID id = sub.generateId();
+ plugin.getLog().info("[Redis Messaging] Sending redis ping with id: " + id.toString());
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.publish(CHANNEL, "update:" + id.toString());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ @RequiredArgsConstructor
+ private static class LPSub extends JedisPubSub {
+ private final LuckPermsPlugin plugin;
+ private final Set receivedMsgs = Collections.synchronizedSet(new HashSet<>());
+
+ public UUID generateId() {
+ UUID uuid = UUID.randomUUID();
+ receivedMsgs.add(uuid);
+ return uuid;
+ }
+
+ @Override
+ public void onMessage(String channel, String msg) {
+ if (!channel.equals(CHANNEL)) {
+ return;
+ }
+
+ if (!msg.startsWith("update:")) {
+ return;
+ }
+
+ String requestId = msg.substring("update:".length());
+ UUID uuid;
+ try {
+ uuid = UUID.fromString(requestId);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ if (!receivedMsgs.add(uuid)) {
+ return;
+ }
+
+ plugin.getLog().info("[Redis Messaging] Received update ping with id: " + uuid.toString());
+ plugin.getUpdateTaskBuffer().request();
+ }
+ }
+
+}
diff --git a/default-lang.yml b/default-lang.yml
index f34b51c2..2feab4c8 100644
--- a/default-lang.yml
+++ b/default-lang.yml
@@ -71,6 +71,9 @@ track-empty: "The track cannot be used as it is empty or contains only one group
update-task-request: "&bUpdate task scheduled."
update-task-complete: "&aUpdate task finished."
+update-task-complete-network: "&aUpdate task finished. Now attempting to push to other servers."
+update-task-push-success: "&aOther servers were notified successfully."
+update-task-push-failure: "&cError whilst pushing changes to other servers. Is Redis enabled?"
info: >
{PREFIX}&2Running &bLuckPerms v{0}&2 by &bLuck&2.\n
{PREFIX}&f-> &3Platform: &f{1}\n
diff --git a/sponge/pom.xml b/sponge/pom.xml
index 6f13a848..2c083d87 100644
--- a/sponge/pom.xml
+++ b/sponge/pom.xml
@@ -68,6 +68,18 @@
com.mysql
me.lucko.luckperms.lib.mysql
+
+ redis.clients.jedis
+ me.lucko.luckperms.lib.jedis.jedis
+
+
+ redis.clients.util
+ me.lucko.luckperms.lib.jedis.util
+
+
+ org.apache.commons.pool2
+ me.lucko.luckperms.lib.jedis.pool2
+
diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java
index 01d623a3..a708a9db 100644
--- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java
+++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java
@@ -40,6 +40,7 @@ import me.lucko.luckperms.common.contexts.ServerCalculator;
import me.lucko.luckperms.common.core.UuidCache;
import me.lucko.luckperms.common.data.Importer;
import me.lucko.luckperms.common.groups.GroupManager;
+import me.lucko.luckperms.common.messaging.RedisMessaging;
import me.lucko.luckperms.common.runnables.ExpireTemporaryTask;
import me.lucko.luckperms.common.runnables.UpdateTask;
import me.lucko.luckperms.common.storage.Datastore;
@@ -107,6 +108,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
private GroupManager groupManager;
private TrackManager trackManager;
private Datastore datastore;
+ private RedisMessaging redisMessaging = null;
private UuidCache uuidCache;
private ApiProvider apiProvider;
private me.lucko.luckperms.api.Logger log;
@@ -125,6 +127,37 @@ public class LPSpongePlugin implements LuckPermsPlugin {
getLog().info("Loading configuration...");
configuration = new SpongeConfig(this);
+ // register events
+ Sponge.getEventManager().registerListeners(this, new SpongeListener(this));
+
+ // initialise datastore
+ datastore = StorageFactory.getDatastore(this, "h2");
+
+ // initialise redis
+ if (getConfiguration().isRedisEnabled()) {
+ getLog().info("Loading redis...");
+ redisMessaging = new RedisMessaging(this);
+ try {
+ redisMessaging.init(getConfiguration().getRedisAddress(), getConfiguration().getRedisPassword());
+ getLog().info("Loaded redis successfully...");
+ } catch (Exception e) {
+ getLog().info("Couldn't load redis...");
+ e.printStackTrace();
+ redisMessaging = null;
+ }
+ }
+
+ // setup the update task buffer
+ final LPSpongePlugin i = this;
+ updateTaskBuffer = new BufferedRequest(1000L, this::doAsync) {
+ @Override
+ protected Void perform() {
+ scheduler.createTaskBuilder().async().execute(new UpdateTask(i)).submit(i);
+ return null;
+ }
+ };
+
+ // load locale
localeManager = new LocaleManager();
File locale = new File(getMainDir(), "lang.yml");
if (locale.exists()) {
@@ -136,17 +169,13 @@ public class LPSpongePlugin implements LuckPermsPlugin {
}
}
- // register events
- Sponge.getEventManager().registerListeners(this, new SpongeListener(this));
-
// register commands
getLog().info("Registering commands...");
CommandManager cmdService = Sponge.getCommandManager();
SpongeCommand commandManager = new SpongeCommand(this);
cmdService.register(this, commandManager, "luckperms", "perms", "lp", "permissions", "p", "perm");
- datastore = StorageFactory.getDatastore(this, "h2");
-
+ // load internal managers
getLog().info("Loading internal permission managers...");
uuidCache = new UuidCache(getConfiguration().isOnlineMode());
userManager = new UserManager(this);
@@ -160,23 +189,17 @@ public class LPSpongePlugin implements LuckPermsPlugin {
contextManager.registerCalculator(new ServerCalculator<>(getConfiguration().getServer()));
contextManager.registerCalculator(new WorldCalculator(this));
+ // register the PermissionService with Sponge
getLog().info("Registering PermissionService...");
Sponge.getServiceManager().setProvider(this, PermissionService.class, (service = new LuckPermsService(this)));
+ // register with the LP API
getLog().info("Registering API...");
apiProvider = new ApiProvider(this);
ApiHandler.registerProvider(apiProvider);
Sponge.getServiceManager().setProvider(this, LuckPermsApi.class, apiProvider);
- final LPSpongePlugin i = this;
- updateTaskBuffer = new BufferedRequest(1000L, this::doAsync) {
- @Override
- protected Void perform() {
- scheduler.createTaskBuilder().async().execute(new UpdateTask(i)).submit(i);
- return null;
- }
- };
-
+ // schedule update tasks
int mins = getConfiguration().getSyncTime();
if (mins > 0) {
scheduler.createTaskBuilder().async().interval(mins, TimeUnit.MINUTES).execute(new UpdateTask(this))
@@ -186,6 +209,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
updateTaskBuffer.request();
}
+ // register tasks
scheduler.createTaskBuilder().intervalTicks(1L).execute(SpongeSenderFactory.get(this)).submit(this);
scheduler.createTaskBuilder().async().intervalTicks(60L).execute(new ExpireTemporaryTask(this)).submit(this);
scheduler.createTaskBuilder().async().intervalTicks(20L).execute(consecutiveExecutor).submit(this);
@@ -198,6 +222,11 @@ public class LPSpongePlugin implements LuckPermsPlugin {
getLog().info("Closing datastore...");
datastore.shutdown();
+ if (redisMessaging != null) {
+ getLog().info("Closing redis...");
+ redisMessaging.shutdown();
+ }
+
getLog().info("Unregistering API...");
ApiHandler.unregisterProvider();
}
diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf
index 4cb0eb96..6ff916b2 100644
--- a/sponge/src/main/resources/luckperms.conf
+++ b/sponge/src/main/resources/luckperms.conf
@@ -114,6 +114,20 @@ data {
sync-minutes=3
}
+# Settings for Redis.
+#
+# If enabled and configured, LuckPerms will use the Redis PubSub system to inform other
+# connected servers of changes. Use the command "/luckperms networksync" to push changes.
+# Data is NOT stored on redis. It is only used as a messaging platform.
+#
+# If you decide to enable this feature, you should set "sync-minutes" to -1, as there is no need for LuckPerms
+# to poll the database for changes.
+redis {
+ enabled=false
+ address="localhost:6379"
+ password=""
+}
+