RedisBungee 0.3 base code. A lot has changed. There is more to come.

This commit is contained in:
Tux 2014-04-20 01:12:28 -04:00
parent d3a6170e78
commit 1362739b27
17 changed files with 692 additions and 222 deletions

View File

@ -14,7 +14,7 @@
<groupId>com.imaginarycode.minecraft</groupId> <groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee</artifactId> <artifactId>RedisBungee</artifactId>
<version>0.2.6-SNAPSHOT</version> <version>0.3-SNAPSHOT</version>
<repositories> <repositories>
<repository> <repository>

View File

@ -12,7 +12,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.util.UUIDTranslator;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
@ -31,10 +33,7 @@ import redis.clients.jedis.exceptions.JedisException;
import java.io.*; import java.io.*;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Collections; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -51,6 +50,12 @@ public final class RedisBungee extends Plugin {
private static Configuration configuration; private static Configuration configuration;
@Getter @Getter
private JedisPool pool; private JedisPool pool;
@Getter
private RedisBungeeConsumer consumer;
@Getter
private UUIDTranslator uuidTranslator;
@Getter
private static Gson gson = new Gson();
private static RedisBungeeAPI api; private static RedisBungeeAPI api;
private static PubSubListener psl = null; private static PubSubListener psl = null;
private List<String> serverIds; private List<String> serverIds;
@ -81,7 +86,7 @@ public final class RedisBungee extends Plugin {
for (Map.Entry<String, String> entry : heartbeats.entrySet()) { for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
try { try {
long stamp = Long.valueOf(entry.getValue()); long stamp = Long.valueOf(entry.getValue());
if (stamp + 30000 < System.currentTimeMillis()) if (System.currentTimeMillis() < stamp + 30000)
servers.add(entry.getKey()); servers.add(entry.getKey());
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
} }
@ -99,9 +104,9 @@ public final class RedisBungee extends Plugin {
return psl; return psl;
} }
final Multimap<String, String> serversToPlayers() { final Multimap<String, UUID> serversToPlayers() {
ImmutableMultimap.Builder<String, String> multimapBuilder = ImmutableMultimap.builder(); ImmutableMultimap.Builder<String, UUID> multimapBuilder = ImmutableMultimap.builder();
for (String p : getPlayers()) { for (UUID p : getPlayers()) {
ServerInfo si = getServerFor(p); ServerInfo si = getServerFor(p);
if (si != null) if (si != null)
multimapBuilder = multimapBuilder.put(si.getName(), p); multimapBuilder = multimapBuilder.put(si.getName(), p);
@ -118,15 +123,19 @@ public final class RedisBungee extends Plugin {
if (pool != null) { if (pool != null) {
Jedis rsc = pool.getResource(); Jedis rsc = pool.getResource();
try { try {
for (String i : getServerIds()) { List<String> serverIds = getServerIds();
if (i.equals(configuration.getString("server-id"))) continue; Map<String, String> counts = rsc.hgetAll("playerCounts");
if (rsc.exists("server:" + i + ":playerCount")) for (Map.Entry<String, String> entry : counts.entrySet()) {
try { if (!serverIds.contains(entry.getKey()))
c += Integer.valueOf(rsc.get("server:" + i + ":playerCount")); continue;
} catch (NumberFormatException e) {
getLogger().severe("I found a funny number for " + i + "'s player count. Resetting it to 0."); if (entry.getKey().equals(configuration.getString("server-id"))) continue;
rsc.set("server:" + i + ":playerCount", "0");
} try {
c += Integer.valueOf(entry.getValue());
} catch (NumberFormatException e) {
rsc.hset("playerCounts", entry.getKey(), "0");
}
} }
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
// Redis server has disappeared! // Redis server has disappeared!
@ -140,23 +149,35 @@ public final class RedisBungee extends Plugin {
return c; return c;
} }
final Set<String> getLocalPlayers() { final Set<UUID> getLocalPlayers() {
ImmutableSet.Builder<String> setBuilder = ImmutableSet.builder(); ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
for (ProxiedPlayer pp : getProxy().getPlayers()) for (ProxiedPlayer pp : getProxy().getPlayers())
setBuilder = setBuilder.add(pp.getName()); setBuilder = setBuilder.add(pp.getUniqueId());
return setBuilder.build(); return setBuilder.build();
} }
final Set<String> getPlayers() { final Set<String> getLocalPlayersAsUuidStrings() {
ImmutableSet.Builder<String> setBuilder = ImmutableSet.<String>builder().addAll(getLocalPlayers()); ImmutableSet.Builder<String> setBuilder = ImmutableSet.builder();
for (ProxiedPlayer pp : getProxy().getPlayers())
setBuilder = setBuilder.add(pp.getUniqueId().toString());
return setBuilder.build();
}
final Set<UUID> getPlayers() {
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.<UUID>builder().addAll(getLocalPlayers());
if (pool != null) { if (pool != null) {
Jedis rsc = pool.getResource(); Jedis rsc = pool.getResource();
try { try {
for (String i : getServerIds()) { for (String i : getServerIds()) {
if (i.equals(configuration.getString("server-id"))) continue; if (i.equals(configuration.getString("server-id"))) continue;
Set<String> users = rsc.smembers("server:" + i + ":usersOnline"); Set<String> users = rsc.smembers("server:" + i + ":usersOnline");
if (users != null && !users.isEmpty()) if (users != null && !users.isEmpty()) {
setBuilder = setBuilder.addAll(users); for (String user : users) {
if (UUIDTranslator.UUID_PATTERN.matcher(user).find()) {
setBuilder = setBuilder.add(UUID.fromString(user));
}
}
}
} }
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
// Redis server has disappeared! // Redis server has disappeared!
@ -170,24 +191,24 @@ public final class RedisBungee extends Plugin {
return setBuilder.build(); return setBuilder.build();
} }
final Set<String> getPlayersOnServer(@NonNull String server) { final Set<UUID> getPlayersOnServer(@NonNull String server) {
checkArgument(getProxy().getServerInfo(server) != null, "server doesn't exist"); checkArgument(getProxy().getServerInfo(server) != null, "server doesn't exist");
return ImmutableSet.copyOf(serversToPlayers().get(server)); return ImmutableSet.copyOf(serversToPlayers().get(server));
} }
final ServerInfo getServerFor(@NonNull String name) { final ServerInfo getServerFor(@NonNull UUID uuid) {
ServerInfo server = null; ServerInfo server = null;
if (getProxy().getPlayer(name) != null) return getProxy().getPlayer(name).getServer().getInfo(); if (getProxy().getPlayer(uuid) != null) return getProxy().getPlayer(uuid).getServer().getInfo();
if (pool != null) { if (pool != null) {
Jedis tmpRsc = pool.getResource(); Jedis tmpRsc = pool.getResource();
try { try {
if (tmpRsc.hexists("player:" + name, "server")) if (tmpRsc.hexists("player:" + uuid, "server"))
server = getProxy().getServerInfo(tmpRsc.hget("player:" + name, "server")); server = getProxy().getServerInfo(tmpRsc.hget("player:" + uuid, "server"));
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
// Redis server has disappeared! // Redis server has disappeared!
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e); getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
pool.returnBrokenResource(tmpRsc); pool.returnBrokenResource(tmpRsc);
throw new RuntimeException("Unable to get server for " + name, e); throw new RuntimeException("Unable to get server for " + uuid, e);
} finally { } finally {
pool.returnResource(tmpRsc); pool.returnResource(tmpRsc);
} }
@ -195,21 +216,21 @@ public final class RedisBungee extends Plugin {
return server; return server;
} }
final long getLastOnline(@NonNull String name) { final long getLastOnline(@NonNull UUID uuid) {
long time = -1L; long time = -1L;
if (getProxy().getPlayer(name) != null) return 0; if (getProxy().getPlayer(uuid) != null) return 0;
if (pool != null) { if (pool != null) {
Jedis tmpRsc = pool.getResource(); Jedis tmpRsc = pool.getResource();
try { try {
if (tmpRsc.hexists("player:" + name, "online")) if (tmpRsc.hexists("player:" + uuid, "online"))
try { try {
time = Long.valueOf(tmpRsc.hget("player:" + name, "online")); time = Long.valueOf(tmpRsc.hget("player:" + uuid, "online"));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
getLogger().info("I found a funny number for when " + name + " was last online!"); getLogger().info("I found a funny number for when " + uuid + " was last online!");
boolean found = false; boolean found = false;
for (String proxyId : getServerIds()) { for (String proxyId : getServerIds()) {
if (proxyId.equals(configuration.getString("server-id"))) continue; if (proxyId.equals(configuration.getString("server-id"))) continue;
if (tmpRsc.sismember("server:" + proxyId + ":usersOnline", name)) { if (tmpRsc.sismember("server:" + proxyId + ":usersOnline", uuid.toString())) {
found = true; found = true;
break; break;
} }
@ -217,18 +238,18 @@ public final class RedisBungee extends Plugin {
String value = "0"; String value = "0";
if (!found) { if (!found) {
value = String.valueOf(System.currentTimeMillis()); value = String.valueOf(System.currentTimeMillis());
getLogger().info(name + " isn't online. Setting to current time."); getLogger().info(uuid + " isn't online. Setting to current time.");
} else { } else {
getLogger().info(name + " is online. Setting to 0. Please check your BungeeCord instances."); getLogger().info(uuid + " is online. Setting to 0. Please check your BungeeCord instances.");
getLogger().info("If they are working properly, and this error does not resolve in a few minutes, please let Tux know!"); getLogger().info("If they are working properly, and this error does not resolve in a few minutes, please let Tux know!");
} }
tmpRsc.hset("player:" + name, "online", value); tmpRsc.hset("player:" + uuid, "online", value);
} }
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
// Redis server has disappeared! // Redis server has disappeared!
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e); getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
pool.returnBrokenResource(tmpRsc); pool.returnBrokenResource(tmpRsc);
throw new RuntimeException("Unable to get last time online for " + name, e); throw new RuntimeException("Unable to get last time online for " + uuid, e);
} finally { } finally {
pool.returnResource(tmpRsc); pool.returnResource(tmpRsc);
} }
@ -236,20 +257,20 @@ public final class RedisBungee extends Plugin {
return time; return time;
} }
final InetAddress getIpAddress(@NonNull String name) { final InetAddress getIpAddress(@NonNull UUID uuid) {
if (getProxy().getPlayer(name) != null) if (getProxy().getPlayer(uuid) != null)
return getProxy().getPlayer(name).getAddress().getAddress(); return getProxy().getPlayer(uuid).getAddress().getAddress();
InetAddress ia = null; InetAddress ia = null;
if (pool != null) { if (pool != null) {
Jedis tmpRsc = pool.getResource(); Jedis tmpRsc = pool.getResource();
try { try {
if (tmpRsc.hexists("player:" + name, "ip")) if (tmpRsc.hexists("player:" + uuid, "ip"))
ia = InetAddress.getByName(tmpRsc.hget("player:" + name, "ip")); ia = InetAddress.getByName(tmpRsc.hget("player:" + uuid, "ip"));
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
// Redis server has disappeared! // Redis server has disappeared!
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e); getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
pool.returnBrokenResource(tmpRsc); pool.returnBrokenResource(tmpRsc);
throw new RuntimeException("Unable to fetch IP address for " + name, e); throw new RuntimeException("Unable to fetch IP address for " + uuid, e);
} catch (UnknownHostException ignored) { } catch (UnknownHostException ignored) {
// Best to just return null // Best to just return null
} finally { } finally {
@ -286,36 +307,20 @@ public final class RedisBungee extends Plugin {
if (pool != null) { if (pool != null) {
Jedis tmpRsc = pool.getResource(); Jedis tmpRsc = pool.getResource();
try { try {
tmpRsc.set("server:" + configuration.getString("server-id") + ":playerCount", "0"); // reset tmpRsc.hset("playerCounts", configuration.getString("server-id"), "0"); // reset
tmpRsc.hset("heartbeats", configuration.getString("server-id"), String.valueOf(System.currentTimeMillis())); tmpRsc.hset("heartbeats", configuration.getString("server-id"), String.valueOf(System.currentTimeMillis()));
if (tmpRsc.scard("server:" + configuration.getString("server-id") + ":usersOnline") > 0) {
for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline")) {
// Are they simply on a different proxy?
boolean found = false;
for (String proxyId : tmpRsc.smembers("servers")) {
if (proxyId.equals(configuration.getString("server-id"))) continue;
if (tmpRsc.sismember("server:" + proxyId + ":usersOnline", member)) {
found = true;
break;
}
}
if (!found)
RedisUtil.cleanUpPlayer(member, tmpRsc);
else
tmpRsc.srem("server:" + configuration.getString("server-id") + ":usersOnline", member);
}
}
} finally { } finally {
pool.returnResource(tmpRsc); pool.returnResource(tmpRsc);
} }
globalCount = getCurrentCount();
serverIds = getCurrentServerIds(); serverIds = getCurrentServerIds();
globalCount = getCurrentCount();
uuidTranslator = new UUIDTranslator(this);
getProxy().getScheduler().schedule(this, new Runnable() { getProxy().getScheduler().schedule(this, new Runnable() {
@Override @Override
public void run() { public void run() {
Jedis rsc = pool.getResource(); Jedis rsc = pool.getResource();
try { try {
rsc.set("server:" + configuration.getString("server-id") + ":playerCount", String.valueOf(getProxy().getOnlineCount())); rsc.hset("playerCounts", configuration.getString("server-id"), String.valueOf(getProxy().getOnlineCount()));
rsc.hset("heartbeats", configuration.getString("server-id"), String.valueOf(System.currentTimeMillis())); rsc.hset("heartbeats", configuration.getString("server-id"), String.valueOf(System.currentTimeMillis()));
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
// Redis server has disappeared! // Redis server has disappeared!
@ -324,16 +329,19 @@ public final class RedisBungee extends Plugin {
} finally { } finally {
pool.returnResource(rsc); pool.returnResource(rsc);
} }
globalCount = getCurrentCount();
serverIds = getCurrentServerIds(); serverIds = getCurrentServerIds();
globalCount = getCurrentCount();
} }
}, 0, 3, TimeUnit.SECONDS); }, 0, 3, TimeUnit.SECONDS);
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand()); consumer = new RedisBungeeConsumer(this);
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand()); new Thread(consumer, "RedisBungee Consumer Thread").start();
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand()); getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand()); getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll()); getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId()); getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerIds());
getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this)); getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this));
api = new RedisBungeeAPI(this); api = new RedisBungeeAPI(this);
psl = new PubSubListener(); psl = new PubSubListener();
@ -343,7 +351,7 @@ public final class RedisBungee extends Plugin {
public void run() { public void run() {
Jedis tmpRsc = pool.getResource(); Jedis tmpRsc = pool.getResource();
try { try {
Set<String> players = getLocalPlayers(); Set<String> players = getLocalPlayersAsUuidStrings();
for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline")) for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline"))
if (!players.contains(member)) { if (!players.contains(member)) {
// Are they simply on a different proxy? // Are they simply on a different proxy?
@ -368,7 +376,7 @@ public final class RedisBungee extends Plugin {
pool.returnResource(tmpRsc); pool.returnResource(tmpRsc);
} }
} }
}, 1, 3, TimeUnit.MINUTES); }, 0, 3, TimeUnit.MINUTES);
} }
getProxy().registerChannel("RedisBungee"); getProxy().registerChannel("RedisBungee");
} }
@ -378,6 +386,8 @@ public final class RedisBungee extends Plugin {
if (pool != null) { if (pool != null) {
// Poison the PubSub listener // Poison the PubSub listener
getProxy().getScheduler().cancel(this); getProxy().getScheduler().cancel(this);
getLogger().info("Waiting for consumer to finish writing data...");
consumer.stop();
Jedis tmpRsc = pool.getResource(); Jedis tmpRsc = pool.getResource();
try { try {
tmpRsc.set("server:" + configuration.getString("server-id") + ":playerCount", "0"); // reset tmpRsc.set("server:" + configuration.getString("server-id") + ":playerCount", "0"); // reset
@ -385,7 +395,7 @@ public final class RedisBungee extends Plugin {
for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline")) for (String member : tmpRsc.smembers("server:" + configuration.getString("server-id") + ":usersOnline"))
RedisUtil.cleanUpPlayer(member, tmpRsc); RedisUtil.cleanUpPlayer(member, tmpRsc);
} }
tmpRsc.srem("servers", configuration.getString("server-id")); tmpRsc.hdel("heartbeats", configuration.getString("server-id"));
} finally { } finally {
pool.returnResource(tmpRsc); pool.returnResource(tmpRsc);
} }
@ -437,13 +447,12 @@ public final class RedisBungee extends Plugin {
File crashFile = new File(getDataFolder(), "restarted_from_crash.txt"); File crashFile = new File(getDataFolder(), "restarted_from_crash.txt");
if (crashFile.exists()) if (crashFile.exists())
crashFile.delete(); crashFile.delete();
else if (rsc.sismember("servers", configuration.getString("server-id"))) { else if (rsc.hexists("heartbeat", configuration.getString("server-id"))) {
getLogger().severe("You have launched a possible imposter BungeeCord instance. Another instance is already running."); getLogger().severe("You have launched a possible imposter BungeeCord instance. Another instance is already running.");
getLogger().severe("For data consistency reasons, RedisBungee will now disable itself."); getLogger().severe("For data consistency reasons, RedisBungee will now disable itself.");
getLogger().severe("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check."); getLogger().severe("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check.");
throw new RuntimeException("Possible imposter instance!"); throw new RuntimeException("Possible imposter instance!");
} }
rsc.sadd("servers", configuration.getString("server-id"));
getLogger().log(Level.INFO, "Successfully connected to Redis."); getLogger().log(Level.INFO, "Successfully connected to Redis.");
} catch (JedisConnectionException e) { } catch (JedisConnectionException e) {
if (rsc != null) if (rsc != null)

View File

@ -13,6 +13,7 @@ import net.md_5.bungee.api.config.ServerInfo;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID;
/** /**
* This class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungee#getApi()}. * This class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungee#getApi()}.
@ -43,7 +44,7 @@ public class RedisBungeeAPI {
* @param player a player name * @param player a player name
* @return the last time a player was on, if online returns a 0 * @return the last time a player was on, if online returns a 0
*/ */
public final long getLastOnline(@NonNull String player) { public final long getLastOnline(@NonNull UUID player) {
return plugin.getLastOnline(player); return plugin.getLastOnline(player);
} }
@ -54,36 +55,38 @@ public class RedisBungeeAPI {
* @param player a player name * @param player a player name
* @return a {@link net.md_5.bungee.api.config.ServerInfo} for the server the player is on. * @return a {@link net.md_5.bungee.api.config.ServerInfo} for the server the player is on.
*/ */
public final ServerInfo getServerFor(@NonNull String player) { public final ServerInfo getServerFor(@NonNull UUID player) {
return plugin.getServerFor(player); return plugin.getServerFor(player);
} }
/** /**
* Get a combined list of players on this network. * Get a combined list of players on this network.
* <p> * <p/>
* <strong>Note that this function returns an immutable {@link java.util.Set}.</strong> * <strong>Note that this function returns an immutable {@link java.util.Set}.</strong>
* *
* @return a Set with all players found * @return a Set with all players found
*/ */
public final Set<String> getPlayersOnline() { public final Set<UUID> getPlayersOnline() {
return plugin.getPlayers(); return plugin.getPlayers();
} }
/** /**
* Get a full list of players on all servers. * Get a full list of players on all servers.
*
* @return a immutable Multimap with all players found on this server * @return a immutable Multimap with all players found on this server
* @since 0.2.5 * @since 0.2.5
*/ */
public final Multimap<String, String> getServerToPlayers() { public final Multimap<String, UUID> getServerToPlayers() {
return plugin.serversToPlayers(); return plugin.serversToPlayers();
} }
/** /**
* Get a list of players on the server with the given name. * Get a list of players on the server with the given name.
*
* @param server a server name * @param server a server name
* @return a Set with all players found on this server * @return a Set with all players found on this server
*/ */
public final Set<String> getPlayersOnServer(@NonNull String server) { public final Set<UUID> getPlayersOnServer(@NonNull String server) {
return plugin.getPlayersOnServer(server); return plugin.getPlayersOnServer(server);
} }
@ -93,7 +96,7 @@ public class RedisBungeeAPI {
* @param player a player name * @param player a player name
* @return if the player is online * @return if the player is online
*/ */
public final boolean isPlayerOnline(@NonNull String player) { public final boolean isPlayerOnline(@NonNull UUID player) {
return getLastOnline(player) == 0; return getLastOnline(player) == 0;
} }
@ -104,12 +107,13 @@ public class RedisBungeeAPI {
* @return an {@link java.net.InetAddress} if the player is online, null otherwise * @return an {@link java.net.InetAddress} if the player is online, null otherwise
* @since 0.2.4 * @since 0.2.4
*/ */
public final InetAddress getPlayerIp(@NonNull String player) { public final InetAddress getPlayerIp(@NonNull UUID player) {
return plugin.getIpAddress(player); return plugin.getIpAddress(player);
} }
/** /**
* Sends a proxy command to all proxies. * Sends a proxy command to all proxies.
*
* @param command the command to send and execute * @param command the command to send and execute
* @see #sendProxyCommand(String, String) * @see #sendProxyCommand(String, String)
* @since 0.2.5 * @since 0.2.5
@ -120,6 +124,7 @@ public class RedisBungeeAPI {
/** /**
* Sends a proxy command to the proxy with the given ID. "allservers" means all proxies. * Sends a proxy command to the proxy with the given ID. "allservers" means all proxies.
*
* @param proxyId a proxy ID * @param proxyId a proxy ID
* @param command the command to send and execute * @param command the command to send and execute
* @see #getServerId() * @see #getServerId()
@ -132,9 +137,10 @@ public class RedisBungeeAPI {
/** /**
* Get the current BungeeCord server ID for this server. * Get the current BungeeCord server ID for this server.
*
* @return the current server ID * @return the current server ID
* @since 0.2.5
* @see #getAllServers() * @see #getAllServers()
* @since 0.2.5
*/ */
public final String getServerId() { public final String getServerId() {
return RedisBungee.getConfiguration().getString("server-id"); return RedisBungee.getConfiguration().getString("server-id");
@ -142,9 +148,10 @@ public class RedisBungeeAPI {
/** /**
* Get all the linked proxies in this network. * Get all the linked proxies in this network.
*
* @return the list of all proxies * @return the list of all proxies
* @since 0.2.5
* @see #getServerId() * @see #getServerId()
* @since 0.2.5
*/ */
public final List<String> getAllServers() { public final List<String> getAllServers() {
return plugin.getServerIds(); return plugin.getServerIds();
@ -169,4 +176,26 @@ public class RedisBungeeAPI {
public final void unregisterPubSubChannels(String... channels) { public final void unregisterPubSubChannels(String... channels) {
RedisBungee.getPubSubListener().removeChannel(channels); RedisBungee.getPubSubListener().removeChannel(channels);
} }
/**
* Fetch a name from the specified UUID.
*
* @param uuid the UUID to fetch the name for
* @return the name for the UUID
* @since 0.3
*/
public final String getNameFromUuid(UUID uuid) {
return plugin.getUuidTranslator().getNameFromUuid(uuid);
}
/**
* Fetch a UUID from the specified name.
*
* @param name the UUID to fetch the name for
* @return the UUID for the name
* @since 0.3
*/
public final UUID getUuidFromName(String name) {
return plugin.getUuidTranslator().getTranslatedUuid(name);
}
} }

View File

@ -16,7 +16,7 @@ import java.util.Collections;
/** /**
* This class is the CommandSender that RedisBungee uses to dispatch commands to BungeeCord. * This class is the CommandSender that RedisBungee uses to dispatch commands to BungeeCord.
* <p> * <p/>
* It inherits all permissions of the console command sender. Sending messages and modifying permissions are no-ops. * It inherits all permissions of the console command sender. Sending messages and modifying permissions are no-ops.
* *
* @author tuxed * @author tuxed

View File

@ -7,6 +7,7 @@
package com.imaginarycode.minecraft.redisbungee; package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
@ -18,7 +19,9 @@ import net.md_5.bungee.api.plugin.Command;
import java.net.InetAddress; import java.net.InetAddress;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID;
/** /**
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen. * This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
@ -37,119 +40,158 @@ class RedisBungeeCommands {
new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create(); new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
public static class GlistCommand extends Command { public static class GlistCommand extends Command {
GlistCommand() { private final RedisBungee plugin;
GlistCommand(RedisBungee plugin) {
super("glist", "bungeecord.command.list", "redisbungee", "rglist"); super("glist", "bungeecord.command.list", "redisbungee", "rglist");
this.plugin = plugin;
} }
@Override @Override
public void execute(CommandSender sender, String[] args) { public void execute(final CommandSender sender, final String[] args) {
int count = RedisBungee.getApi().getPlayerCount(); plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW).append(String.valueOf(count)) @Override
.append(" player(s) are currently online.").create(); public void run() {
if (args.length > 0 && args[0].equals("showall")) { int count = RedisBungee.getApi().getPlayerCount();
if (RedisBungee.getConfiguration().getBoolean("canonical-glist", true)) { BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW).append(String.valueOf(count))
Multimap<String, String> serverToPlayers = RedisBungee.getApi().getServerToPlayers(); .append(" player(s) are currently online.").create();
for (String server : new TreeSet<>(serverToPlayers.keySet())) { if (args.length > 0 && args[0].equals("showall")) {
TextComponent serverName = new TextComponent(); if (RedisBungee.getConfiguration().getBoolean("canonical-glist", true)) {
serverName.setColor(ChatColor.GREEN); Multimap<String, UUID> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
serverName.setText("[" + server + "] "); Multimap<String, String> human = HashMultimap.create();
TextComponent serverCount = new TextComponent(); for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
serverCount.setColor(ChatColor.YELLOW); human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue()));
serverCount.setText("(" + serverToPlayers.get(server).size() + "): "); }
TextComponent serverPlayers = new TextComponent(); for (String server : new TreeSet<>(serverToPlayers.keySet())) {
serverPlayers.setColor(ChatColor.WHITE); TextComponent serverName = new TextComponent();
serverPlayers.setText(Joiner.on(", ").join(serverToPlayers.get(server))); serverName.setColor(ChatColor.GREEN);
sender.sendMessage(serverName, serverCount, serverPlayers); serverName.setText("[" + server + "] ");
TextComponent serverCount = new TextComponent();
serverCount.setColor(ChatColor.YELLOW);
serverCount.setText("(" + serverToPlayers.get(server).size() + "): ");
TextComponent serverPlayers = new TextComponent();
serverPlayers.setColor(ChatColor.WHITE);
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
sender.sendMessage(serverName, serverCount, serverPlayers);
}
} else {
sender.sendMessage(new ComponentBuilder("Players: " + Joiner.on(", ").join(RedisBungee.getApi().getPlayersOnline()))
.color(ChatColor.YELLOW).create());
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
} }
} else {
sender.sendMessage(new ComponentBuilder("Players: " + Joiner.on(", ").join(RedisBungee.getApi().getPlayersOnline()))
.color(ChatColor.YELLOW).create());
} }
sender.sendMessage(playersOnline); });
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
}
} }
} }
public static class FindCommand extends Command { public static class FindCommand extends Command {
FindCommand() { private final RedisBungee plugin;
FindCommand(RedisBungee plugin) {
super("find", "bungeecord.command.find", "rfind"); super("find", "bungeecord.command.find", "rfind");
this.plugin = plugin;
} }
@Override @Override
public void execute(CommandSender sender, String[] args) { public void execute(final CommandSender sender, final String[] args) {
if (args.length > 0) { plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
ServerInfo si = RedisBungee.getApi().getServerFor(args[0]); @Override
if (si != null) { public void run() {
TextComponent message = new TextComponent(); if (args.length > 0) {
message.setColor(ChatColor.BLUE); ServerInfo si = RedisBungee.getApi().getServerFor(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
message.setText(args[0] + " is on " + si.getName() + "."); if (si != null) {
sender.sendMessage(message); TextComponent message = new TextComponent();
} else { message.setColor(ChatColor.BLUE);
sender.sendMessage(PLAYER_NOT_FOUND); message.setText(args[0] + " is on " + si.getName() + ".");
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
} }
} else { });
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
} }
} }
public static class LastSeenCommand extends Command { public static class LastSeenCommand extends Command {
LastSeenCommand() { private final RedisBungee plugin;
LastSeenCommand(RedisBungee plugin) {
super("lastseen", "redisbungee.command.lastseen", "rlastseen"); super("lastseen", "redisbungee.command.lastseen", "rlastseen");
this.plugin = plugin;
} }
@Override @Override
public void execute(CommandSender sender, String[] args) { public void execute(final CommandSender sender, final String[] args) {
if (args.length > 0) { plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
long secs = RedisBungee.getApi().getLastOnline(args[0]); @Override
TextComponent message = new TextComponent(); public void run() {
if (secs == 0) { if (args.length > 0) {
message.setColor(ChatColor.GREEN); long secs = RedisBungee.getApi().getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
message.setText(args[0] + " is currently online."); TextComponent message = new TextComponent();
sender.sendMessage(message); if (secs == 0) {
} else if (secs != -1) { message.setColor(ChatColor.GREEN);
message.setColor(ChatColor.BLUE); message.setText(args[0] + " is currently online.");
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); sender.sendMessage(message);
sender.sendMessage(message); } else if (secs != -1) {
} else { message.setColor(ChatColor.BLUE);
message.setColor(ChatColor.RED); message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
message.setText(args[0] + " has never been online."); sender.sendMessage(message);
sender.sendMessage(message); } else {
message.setColor(ChatColor.RED);
message.setText(args[0] + " has never been online.");
sender.sendMessage(message);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
} }
} else { });
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
} }
} }
public static class IpCommand extends Command { public static class IpCommand extends Command {
IpCommand() { private final RedisBungee plugin;
IpCommand(RedisBungee plugin) {
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip"); super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
this.plugin = plugin;
} }
@Override @Override
public void execute(CommandSender sender, String[] args) { public void execute(final CommandSender sender, final String[] args) {
if (args.length > 0) { plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
InetAddress ia = RedisBungee.getApi().getPlayerIp(args[0]); @Override
if (ia != null) { public void run() {
TextComponent message = new TextComponent(); if (args.length > 0) {
message.setColor(ChatColor.GREEN); InetAddress ia = RedisBungee.getApi().getPlayerIp(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
message.setText(args[0] + " is connected from " + ia.toString() + "."); if (ia != null) {
} else { TextComponent message = new TextComponent();
sender.sendMessage(PLAYER_NOT_FOUND); message.setColor(ChatColor.GREEN);
message.setText(args[0] + " is connected from " + ia.toString() + ".");
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
} }
} else { });
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
} }
} }
public static class SendToAll extends Command { public static class SendToAll extends Command {
SendToAll() { private final RedisBungee plugin;
SendToAll(RedisBungee plugin) {
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall"); super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
this.plugin = plugin;
} }
@Override @Override
@ -167,8 +209,11 @@ class RedisBungeeCommands {
} }
public static class ServerId extends Command { public static class ServerId extends Command {
ServerId() { private final RedisBungee plugin;
ServerId(RedisBungee plugin) {
super("serverid", "redisbungee.command.serverid", "rserverid"); super("serverid", "redisbungee.command.serverid", "rserverid");
this.plugin = plugin;
} }
@Override @Override
@ -179,4 +224,18 @@ class RedisBungeeCommands {
sender.sendMessage(textComponent); sender.sendMessage(textComponent);
} }
} }
public static class ServerIds extends Command {
public ServerIds() {
super("serverids", "redisbungee.command.serverids");
}
@Override
public void execute(CommandSender sender, String[] strings) {
TextComponent textComponent = new TextComponent();
textComponent.setText("All server IDs: " + Joiner.on(", ").join(RedisBungee.getApi().getAllServers()));
textComponent.setColor(ChatColor.YELLOW);
sender.sendMessage(textComponent);
}
}
} }

View File

@ -0,0 +1,73 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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 com.imaginarycode.minecraft.redisbungee.consumerevents.ConsumerEvent;
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerChangedServerConsumerEvent;
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedInConsumerEvent;
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedOffConsumerEvent;
import lombok.RequiredArgsConstructor;
import redis.clients.jedis.Jedis;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@RequiredArgsConstructor
public class RedisBungeeConsumer implements Runnable {
private final RedisBungee plugin;
private BlockingQueue<ConsumerEvent> consumerQueue = new LinkedBlockingQueue<>();
private boolean stopped = false;
@Override
public void run() {
Jedis jedis = plugin.getPool().getResource();
try {
while (!stopped) {
ConsumerEvent event;
try {
event = consumerQueue.take();
} catch (InterruptedException e) {
continue;
}
handle(event, jedis);
}
for (ConsumerEvent event : consumerQueue)
handle(event, jedis);
consumerQueue.clear();
} finally {
plugin.getPool().returnResource(jedis);
}
}
private void handle(ConsumerEvent event, Jedis jedis) {
if (event instanceof PlayerLoggedInConsumerEvent) {
PlayerLoggedInConsumerEvent event1 = (PlayerLoggedInConsumerEvent) event;
jedis.sadd("server:" + RedisBungee.getConfiguration().getString("server-id", "") + ":usersOnline", event1.getPlayer().getUniqueId().toString());
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "online", "0");
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "ip", event1.getPlayer().getAddress().getAddress().getHostAddress());
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "name", event1.getPlayer().getName());
jedis.hset("uuids", event1.getPlayer().getName(), event1.getPlayer().getUniqueId().toString());
} else if (event instanceof PlayerLoggedOffConsumerEvent) {
PlayerLoggedOffConsumerEvent event1 = (PlayerLoggedOffConsumerEvent) event;
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "online", String.valueOf(System.currentTimeMillis()));
RedisUtil.cleanUpPlayer(event1.getPlayer().getUniqueId().toString(), jedis);
} else if (event instanceof PlayerChangedServerConsumerEvent) {
PlayerChangedServerConsumerEvent event1 = (PlayerChangedServerConsumerEvent) event;
jedis.hset("player:" + event1.getPlayer().getUniqueId().toString(), "server", event1.getNewServer().getName());
}
}
public void queue(ConsumerEvent event) {
if (!stopped)
consumerQueue.add(event);
}
public void stop() {
stopped = true;
while (!consumerQueue.isEmpty()) ;
}
}

View File

@ -1,9 +1,18 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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; package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerChangedServerConsumerEvent;
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedInConsumerEvent;
import com.imaginarycode.minecraft.redisbungee.consumerevents.PlayerLoggedOffConsumerEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.ServerPing;
@ -14,7 +23,9 @@ import net.md_5.bungee.event.EventHandler;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID;
@AllArgsConstructor @AllArgsConstructor
public class RedisBungeeListener implements Listener { public class RedisBungeeListener implements Listener {
@ -40,60 +51,17 @@ public class RedisBungeeListener implements Listener {
@EventHandler @EventHandler
public void onPlayerConnect(final PostLoginEvent event) { public void onPlayerConnect(final PostLoginEvent event) {
if (plugin.getPool() != null) { plugin.getConsumer().queue(new PlayerLoggedInConsumerEvent(event.getPlayer()));
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
Jedis rsc = plugin.getPool().getResource();
try {
rsc.sadd("server:" + RedisBungee.getConfiguration().getString("server-id", "") + ":usersOnline", event.getPlayer().getName());
rsc.hset("player:" + event.getPlayer().getName(), "online", "0");
rsc.hset("player:" + event.getPlayer().getName(), "ip", event.getPlayer().getAddress().getAddress().getHostAddress());
rsc.hset("player:" + event.getPlayer().getName(), "name", event.getPlayer().getName());
} finally {
plugin.getPool().returnResource(rsc);
}
}
});
}
// I used to have a task that eagerly waited for the user to be connected.
// Well, upon further inspection of BungeeCord's source code, this turned
// out to not be needed at all, since ServerConnectedEvent is called anyway.
} }
@EventHandler @EventHandler
public void onPlayerDisconnect(final PlayerDisconnectEvent event) { public void onPlayerDisconnect(final PlayerDisconnectEvent event) {
if (plugin.getPool() != null) { plugin.getConsumer().queue(new PlayerLoggedOffConsumerEvent(event.getPlayer()));
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
Jedis rsc = plugin.getPool().getResource();
try {
rsc.hset("player:" + event.getPlayer().getName(), "online", String.valueOf(System.currentTimeMillis()));
RedisUtil.cleanUpPlayer(event.getPlayer().getName(), rsc);
} finally {
plugin.getPool().returnResource(rsc);
}
}
});
}
} }
@EventHandler @EventHandler
public void onServerChange(final ServerConnectedEvent event) { public void onServerChange(final ServerConnectedEvent event) {
if (plugin.getPool() != null) { plugin.getConsumer().queue(new PlayerChangedServerConsumerEvent(event.getPlayer(), event.getServer().getInfo()));
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
Jedis rsc = plugin.getPool().getResource();
try {
rsc.hset("player:" + event.getPlayer().getName(), "server", event.getServer().getInfo().getName());
} finally {
plugin.getPool().returnResource(rsc);
}
}
});
}
} }
@EventHandler @EventHandler
@ -101,11 +69,11 @@ public class RedisBungeeListener implements Listener {
ServerPing old = event.getResponse(); ServerPing old = event.getResponse();
ServerPing reply = new ServerPing(); ServerPing reply = new ServerPing();
if (RedisBungee.getConfiguration().getBoolean("player-list-in-ping", false)) { if (RedisBungee.getConfiguration().getBoolean("player-list-in-ping", false)) {
Set<String> players = plugin.getPlayers(); Set<UUID> players = plugin.getPlayers();
ServerPing.PlayerInfo[] info = new ServerPing.PlayerInfo[players.size()]; ServerPing.PlayerInfo[] info = new ServerPing.PlayerInfo[players.size()];
int idx = 0; int idx = 0;
for (String player : players) { for (UUID player : players) {
info[idx] = new ServerPing.PlayerInfo(player, ""); info[idx] = new ServerPing.PlayerInfo(plugin.getUuidTranslator().getNameFromUuid(player), "");
idx++; idx++;
} }
reply.setPlayers(new ServerPing.Players(old.getPlayers().getMax(), players.size(), info)); reply.setPlayers(new ServerPing.Players(old.getPlayers().getMax(), players.size(), info));
@ -130,18 +98,21 @@ public class RedisBungeeListener implements Listener {
switch (subchannel) { switch (subchannel) {
case "PlayerList": case "PlayerList":
out.writeUTF("Players"); out.writeUTF("Players");
Set<String> source = Collections.emptySet(); Set<UUID> original = Collections.emptySet();
type = in.readUTF(); type = in.readUTF();
if (type.equals("ALL")) { if (type.equals("ALL")) {
out.writeUTF("ALL"); out.writeUTF("ALL");
source = plugin.getPlayers(); original = plugin.getPlayers();
} else { } else {
try { try {
source = plugin.getPlayersOnServer(type); original = plugin.getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) { } catch (IllegalArgumentException ignored) {
} }
} }
out.writeUTF(Joiner.on(',').join(source)); Set<String> players = new HashSet<>();
for (UUID uuid : original)
players.add(plugin.getUuidTranslator().getNameFromUuid(uuid));
out.writeUTF(Joiner.on(',').join(players));
break; break;
case "PlayerCount": case "PlayerCount":
out.writeUTF("PlayerCount"); out.writeUTF("PlayerCount");
@ -163,7 +134,7 @@ public class RedisBungeeListener implements Listener {
String user = in.readUTF(); String user = in.readUTF();
out.writeUTF("LastOnline"); out.writeUTF("LastOnline");
out.writeUTF(user); out.writeUTF(user);
out.writeLong(plugin.getLastOnline(user)); out.writeLong(plugin.getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(user)));
break; break;
default: default:
break; break;

View File

@ -1,3 +1,9 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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; package com.imaginarycode.minecraft.redisbungee;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;

View File

@ -0,0 +1,10 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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.consumerevents;
public interface ConsumerEvent {
}

View File

@ -0,0 +1,19 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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.consumerevents;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@AllArgsConstructor
@Getter
public class PlayerChangedServerConsumerEvent implements ConsumerEvent {
private final ProxiedPlayer player;
private final ServerInfo newServer;
}

View File

@ -0,0 +1,17 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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.consumerevents;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@AllArgsConstructor
@Getter
public class PlayerLoggedInConsumerEvent implements ConsumerEvent {
private final ProxiedPlayer player;
}

View File

@ -0,0 +1,17 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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.consumerevents;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@AllArgsConstructor
@Getter
public class PlayerLoggedOffConsumerEvent implements ConsumerEvent {
private final ProxiedPlayer player;
}

View File

@ -11,7 +11,7 @@ import net.md_5.bungee.api.plugin.Event;
/** /**
* This event is posted when a PubSub message is received. * This event is posted when a PubSub message is received.
* * <p>
* <strong>Warning</strong>: This event is fired in a separate thread! * <strong>Warning</strong>: This event is fired in a separate thread!
* *
* @since 0.2.6 * @since 0.2.6

View File

@ -0,0 +1,51 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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.util;
import com.google.common.collect.ImmutableList;
import com.google.gson.reflect.TypeToken;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
/* Credits to evilmidget38 for this class. I modified it to use Gson. */
public class NameFetcher implements Callable<Map<UUID, String>> {
private static final String PROFILE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/";
private final List<UUID> uuids;
public NameFetcher(List<UUID> uuids) {
this.uuids = ImmutableList.copyOf(uuids);
}
@Override
public Map<UUID, String> call() throws Exception {
Map<UUID, String> uuidStringMap = new HashMap<>();
for (UUID uuid : uuids) {
HttpURLConnection connection = (HttpURLConnection) new URL(PROFILE_URL + uuid.toString().replace("-", "")).openConnection();
Map<String, String> response = RedisBungee.getGson().fromJson(new InputStreamReader(connection.getInputStream()), new TypeToken<Map<String, String>>() {
}.getType());
String name = response.get("name");
if (name == null) {
continue;
}
String cause = response.get("cause");
String errorMessage = response.get("errorMessage");
if (cause != null && cause.length() > 0) {
throw new IllegalStateException(errorMessage);
}
uuidStringMap.put(uuid, name);
}
return uuidStringMap;
}
}

View File

@ -0,0 +1,102 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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.util;
import com.google.common.collect.ImmutableList;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.Callable;
/* Credits to evilmidget38 for this class. I modified it to use Gson. */
public class UUIDFetcher implements Callable<Map<String, UUID>> {
private static final double PROFILES_PER_REQUEST = 100;
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
private final List<String> names;
private final boolean rateLimiting;
public UUIDFetcher(List<String> names, boolean rateLimiting) {
this.names = ImmutableList.copyOf(names);
this.rateLimiting = rateLimiting;
}
public UUIDFetcher(List<String> names) {
this(names, true);
}
public Map<String, UUID> call() throws Exception {
Map<String, UUID> uuidMap = new HashMap<>();
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
for (int i = 0; i < requests; i++) {
HttpURLConnection connection = createConnection();
String body = RedisBungee.getGson().toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
writeBody(connection, body);
Profile[] array = RedisBungee.getGson().fromJson(new InputStreamReader(connection.getInputStream()), Profile[].class);
for (Profile profile : array) {
UUID uuid = UUIDFetcher.getUUID(profile.id);
uuidMap.put(profile.name, uuid);
}
if (rateLimiting && i != requests - 1) {
Thread.sleep(100L);
}
}
return uuidMap;
}
private static void writeBody(HttpURLConnection connection, String body) throws Exception {
OutputStream stream = connection.getOutputStream();
stream.write(body.getBytes());
stream.flush();
stream.close();
}
private static HttpURLConnection createConnection() throws Exception {
URL url = new URL(PROFILE_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
private static UUID getUUID(String id) {
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32));
}
public static byte[] toBytes(UUID uuid) {
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
byteBuffer.putLong(uuid.getMostSignificantBits());
byteBuffer.putLong(uuid.getLeastSignificantBits());
return byteBuffer.array();
}
public static UUID fromBytes(byte[] array) {
if (array.length != 16) {
throw new IllegalArgumentException("Illegal byte array length: " + array.length);
}
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
long mostSignificant = byteBuffer.getLong();
long leastSignificant = byteBuffer.getLong();
return new UUID(mostSignificant, leastSignificant);
}
public static UUID getUUIDOf(String name) throws Exception {
return new UUIDFetcher(Collections.singletonList(name)).call().get(name);
}
private class Profile {
String id;
String name;
}
}

View File

@ -0,0 +1,107 @@
/**
* Copyright © 2013 tuxed <write@imaginarycode.com>
* 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.util;
import com.google.common.base.Charsets;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ProxyServer;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.UUID;
import java.util.logging.Level;
import java.util.regex.Pattern;
@RequiredArgsConstructor
public class UUIDTranslator {
private final RedisBungee plugin;
private BiMap<String, UUID> uuidMap = Maps.synchronizedBiMap(HashBiMap.<String, UUID>create());
public static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
public UUID getTranslatedUuid(String player) {
if (ProxyServer.getInstance().getPlayer(player) != null)
return ProxyServer.getInstance().getPlayer(player).getUniqueId();
UUID uuid = uuidMap.get(player);
if (uuid != null)
return uuid;
if (!plugin.getProxy().getConfig().isOnlineMode()) {
uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
uuidMap.put(player, uuid);
return uuid;
}
// Okay, it wasn't locally cached. Let's try Redis.
Jedis jedis = plugin.getPool().getResource();
try {
String stored = jedis.hget("uuids", player);
if (stored != null && UUID_PATTERN.matcher(stored).find()) {
// This is it!
uuid = UUID.fromString(stored);
uuidMap.put(stored, UUID.fromString(stored));
return uuid;
}
// That didn't work. Let's ask Mojang.
uuid = UUIDFetcher.getUUIDOf(player);
if (uuid != null) {
uuidMap.put(stored, uuid);
jedis.hset("uuids", player, uuid.toString());
}
return uuid;
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Unable to fetch UUID for " + player, e);
return null;
} finally {
plugin.getPool().returnResource(jedis);
}
}
public String getNameFromUuid(UUID player) {
if (ProxyServer.getInstance().getPlayer(player) != null)
return ProxyServer.getInstance().getPlayer(player).getName();
String name = uuidMap.inverse().get(player);
if (name != null)
return name;
// Okay, it wasn't locally cached. Let's try Redis.
Jedis jedis = plugin.getPool().getResource();
try {
String stored = jedis.hget("player:" + player, "name");
if (stored != null) {
name = stored;
uuidMap.put(name, player);
return name;
}
// That didn't work. Let's ask Mojang.
name = new NameFetcher(Collections.singletonList(player)).call().get(player);
if (name != null) {
jedis.hset("player:" + player, "name", name);
uuidMap.put(name, player);
return name;
}
return null;
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Unable to fetch name for " + player, e);
return null;
} finally {
plugin.getPool().returnResource(jedis);
}
}
}

View File

@ -1,6 +1,6 @@
name: RedisBungee name: RedisBungee
main: com.imaginarycode.minecraft.redisbungee.RedisBungee main: com.imaginarycode.minecraft.redisbungee.RedisBungee
version: 0.2.6 version: 0.3
author: tuxed author: tuxed
# This is used so that we can automagically override default BungeeCord behavior. # This is used so that we can automagically override default BungeeCord behavior.
softDepends: ["cmd_find", "cmd_list"] softDepends: ["cmd_find", "cmd_list"]