From 327c8b83be256ac0c81bad3ef83f80f21686b726 Mon Sep 17 00:00:00 2001 From: Luck Date: Sun, 22 Jan 2017 21:46:22 +0000 Subject: [PATCH] Implement BungeeCord & LilyPad messaging services - closes #142 --- bukkit/pom.xml | 7 ++ .../luckperms/bukkit/LPBukkitPlugin.java | 54 ++++++--- .../messaging/BungeeMessagingService.java | 95 ++++++++++++++++ .../messaging/LilyPadMessagingService.java | 90 +++++++++++++++ bukkit/src/main/resources/config.yml | 14 ++- bukkit/src/main/resources/plugin.yml | 1 + .../luckperms/bungee/LPBungeePlugin.java | 42 ++++--- .../messaging/BungeeMessagingService.java | 92 ++++++++++++++++ bungee/src/main/resources/config.yml | 13 ++- .../luckperms/common/LuckPermsPlugin.java | 4 +- .../luckperms/common/api/ApiProvider.java | 2 +- .../commands/misc/NetworkSyncCommand.java | 4 +- .../luckperms/common/config/ConfigKeys.java | 1 + .../messaging/AbstractMessagingService.java | 103 ++++++++++++++++++ .../common/messaging/RedisMessaging.java | 72 +++--------- pom.xml | 4 + .../luckperms/sponge/LPSpongePlugin.java | 43 +++++--- .../messaging/BungeeMessagingService.java | 83 ++++++++++++++ sponge/src/main/resources/luckperms.conf | 13 ++- 19 files changed, 628 insertions(+), 109 deletions(-) create mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/BungeeMessagingService.java create mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/LilyPadMessagingService.java create mode 100644 bungee/src/main/java/me/lucko/luckperms/bungee/messaging/BungeeMessagingService.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/messaging/AbstractMessagingService.java create mode 100644 sponge/src/main/java/me/lucko/luckperms/sponge/messaging/BungeeMessagingService.java diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 7f4a22ac3..6f81b7533 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -108,6 +108,13 @@ ${project.version} compile + + + lilypad.client.connect + api + 0.0.1-SNAPSHOT + provided + com.zaxxer 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 448fbc627..3c4084ce0 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -33,6 +33,8 @@ import me.lucko.luckperms.api.PlatformType; import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.bukkit.inject.Injector; +import me.lucko.luckperms.bukkit.messaging.BungeeMessagingService; +import me.lucko.luckperms.bukkit.messaging.LilyPadMessagingService; import me.lucko.luckperms.bukkit.model.ChildPermissionProvider; import me.lucko.luckperms.bukkit.model.DefaultsProvider; import me.lucko.luckperms.bukkit.model.LPPermissible; @@ -61,6 +63,7 @@ import me.lucko.luckperms.common.managers.UserManager; import me.lucko.luckperms.common.managers.impl.GenericGroupManager; import me.lucko.luckperms.common.managers.impl.GenericTrackManager; import me.lucko.luckperms.common.managers.impl.GenericUserManager; +import me.lucko.luckperms.common.messaging.AbstractMessagingService; import me.lucko.luckperms.common.messaging.RedisMessaging; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.storage.StorageFactory; @@ -114,7 +117,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { private GroupManager groupManager; private TrackManager trackManager; private Storage storage; - private RedisMessaging redisMessaging = null; + private AbstractMessagingService messagingService = null; private UuidCache uuidCache; private BukkitListener listener; private ApiProvider apiProvider; @@ -197,17 +200,40 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { // initialise datastore storage = StorageFactory.getInstance(this, StorageType.H2); - // initialise redis - if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) { + // initialise messaging + String messagingType = getConfiguration().get(ConfigKeys.MESSAGING_SERVICE).toLowerCase(); + if (messagingType.equals("redis")) { getLog().info("Loading redis..."); - redisMessaging = new RedisMessaging(this); - try { - redisMessaging.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD)); - getLog().info("Loaded redis successfully..."); - } catch (Exception e) { - getLog().info("Couldn't load redis..."); - e.printStackTrace(); + if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) { + RedisMessaging redis = new RedisMessaging(this); + try { + redis.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD)); + getLog().info("Loaded redis successfully..."); + + messagingService = redis; + } catch (Exception e) { + getLog().warn("Couldn't load redis..."); + e.printStackTrace(); + } + } else { + getLog().warn("Messaging Service was set to redis, but redis is not enabled!"); } + } else if (messagingType.equals("bungee")) { + getLog().info("Loading bungee messaging service..."); + BungeeMessagingService bungeeMessaging = new BungeeMessagingService(this); + bungeeMessaging.init(); + messagingService = bungeeMessaging; + } else if (messagingType.equals("lilypad")) { + getLog().info("Loading LilyPad messaging service..."); + if (getServer().getPluginManager().getPlugin("LilyPad-Connect") == null) { + getLog().warn("LilyPad-Connect plugin not present."); + } else { + LilyPadMessagingService lilyPadMessaging = new LilyPadMessagingService(this); + lilyPadMessaging.init(); + messagingService = lilyPadMessaging; + } + } else if (!messagingType.equals("none")) { + getLog().warn("Messaging service '" + messagingType + "' not recognised."); } // setup the update task buffer @@ -341,9 +367,9 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { getLog().info("Closing datastore..."); storage.shutdown(); - if (redisMessaging != null) { - getLog().info("Closing redis..."); - redisMessaging.shutdown(); + if (messagingService != null) { + getLog().info("Closing messaging service..."); + messagingService.close(); } getLog().info("Unregistering API..."); @@ -374,7 +400,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { groupManager = null; trackManager = null; storage = null; - redisMessaging = null; + messagingService = null; uuidCache = null; listener = null; apiProvider = null; diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/BungeeMessagingService.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/BungeeMessagingService.java new file mode 100644 index 000000000..10fd2f78f --- /dev/null +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/BungeeMessagingService.java @@ -0,0 +1,95 @@ +/* + * 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.bukkit.messaging; + +import com.google.common.collect.Iterables; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; + +import me.lucko.luckperms.bukkit.LPBukkitPlugin; +import me.lucko.luckperms.common.messaging.AbstractMessagingService; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Collection; + +/** + * An implementation of {@link me.lucko.luckperms.api.MessagingService} using the plugin messaging channels. + */ +public class BungeeMessagingService extends AbstractMessagingService implements PluginMessageListener { + private final LPBukkitPlugin plugin; + + public BungeeMessagingService(LPBukkitPlugin plugin) { + super(plugin, "Bungee"); + this.plugin = plugin; + } + + public void init() { + plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL); + plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL, this); + } + + @Override + public void close() { + plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL); + plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL); + } + + @Override + protected void sendMessage(String channel, String message) { + new BukkitRunnable() { + @Override + public void run() { + Collection players = plugin.getServer().getOnlinePlayers(); + if (players.isEmpty()) { + return; + } + + Player p = Iterables.getFirst(players, null); + + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF(message); + + byte[] data = out.toByteArray(); + + p.sendPluginMessage(plugin, channel, data); + cancel(); + } + }.runTaskTimer(plugin, 1L, 100L); + } + + @Override + public void onPluginMessageReceived(String s, Player player, byte[] bytes) { + if (!s.equals(CHANNEL)) { + return; + } + + ByteArrayDataInput in = ByteStreams.newDataInput(bytes); + String msg = in.readUTF(); + + onMessage(s, msg, null); + } +} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/LilyPadMessagingService.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/LilyPadMessagingService.java new file mode 100644 index 000000000..f62879239 --- /dev/null +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/messaging/LilyPadMessagingService.java @@ -0,0 +1,90 @@ +/* + * 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.bukkit.messaging; + +import me.lucko.luckperms.bukkit.LPBukkitPlugin; +import me.lucko.luckperms.common.messaging.AbstractMessagingService; + +import lilypad.client.connect.api.Connect; +import lilypad.client.connect.api.event.EventListener; +import lilypad.client.connect.api.event.MessageEvent; +import lilypad.client.connect.api.request.RequestException; +import lilypad.client.connect.api.request.impl.MessageRequest; + +import java.io.UnsupportedEncodingException; +import java.util.Collections; + +/** + * An implementation of {@link me.lucko.luckperms.api.MessagingService} using LilyPad. + */ +public class LilyPadMessagingService extends AbstractMessagingService { + private final LPBukkitPlugin plugin; + private Connect connect; + + public LilyPadMessagingService(LPBukkitPlugin plugin) { + super(plugin, "LilyPad"); + this.plugin = plugin; + } + + public void init() { + connect = plugin.getServer().getServicesManager().getRegistration(Connect.class).getProvider(); + connect.registerEvents(this); + } + + @Override + public void close() { + connect.unregisterEvents(this); + } + + @Override + protected void sendMessage(String channel, String message) { + MessageRequest request; + + try { + request = new MessageRequest(Collections.emptyList(), channel, message); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return; + } + + try { + connect.request(request); + } catch (RequestException e) { + e.printStackTrace(); + } + } + + @EventListener + public void onMessage(MessageEvent event) { + plugin.doAsync(() -> { + try { + String channel = event.getChannel(); + String message = event.getMessageAsString(); + + onMessage(channel, message, null); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } +} diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index b109e3de1..632bddee8 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -213,15 +213,23 @@ 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. +# Settings for the messaging service # -# If enabled and configured, LuckPerms will use the Redis PubSub system to inform other +# If enabled and configured, LuckPerms will use the messaging 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. +# Data is NOT stored using this service. 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. # +# Available options: +# bungee ==> uses the plugin messaging channels. Must be enabled on all connected servers to work. +# lilypad ==> uses lilypad pub sub to push changes. You need to have the LilyPad-Connect plugin installed. +# redis ==> uses redis pub sub to push changes. Your redis server must be configured below. +# none ==> nothing +messaging-service: none + +# Settings for Redis. # Port 6379 is used by default; set address to "host:port" if differs redis: enabled: false diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index 46d355255..abfd42219 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -11,6 +11,7 @@ load: STARTUP # It in turn fixes issues where plugins using Vault cache the provided instance when their plugin enables, or # when they check for the presence of a service provider, before LuckPerms has enabled. loadbefore: [Vault] +softdepend: [LilyPad-Connect] commands: luckperms: 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 18bf77d3d..6cf883a20 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java @@ -29,6 +29,7 @@ import me.lucko.luckperms.api.Logger; import me.lucko.luckperms.api.PlatformType; import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.context.MutableContextSet; +import me.lucko.luckperms.bungee.messaging.BungeeMessagingService; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.ApiHandler; import me.lucko.luckperms.common.api.ApiProvider; @@ -53,6 +54,7 @@ import me.lucko.luckperms.common.managers.UserManager; import me.lucko.luckperms.common.managers.impl.GenericGroupManager; import me.lucko.luckperms.common.managers.impl.GenericTrackManager; import me.lucko.luckperms.common.managers.impl.GenericUserManager; +import me.lucko.luckperms.common.messaging.AbstractMessagingService; import me.lucko.luckperms.common.messaging.RedisMessaging; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.storage.StorageFactory; @@ -91,7 +93,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { private GroupManager groupManager; private TrackManager trackManager; private Storage storage; - private RedisMessaging redisMessaging = null; + private AbstractMessagingService messagingService = null; private UuidCache uuidCache; private ApiProvider apiProvider; private Logger log; @@ -129,17 +131,31 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { // initialise datastore storage = StorageFactory.getInstance(this, StorageType.H2); - // initialise redis - if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) { + // initialise messaging + String messagingType = getConfiguration().get(ConfigKeys.MESSAGING_SERVICE).toLowerCase(); + if (messagingType.equals("redis")) { getLog().info("Loading redis..."); - redisMessaging = new RedisMessaging(this); - try { - redisMessaging.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD)); - getLog().info("Loaded redis successfully..."); - } catch (Exception e) { - getLog().info("Couldn't load redis..."); - e.printStackTrace(); + if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) { + RedisMessaging redis = new RedisMessaging(this); + try { + redis.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD)); + getLog().info("Loaded redis successfully..."); + + messagingService = redis; + } catch (Exception e) { + getLog().warn("Couldn't load redis..."); + e.printStackTrace(); + } + } else { + getLog().warn("Messaging Service was set to redis, but redis is not enabled!"); } + } else if (messagingType.equals("bungee")) { + getLog().info("Loading bungee messaging service..."); + BungeeMessagingService bungeeMessaging = new BungeeMessagingService(this); + bungeeMessaging.init(); + messagingService = bungeeMessaging; + } else if (!messagingType.equals("none")) { + getLog().warn("Messaging service '" + messagingType + "' not recognised."); } // setup the update task buffer @@ -215,9 +231,9 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { getLog().info("Closing datastore..."); storage.shutdown(); - if (redisMessaging != null) { - getLog().info("Closing redis..."); - redisMessaging.shutdown(); + if (messagingService != null) { + getLog().info("Closing messaging service..."); + messagingService.close(); } getLog().info("Unregistering API..."); diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/messaging/BungeeMessagingService.java b/bungee/src/main/java/me/lucko/luckperms/bungee/messaging/BungeeMessagingService.java new file mode 100644 index 000000000..b85b131c0 --- /dev/null +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/messaging/BungeeMessagingService.java @@ -0,0 +1,92 @@ +/* + * 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.bungee.messaging; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; + +import me.lucko.luckperms.bungee.LPBungeePlugin; +import me.lucko.luckperms.common.messaging.AbstractMessagingService; + +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +/** + * An implementation of {@link me.lucko.luckperms.api.MessagingService} using the plugin messaging channels. + */ +public class BungeeMessagingService extends AbstractMessagingService implements Listener { + private final LPBungeePlugin plugin; + + public BungeeMessagingService(LPBungeePlugin plugin) { + super(plugin, "Bungee"); + this.plugin = plugin; + } + + public void init() { + plugin.getProxy().getPluginManager().registerListener(plugin, this); + plugin.getProxy().registerChannel(CHANNEL); + } + + @Override + public void close() { + plugin.getProxy().unregisterChannel(CHANNEL); + } + + @Override + protected void sendMessage(String channel, String message) { + + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF(message); + + byte[] data = out.toByteArray(); + + for (ServerInfo server : plugin.getProxy().getServers().values()) { + server.sendData(channel, data, true); + } + } + + @EventHandler + public void onPluginMessage(PluginMessageEvent e) { + if (!e.getTag().equals(CHANNEL)) { + return; + } + + e.setCancelled(true); + + if (e.getSender() instanceof ProxiedPlayer) { + return; + } + + ByteArrayDataInput in = ByteStreams.newDataInput(e.getData()); + String msg = in.readUTF(); + + onMessage(e.getTag(), msg, u -> { + // Forward to other servers + plugin.doAsync(() -> sendMessage(CHANNEL, "update:" + u.toString())); + }); + } +} diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml index dc9b5b2de..6ba7571f9 100644 --- a/bungee/src/main/resources/config.yml +++ b/bungee/src/main/resources/config.yml @@ -155,15 +155,22 @@ 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. +# Settings for the messaging service # -# If enabled and configured, LuckPerms will use the Redis PubSub system to inform other +# If enabled and configured, LuckPerms will use the messaging 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. +# Data is NOT stored using this service. 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. # +# Available options: +# bungee ==> uses the plugin messaging channels. Must be enabled on all connected servers to work. +# redis ==> uses redis pub sub to push changes. Your redis server must be configured below. +# none ==> nothing +messaging-service: none + +# Settings for Redis. # Port 6379 is used by default; set address to "host:port" if differs redis: enabled: false 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 85fdd5b9c..b3d364e6e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java @@ -41,7 +41,7 @@ import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.managers.GroupManager; import me.lucko.luckperms.common.managers.TrackManager; import me.lucko.luckperms.common.managers.UserManager; -import me.lucko.luckperms.common.messaging.RedisMessaging; +import me.lucko.luckperms.common.messaging.AbstractMessagingService; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.utils.BufferedRequest; import me.lucko.luckperms.common.utils.DebugHandler; @@ -103,7 +103,7 @@ public interface LuckPermsPlugin { * * @return the redis messaging service */ - RedisMessaging getRedisMessaging(); + AbstractMessagingService getMessagingService(); /** * Gets a wrapped logger instance for the platform. diff --git a/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java b/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java index 201a893a1..937821f82 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java @@ -135,7 +135,7 @@ public class ApiProvider implements LuckPermsApi { @Override public Optional getMessagingService() { - return Optional.ofNullable(plugin.getRedisMessaging()); + return Optional.ofNullable(plugin.getMessagingService()); } @Override 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 index eab9fb2d6..d527be455 100644 --- 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 @@ -44,8 +44,8 @@ public class NetworkSyncCommand extends SingleCommand { plugin.getUpdateTaskBuffer().request().join(); Message.UPDATE_TASK_COMPLETE_NETWORK.send(sender); - if (plugin.getRedisMessaging() != null) { - plugin.getRedisMessaging().pushUpdate(); + if (plugin.getMessagingService() != null) { + plugin.getMessagingService().pushUpdate(); Message.UPDATE_TASK_PUSH_SUCCESS.send(sender); } else { Message.UPDATE_TASK_PUSH_FAILURE.send(sender); diff --git a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java index 033007ca0..5af6c75cc 100644 --- a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java +++ b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java @@ -139,6 +139,7 @@ public class ConfigKeys { .put("log", c.getString("split-storage.methods.log", "h2")) .build(); })); + public static final ConfigKey MESSAGING_SERVICE = EnduringKey.wrap(StringKey.of("messaging-service", "none")); public static final ConfigKey REDIS_ENABLED = EnduringKey.wrap(BooleanKey.of("redis.enabled", false)); public static final ConfigKey REDIS_ADDRESS = EnduringKey.wrap(StringKey.of("redis.address", null)); public static final ConfigKey REDIS_PASSWORD = EnduringKey.wrap(StringKey.of("redis.password", "")); diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/AbstractMessagingService.java b/common/src/main/java/me/lucko/luckperms/common/messaging/AbstractMessagingService.java new file mode 100644 index 000000000..ffed1614e --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/AbstractMessagingService.java @@ -0,0 +1,103 @@ +/* + * 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.api.MessagingService; +import me.lucko.luckperms.common.LuckPermsPlugin; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; + +/** + * An abstract implementation of {@link me.lucko.luckperms.api.MessagingService}. + */ +@RequiredArgsConstructor +public abstract class AbstractMessagingService implements MessagingService { + public static final String CHANNEL = "lpuc"; + + private final LuckPermsPlugin plugin; + private final String name; + + private final Set receivedMsgs = Collections.synchronizedSet(new HashSet<>()); + + public abstract void close(); + + protected abstract void sendMessage(String channel, String message); + + protected void onMessage(String channel, String msg, Consumer callback) { + if (!channel.equals(CHANNEL)) { + return; + } + + UUID uuid = parseUpdateMessage(msg); + if (uuid == null) { + return; + } + + if (!receivedMsgs.add(uuid)) { + return; + } + + plugin.getLog().info("[" + name + " Messaging] Received update ping with id: " + uuid.toString()); + plugin.getUpdateTaskBuffer().request(); + + if (callback != null) { + callback.accept(uuid); + } + } + + @Override + public void pushUpdate() { + plugin.doAsync(() -> { + UUID id = generateId(); + plugin.getLog().info("[" + name + " Messaging] Sending ping with id: " + id.toString()); + + sendMessage(CHANNEL, "update:" + id.toString()); + }); + } + + private UUID generateId() { + UUID uuid = UUID.randomUUID(); + receivedMsgs.add(uuid); + return uuid; + } + + private static UUID parseUpdateMessage(String msg) { + if (!msg.startsWith("update:")) { + return null; + } + + String requestId = msg.substring("update:".length()); + try { + return UUID.fromString(requestId); + } catch (IllegalArgumentException e) { + return null; + } + } + +} 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 index 29f0d0739..24b4b9cd9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/RedisMessaging.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/RedisMessaging.java @@ -24,7 +24,6 @@ package me.lucko.luckperms.common.messaging; import lombok.RequiredArgsConstructor; -import me.lucko.luckperms.api.MessagingService; import me.lucko.luckperms.common.LuckPermsPlugin; import redis.clients.jedis.Jedis; @@ -32,22 +31,19 @@ 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 + * An implementation of {@link me.lucko.luckperms.api.MessagingService} using Redis. */ -@RequiredArgsConstructor -public class RedisMessaging implements MessagingService { - private static final String CHANNEL = "luckperms"; - +public class RedisMessaging extends AbstractMessagingService { private final LuckPermsPlugin plugin; private JedisPool jedisPool; private LPSub sub; + public RedisMessaging(LuckPermsPlugin plugin) { + super(plugin, "Redis"); + this.plugin = plugin; + } + public void init(String address, String password) { String[] addressSplit = address.split(":"); String host = addressSplit[0]; @@ -60,7 +56,7 @@ public class RedisMessaging implements MessagingService { } plugin.doAsync(() -> { - sub = new LPSub(plugin); + sub = new LPSub(this); try (Jedis jedis = jedisPool.getResource()) { jedis.subscribe(sub, CHANNEL); } catch (Exception e) { @@ -69,60 +65,28 @@ public class RedisMessaging implements MessagingService { }); } - public void shutdown() { + @Override + public void close() { sub.unsubscribe(); jedisPool.destroy(); } @Override - 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(); - } - }); + protected void sendMessage(String channel, String message) { + try (Jedis jedis = jedisPool.getResource()) { + jedis.publish(CHANNEL, message); + } catch (Exception e) { + e.printStackTrace(); + } } @RequiredArgsConstructor private static class LPSub extends JedisPubSub { - private final LuckPermsPlugin plugin; - private final Set receivedMsgs = Collections.synchronizedSet(new HashSet<>()); - - private UUID generateId() { - UUID uuid = UUID.randomUUID(); - receivedMsgs.add(uuid); - return uuid; - } + private final RedisMessaging parent; @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(); + parent.onMessage(channel, msg, null); } } diff --git a/pom.xml b/pom.xml index 069da0328..440f786ad 100644 --- a/pom.xml +++ b/pom.xml @@ -85,5 +85,9 @@ placeholderapi http://repo.extendedclip.com/content/repositories/placeholderapi/ + + lilypad-repo + http://ci.lilypadmc.org/plugin/repository/everything + 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 84409abf6..488817204 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java @@ -50,6 +50,7 @@ import me.lucko.luckperms.common.locale.NoopLocaleManager; import me.lucko.luckperms.common.locale.SimpleLocaleManager; import me.lucko.luckperms.common.managers.TrackManager; import me.lucko.luckperms.common.managers.impl.GenericTrackManager; +import me.lucko.luckperms.common.messaging.AbstractMessagingService; import me.lucko.luckperms.common.messaging.RedisMessaging; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.storage.StorageFactory; @@ -65,6 +66,7 @@ import me.lucko.luckperms.sponge.commands.SpongeMainCommand; import me.lucko.luckperms.sponge.contexts.WorldCalculator; import me.lucko.luckperms.sponge.managers.SpongeGroupManager; import me.lucko.luckperms.sponge.managers.SpongeUserManager; +import me.lucko.luckperms.sponge.messaging.BungeeMessagingService; import me.lucko.luckperms.sponge.service.LuckPermsService; import me.lucko.luckperms.sponge.service.ServiceCacheHousekeepingTask; import me.lucko.luckperms.sponge.service.base.LPSubjectCollection; @@ -146,7 +148,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { private SpongeGroupManager groupManager; private TrackManager trackManager; private Storage storage; - private RedisMessaging redisMessaging = null; + private AbstractMessagingService messagingService = null; private UuidCache uuidCache; private ApiProvider apiProvider; private me.lucko.luckperms.api.Logger log; @@ -185,18 +187,31 @@ public class LPSpongePlugin implements LuckPermsPlugin { // initialise datastore storage = StorageFactory.getInstance(this, StorageType.H2); - // initialise redis - if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) { + // initialise messaging + String messagingType = getConfiguration().get(ConfigKeys.MESSAGING_SERVICE).toLowerCase(); + if (messagingType.equals("redis")) { getLog().info("Loading redis..."); - redisMessaging = new RedisMessaging(this); - try { - redisMessaging.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD)); - getLog().info("Loaded redis successfully..."); - } catch (Exception e) { - getLog().info("Couldn't load redis..."); - e.printStackTrace(); - redisMessaging = null; + if (getConfiguration().get(ConfigKeys.REDIS_ENABLED)) { + RedisMessaging redis = new RedisMessaging(this); + try { + redis.init(getConfiguration().get(ConfigKeys.REDIS_ADDRESS), getConfiguration().get(ConfigKeys.REDIS_PASSWORD)); + getLog().info("Loaded redis successfully..."); + + messagingService = redis; + } catch (Exception e) { + getLog().warn("Couldn't load redis..."); + e.printStackTrace(); + } + } else { + getLog().warn("Messaging Service was set to redis, but redis is not enabled!"); } + } else if (messagingType.equals("bungee")) { + getLog().info("Loading bungee messaging service..."); + BungeeMessagingService bungeeMessaging = new BungeeMessagingService(this); + bungeeMessaging.init(); + messagingService = bungeeMessaging; + } else if (!messagingType.equals("none")) { + getLog().warn("Messaging service '" + messagingType + "' not recognised."); } // setup the update task buffer @@ -295,9 +310,9 @@ public class LPSpongePlugin implements LuckPermsPlugin { getLog().info("Closing datastore..."); storage.shutdown(); - if (redisMessaging != null) { - getLog().info("Closing redis..."); - redisMessaging.shutdown(); + if (messagingService != null) { + getLog().info("Closing messaging service..."); + messagingService.close(); } getLog().info("Unregistering API..."); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/messaging/BungeeMessagingService.java b/sponge/src/main/java/me/lucko/luckperms/sponge/messaging/BungeeMessagingService.java new file mode 100644 index 000000000..55bc1826d --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/messaging/BungeeMessagingService.java @@ -0,0 +1,83 @@ +/* + * 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.sponge.messaging; + +import com.google.common.collect.Iterables; + +import me.lucko.luckperms.common.messaging.AbstractMessagingService; +import me.lucko.luckperms.sponge.LPSpongePlugin; + +import org.spongepowered.api.Platform; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.network.ChannelBinding; +import org.spongepowered.api.network.ChannelBuf; +import org.spongepowered.api.network.RawDataListener; +import org.spongepowered.api.network.RemoteConnection; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +/** + * An implementation of {@link me.lucko.luckperms.api.MessagingService} using the plugin messaging channels. + */ +public class BungeeMessagingService extends AbstractMessagingService implements RawDataListener { + private final LPSpongePlugin plugin; + private ChannelBinding.RawDataChannel channel = null; + + public BungeeMessagingService(LPSpongePlugin plugin) { + super(plugin, "Bungee"); + this.plugin = plugin; + } + + public void init() { + channel = plugin.getGame().getChannelRegistrar().createRawChannel(plugin, CHANNEL); + channel.addListener(Platform.Type.SERVER, this); + } + + @Override + public void close() { + if (channel != null) { + plugin.getGame().getChannelRegistrar().unbindChannel(channel); + } + } + + @Override + protected void sendMessage(String channel, String message) { + plugin.getScheduler().createTaskBuilder().interval(10, TimeUnit.SECONDS).execute(task -> { + Collection players = plugin.getGame().getServer().getOnlinePlayers(); + if (players.isEmpty()) { + return; + } + + Player p = Iterables.getFirst(players, null); + this.channel.sendTo(p, buf -> buf.writeUTF(message)); + + }).submit(plugin); + } + + @Override + public void handlePayload(ChannelBuf buf, RemoteConnection connection, Platform.Type type) { + String msg = buf.readUTF(); + onMessage(CHANNEL, msg, null); + } +} diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index 121aeac94..d22dd1724 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -162,15 +162,22 @@ data { sync-minutes=3 } -# Settings for Redis. +# Settings for the messaging service # -# If enabled and configured, LuckPerms will use the Redis PubSub system to inform other +# If enabled and configured, LuckPerms will use the messaging 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. +# Data is NOT stored using this service. 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. # +# Available options: +# bungee ==> uses the plugin messaging channels. Must be enabled on all connected servers to work. +# redis ==> uses redis pub sub to push changes. Your redis server must be configured below. +# none ==> nothing +messaging-service="none" + +# Settings for Redis. # Port 6379 is used by default; set address to "host:port" if differs redis { enabled=false