diff --git a/pom.xml b/pom.xml index 39457bd..f31b3c3 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ redis.clients jedis - 2.0.0 + 2.2.1 net.md-5 diff --git a/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index cb6293d..e5a5475 100644 --- a/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -7,7 +7,10 @@ package com.imaginarycode.minecraft.redisbungee; import com.google.common.base.Joiner; -import com.google.common.collect.*; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; import com.google.common.io.ByteStreams; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; @@ -21,31 +24,48 @@ import net.md_5.bungee.api.event.ServerConnectedEvent; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.ScheduledTask; import net.md_5.bungee.event.EventHandler; import org.apache.commons.lang3.time.FastDateFormat; import org.yaml.snakeyaml.Yaml; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.exceptions.JedisConnectionException; +import redis.clients.jedis.exceptions.JedisException; import java.io.*; import java.util.*; import java.util.concurrent.TimeUnit; +/** + * The RedisBungee plugin. + *

+ * The only function of interest is {@link #getApi()}, which exposes some functions in this class. + */ public class RedisBungee extends Plugin implements Listener { private static final ServerPing.PlayerInfo[] EMPTY_PLAYERINFO = new ServerPing.PlayerInfo[]{}; - private static JedisPool pool; - private static String serverId; - private static List servers = Lists.newArrayList(); - private static RedisBungee plugin; + private RedisBungeeCommandSender commandSender = new RedisBungeeCommandSender(); + private JedisPool pool; + private String serverId; + private List servers = Lists.newArrayList(); + private RedisBungee plugin; + private static RedisBungeeAPI api; + private PubSubListener psl; + private ScheduledTask pubSubTask; private boolean canonicalGlist = true; /** - * Get a combined count of all players on this network. + * Fetch the {@link RedisBungeeAPI} object created on plugin start. * - * @return a count of all players found + * @return the {@link RedisBungeeAPI} object */ - public static int getCount() { + public static RedisBungeeAPI getApi() { + return api; + } + + public int getCount() { Jedis rsc = pool.getResource(); int c = 0; try { @@ -61,14 +81,7 @@ public class RedisBungee extends Plugin implements Listener { return c; } - /** - * Get a combined list of players on this network. - *

- * Note that this function returns an immutable {@link java.util.Set}. - * - * @return a Set with all players found - */ - public static Set getPlayers() { + public Set getPlayers() { Set players = new HashSet<>(); for (ProxiedPlayer pp : plugin.getProxy().getPlayers()) { players.add(pp.getName()); @@ -91,14 +104,7 @@ public class RedisBungee extends Plugin implements Listener { return ImmutableSet.copyOf(players); } - /** - * Get the server where the specified player is playing. This function also deals with the case of local players - * as well, and will return local information on them. - * - * @param name a player name - * @return a {@link net.md_5.bungee.api.config.ServerInfo} for the server the player is on. - */ - public static ServerInfo getServerFor(String name) { + public ServerInfo getServerFor(String name) { ServerInfo server = null; if (plugin.getProxy().getPlayer(name) != null) return plugin.getProxy().getPlayer(name).getServer().getInfo(); if (pool != null) { @@ -113,18 +119,11 @@ public class RedisBungee extends Plugin implements Listener { return server; } - private static long getUnixTimestamp() { + private long getUnixTimestamp() { return TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); } - /** - * Get the last time a player was on. If the player is currently online, this will return 0. If the player has not been recorded, - * this will return -1. Otherwise it will return a value in seconds. - * - * @param name a player name - * @return the last time a player was on, if online returns a 0 - */ - public static long getLastOnline(String name) { + public long getLastOnline(String name) { long time = -1L; if (plugin.getProxy().getPlayer(name) != null) return 0; if (pool != null) { @@ -231,18 +230,25 @@ public class RedisBungee extends Plugin implements Listener { } }); getProxy().getPluginManager().registerListener(this, this); + api = new RedisBungeeAPI(this); + psl = new PubSubListener(); + pubSubTask = getProxy().getScheduler().runAsync(this, psl); } } @Override public void onDisable() { if (pool != null) { + // Poison the PubSub listener + psl.poison(); + getProxy().getScheduler().cancel(this); Jedis tmpRsc = pool.getResource(); try { tmpRsc.set("server:" + serverId + ":playerCount", "0"); // reset for (String i : tmpRsc.smembers("server:" + serverId + ":usersOnline")) { tmpRsc.srem("server:" + serverId + ":usersOnline", i); } + } catch (JedisException | ClassCastException ignored) { } finally { pool.returnResource(tmpRsc); } @@ -354,4 +360,58 @@ public class RedisBungee extends Plugin implements Listener { reply.setVersion(old.getVersion()); event.setResponse(reply); } + + private class PubSubListener implements Runnable { + + private Jedis rsc; + private JedisPubSubHandler jpsh; + + @Override + public void run() { + try { + rsc = pool.getResource(); + jpsh = new JedisPubSubHandler(); + rsc.subscribe(jpsh, "redisbungee-" + serverId, "redisbungee-allservers"); + } catch (JedisException | ClassCastException ignored) { + } + } + + public void poison() { + jpsh.unsubscribe(); + pool.returnResource(rsc); + } + } + + private class JedisPubSubHandler extends JedisPubSub { + @Override + public void onMessage(String s, String s2) { + String cmd; + if (s2.startsWith("/")) { + cmd = s2.substring(1); + } else { + cmd = s2; + } + getProxy().getPluginManager().dispatchCommand(commandSender, cmd); + } + + @Override + public void onPMessage(String s, String s2, String s3) { + } + + @Override + public void onSubscribe(String s, int i) { + } + + @Override + public void onUnsubscribe(String s, int i) { + } + + @Override + public void onPUnsubscribe(String s, int i) { + } + + @Override + public void onPSubscribe(String s, int i) { + } + } } diff --git a/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java new file mode 100644 index 0000000..dfcdc06 --- /dev/null +++ b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java @@ -0,0 +1,67 @@ +/** + * Copyright © 2013 tuxed + * This work is free. You can redistribute it and/or modify it under the + * terms of the Do What The Fuck You Want To Public License, Version 2, + * as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + */ +package com.imaginarycode.minecraft.redisbungee; + +import net.md_5.bungee.api.config.ServerInfo; + +import java.util.Set; + +/** + * This class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungee#getApi()}. + * + * @author tuxed + * @since 0.2.3 + */ +public class RedisBungeeAPI { + private RedisBungee plugin; + + protected RedisBungeeAPI(RedisBungee plugin) { + this.plugin = plugin; + } + + /** + * Get a combined count of all players on this network. + * + * @return a count of all players found + */ + public int getPlayerCount() { + return plugin.getCount(); + } + + /** + * Get the last time a player was on. If the player is currently online, this will return 0. If the player has not been recorded, + * this will return -1. Otherwise it will return a value in seconds. + * + * @param player a player name + * @return the last time a player was on, if online returns a 0 + */ + public long getLastOnline(String player) { + return plugin.getLastOnline(player); + } + + /** + * Get the server where the specified player is playing. This function also deals with the case of local players + * as well, and will return local information on them. + * + * @param player a player name + * @return a {@link net.md_5.bungee.api.config.ServerInfo} for the server the player is on. + */ + public ServerInfo getServerFor(String player) { + return plugin.getServerFor(player); + } + + /** + * Get a combined list of players on this network. + *

+ * Note that this function returns an immutable {@link java.util.Set}. + * + * @return a Set with all players found + */ + public Set getPlayersOnline() { + return plugin.getPlayers(); + } +} diff --git a/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeCommandSender.java b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeCommandSender.java new file mode 100644 index 0000000..c70b1f1 --- /dev/null +++ b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeCommandSender.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2013 tuxed + * This work is free. You can redistribute it and/or modify it under the + * terms of the Do What The Fuck You Want To Public License, Version 2, + * as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + */ +package com.imaginarycode.minecraft.redisbungee; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; + +import java.util.Collection; + +/** + * This class is the CommandSender that RedisBungee uses to dispatch commands to BungeeCord. + * + * It inherits all permissions of the console command sender. Sending messages and modifying permissions are no-ops. + * + * @author tuxed + * @since 0.2.3 + */ +public class RedisBungeeCommandSender implements CommandSender { + @Override + public String getName() { + return "RedisBungee"; + } + + @Override + public void sendMessage(String s) { + // no-op + } + + @Override + public void sendMessages(String... strings) { + // no-op + } + + @Override + public Collection getGroups() { + return ProxyServer.getInstance().getConsole().getGroups(); + } + + @Override + public void addGroups(String... strings) { + // no-op + } + + @Override + public void removeGroups(String... strings) { + // no-op + } + + @Override + public boolean hasPermission(String s) { + return ProxyServer.getInstance().getConsole().hasPermission(s); + } + + @Override + public void setPermission(String s, boolean b) { + // no-op + } +}