diff --git a/RedisBungee-API/pom.xml b/RedisBungee-API/pom.xml index 74411f1..13592f1 100644 --- a/RedisBungee-API/pom.xml +++ b/RedisBungee-API/pom.xml @@ -10,6 +10,18 @@ 4.0.0 RedisBungee-API + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.0 + + 8 + + + + diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/DataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/DataManager.java index 63334e0..94121b8 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/DataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/DataManager.java @@ -147,9 +147,10 @@ public abstract class DataManager { serverCache.invalidate(uuid); proxyCache.invalidate(uuid); } - // Invalidate all entries related to this player, since they now lie. (call invalidate(uuid)) + + public abstract void onPostLogin(PL event); - // Invalidate all entries related to this player, since they now lie. (call invalidate(uuid)) + public abstract void onPlayerDisconnect(PD event); public abstract void onPubSubMessage(PS event); @@ -227,7 +228,6 @@ public abstract class DataManager { } } - public static class DataManagerMessage { private final UUID target; private final String source; diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/RedisBungeePlugin.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/RedisBungeePlugin.java index 02a193e..0461672 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/RedisBungeePlugin.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/internal/RedisBungeePlugin.java @@ -17,20 +17,30 @@ import java.util.concurrent.TimeUnit; public interface RedisBungeePlugin

{ - void enable(); + default void enable() { - void disable(); + } + + default void disable() { + + } RedisBungeeConfiguration getConfiguration(); int getCount(); + int getCurrentCount(); + + Set getLocalPlayersAsUuidStrings(); + DataManager getDataManager(); Set getPlayers(); Jedis requestJedis(); + boolean isJedisAvailable(); + RedisBungeeAPI getApi(); UUIDTranslator getUuidTranslator(); @@ -43,15 +53,17 @@ public interface RedisBungeePlugin

{ List getServerIds(); + List getCurrentServerIds(boolean nag, boolean lagged); + PubSubListener getPubSubListener(); void sendChannelMessage(String channel, String message); void executeAsync(Runnable runnable); - void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int seconds); + void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time); - void callEvent(Object object); + void callEvent(Object event); boolean isOnlineMode(); @@ -61,8 +73,6 @@ public interface RedisBungeePlugin

