2
0
mirror of https://github.com/proxiodev/RedisBungee.git synced 2024-12-23 18:48:02 +00:00

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>
<artifactId>RedisBungee</artifactId>
<version>0.2.6-SNAPSHOT</version>
<version>0.3-SNAPSHOT</version>
<repositories>
<repository>

View File

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

View File

@ -13,6 +13,7 @@ import net.md_5.bungee.api.config.ServerInfo;
import java.net.InetAddress;
import java.util.List;
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()}.
@ -43,7 +44,7 @@ public class RedisBungeeAPI {
* @param player a player name
* @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);
}
@ -54,36 +55,38 @@ public class RedisBungeeAPI {
* @param player a player name
* @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);
}
/**
* Get a combined list of players on this network.
* <p>
* <p/>
* <strong>Note that this function returns an immutable {@link java.util.Set}.</strong>
*
* @return a Set with all players found
*/
public final Set<String> getPlayersOnline() {
public final Set<UUID> getPlayersOnline() {
return plugin.getPlayers();
}
/**
* Get a full list of players on all servers.
*
* @return a immutable Multimap with all players found on this server
* @since 0.2.5
*/
public final Multimap<String, String> getServerToPlayers() {
public final Multimap<String, UUID> getServerToPlayers() {
return plugin.serversToPlayers();
}
/**
* Get a list of players on the server with the given name.
*
* @param server a server name
* @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);
}
@ -93,7 +96,7 @@ public class RedisBungeeAPI {
* @param player a player name
* @return if the player is online
*/
public final boolean isPlayerOnline(@NonNull String player) {
public final boolean isPlayerOnline(@NonNull UUID player) {
return getLastOnline(player) == 0;
}
@ -104,12 +107,13 @@ public class RedisBungeeAPI {
* @return an {@link java.net.InetAddress} if the player is online, null otherwise
* @since 0.2.4
*/
public final InetAddress getPlayerIp(@NonNull String player) {
public final InetAddress getPlayerIp(@NonNull UUID player) {
return plugin.getIpAddress(player);
}
/**
* Sends a proxy command to all proxies.
*
* @param command the command to send and execute
* @see #sendProxyCommand(String, String)
* @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.
*
* @param proxyId a proxy ID
* @param command the command to send and execute
* @see #getServerId()
@ -132,9 +137,10 @@ public class RedisBungeeAPI {
/**
* Get the current BungeeCord server ID for this server.
*
* @return the current server ID
* @since 0.2.5
* @see #getAllServers()
* @since 0.2.5
*/
public final String getServerId() {
return RedisBungee.getConfiguration().getString("server-id");
@ -142,9 +148,10 @@ public class RedisBungeeAPI {
/**
* Get all the linked proxies in this network.
*
* @return the list of all proxies
* @since 0.2.5
* @see #getServerId()
* @since 0.2.5
*/
public final List<String> getAllServers() {
return plugin.getServerIds();
@ -169,4 +176,26 @@ public class RedisBungeeAPI {
public final void unregisterPubSubChannels(String... 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.
* <p>
* <p/>
* It inherits all permissions of the console command sender. Sending messages and modifying permissions are no-ops.
*
* @author tuxed

View File

@ -7,6 +7,7 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import net.md_5.bungee.api.ChatColor;
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.text.SimpleDateFormat;
import java.util.Map;
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.
@ -37,119 +40,158 @@ class RedisBungeeCommands {
new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
public static class GlistCommand extends Command {
GlistCommand() {
private final RedisBungee plugin;
GlistCommand(RedisBungee plugin) {
super("glist", "bungeecord.command.list", "redisbungee", "rglist");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
int count = RedisBungee.getApi().getPlayerCount();
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW).append(String.valueOf(count))
.append(" player(s) are currently online.").create();
if (args.length > 0 && args[0].equals("showall")) {
if (RedisBungee.getConfiguration().getBoolean("canonical-glist", true)) {
Multimap<String, String> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
TextComponent serverName = new TextComponent();
serverName.setColor(ChatColor.GREEN);
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(serverToPlayers.get(server)));
sender.sendMessage(serverName, serverCount, serverPlayers);
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
int count = RedisBungee.getApi().getPlayerCount();
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW).append(String.valueOf(count))
.append(" player(s) are currently online.").create();
if (args.length > 0 && args[0].equals("showall")) {
if (RedisBungee.getConfiguration().getBoolean("canonical-glist", true)) {
Multimap<String, UUID> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue()));
}
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
TextComponent serverName = new TextComponent();
serverName.setColor(ChatColor.GREEN);
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 {
FindCommand() {
private final RedisBungee plugin;
FindCommand(RedisBungee plugin) {
super("find", "bungeecord.command.find", "rfind");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (args.length > 0) {
ServerInfo si = RedisBungee.getApi().getServerFor(args[0]);
if (si != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.BLUE);
message.setText(args[0] + " is on " + si.getName() + ".");
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
if (args.length > 0) {
ServerInfo si = RedisBungee.getApi().getServerFor(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
if (si != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.BLUE);
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 {
LastSeenCommand() {
private final RedisBungee plugin;
LastSeenCommand(RedisBungee plugin) {
super("lastseen", "redisbungee.command.lastseen", "rlastseen");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (args.length > 0) {
long secs = RedisBungee.getApi().getLastOnline(args[0]);
TextComponent message = new TextComponent();
if (secs == 0) {
message.setColor(ChatColor.GREEN);
message.setText(args[0] + " is currently online.");
sender.sendMessage(message);
} else if (secs != -1) {
message.setColor(ChatColor.BLUE);
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
sender.sendMessage(message);
} else {
message.setColor(ChatColor.RED);
message.setText(args[0] + " has never been online.");
sender.sendMessage(message);
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
if (args.length > 0) {
long secs = RedisBungee.getApi().getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
TextComponent message = new TextComponent();
if (secs == 0) {
message.setColor(ChatColor.GREEN);
message.setText(args[0] + " is currently online.");
sender.sendMessage(message);
} else if (secs != -1) {
message.setColor(ChatColor.BLUE);
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
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 {
IpCommand() {
private final RedisBungee plugin;
IpCommand(RedisBungee plugin) {
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (args.length > 0) {
InetAddress ia = RedisBungee.getApi().getPlayerIp(args[0]);
if (ia != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.GREEN);
message.setText(args[0] + " is connected from " + ia.toString() + ".");
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
public void execute(final CommandSender sender, final String[] args) {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
if (args.length > 0) {
InetAddress ia = RedisBungee.getApi().getPlayerIp(plugin.getUuidTranslator().getTranslatedUuid(args[0]));
if (ia != null) {
TextComponent message = new TextComponent();
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 {
SendToAll() {
private final RedisBungee plugin;
SendToAll(RedisBungee plugin) {
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
this.plugin = plugin;
}
@Override
@ -167,8 +209,11 @@ class RedisBungeeCommands {
}
public static class ServerId extends Command {
ServerId() {
private final RedisBungee plugin;
ServerId(RedisBungee plugin) {
super("serverid", "redisbungee.command.serverid", "rserverid");
this.plugin = plugin;
}
@Override
@ -179,4 +224,18 @@ class RedisBungeeCommands {
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;
import com.google.common.base.Joiner;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
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 lombok.AllArgsConstructor;
import net.md_5.bungee.api.ServerPing;
@ -14,7 +23,9 @@ import net.md_5.bungee.event.EventHandler;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@AllArgsConstructor
public class RedisBungeeListener implements Listener {
@ -40,60 +51,17 @@ public class RedisBungeeListener implements Listener {
@EventHandler
public void onPlayerConnect(final PostLoginEvent event) {
if (plugin.getPool() != null) {
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.
plugin.getConsumer().queue(new PlayerLoggedInConsumerEvent(event.getPlayer()));
}
@EventHandler
public void onPlayerDisconnect(final PlayerDisconnectEvent event) {
if (plugin.getPool() != null) {
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);
}
}
});
}
plugin.getConsumer().queue(new PlayerLoggedOffConsumerEvent(event.getPlayer()));
}
@EventHandler
public void onServerChange(final ServerConnectedEvent event) {
if (plugin.getPool() != null) {
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);
}
}
});
}
plugin.getConsumer().queue(new PlayerChangedServerConsumerEvent(event.getPlayer(), event.getServer().getInfo()));
}
@EventHandler
@ -101,11 +69,11 @@ public class RedisBungeeListener implements Listener {
ServerPing old = event.getResponse();
ServerPing reply = new ServerPing();
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()];
int idx = 0;
for (String player : players) {
info[idx] = new ServerPing.PlayerInfo(player, "");
for (UUID player : players) {
info[idx] = new ServerPing.PlayerInfo(plugin.getUuidTranslator().getNameFromUuid(player), "");
idx++;
}
reply.setPlayers(new ServerPing.Players(old.getPlayers().getMax(), players.size(), info));
@ -130,18 +98,21 @@ public class RedisBungeeListener implements Listener {
switch (subchannel) {
case "PlayerList":
out.writeUTF("Players");
Set<String> source = Collections.emptySet();
Set<UUID> original = Collections.emptySet();
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
source = plugin.getPlayers();
original = plugin.getPlayers();
} else {
try {
source = plugin.getPlayersOnServer(type);
original = plugin.getPlayersOnServer(type);
} 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;
case "PlayerCount":
out.writeUTF("PlayerCount");
@ -163,7 +134,7 @@ public class RedisBungeeListener implements Listener {
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(plugin.getLastOnline(user));
out.writeLong(plugin.getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(user)));
break;
default:
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;
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.
*
* <p>
* <strong>Warning</strong>: This event is fired in a separate thread!
*
* @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
main: com.imaginarycode.minecraft.redisbungee.RedisBungee
version: 0.2.6
version: 0.3
author: tuxed
# This is used so that we can automagically override default BungeeCord behavior.
softDepends: ["cmd_find", "cmd_list"]