{ void logFatal(String msg); - boolean isPlayerServerNull(P player); - P getPlayer(UUID uuid); P getPlayer(String name); @@ -77,7 +87,7 @@ public interface RedisBungeePlugin

{ InetAddress getPlayerIp(P player); - void executeProxyCommand(String cmd); + void sendProxyCommand(String cmd); default Class getPubSubEventClass() { return PubSubMessageEvent.class; @@ -95,4 +105,8 @@ public interface RedisBungeePlugin

{ return PlayerLeftNetworkEvent.class; } + long getRedisTime(List timeRes); + + void loadConfig() throws Exception; + } diff --git a/RedisBungee-API/src/main/resources/lua/get_player_count.lua b/RedisBungee-API/src/main/resources/lua/get_player_count.lua new file mode 100644 index 0000000..0882aec --- /dev/null +++ b/RedisBungee-API/src/main/resources/lua/get_player_count.lua @@ -0,0 +1,24 @@ +local c = redis.call + +local curTime = c("TIME") +local time = tonumber(curTime[1]) + +local heartbeats = c("HGETALL", "heartbeats") +local total = 0 +local key + +for _, v in ipairs(heartbeats) do + if not key then + key = v + else + local n = tonumber(v) + if n then + if n + 30 >= time then + total = total + c("SCARD", "proxy:" .. key .. ":usersOnline") + end + end + key = nil + end +end + +return total diff --git a/RedisBungee-API/src/main/resources/lua/server_to_players.lua b/RedisBungee-API/src/main/resources/lua/server_to_players.lua new file mode 100644 index 0000000..ee66398 --- /dev/null +++ b/RedisBungee-API/src/main/resources/lua/server_to_players.lua @@ -0,0 +1,18 @@ +local call = redis.call +local ipairs = ipairs + +local serverToData = {} + +for _, proxy in ipairs(ARGV) do + local players = call("SMEMBERS", "proxy:" .. proxy .. ":usersOnline") + for _, player in ipairs(players) do + local server = call("HGET", "player:" .. player, "server") + if server then + local sz = #serverToData + serverToData[sz + 1] = server + serverToData[sz + 2] = player + end + end +end + +return serverToData \ No newline at end of file diff --git a/RedisBungee-Bungee/pom.xml b/RedisBungee-Bungee/pom.xml index 24d951f..f511693 100644 --- a/RedisBungee-Bungee/pom.xml +++ b/RedisBungee-Bungee/pom.xml @@ -81,14 +81,6 @@ - - org.apache.maven.plugins - maven-javadoc-plugin - 3.3.0 - - 8 - - diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java index 2532225..ee7b65c 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java @@ -6,24 +6,29 @@ import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; -public class BungeeDataManager extends DataManager { +public class BungeeDataManager extends DataManager implements Listener { public BungeeDataManager(RedisBungeePlugin plugin) { super(plugin); } @Override + @EventHandler public void onPostLogin(PostLoginEvent event) { invalidate(event.getPlayer().getUniqueId()); } @Override + @EventHandler public void onPlayerDisconnect(PlayerDisconnectEvent event) { invalidate(event.getPlayer().getUniqueId()); } @Override + @EventHandler public void onPubSubMessage(PubSubMessageEvent event) { handlePubSubMessage(event.getChannel(), event.getMessage()); } diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeBungeePlugin.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeBungeePlugin.java new file mode 100644 index 0000000..ec97359 --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeBungeePlugin.java @@ -0,0 +1,571 @@ +package com.imaginarycode.minecraft.redisbungee; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteStreams; +import com.google.gson.Gson; +import com.imaginarycode.minecraft.redisbungee.internal.*; +import com.imaginarycode.minecraft.redisbungee.internal.util.IOUtil; +import com.imaginarycode.minecraft.redisbungee.internal.util.LuaManager; +import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.NameFetcher; +import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDFetcher; +import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDTranslator; +import com.squareup.okhttp.Dispatcher; +import com.squareup.okhttp.OkHttpClient; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Event; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.exceptions.JedisConnectionException; + +import java.io.*; +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; + +import static com.google.common.base.Preconditions.checkArgument; + +public class RedisBungeeBungeePlugin extends Plugin implements RedisBungeePlugin { + + private static final Gson gson = new Gson(); + private RedisBungeeAPI api; + private PubSubListener psl = null; + private JedisPool jedisPool; + private UUIDTranslator uuidTranslator; + private RedisBungeeConfiguration configuration; + private BungeeDataManager dataManager; + private OkHttpClient httpClient; + private volatile List serverIds; + private final AtomicInteger nagAboutServers = new AtomicInteger(); + private final AtomicInteger globalPlayerCount = new AtomicInteger(); + private Future integrityCheck; + private Future heartbeatTask; + private LuaManager.Script serverToPlayersScript; + private LuaManager.Script getPlayerCountScript; + + private static final Object SERVER_TO_PLAYERS_KEY = new Object(); + private final Cache> serverToPlayersCache = CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.SECONDS) + .build(); + + + @Override + public RedisBungeeConfiguration getConfiguration() { + return this.configuration; + } + + @Override + public int getCount() { + return this.globalPlayerCount.get(); + } + + @Override + public int getCurrentCount() { + Long count = (Long) getPlayerCountScript.eval(ImmutableList.of(), ImmutableList.of()); + return count.intValue(); + } + + @Override + public Set getLocalPlayersAsUuidStrings() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (ProxiedPlayer player : getProxy().getPlayers()) { + builder.add(player.getUniqueId().toString()); + } + return builder.build(); + } + + @Override + public DataManager getDataManager() { + return this.dataManager; + } + + @Override + public Set getPlayers() { + ImmutableSet.Builder setBuilder = ImmutableSet.builder(); + if (isJedisAvailable()) { + try (Jedis rsc = requestJedis()) { + List keys = new ArrayList<>(); + for (String i : getServerIds()) { + keys.add("proxy:" + i + ":usersOnline"); + } + if (!keys.isEmpty()) { + Set users = rsc.sunion(keys.toArray(new String[keys.size()])); + if (users != null && !users.isEmpty()) { + for (String user : users) { + try { + setBuilder = setBuilder.add(UUID.fromString(user)); + } catch (IllegalArgumentException ignored) { + } + } + } + } + } catch (JedisConnectionException e) { + // Redis server has disappeared! + getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e); + throw new RuntimeException("Unable to get all players online", e); + } + } + return setBuilder.build(); + } + + @Override + public Jedis requestJedis() { + return this.jedisPool.getResource(); + } + + @Override + public boolean isJedisAvailable() { + return !jedisPool.isClosed(); + } + + @Override + public RedisBungeeAPI getApi() { + return this.api; + } + + @Override + public UUIDTranslator getUuidTranslator() { + return this.uuidTranslator; + } + + @Override + public Multimap serversToPlayers() { + try { + return serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, new Callable>() { + @Override + public Multimap call() throws Exception { + Collection data = (Collection) serverToPlayersScript.eval(ImmutableList.of(), getServerIds()); + ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + String key = null; + for (String s : data) { + if (key == null) { + key = s; + continue; + } + + builder.put(key, UUID.fromString(s)); + key = null; + } + + return builder.build(); + } + }); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public Set getPlayersOnProxy(String proxyId) { + return null; + } + + @Override + public void sendProxyCommand(String serverId, String command) { + + } + + @Override + public List getServerIds() { + return null; + } + + @Override + public List getCurrentServerIds(boolean nag, boolean lagged) { + return null; + } + + @Override + public PubSubListener getPubSubListener() { + return null; + } + + @Override + public void sendChannelMessage(String channel, String message) { + + } + + @Override + public void executeAsync(Runnable runnable) { + this.getProxy().getScheduler().runAsync(this, runnable); + } + + @Override + public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) { + this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit); + } + + @Override + public void callEvent(Object event) { + this.getProxy().getPluginManager().callEvent((Event) event); + } + + @Override + public boolean isOnlineMode() { + return this.getProxy().getConfig().isOnlineMode(); + } + + @Override + public void logInfo(String msg) { + this.getLogger().info(msg); + } + + @Override + public void logWarn(String msg) { + this.getLogger().warning(msg); + } + + @Override + public void logFatal(String msg) { + this.getLogger().severe(msg); + } + + @Override + public ProxiedPlayer getPlayer(UUID uuid) { + return this.getProxy().getPlayer(uuid); + } + + @Override + public ProxiedPlayer getPlayer(String name) { + return this.getProxy().getPlayer(name); + } + + @Override + public UUID getPlayerUUID(String player) { + return this.getProxy().getPlayer(player).getUniqueId(); + } + + @Override + public String getPlayerName(UUID player) { + return this.getProxy().getPlayer(player).getName(); + } + + @Override + public String getPlayerServerName(ProxiedPlayer player) { + return player.getServer().getInfo().getName(); + } + + @Override + public boolean isPlayerOnAServer(ProxiedPlayer player) { + return player.getServer() != null; + } + + @Override + public InetAddress getPlayerIp(ProxiedPlayer player) { + return player.getAddress().getAddress(); + } + + @Override + public void sendProxyCommand(String cmd) { + checkArgument(getServerIds().contains(this.configuration.getServerId()) || this.configuration.getServerId().equals("allservers"), "proxyId is invalid"); + sendChannelMessage("redisbungee-" + this.configuration.getServerId(), cmd); + } + + @Override + public long getRedisTime(List timeRes) { + return Long.parseLong(timeRes.get(0)); + } + + + @Override + public void enable() { + ThreadFactory factory = ((ThreadPoolExecutor) getExecutorService()).getThreadFactory(); + ScheduledExecutorService service = Executors.newScheduledThreadPool(24, factory); + try { + Field field = Plugin.class.getDeclaredField("service"); + field.setAccessible(true); + ExecutorService builtinService = (ExecutorService) field.get(this); + field.set(this, service); + builtinService.shutdownNow(); + } catch (IllegalAccessException | NoSuchFieldException e) { + getLogger().log(Level.WARNING, "Can't replace BungeeCord thread pool with our own"); + getLogger().log(Level.INFO, "skipping replacement....."); + } + try { + loadConfig(); + } catch (IOException e) { + throw new RuntimeException("Unable to load/save config", e); + } catch (JedisConnectionException e) { + throw new RuntimeException("Unable to connect to your Redis server!", e); + } + api = new RedisBungeeAPI(this); + if (isJedisAvailable()) { + try (Jedis tmpRsc = requestJedis()) { + // This is more portable than INFO

+ String info = tmpRsc.info(); + for (String s : info.split("\r\n")) { + if (s.startsWith("redis_version:")) { + String version = s.split(":")[1]; + getLogger().info(version + " <- redis version"); + if (!RedisUtil.isRedisVersionRight(version)) { + getLogger().warning("Your version of Redis (" + version + ") is not at least version 6.0 RedisBungee requires a newer version of Redis."); + throw new RuntimeException("Unsupported Redis version detected"); + } else { + LuaManager manager = new LuaManager(this); + serverToPlayersScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/server_to_players.lua"))); + getPlayerCountScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/get_player_count.lua"))); + } + break; + } + } + + tmpRsc.hset("heartbeats", configuration.getServerId(), tmpRsc.time().get(0)); + + long uuidCacheSize = tmpRsc.hlen("uuid-cache"); + if (uuidCacheSize > 750000) { + getLogger().info("Looks like you have a really big UUID cache! Run https://www.spigotmc.org/resources/redisbungeecleaner.8505/ as soon as possible."); + } + } + serverIds = getCurrentServerIds(true, false); + uuidTranslator = new UUIDTranslator(this); + heartbeatTask = service.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try (Jedis rsc = requestJedis()) { + long redisTime = getRedisTime(rsc.time()); + rsc.hset("heartbeats", configuration.getServerId(), String.valueOf(redisTime)); + } catch (JedisConnectionException e) { + // Redis server has disappeared! + getLogger().log(Level.SEVERE, "Unable to update heartbeat - did your Redis server go away?", e); + return; + } + try { + serverIds = getCurrentServerIds(true, false); + globalPlayerCount.set(getCurrentCount()); + } catch (Throwable e) { + getLogger().log(Level.SEVERE, "Unable to update data - did your Redis server go away?", e); + } + } + }, 0, 3, TimeUnit.SECONDS); + dataManager = new BungeeDataManager(this); + /*if (configuration.isRegisterBungeeCommands()) { + 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().registerCommand(this, new RedisBungeeCommands.PlayerProxyCommand(this)); + getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlistCommand(this)); + getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.DebugCommand(this)); + */ + + getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this, configuration.getExemptAddresses())); + getProxy().getPluginManager().registerListener(this, dataManager); + psl = new PubSubListener(this); + getProxy().getScheduler().runAsync(this, psl); + integrityCheck = service.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try (Jedis tmpRsc = requestJedis()) { + Set players = getLocalPlayersAsUuidStrings(); + Set playersInRedis = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline"); + List lagged = getCurrentServerIds(false, true); + + // Clean up lagged players. + for (String s : lagged) { + Set laggedPlayers = tmpRsc.smembers("proxy:" + s + ":usersOnline"); + tmpRsc.del("proxy:" + s + ":usersOnline"); + if (!laggedPlayers.isEmpty()) { + getLogger().info("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)..."); + for (String laggedPlayer : laggedPlayers) { + RedisUtil.cleanUpPlayer(laggedPlayer, tmpRsc); + } + } + } + + Set absentLocally = new HashSet<>(playersInRedis); + absentLocally.removeAll(players); + Set absentInRedis = new HashSet<>(players); + absentInRedis.removeAll(playersInRedis); + + for (String member : absentLocally) { + boolean found = false; + for (String proxyId : getServerIds()) { + if (proxyId.equals(configuration.getServerId())) continue; + if (tmpRsc.sismember("proxy:" + proxyId + ":usersOnline", member)) { + // Just clean up the set. + found = true; + break; + } + } + if (!found) { + RedisUtil.cleanUpPlayer(member, tmpRsc); + getLogger().warning("Player found in set that was not found locally and globally: " + member); + } else { + tmpRsc.srem("proxy:" + configuration.getServerId() + ":usersOnline", member); + getLogger().warning("Player found in set that was not found locally, but is on another proxy: " + member); + } + } + + Pipeline pipeline = tmpRsc.pipelined(); + + for (String player : absentInRedis) { + // Player not online according to Redis but not BungeeCord. + getLogger().warning("Player " + player + " is on the proxy but not in Redis."); + + ProxiedPlayer proxiedPlayer = ProxyServer.getInstance().getPlayer(UUID.fromString(player)); + if (proxiedPlayer == null) + continue; // We'll deal with it later. + + RBUtils.createPlayer(proxiedPlayer, pipeline, true); + } + + pipeline.sync(); + } catch (Throwable e) { + getLogger().log(Level.SEVERE, "Unable to fix up stored player data", e); + } + } + }, 0, 1, TimeUnit.MINUTES); + } + getProxy().registerChannel("legacy:redisbungee"); + getProxy().registerChannel("RedisBungee"); + } + + @Override + public void disable() { + if (isJedisAvailable()) { + // Poison the PubSub listener + psl.poison(); + integrityCheck.cancel(true); + heartbeatTask.cancel(true); + getProxy().getPluginManager().unregisterListeners(this); + + try (Jedis tmpRsc = requestJedis()) { + tmpRsc.hdel("heartbeats", configuration.getServerId()); + if (tmpRsc.scard("proxy:" + configuration.getServerId() + ":usersOnline") > 0) { + Set players = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline"); + for (String member : players) + RedisUtil.cleanUpPlayer(member, tmpRsc); + } + } + + this.jedisPool.destroy(); + } + } + + @Override + public void loadConfig() throws IOException { + if (!getDataFolder().exists()) { + getDataFolder().mkdir(); + } + + File file = new File(getDataFolder(), "config.yml"); + + if (!file.exists()) { + file.createNewFile(); + try (InputStream in = getResourceAsStream("example_config.yml"); + OutputStream out = new FileOutputStream(file)) { + ByteStreams.copy(in, out); + } + } + + final Configuration yamlConfiguration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file); + + final String redisServer = yamlConfiguration.getString("redis-server", "localhost"); + final int redisPort = yamlConfiguration.getInt("redis-port", 6379); + final boolean useSSL = yamlConfiguration.getBoolean("useSSL", false); + String redisPassword = yamlConfiguration.getString("redis-password", ""); + String serverId = yamlConfiguration.getString("server-id"); + final String randomUUID = UUID.randomUUID().toString(); + + // check redis password + if (redisPassword != null && (redisPassword.isEmpty() || redisPassword.equals("none"))) { + redisPassword = null; + getLogger().warning("INSECURE setup was detected Please set password for your redis instance."); + } + if (!useSSL) { + getLogger().warning("INSECURE setup was detected Please setup ssl for your redis instance."); + } + // Configuration sanity checks. + if (serverId == null || serverId.isEmpty()) { + /* + * this check causes the config comments to disappear somehow + * I think due snake yaml limitations so as todo: write our own yaml parser? + */ + String genId = UUID.randomUUID().toString(); + getLogger().info("Generated server id " + genId + " and saving it to config."); + yamlConfiguration.set("server-id", genId); + ConfigurationProvider.getProvider(YamlConfiguration.class).save(yamlConfiguration, new File(getDataFolder(), "config.yml")); + getLogger().info("Server id was generated: " + serverId); + } else { + getLogger().info("Loaded server id " + serverId + '.'); + } + this.configuration = new RedisBungeeConfiguration(serverId, yamlConfiguration.getStringList("exempt-ip-addresses")); + + if (redisServer != null && !redisServer.isEmpty()) { + try { + JedisPoolConfig config = new JedisPoolConfig(); + config.setMaxTotal(yamlConfiguration.getInt("max-redis-connections", 8)); + this.jedisPool = new JedisPool(config, redisServer, redisPort, 0, redisPassword, useSSL); + + } catch (JedisConnectionException e) { + throw new RuntimeException("Unable to create Redis pool", e); + } + + // Test the connection + try (Jedis rsc = requestJedis()) { + rsc.ping(); + // If that worked, now we can check for an existing, alive Bungee: + File crashFile = new File(getDataFolder(), "restarted_from_crash.txt"); + if (crashFile.exists()) { + crashFile.delete(); + } else if (rsc.hexists("heartbeats", serverId)) { + try { + long value = Long.parseLong(rsc.hget("heartbeats", serverId)); + long redisTime = getRedisTime(rsc.time()); + if (redisTime < value + 20) { + getLogger().severe("You have launched a possible impostor 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 impostor instance!"); + } + } catch (NumberFormatException ignored) { + } + } + + + httpClient = new OkHttpClient(); + Dispatcher dispatcher = new Dispatcher(getExecutorService()); + httpClient.setDispatcher(dispatcher); + NameFetcher.setHttpClient(httpClient); + UUIDFetcher.setHttpClient(httpClient); + + getLogger().log(Level.INFO, "Successfully connected to Redis."); + } catch (JedisConnectionException e) { + this.jedisPool.destroy(); + this.jedisPool = null; + throw e; + } + } else { + throw new RuntimeException("No redis server specified!"); + } + } + + + @Override + public void onEnable() { + enable(); + } + + @Override + public void onDisable() { + disable(); + } +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java index ccbea6f..167bbce 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -254,7 +254,7 @@ public class RedisBungeeListener extends AbstractRedisBungeeListener