diff --git a/README.md b/README.md index c9f1789..97ba182 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # RedisBungee fork By Limework -*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do it, tell mojang to implement transfer packet*. +*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do +it, tell mojang to implement transfer packet*. [Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/) The original project of RedisBungee is no longer maintained, so we have forked the plugin. -RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/) -to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) or [Velocity*](https://github.com/PaperMC/Velocity) proxies +RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/) +to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) +or [Velocity*](https://github.com/PaperMC/Velocity) proxies -Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40) +Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be +unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40) + +## Downloads -## Downloads [![](https://raw.githubusercontent.com/Prospector/badges/master/modrinth-badge-72h-padded.png)](https://modrinth.com/plugin/redisbungee) or from github releases @@ -17,12 +21,17 @@ or from github releases https://github.com/ProxioDev/RedisBungee/releases ## notes + If you are looking to use Original RedisBungee without a change to internals, -with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/) -as its last version before internal changes. please note that you will not get support for any old builds unless critical bugs effecting both 0.6.5 and 0.7.0 or above. +with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and +java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/) +as its last version before internal changes. please note that you will not get support for any old builds unless +critical bugs effecting both 0.6.5 and 0.7.0 or above. SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.87700/) + ## Supported Redis versions + | Redis version | Supported | |:-------------:|:---------:| | 1.x.x | ✖ | @@ -33,7 +42,6 @@ SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.8 | 6.x.x | ✔ | | 7.x.x | ✔ | - ## Implementing RedisBungee in your plugin: [![RedisBungee Build](https://github.com/proxiodev/RedisBungee/actions/workflows/maven.yml/badge.svg)](https://github.com/Limework/RedisBungee/actions/workflows/maven.yml) [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) RedisBungee is distributed as a [Gradle](https://gradle.org/) project. @@ -41,7 +49,9 @@ RedisBungee is distributed as a [Gradle](https://gradle.org/) project. By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) # Setup jitpack repository + ## maven + ```xml @@ -50,7 +60,9 @@ By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://j ``` + ## gradle (kotlin dsl) + ```kotlin repositories { maven("https://jitpack.io/") @@ -58,8 +70,11 @@ repositories { ``` # [BungeeCord](https://github.com/SpigotMC/BungeeCord) -add this in your project dependencies + +add this in your project dependencies + ## maven + ```xml com.github.proxiodev.redisbungee @@ -70,7 +85,9 @@ add this in your project dependencies ``` + ## gradle (kotlin dsl) + ``` implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0") @@ -79,7 +96,9 @@ implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0:all") exclude("redis.clients", "jedis") } ``` + then in your project plugin.yml add `RedisBungee` to `depends` like this + ```yaml name: "yourplugin" main: your.main.class @@ -88,9 +107,10 @@ author: idk depends: [ RedisBungee ] ``` - ## [Velocity](https://github.com/PaperMC/Velocity) + ## maven + ```xml com.github.proxiodev.redisbungee @@ -100,7 +120,9 @@ depends: [ RedisBungee ] ``` + ## gradle (kotlin dsl) + ``` implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0") @@ -110,7 +132,9 @@ implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0:all } ``` -then to make your plugin depends on RedisBungee, make sure your plugin class Annotation have `@Dependency(id = "redisbungee")` like this +then to make your plugin depends on RedisBungee, make sure your plugin class Annotation +have `@Dependency(id = "redisbungee")` like this + ```java @Plugin( id = "myplugin", @@ -124,15 +148,20 @@ public class PluginMainClass { } ``` + ## Getting the latest commits to your code + If you want to use the latest commits without waiting for releases. first, install it to your maven local repo + ```bash git clone https://github.com/ProxioDev/RedisBungee.git cd RedisBungee ./gradlew publishToMavenLocal ``` + then use any of these in your project. + ```xml com.imaginarycode.minecraft @@ -142,6 +171,7 @@ then use any of these in your project. ``` + ```xml com.imaginarycode.minecraft @@ -151,6 +181,7 @@ then use any of these in your project. ``` + ## Javadocs * API: https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc/ @@ -159,20 +190,25 @@ then use any of these in your project. ## Configuration -**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is saved when the plugin first starts. - +**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The +default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is +saved when the plugin first starts. ## compatibility with original RedisBungee in Bungeecord ecosystem + This fork ensures compatibility with old plugins, so it should work as drop replacement, -but since Api has been split from the platform there some changes that have to be done, so your plugin might not work if: +but since Api has been split from the platform there some changes that have to be done, so your plugin might not work +if: * there is none at the moment, please report any findings at the issue page. Cluster mode compatibility in version 0.8.0: If you are using static legacy method `RedisBungee#getPool()` it might fail in: + * if Cluster mode is enabled, due fact its Uses different classes -* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than Jedis +* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than + Jedis ## Support @@ -184,12 +220,15 @@ This project is distributed under Eclipse Public License 1.0 You can find it [here](https://github.com/proxiodev/RedisBungee/blob/master/LICENSE) -You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be found [here](https://github.com/minecrafter/RedisBungee) or spigot page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/) - - +You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be +found [here](https://github.com/minecrafter/RedisBungee) or spigot +page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/) ## YourKit -YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). +YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET +applications. YourKit is the creator +of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) +and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). ![YourKit](https://www.yourkit.com/images/yklogo.png) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 3af847e..933d566 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { api("redis.clients:jedis:$jedisVersion") api("com.squareup.okhttp:okhttp:2.7.5") api("org.spongepowered:configurate-yaml:$configurateVersion") - + api("com.github.ben-manes.caffeine:caffeine:3.1.8") // tests testImplementation("junit:junit:4.13.2") } @@ -68,11 +68,10 @@ tasks { compileJava { options.encoding = Charsets.UTF_8.name() - options.release.set(8) + options.release.set(17) } javadoc { options.encoding = Charsets.UTF_8.name() - } processResources { filteringCharset = Charsets.UTF_8.name() diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java index ccbb95b..70e3e7e 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java @@ -10,8 +10,6 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; @@ -40,21 +38,13 @@ import java.util.*; public abstract class AbstractRedisBungeeAPI { protected final RedisBungeePlugin plugin; private static AbstractRedisBungeeAPI abstractRedisBungeeAPI; - protected final List reservedChannels; - AbstractRedisBungeeAPI(RedisBungeePlugin plugin) { - // this does make sure that no one can place first initiated API class. + public AbstractRedisBungeeAPI(RedisBungeePlugin plugin) { + // this does make sure that no one can replace first initiated API class. if (abstractRedisBungeeAPI == null) { abstractRedisBungeeAPI = this; } - this.reservedChannels = ImmutableList.of( - "redisbungee-allservers", - "redisbungee-" + plugin.getConfiguration().getProxyId(), - "redisbungee-data" - ); - this.plugin = plugin; - } /** @@ -63,7 +53,7 @@ public abstract class AbstractRedisBungeeAPI { * @return a count of all players found */ public final int getPlayerCount() { - return plugin.getCount(); + return plugin.proxyDataManager().totalNetworkPlayers(); } /** @@ -74,7 +64,7 @@ public abstract class AbstractRedisBungeeAPI { * @return the last time a player was on, if online returns a 0 */ public final long getLastOnline(@NonNull UUID player) { - return plugin.getDataManager().getLastOnline(player); + return plugin.playerDataManager().getLastOnline(player); } /** @@ -86,7 +76,7 @@ public abstract class AbstractRedisBungeeAPI { */ @Nullable public final String getServerNameFor(@NonNull UUID player) { - return plugin.getDataManager().getServer(player); + return plugin.playerDataManager().getServerFor(player); } /** @@ -97,7 +87,7 @@ public abstract class AbstractRedisBungeeAPI { * @return a Set with all players found */ public final Set getPlayersOnline() { - return plugin.getPlayers(); + return plugin.proxyDataManager().networkPlayers(); } /** @@ -118,11 +108,11 @@ public abstract class AbstractRedisBungeeAPI { /** * 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 network * @since 0.2.5 */ public final Multimap getServerToPlayers() { - return plugin.serverToPlayersCache(); + return plugin.playerDataManager().serversToPlayers(); } /** @@ -138,11 +128,11 @@ public abstract class AbstractRedisBungeeAPI { /** * Get a list of players on the specified proxy. * - * @param server a server name + * @param proxyID proxy id * @return a Set with all UUIDs found on this proxy */ - public final Set getPlayersOnProxy(@NonNull String server) { - return plugin.getPlayersOnProxy(server); + public final Set getPlayersOnProxy(@NonNull String proxyID) { + return plugin.proxyDataManager().getPlayersOn(proxyID); } /** @@ -163,7 +153,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.4 */ public final InetAddress getPlayerIp(@NonNull UUID player) { - return plugin.getDataManager().getIp(player); + return plugin.playerDataManager().getIpFor(player); } /** @@ -174,7 +164,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.3.3 */ public final String getProxy(@NonNull UUID player) { - return plugin.getDataManager().getProxy(player); + return plugin.playerDataManager().getProxyFor(player); } /** @@ -185,7 +175,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.5 */ public final void sendProxyCommand(@NonNull String command) { - plugin.sendProxyCommand("allservers", command); + sendProxyCommand("allservers", command); } /** @@ -198,19 +188,20 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.5 */ public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) { - plugin.sendProxyCommand(proxyId, command); + plugin.proxyDataManager().sendCommandTo(proxyId, command); } /** - * Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for - * PubSubMessageEvent to fire. + * Sends a message to a PubSub channel which makes PubSubMessageEvent fire. + *

+ * Note: Since 0.12.0 registering a channel api is no longer required * * @param channel The PubSub channel * @param message the message body to send * @since 0.3.3 */ public final void sendChannelMessage(@NonNull String channel, @NonNull String message) { - plugin.sendChannelMessage(channel, message); + plugin.proxyDataManager().sendChannelMessage(channel, message); } /** @@ -221,7 +212,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public final String getProxyId() { - return plugin.getConfiguration().getProxyId(); + return plugin.proxyDataManager().proxyId(); } /** @@ -245,7 +236,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public final List getAllProxies() { - return plugin.getProxiesIds(); + return plugin.proxyDataManager().proxiesIds(); } /** @@ -266,9 +257,10 @@ public abstract class AbstractRedisBungeeAPI { * * @param channels the channels to register * @since 0.3 + * @deprecated No longer required */ + @Deprecated public final void registerPubSubChannels(String... channels) { - plugin.getPubSubListener().addChannel(channels); } /** @@ -276,13 +268,10 @@ public abstract class AbstractRedisBungeeAPI { * * @param channels the channels to unregister * @since 0.3 + * @deprecated No longer required */ + @Deprecated public final void unregisterPubSubChannels(String... channels) { - for (String channel : channels) { - Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel"); - } - - plugin.getPubSubListener().removeChannel(channels); } /** @@ -355,6 +344,7 @@ public abstract class AbstractRedisBungeeAPI { /** * Kicks a player from the network + * calls {@link #getUuidFromName(String)} to get uuid * * @param playerName player name * @param message kick message that player will see on kick @@ -362,7 +352,7 @@ public abstract class AbstractRedisBungeeAPI { */ public void kickPlayer(String playerName, String message) { - plugin.kickPlayer(playerName, message); + kickPlayer(getUuidFromName(playerName), message); } /** @@ -373,7 +363,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public void kickPlayer(UUID playerUUID, String message) { - plugin.kickPlayer(playerUUID, message); + this.plugin.playerDataManager().kickPlayer(playerUUID, message); } /** @@ -457,6 +447,7 @@ public abstract class AbstractRedisBungeeAPI { /** * shows what mode is RedisBungee is on + * Basically what every redis mode is used like cluster or single instance. * * @return {@link RedisBungeeMode} * @since 0.8.0 diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java deleted file mode 100644 index 1bff5b5..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.net.InetAddresses; -import com.google.common.reflect.TypeToken; -import com.google.common.util.concurrent.UncheckedExecutionException; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -/** - * This class manages all the data that RedisBungee fetches from Redis, along with updates to that data. - * - * @since 0.3.3 - */ -public abstract class AbstractDataManager { - protected final RedisBungeePlugin

plugin; - private final Cache serverCache = createCache(); - private final Cache proxyCache = createCache(); - private final Cache ipCache = createCache(); - private final Cache lastOnlineCache = createCache(); - private final Gson gson = new Gson(); - - public AbstractDataManager(RedisBungeePlugin

plugin) { - this.plugin = plugin; - } - - private static Cache createCache() { - // TODO: Allow customization via cache specification, ala ServerListPlus - return CacheBuilder.newBuilder() - .maximumSize(1000) - .expireAfterWrite(1, TimeUnit.HOURS) - .build(); - } - - public String getServer(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.isPlayerOnAServer(player) ? plugin.getPlayerServerName(player) : null; - - try { - return serverCache.get(uuid, new RedisTask(plugin) { - @Override - public String unifiedJedisTask(UnifiedJedis unifiedJedis) { - return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "server"), "user not found"); - - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get server"); - throw new RuntimeException("Unable to get server for " + uuid, e); - } - } - - - public String getProxy(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.getConfiguration().getProxyId(); - - try { - return proxyCache.get(uuid, new RedisTask(plugin) { - @Override - public String unifiedJedisTask(UnifiedJedis unifiedJedis) { - return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "proxy"), "user not found"); - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get proxy"); - throw new RuntimeException("Unable to get proxy for " + uuid, e); - } - } - - public InetAddress getIp(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.getPlayerIp(player); - - try { - return ipCache.get(uuid, new RedisTask(plugin) { - @Override - public InetAddress unifiedJedisTask(UnifiedJedis unifiedJedis) { - String result = unifiedJedis.hget("player:" + uuid, "ip"); - if (result == null) - throw new NullPointerException("user not found"); - return InetAddresses.forString(result); - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get IP"); - throw new RuntimeException("Unable to get IP for " + uuid, e); - } - } - - public long getLastOnline(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return 0; - - try { - return lastOnlineCache.get(uuid, new RedisTask(plugin) { - - @Override - public Long unifiedJedisTask(UnifiedJedis unifiedJedis) { - String result = unifiedJedis.hget("player:" + uuid, "online"); - return result == null ? -1 : Long.parseLong(result); - } - }); - } catch (ExecutionException e) { - plugin.logFatal("Unable to get last time online"); - throw new RuntimeException("Unable to get last time online for " + uuid, e); - } - } - - protected void invalidate(UUID uuid) { - ipCache.invalidate(uuid); - lastOnlineCache.invalidate(uuid); - 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); - - public abstract boolean handleKick(UUID target, String message); - - protected void handlePubSubMessage(String channel, String message) { - if (!channel.equals("redisbungee-data")) - return; - - // Partially deserialize the message so we can look at the action - JsonObject jsonObject = JsonParser.parseString(message).getAsJsonObject(); - - final String source = jsonObject.get("source").getAsString(); - - if (source.equals(plugin.getConfiguration().getProxyId())) - return; - - DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString()); - - switch (action) { - case JOIN: - final DataManagerMessage message1 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - proxyCache.put(message1.getTarget(), message1.getSource()); - lastOnlineCache.put(message1.getTarget(), (long) 0); - ipCache.put(message1.getTarget(), message1.getPayload().getAddress()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerJoinedNetworkEvent(message1.getTarget()); - plugin.fireEvent(event); - }); - break; - case LEAVE: - final DataManagerMessage message2 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - invalidate(message2.getTarget()); - lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerLeftNetworkEvent(message2.getTarget()); - plugin.fireEvent(event); - }); - break; - case SERVER_CHANGE: - final DataManagerMessage message3 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - serverCache.put(message3.getTarget(), message3.getPayload().getServer()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer()); - plugin.fireEvent(event); - }); - break; - case KICK: - final DataManagerMessage kickPayload = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - plugin.executeAsync(() -> handleKick(kickPayload.target, kickPayload.payload.message)); - break; - - } - } - - public static class DataManagerMessage { - private final UUID target; - private final String source; - private final Action action; // for future use! - private final T payload; - - public DataManagerMessage(UUID target, String source, Action action, T payload) { - this.target = target; - this.source = source; - this.action = action; - this.payload = payload; - } - - public UUID getTarget() { - return target; - } - - public String getSource() { - return source; - } - - public Action getAction() { - return action; - } - - public T getPayload() { - return payload; - } - - public enum Action { - JOIN, - LEAVE, - KICK, - SERVER_CHANGE - } - } - - public static abstract class Payload { - } - - public static class KickPayload extends Payload { - - private final String message; - - public KickPayload(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - } - - public static class LoginPayload extends Payload { - private final InetAddress address; - - public LoginPayload(InetAddress address) { - this.address = address; - } - - public InetAddress getAddress() { - return address; - } - } - - public static class ServerChangePayload extends Payload { - private final String server; - private final String oldServer; - - public ServerChangePayload(String server, String oldServer) { - this.server = server; - this.oldServer = oldServer; - } - - public String getServer() { - return server; - } - - public String getOldServer() { - return oldServer; - } - } - - - public static class LogoutPayload extends Payload { - private final long timestamp; - - public LogoutPayload(long timestamp) { - this.timestamp = timestamp; - } - - public long getTimestamp() { - return timestamp; - } - } -} \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java deleted file mode 100644 index 64b42ab..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - - -import com.google.common.collect.Multimap; -import com.google.common.collect.Multiset; -import com.google.common.io.ByteArrayDataOutput; -import com.google.gson.Gson; - -import java.net.InetAddress; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -public abstract class AbstractRedisBungeeListener { - - protected final RedisBungeePlugin plugin; - protected final List exemptAddresses; - protected final Gson gson = new Gson(); - - public AbstractRedisBungeeListener(RedisBungeePlugin plugin, List exemptAddresses) { - this.plugin = plugin; - this.exemptAddresses = exemptAddresses; - } - - public void onLogin(LE event) {} - - public abstract void onPostLogin(PLE event); - - public abstract void onPlayerDisconnect(PD event); - - public abstract void onServerChange(SC event); - - public abstract void onPing(PP event); - - public abstract void onPluginMessage(PM event); - - public abstract void onPubSubMessage(PS event); - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java deleted file mode 100644 index d3974a5..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - - -import redis.clients.jedis.JedisPubSub; - - -public class JedisPubSubHandler extends JedisPubSub { - - private final RedisBungeePlugin plugin; - - public JedisPubSubHandler(RedisBungeePlugin plugin) { - this.plugin = plugin; - } - - @Override - public void onMessage(final String s, final String s2) { - if (s2.trim().length() == 0) return; - plugin.executeAsync(new Runnable() { - @Override - public void run() { - Object event = plugin.createPubSubEvent(s, s2); - plugin.fireEvent(event); - } - }); - } -} \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java new file mode 100644 index 0000000..c6ccbb6 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.net.InetAddresses; +import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import org.json.JSONObject; +import redis.clients.jedis.ClusterPipeline; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.Response; +import redis.clients.jedis.UnifiedJedis; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public abstract class PlayerDataManager { + + protected final RedisBungeePlugin

plugin; + private final LoadingCache serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis); + private final LoadingCache proxyCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getProxyFromRedis); + private final LoadingCache ipCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getIpAddressFromRedis); + private final LoadingCache lastOnlineCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastOnlineFromRedis); + private final Object SERVERS_TO_PLAYERS_KEY = new Object(); + private final LoadingCache> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder); + private final UnifiedJedis unifiedJedis; + + public PlayerDataManager(RedisBungeePlugin

plugin) { + this.plugin = plugin; + this.unifiedJedis = plugin.proxyDataManager().unifiedJedis(); + } + + // handle network wide + // server change + public abstract void onPlayerChangedServerNetworkEvent(SC event); + + public abstract void onNetworkPlayerQuit(NJE event); + + // local events + public abstract void onPubSubMessageEvent(PS event); + + public abstract void onServerConnectedEvent(CE event); + + public abstract void onLoginEvent(LE event); + + public abstract void onDisconnectEvent(DE event); + + + protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) { + this.serverCache.invalidate(event.getUuid()); + } + + protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) { + this.proxyCache.invalidate(event.getUuid()); + this.serverCache.invalidate(event.getUuid()); + this.ipCache.invalidate(event.getUuid()); + this.lastOnlineCache.invalidate(event.getUuid()); + } + + protected void handlePubSubMessageEvent(IPubSubMessageEvent event) { + // kick api + if (event.getChannel().equals("redisbungee-kick")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + String message = data.getString("message"); + plugin.handlePlatformKick(uuid, message); + return; + } + if (event.getChannel().equals("redisbungee-serverchange")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + String from = data.getString("from"); + String to = data.getString("to"); + plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to)); + return; + } + if (event.getChannel().equals("redisbungee-player-join")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid)); + return; + } + if (event.getChannel().equals("redisbungee-player-leave")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + } + + } + + protected void playerChangedServer(UUID uuid, String from, String to) { + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + data.put("from", from); + data.put("to", to); + plugin.proxyDataManager().sendChannelMessage("redisbungee-serverchange", data.toString()); + plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to)); + handleServerChangeRedis(uuid, to); + } + + public void kickPlayer(UUID uuid, String message) { + if (!plugin.handlePlatformKick(uuid, message)) { // handle locally before SENDING a message + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + data.put("message", message); + plugin.proxyDataManager().sendChannelMessage("redisbungee-kick", data.toString()); + } + } + + private void handleServerChangeRedis(UUID uuid, String server) { + Map data = new HashMap<>(); + data.put("server", server); + data.put("last-server", server); + unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", data); + } + + protected void addPlayer(final UUID uuid, final InetAddress inetAddress) { + Map redisData = new HashMap<>(); + redisData.put("last-online", String.valueOf(0)); + redisData.put("proxy", plugin.configuration().getProxyId()); + redisData.put("ip", inetAddress.toString()); + unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", redisData); + + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + plugin.proxyDataManager().sendChannelMessage("redisbungee-player-join", data.toString()); + plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid)); + this.plugin.proxyDataManager().addPlayer(uuid); + } + + protected void removePlayer(UUID uuid) { + unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", "last-online", String.valueOf(System.currentTimeMillis())); + unifiedJedis.hdel("redis-bungee::player::" + uuid + "::data", "server", "proxy", "ip"); + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + plugin.proxyDataManager().sendChannelMessage("redisbungee-player-leave", data.toString()); + plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + this.plugin.proxyDataManager().removePlayer(uuid); + } + + + protected String getProxyFromRedis(UUID uuid) { + return unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "proxy"); + } + + protected String getServerFromRedis(UUID uuid) { + return unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "server"); + } + + protected InetAddress getIpAddressFromRedis(UUID uuid) { + String ip = unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "ip"); + if (ip == null) return null; + return InetAddresses.forString(ip); + } + + protected long getLastOnlineFromRedis(UUID uuid) { + String unixString = unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "last-online"); + if (unixString == null) return -1; + return Long.parseLong(unixString); + } + + public String getServerFor(UUID uuid) { + return this.serverCache.get(uuid); + } + + public String getProxyFor(UUID uuid) { + return this.proxyCache.get(uuid); + } + + public InetAddress getIpFor(UUID uuid) { + return this.ipCache.get(uuid); + } + + public long getLastOnline(UUID uuid) { + return this.lastOnlineCache.get(uuid); + } + + public Multimap serversToPlayers() { + return this.serverToPlayersCache.get(SERVERS_TO_PLAYERS_KEY); + } + + protected Multimap serversToPlayersBuilder(Object o) { + try { + return new RedisPipelineTask>(plugin) { + private final Set uuids = plugin.proxyDataManager().networkPlayers(); + private final ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + + @Override + public Multimap doPooledPipeline(Pipeline pipeline) { + HashMap> responses = new HashMap<>(); + for (UUID uuid : uuids) { + responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server")); + } + pipeline.sync(); + responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); + return builder.build(); + } + + @Override + public Multimap clusterPipeline(ClusterPipeline pipeline) { + HashMap> responses = new HashMap<>(); + for (UUID uuid : uuids) { + responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server")); + } + pipeline.sync(); + responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); + return builder.build(); + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java new file mode 100644 index 0000000..73a7c14 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.gson.AbstractPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.DeathPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.HeartbeatPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.PubSubPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import redis.clients.jedis.*; +import redis.clients.jedis.params.XAddParams; +import redis.clients.jedis.params.XReadParams; +import redis.clients.jedis.resps.StreamEntry; + +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.google.common.base.Preconditions.checkArgument; + +public abstract class ProxyDataManager implements Runnable, AutoCloseable { + + private static final String STREAM_ID = "redisbungee-stream"; + private static final int MAX_ENTRIES = 10000; + + private final AtomicBoolean closed = new AtomicBoolean(false); + + private final UnifiedJedis unifiedJedis; + + // data: + // Proxy id, heartbeat (unix epoch from instant), players as int + private final ConcurrentHashMap heartbeats = new ConcurrentHashMap<>(); + + private final String proxyId; + + // This different from proxy id, just to detect if there is duplicate proxy using same proxy id + private final UUID dataManagerUUID = UUID.randomUUID(); + + protected final RedisBungeePlugin plugin; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(AbstractPayload.class, new AbstractPayloadSerializer()).registerTypeAdapter(HeartbeatPayload.class, new HeartbeatPayloadSerializer()).registerTypeAdapter(DeathPayload.class, new DeathPayloadSerializer()).registerTypeAdapter(PubSubPayload.class, new PubSubPayloadSerializer()).registerTypeAdapter(RunCommandPayload.class, new RunCommandPayloadSerializer()).create(); + + public ProxyDataManager(RedisBungeePlugin plugin) { + this.plugin = plugin; + this.proxyId = this.plugin.configuration().getProxyId(); + this.unifiedJedis = plugin.getSummoner().obtainResource(); + this.destroyProxyMembers(); + } + + public abstract Set getLocalOnlineUUIDs(); + + public Set getPlayersOn(String proxyId) { + checkArgument(proxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID"); + if (proxyId.equals(this.proxyId)) return this.getLocalOnlineUUIDs(); + if (!this.heartbeats.containsKey(proxyId)) { + return new HashSet<>(); // return empty hashset or null? + } + return getProxyMembers(proxyId); + } + + public List proxiesIds() { + return Collections.list(this.heartbeats.keys()); + } + + public synchronized void sendCommandTo(String proxyToRun, String command) { + if (isClosed()) return; + publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command)); + } + + public synchronized void sendChannelMessage(String channel, String message) { + if (isClosed()) return; + this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message)); + publishPayload(new PubSubPayload(this.proxyId, channel, message)); + } + + // call every 1 second + public synchronized void publishHeartbeat() { + if (isClosed()) return; + HeartbeatPayload.HeartbeatData heartbeatData = new HeartbeatPayload.HeartbeatData(Instant.now().getEpochSecond(), this.getLocalOnlineUUIDs().size()); + this.heartbeats.put(this.proxyId(), heartbeatData); + publishPayload(new HeartbeatPayload(this.proxyId, heartbeatData)); + } + + public Set networkPlayers() { + try { + return new RedisPipelineTask>(this.plugin) { + @Override + public Set doPooledPipeline(Pipeline pipeline) { + HashSet>> responses = new HashSet<>(); + for (String proxyId : proxiesIds()) { + responses.add(pipeline.smembers("redisbungee::proxies::" + proxyId + "::online-players")); + } + pipeline.sync(); + HashSet uuids = new HashSet<>(); + for (Response> response : responses) { + for (String stringUUID : response.get()) { + uuids.add(UUID.fromString(stringUUID)); + } + } + return uuids; + } + + @Override + public Set clusterPipeline(ClusterPipeline pipeline) { + HashSet>> responses = new HashSet<>(); + for (String proxyId : proxiesIds()) { + responses.add(pipeline.smembers("redisbungee::proxies::" + proxyId + "::online-players")); + } + pipeline.sync(); + HashSet uuids = new HashSet<>(); + for (Response> response : responses) { + for (String stringUUID : response.get()) { + uuids.add(UUID.fromString(stringUUID)); + } + } + return uuids; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException("unable to get network players", e); + } + + } + + public int totalNetworkPlayers() { + int players = 0; + for (HeartbeatPayload.HeartbeatData value : this.heartbeats.values()) { + players += value.players(); + } + return players; + } + + // Call on close + private synchronized void publishDeath() { + publishPayload(new DeathPayload(this.proxyId)); + } + + private void publishPayload(AbstractPayload payload) { + Map data = new HashMap<>(); + data.put("payload", gson.toJson(payload)); + data.put("data-manager-uuid", this.dataManagerUUID.toString()); + data.put("class", payload.getClassName()); + this.unifiedJedis.xadd(STREAM_ID, XAddParams.xAddParams().maxLen(MAX_ENTRIES).id(StreamEntryID.NEW_ENTRY), data); + } + + + private void handleHeartBeat(HeartbeatPayload payload) { + String id = payload.senderProxy(); + if (!heartbeats.containsKey(id)) { + plugin.logInfo("Proxy {} has connected", id); + } + heartbeats.put(id, payload.data()); + } + + + // call every 1 minutes + public void correctionTask() { + // let's check this proxy players + Set localOnlineUUIDs = getLocalOnlineUUIDs(); + Set storedRedisUuids = getProxyMembers(this.proxyId); + + if (!localOnlineUUIDs.equals(storedRedisUuids)) { + plugin.logWarn("De-synced playerS set detected correcting...."); + Set add = new HashSet<>(localOnlineUUIDs); + Set remove = new HashSet<>(storedRedisUuids); + add.removeAll(storedRedisUuids); + remove.removeAll(localOnlineUUIDs); + for (UUID uuid : add) { + plugin.logWarn("found {} that isn't in the set, adding it to the Corrected set", uuid); + } + for (UUID uuid : remove) { + plugin.logWarn("found {} that does not belong to this proxy removing it from the corrected set", uuid); + } + try { + new RedisPipelineTask(plugin) { + @Override + public Void doPooledPipeline(Pipeline pipeline) { + Set removeString = new HashSet<>(); + for (UUID uuid : remove) { + removeString.add(uuid.toString()); + } + Set addString = new HashSet<>(); + for (UUID uuid : add) { + addString.add(uuid.toString()); + } + pipeline.srem("redisbungee::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::proxies::" + proxyId() + "::online-players", addString.toArray(new String[]{})); + pipeline.sync(); + return null; + } + + @Override + public Void clusterPipeline(ClusterPipeline pipeline) { + Set removeString = new HashSet<>(); + for (UUID uuid : remove) { + removeString.add(uuid.toString()); + } + Set addString = new HashSet<>(); + for (UUID uuid : add) { + addString.add(uuid.toString()); + } + pipeline.srem("redisbungee::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::proxies::" + proxyId() + "::online-players", addString.toArray(new String[]{})); + pipeline.sync(); + return null; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + plugin.logInfo("Player set has been corrected!"); + } + + + // handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~10 seconds + final Set deadProxies = new HashSet<>(); + for (Map.Entry stringHeartbeatDataEntry : this.heartbeats.entrySet()) { + String id = stringHeartbeatDataEntry.getKey(); + long heartbeat = stringHeartbeatDataEntry.getValue().heartbeat(); + if (Instant.now().getEpochSecond() - heartbeat > 10) { + deadProxies.add(id); + cleanProxy(id); + } + } + try { + new RedisPipelineTask(plugin) { + @Override + public Void doPooledPipeline(Pipeline pipeline) { + for (String deadProxy : deadProxies) { + pipeline.del("redisbungee::proxies::" + deadProxy + "::online-players"); + } + pipeline.sync(); + return null; + } + + @Override + public Void clusterPipeline(ClusterPipeline pipeline) { + for (String deadProxy : deadProxies) { + pipeline.del("redisbungee::proxies::" + deadProxy + "::online-players"); + } + pipeline.sync(); + return null; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void handleProxyDeath(DeathPayload payload) { + cleanProxy(payload.senderProxy()); + } + + private void cleanProxy(String id) { + if (id.equals(this.proxyId())) { + return; + } + for (UUID uuid : getProxyMembers(id)) plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + plugin.logInfo("Proxy {} has disconnected", id); + } + + private void handleChannelMessage(PubSubPayload payload) { + String channel = payload.channel(); + String message = payload.message(); + this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message)); + } + + protected abstract void handlePlatformCommandExecution(String command); + + private void handleCommand(RunCommandPayload payload) { + String proxyToRun = payload.proxyToRun(); + String command = payload.command(); + if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) { + handlePlatformCommandExecution(command); + } + } + + + public void addPlayer(UUID uuid) { + this.unifiedJedis.sadd("redisbungee::proxies::" + this.proxyId + "::online-players", uuid.toString()); + } + + public void removePlayer(UUID uuid) { + this.unifiedJedis.srem("redisbungee::proxies::" + this.proxyId + "::online-players", uuid.toString()); + } + + private void destroyProxyMembers() { + unifiedJedis.del("redisbungee::proxies::" + this.proxyId + "::online-players"); + } + + private Set getProxyMembers(String proxyId) { + Set uuidsStrings = unifiedJedis.smembers("redisbungee::proxies::" + proxyId + "::online-players"); + HashSet uuids = new HashSet<>(); + for (String proxyMember : uuidsStrings) { + uuids.add(UUID.fromString(proxyMember)); + } + return uuids; + } + + private StreamEntryID lastStreamEntryID; + + // polling from stream + @Override + public void run() { + while (!isClosed()) { + try { + List>> data = unifiedJedis.xread(XReadParams.xReadParams().block(0), Collections.singletonMap(STREAM_ID, lastStreamEntryID != null ? lastStreamEntryID : StreamEntryID.LAST_ENTRY)); + for (Map.Entry> datum : data) { + for (StreamEntry streamEntry : datum.getValue()) { + this.lastStreamEntryID = streamEntry.getID(); + String payloadData = streamEntry.getFields().get("payload"); + String clazz = streamEntry.getFields().get("class"); + UUID payloadDataManagerUUID = UUID.fromString(streamEntry.getFields().get("data-manager-uuid")); + + AbstractPayload unknownPayload = (AbstractPayload) gson.fromJson(payloadData, Class.forName(clazz)); + + if (unknownPayload.senderProxy().equals(this.proxyId)) { + if (!payloadDataManagerUUID.equals(this.dataManagerUUID)) { + plugin.logWarn("detected other proxy is using same ID! {} this can cause issues, please shutdown this proxy and change the id!", this.proxyId); + } + break; + } + if (unknownPayload instanceof HeartbeatPayload payload) { + handleHeartBeat(payload); + } else if (unknownPayload instanceof DeathPayload payload) { + handleProxyDeath(payload); + } else if (unknownPayload instanceof RunCommandPayload payload) { + handleCommand(payload); + } else if (unknownPayload instanceof PubSubPayload payload) { + handleChannelMessage(payload); + } else { + plugin.logWarn("got unknown data manager payload: {}", unknownPayload.getClassName()); + } + } + } + } catch (Exception e) { + this.plugin.logFatal("an error has occurred in the stream", e); + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { + } + } + } + } + + @Override + public void close() throws Exception { + closed.set(true); + this.publishDeath(); + this.heartbeats.clear(); + this.destroyProxyMembers(); + } + + public boolean isClosed() { + return closed.get(); + } + + public String proxyId() { + return proxyId; + } + + public UnifiedJedis unifiedJedis() { + return unifiedJedis; + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java deleted file mode 100644 index cd19d71..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public class PubSubListener implements Runnable { - private JedisPubSubHandler jpsh; - private final Set addedChannels = new HashSet(); - - private final RedisBungeePlugin plugin; - - public PubSubListener(RedisBungeePlugin plugin) { - this.plugin = plugin; - } - - @Override - public void run() { - RedisTask subTask = new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - jpsh = new JedisPubSubHandler(plugin); - addedChannels.add("redisbungee-" + plugin.getConfiguration().getProxyId()); - addedChannels.add("redisbungee-allservers"); - addedChannels.add("redisbungee-data"); - unifiedJedis.subscribe(jpsh, addedChannels.toArray(new String[0])); - return null; - } - }; - - try { - subTask.execute(); - } catch (Exception e) { - plugin.logWarn("PubSub error, attempting to recover in 5 secs."); - plugin.executeAsyncAfter(this, TimeUnit.SECONDS, 5); - } - } - - public void addChannel(String... channel) { - addedChannels.addAll(Arrays.asList(channel)); - jpsh.subscribe(channel); - } - - public void removeChannel(String... channel) { - Arrays.asList(channel).forEach(addedChannels::remove); - jpsh.unsubscribe(channel); - } - - public void poison() { - addedChannels.clear(); - jpsh.unsubscribe(); - } -} - diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java index a0e2471..3c827a0 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java @@ -10,28 +10,16 @@ package com.imaginarycode.minecraft.redisbungee.api; -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.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; -import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; -import redis.clients.jedis.Protocol; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; import java.net.InetAddress; -import java.util.*; +import java.util.UUID; import java.util.concurrent.TimeUnit; -import static com.google.common.base.Preconditions.checkArgument; - /** * This Class has all internal methods needed by every redis bungee plugin, and it can be used to implement another platforms than bungeecord or another forks of RedisBungee @@ -51,225 +39,54 @@ public interface RedisBungeePlugin

extends EventsPlatform { } + void logInfo(String msg); + + void logInfo(String format, Object... object); + + void logWarn(String msg); + + void logWarn(String format, Object... object); + + void logFatal(String msg); + + void logFatal(String format, Throwable throwable); + + RedisBungeeConfiguration configuration(); + Summoner getSummoner(); - RedisBungeeConfiguration getConfiguration(); - - int getCount(); - - default int getCurrentCount() { - return new RedisTask(this) { - @Override - public Long unifiedJedisTask(UnifiedJedis unifiedJedis) { - long total = 0; - long redisTime = getRedisTime(unifiedJedis); - Map heartBeats = unifiedJedis.hgetAll("heartbeats"); - for (Map.Entry stringStringEntry : heartBeats.entrySet()) { - String k = stringStringEntry.getKey(); - String v = stringStringEntry.getValue(); - - long heartbeatTime = Long.parseLong(v); - if (heartbeatTime + RedisUtil.PROXY_TIMEOUT >= redisTime) { - total = total + unifiedJedis.scard("proxy:" + k + ":usersOnline"); - } - } - return total; - } - }.execute().intValue(); - } - - Set getLocalPlayersAsUuidStrings(); - - AbstractDataManager getDataManager(); - - default Set getPlayers() { - return new RedisTask>(this) { - @Override - public Set unifiedJedisTask(UnifiedJedis unifiedJedis) { - ImmutableSet.Builder setBuilder = ImmutableSet.builder(); - try { - List keys = new ArrayList<>(); - for (String i : getProxiesIds()) { - keys.add("proxy:" + i + ":usersOnline"); - } - if (!keys.isEmpty()) { - Set users = unifiedJedis.sunion(keys.toArray(new String[0])); - 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! - logFatal("Unable to get connection from pool - did your Redis server go away?"); - throw new RuntimeException("Unable to get all players online", e); - } - return setBuilder.build(); - } - }.execute(); - } + RedisBungeeMode getRedisBungeeMode(); AbstractRedisBungeeAPI getAbstractRedisBungeeApi(); + ProxyDataManager proxyDataManager(); + + PlayerDataManager playerDataManager(); + UUIDTranslator getUuidTranslator(); - Multimap serverToPlayersCache(); + boolean isOnlineMode(); - default Multimap serversToPlayers() { - return new RedisTask>(this) { - @Override - public Multimap unifiedJedisTask(UnifiedJedis unifiedJedis) { - ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); - for (String serverId : getProxiesIds()) { - Set players = unifiedJedis.smembers("proxy:" + serverId + ":usersOnline"); - for (String player : players) { - String playerServer = unifiedJedis.hget("player:" + player, "server"); - if (playerServer == null) { - continue; - } - builder.put(playerServer, UUID.fromString(player)); - } - } - return builder.build(); - } - }.execute(); - } + public P getPlayer(UUID uuid); - default Set getPlayersOnProxy(String proxyId) { - checkArgument(getProxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID"); - return new RedisTask>(this) { - @Override - public Set unifiedJedisTask(UnifiedJedis unifiedJedis) { - Set users = unifiedJedis.smembers("proxy:" + proxyId + ":usersOnline"); - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (String user : users) { - builder.add(UUID.fromString(user)); - } - return builder.build(); - } - }.execute(); - } + public P getPlayer(String name); - default void sendProxyCommand(String proxyId, String command) { - checkArgument(getProxiesIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid"); - sendChannelMessage("redisbungee-" + proxyId, command); - } + public UUID getPlayerUUID(String player); - List getProxiesIds(); - default List getCurrentProxiesIds(boolean lagged) { - return new RedisTask>(this) { - @Override - public List unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - long time = getRedisTime(unifiedJedis); - ImmutableList.Builder servers = ImmutableList.builder(); - Map heartbeats = unifiedJedis.hgetAll("heartbeats"); - for (Map.Entry entry : heartbeats.entrySet()) { - try { - long stamp = Long.parseLong(entry.getValue()); - if (lagged ? time >= stamp + RedisUtil.PROXY_TIMEOUT : time <= stamp + RedisUtil.PROXY_TIMEOUT) { - servers.add(entry.getKey()); - } else if (time > stamp + RedisUtil.PROXY_TIMEOUT) { - logWarn(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat."); - unifiedJedis.hdel("heartbeats", entry.getKey()); - } - } catch (NumberFormatException ignored) { - } - } - return servers.build(); - } catch (JedisConnectionException e) { - logFatal("Unable to fetch server IDs"); - e.printStackTrace(); - return Collections.singletonList(getConfiguration().getProxyId()); - } - } - }.execute(); - } + public String getPlayerName(UUID player); - PubSubListener getPubSubListener(); + boolean handlePlatformKick(UUID uuid, String message); - default void sendChannelMessage(String channel, String message) { - new RedisTask(this) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - unifiedJedis.publish(channel, message); - } catch (JedisConnectionException e) { - // Redis server has disappeared! - logFatal("Unable to get connection from pool - did your Redis server go away?"); - throw new RuntimeException("Unable to publish channel message", e); - } - return null; - } - }.execute(); - } + public String getPlayerServerName(P player); + + public boolean isPlayerOnAServer(P player); + + public InetAddress getPlayerIp(P player); void executeAsync(Runnable runnable); void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time); - boolean isOnlineMode(); - - void logInfo(String msg); - - void logWarn(String msg); - - void logFatal(String msg); - - P getPlayer(UUID uuid); - - P getPlayer(String name); - - UUID getPlayerUUID(String player); - - String getPlayerName(UUID player); - - String getPlayerServerName(P player); - - boolean isPlayerOnAServer(P player); - - InetAddress getPlayerIp(P player); - - default void sendProxyCommand(String cmd) { - sendProxyCommand(getConfiguration().getProxyId(), cmd); - } - - default Long getRedisTime(UnifiedJedis unifiedJedis) { - List data = (List) unifiedJedis.sendCommand(Protocol.Command.TIME); - List times = new ArrayList<>(); - data.forEach((o) -> times.add(new String((byte[])o))); - return getRedisTime(times); - } - default long getRedisTime(List timeRes) { - return Long.parseLong(timeRes.get(0)); - } - - default void kickPlayer(UUID playerUniqueId, String message) { - // first handle on origin proxy if player not found publish the payload - if (!getDataManager().handleKick(playerUniqueId, message)) { - new RedisTask(this) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PayloadUtils.kickPlayerPayload(playerUniqueId, message, unifiedJedis); - return null; - } - }.execute(); - } - } - - default void kickPlayer(String playerName, String message) { - // fetch the uuid from name - UUID playerUUID = getUuidTranslator().getTranslatedUuid(playerName, true); - kickPlayer(playerUUID, message); - } - - RedisBungeeMode getRedisBungeeMode(); - - void updateProxiesIds(); } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index 2e595f4..e10c9d0 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -12,11 +12,9 @@ package com.imaginarycode.minecraft.redisbungee.api.config; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; import com.google.common.net.InetAddresses; import java.net.InetAddress; -import java.util.HashMap; import java.util.List; public class RedisBungeeConfiguration { @@ -47,6 +45,7 @@ public class RedisBungeeConfiguration { this.overrideBungeeCommands = overrideBungeeCommands; this.restoreOldKickBehavior = restoreOldKickBehavior; } + public String getProxyId() { return proxyId; } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java index 099c075..79dabfa 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java @@ -17,7 +17,6 @@ import java.util.UUID; * * @author Ham1255 * @since 0.7.0 - * */ public interface EventsPlatform { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java new file mode 100644 index 0000000..e41ee5f --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java @@ -0,0 +1,24 @@ + +package com.imaginarycode.minecraft.redisbungee.api.payloads; + +public abstract class AbstractPayload { + + private final String senderProxy; + + public AbstractPayload(String proxyId) { + this.senderProxy = proxyId; + } + + public AbstractPayload(String senderProxy, String className) { + this.senderProxy = senderProxy; + } + + public String senderProxy() { + return senderProxy; + } + + public String getClassName() { + return getClass().getName(); + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java new file mode 100644 index 0000000..6769ff2 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +import java.lang.reflect.Type; + +public class AbstractPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public AbstractPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + return new AbstractPayload(jsonObject.get("proxy").getAsString(), jsonObject.get("class").getAsString()) { + }; + } + + @Override + public JsonElement serialize(AbstractPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java new file mode 100644 index 0000000..399071a --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class DeathPayload extends AbstractPayload { + public DeathPayload(String proxyId) { + super(proxyId); + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java new file mode 100644 index 0000000..02268fd --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class HeartbeatPayload extends AbstractPayload { + + public record HeartbeatData(long heartbeat, int players) { + + } + + private final HeartbeatData data; + + public HeartbeatPayload(String proxyId, HeartbeatData data) { + super(proxyId); + this.data = data; + } + + public HeartbeatData data() { + return data; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java new file mode 100644 index 0000000..eaa9092 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class PubSubPayload extends AbstractPayload { + + private final String channel; + private final String message; + + + public PubSubPayload(String proxyId, String channel, String message) { + super(proxyId); + this.channel = channel; + this.message = message; + } + + public String channel() { + return channel; + } + + public String message() { + return message; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java new file mode 100644 index 0000000..6374e5c --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class RunCommandPayload extends AbstractPayload { + + + private final String proxyToRun; + + private final String command; + + + public RunCommandPayload(String proxyId, String proxyToRun, String command) { + super(proxyId); + this.proxyToRun = proxyToRun; + this.command = command; + } + + public String proxyToRun() { + return proxyToRun; + } + + public String command() { + return command; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java new file mode 100644 index 0000000..d77dd51 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload; + +import java.lang.reflect.Type; + +public class DeathPayloadSerializer implements JsonSerializer, JsonDeserializer { + + private static final Gson gson = new Gson(); + + + @Override + public DeathPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + return new DeathPayload(senderProxy); + } + + @Override + public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java new file mode 100644 index 0000000..1f301f2 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload; + +import java.lang.reflect.Type; + +public class HeartbeatPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public HeartbeatPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + long heartbeat = jsonObject.get("heartbeat").getAsLong(); + int players = jsonObject.get("players").getAsInt(); + return new HeartbeatPayload(senderProxy, new HeartbeatPayload.HeartbeatData(heartbeat, players)); + } + + @Override + public JsonElement serialize(HeartbeatPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("heartbeat", new JsonPrimitive(src.data().heartbeat())); + jsonObject.add("players", new JsonPrimitive(src.data().players())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java new file mode 100644 index 0000000..01d66a5 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload; + +import java.lang.reflect.Type; + +public class PubSubPayloadSerializer implements JsonSerializer, JsonDeserializer { + + private static final Gson gson = new Gson(); + + + @Override + public PubSubPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + String channel = jsonObject.get("channel").getAsString(); + String message = jsonObject.get("message").getAsString(); + return new PubSubPayload(senderProxy, channel, message); + } + + @Override + public JsonElement serialize(PubSubPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("channel", new JsonPrimitive(src.channel())); + jsonObject.add("message", context.serialize(src.message())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java new file mode 100644 index 0000000..2a7de33 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload; + +import java.lang.reflect.Type; + +public class RunCommandPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public RunCommandPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + String proxyToRun = jsonObject.get("proxy-to-run").getAsString(); + String command = jsonObject.get("command").getAsString(); + return new RunCommandPayload(senderProxy, proxyToRun, command); + } + + @Override + public JsonElement serialize(RunCommandPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("proxy-to-run", new JsonPrimitive(src.proxyToRun())); + jsonObject.add("command", context.serialize(src.command())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java index 99d8e19..14c2514 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java @@ -17,7 +17,7 @@ import java.io.IOException; import java.time.Duration; public class JedisClusterSummoner implements Summoner { - public final ClusterConnectionProvider clusterConnectionProvider; + private final ClusterConnectionProvider clusterConnectionProvider; public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) { this.clusterConnectionProvider = clusterConnectionProvider; @@ -35,6 +35,8 @@ public class JedisClusterSummoner implements Summoner { @Override public JedisCluster obtainResource() { - return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(30000)); + return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(10)); } + + } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java index 84eb85a..5e09859 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java @@ -11,9 +11,7 @@ package com.imaginarycode.minecraft.redisbungee.api.summoners; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.JedisPooled; import redis.clients.jedis.providers.ClusterConnectionProvider; -import redis.clients.jedis.providers.PooledConnectionProvider; import java.time.Duration; diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java index 6b511e7..36beac5 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java @@ -10,6 +10,8 @@ package com.imaginarycode.minecraft.redisbungee.api.summoners; +import redis.clients.jedis.UnifiedJedis; + import java.io.Closeable; @@ -18,9 +20,8 @@ import java.io.Closeable; * * @author Ham1255 * @since 0.7.0 - * */ -public interface Summoner

extends Closeable { +public interface Summoner

extends Closeable { P obtainResource(); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java deleted file mode 100644 index 669ba8c..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class HeartbeatTask extends RedisTask{ - - public final static TimeUnit REPEAT_INTERVAL_TIME_UNIT = TimeUnit.SECONDS; - public final static int INTERVAL = 1; - private final AtomicInteger globalPlayerCount; - - public HeartbeatTask(RedisBungeePlugin plugin, AtomicInteger globalPlayerCount) { - super(plugin); - this.globalPlayerCount = globalPlayerCount; - } - - - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - long redisTime = plugin.getRedisTime(unifiedJedis); - unifiedJedis.hset("heartbeats", plugin.getConfiguration().getProxyId(), String.valueOf(redisTime)); - } catch (JedisConnectionException e) { - // Redis server has disappeared! - plugin.logFatal("Unable to update heartbeat - did your Redis server go away?"); - e.printStackTrace(); - return null; - } - try { - plugin.updateProxiesIds(); - globalPlayerCount.set(plugin.getCurrentCount()); - } catch (Throwable e) { - plugin.logFatal("Unable to update data - did your Redis server go away?"); - e.printStackTrace(); - } - return null; - } - - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java deleted file mode 100644 index 8a2986f..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; -import redis.clients.jedis.Protocol; -import redis.clients.jedis.UnifiedJedis; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -public class InitialUtils { - - public static void checkRedisVersion(RedisBungeePlugin plugin) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - // This is more portable than INFO

- String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO)); - for (String s : info.split("\r\n")) { - if (s.startsWith("redis_version:")) { - String version = s.split(":")[1]; - plugin.logInfo("Redis server version: " + version); - if (!RedisUtil.isRedisVersionRight(version)) { - plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis."); - throw new RuntimeException("Unsupported Redis version detected"); - } - long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); - if (uuidCacheSize > 750000) { - plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains"); - } - break; - } - } - return null; - } - }.execute(); - } - - - public static void checkIfRecovering(RedisBungeePlugin plugin, Path dataFolder) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - Path crashFile = dataFolder.resolve("restarted_from_crash.txt"); - if (Files.exists(crashFile)) { - try { - Files.delete(crashFile); - } catch (IOException e) { - throw new RuntimeException(e); - } - plugin.logInfo("crash file was deleted continuing RedisBungee startup "); - } else if (unifiedJedis.hexists("heartbeats", plugin.getConfiguration().getProxyId())) { - try { - long value = Long.parseLong(unifiedJedis.hget("heartbeats", plugin.getConfiguration().getProxyId())); - long redisTime = plugin.getRedisTime(unifiedJedis); - - if (redisTime < value + RedisUtil.PROXY_TIMEOUT) { - logImposter(plugin); - throw new RuntimeException("Possible impostor instance!"); - } - } catch (NumberFormatException ignored) { - } - } - return null; - } - }.execute(); - } - - private static void logImposter(RedisBungeePlugin plugin) { - plugin.logFatal("You have launched a possible impostor Velocity / Bungeecord instance. Another instance is already running."); - plugin.logFatal("For data consistency reasons, RedisBungee will now disable itself."); - plugin.logFatal("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."); - } - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java deleted file mode 100644 index c13742e..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.UnifiedJedis; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public abstract class IntegrityCheckTask extends RedisTask { - - public static int INTERVAL = 30; - public static TimeUnit TIMEUNIT = TimeUnit.SECONDS; - - - public IntegrityCheckTask(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - Set players = plugin.getLocalPlayersAsUuidStrings(); - Set playersInRedis = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline"); - List lagged = plugin.getCurrentProxiesIds(true); - - // Clean up lagged players. - for (String s : lagged) { - Set laggedPlayers = unifiedJedis.smembers("proxy:" + s + ":usersOnline"); - unifiedJedis.del("proxy:" + s + ":usersOnline"); - if (!laggedPlayers.isEmpty()) { - plugin.logInfo("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)..."); - for (String laggedPlayer : laggedPlayers) { - PlayerUtils.cleanUpPlayer(laggedPlayer, unifiedJedis, true); - } - } - } - - 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 : plugin.getProxiesIds()) { - if (proxyId.equals(plugin.getConfiguration().getProxyId())) continue; - if (unifiedJedis.sismember("proxy:" + proxyId + ":usersOnline", member)) { - // Just clean up the set. - found = true; - break; - } - } - if (!found) { - PlayerUtils.cleanUpPlayer(member, unifiedJedis, false); - plugin.logWarn("Player found in set that was not found locally and globally: " + member); - } else { - unifiedJedis.srem("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline", member); - plugin.logWarn("Player found in set that was not found locally, but is on another proxy: " + member); - } - } - // due unifiedJedis does not support pipelined. - //Pipeline pipeline = jedis.pipelined(); - - for (String player : absentInRedis) { - // Player not online according to Redis but not BungeeCord. - handlePlatformPlayer(player, unifiedJedis); - } - } catch (Throwable e) { - plugin.logFatal("Unable to fix up stored player data"); - e.printStackTrace(); - } - return null; - } - - - public abstract void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis); - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java new file mode 100644 index 0000000..21a5d29 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.tasks; + +import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import redis.clients.jedis.*; + +public abstract class RedisPipelineTask extends RedisTask { + + + public RedisPipelineTask(AbstractRedisBungeeAPI api) { + super(api); + } + + public RedisPipelineTask(RedisBungeePlugin plugin) { + super(plugin); + } + + + @Override + public T unifiedJedisTask(UnifiedJedis unifiedJedis) { + if (unifiedJedis instanceof JedisPooled pooled) { + try (Pipeline pipeline = pooled.pipelined()) { + return doPooledPipeline(pipeline); + } + } else if (unifiedJedis instanceof JedisCluster jedisCluster) { + try (ClusterPipeline pipeline = jedisCluster.pipelined()) { + return clusterPipeline(pipeline); + } + } + + return null; + } + + public abstract T doPooledPipeline(Pipeline pipeline); + + public abstract T clusterPipeline(ClusterPipeline pipeline); + + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java index eb1b416..9a6da17 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java @@ -11,11 +11,11 @@ package com.imaginarycode.minecraft.redisbungee.api.tasks; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import redis.clients.jedis.UnifiedJedis; import java.util.concurrent.Callable; @@ -27,23 +27,22 @@ import java.util.concurrent.Callable; public abstract class RedisTask implements Runnable, Callable { protected final Summoner summoner; - protected final AbstractRedisBungeeAPI api; - protected RedisBungeePlugin plugin; + + protected final RedisBungeeMode mode; @Override public V call() throws Exception { - return execute(); + return this.execute(); } public RedisTask(AbstractRedisBungeeAPI api) { - this.api = api; this.summoner = api.getSummoner(); + this.mode = api.getMode(); } public RedisTask(RedisBungeePlugin plugin) { - this.plugin = plugin; - this.api = plugin.getAbstractRedisBungeeApi(); - this.summoner = api.getSummoner(); + this.summoner = plugin.getSummoner(); + this.mode = plugin.getRedisBungeeMode(); } public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis); @@ -53,22 +52,16 @@ public abstract class RedisTask implements Runnable, Callable { this.execute(); } - public V execute(){ + public V execute() { // JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its single instance anyway. - if (api.getMode() == RedisBungeeMode.SINGLE) { + if (mode == RedisBungeeMode.SINGLE) { JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner; return this.unifiedJedisTask(jedisSummoner.obtainResource()); - } else if (api.getMode() == RedisBungeeMode.CLUSTER) { + } else if (mode == RedisBungeeMode.CLUSTER) { JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner; return this.unifiedJedisTask(jedisClusterSummoner.obtainResource()); } return null; } - public RedisBungeePlugin getPlugin() { - if (plugin == null) { - throw new NullPointerException("Plugin is null in the task"); - } - return plugin; - } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java deleted file mode 100644 index a3fdbcc..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; - -import java.util.Set; - -public class ShutdownUtils { - - public static void shutdownCleanup(RedisBungeePlugin plugin) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hdel("heartbeats", plugin.getConfiguration().getProxyId()); - if (unifiedJedis.scard("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline") > 0) { - Set players = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline"); - for (String member : players) - PlayerUtils.cleanUpPlayer(member, unifiedJedis, true); - } - return null; - } - }.execute(); - } - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java new file mode 100644 index 0000000..6815770 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.util; + +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; +import redis.clients.jedis.Protocol; +import redis.clients.jedis.UnifiedJedis; + + +public class InitialUtils { + + public static void checkRedisVersion(RedisBungeePlugin plugin) { + new RedisTask(plugin) { + @Override + public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { + // This is more portable than INFO
+ String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO)); + for (String s : info.split("\r\n")) { + if (s.startsWith("redis_version:")) { + String version = s.split(":")[1]; + plugin.logInfo("Redis server version: " + version); + if (!RedisUtil.isRedisVersionRight(version)) { + plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis."); + throw new RuntimeException("Unsupported Redis version detected"); + } + long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); + if (uuidCacheSize > 750000) { + plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains"); + } + break; + } + } + return null; + } + }.execute(); + } + + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java index 9e4bd92..2e00c1e 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java @@ -5,6 +5,7 @@ import com.google.common.annotations.VisibleForTesting; @VisibleForTesting public class RedisUtil { public final static int PROXY_TIMEOUT = 30; + public static boolean isRedisVersionRight(String redisVersion) { String[] args = redisVersion.split("\\."); if (args.length < 2) { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java deleted file mode 100644 index fa4290e..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.io; - -import com.google.common.io.ByteStreams; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - - -public class IOUtil { - public static String readInputStreamAsString(InputStream is) { - String string; - try { - string = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new AssertionError(e); - } - return string; - } -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java deleted file mode 100644 index 36e9b78..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.payload; - -import com.google.gson.Gson; -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.UUID; - -public class PayloadUtils { - private static final Gson gson = new Gson(); - - public static void playerJoinPayload(UUID uuid, UnifiedJedis unifiedJedis, InetAddress inetAddress) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.JOIN, - new AbstractDataManager.LoginPayload(inetAddress)))); - } - - - public static void playerQuitPayload(String uuid, UnifiedJedis unifiedJedis, long timestamp) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - UUID.fromString(uuid), AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.LEAVE, - new AbstractDataManager.LogoutPayload(timestamp)))); - } - - - - public static void playerServerChangePayload(UUID uuid, UnifiedJedis unifiedJedis, String newServer, String oldServer) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.SERVER_CHANGE, - new AbstractDataManager.ServerChangePayload(newServer, oldServer)))); - } - - - public static void kickPlayerPayload(UUID uuid, String message, UnifiedJedis unifiedJedis) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.KICK, - new AbstractDataManager.KickPayload(message)))); - } -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java deleted file mode 100644 index 820ad34..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.player; - -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerJoinPayload; -import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerQuitPayload; - -public class PlayerUtils { - - public static void cleanUpPlayer(String uuid, UnifiedJedis rsc, boolean firePayload) { - final long timestamp = System.currentTimeMillis(); - final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid, rsc); - rsc.srem("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid); - if (!isKickedFromOtherLocation) { - rsc.hdel("player:" + uuid, "server", "ip", "proxy"); - rsc.hset("player:" + uuid, "online", String.valueOf(timestamp)); - } - if (firePayload && !isKickedFromOtherLocation) { - playerQuitPayload(uuid, rsc, timestamp); - } - } - - public static void setKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) { - // set anything for sake of exists check. then expire it after 2 seconds. should be great? - unifiedJedis.set("kicked-other-location::" + uuid, "0"); - unifiedJedis.expire("kicked-other-location::" + uuid, 2); - } - - public static boolean isKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) { - return unifiedJedis.exists("kicked-other-location::" + uuid); - } - - - public static void createPlayer(UUID uuid, UnifiedJedis unifiedJedis, String currentServer, InetAddress hostname, boolean fireEvent) { - final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid.toString(), unifiedJedis); - Map playerData = new HashMap<>(4); - playerData.put("online", "0"); - playerData.put("ip", hostname.getHostName()); - playerData.put("proxy", AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId()); - if (currentServer != null) { - playerData.put("server", currentServer); - } - unifiedJedis.sadd("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid.toString()); - unifiedJedis.hset("player:" + uuid, playerData); - if (fireEvent && !isKickedFromOtherLocation) { - playerJoinPayload(uuid, unifiedJedis, hostname); - } - } - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java similarity index 97% rename from RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java rename to RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java index 7ee9cc5..71df20b 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java @@ -7,7 +7,7 @@ import com.google.common.io.ByteArrayDataOutput; import java.util.Collection; import java.util.Map; -public class Serializations { +public class MultiMapSerialization { public static void serializeMultiset(Multiset collection, ByteArrayDataOutput output) { output.writeInt(collection.elementSet().size()); @@ -36,4 +36,5 @@ public class Serializations { output.writeUTF(o.toString()); } } + } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java index 69eb689..3bcd38c 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java @@ -22,38 +22,38 @@ import java.util.List; import java.util.UUID; public class NameFetcher { - private static OkHttpClient httpClient; - private static final Gson gson = new Gson(); + private static OkHttpClient httpClient; + private static final Gson gson = new Gson(); - public static void setHttpClient(OkHttpClient httpClient) { - NameFetcher.httpClient = httpClient; - } + public static void setHttpClient(OkHttpClient httpClient) { + NameFetcher.httpClient = httpClient; + } - public static List nameHistoryFromUuid(UUID uuid) throws IOException { - String name = getName(uuid); - if (name == null) return Collections.emptyList(); - return Collections.singletonList(name); - } + public static List nameHistoryFromUuid(UUID uuid) throws IOException { + String name = getName(uuid); + if (name == null) return Collections.emptyList(); + return Collections.singletonList(name); + } - public static String getName(UUID uuid) throws IOException { - String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString(); - Request request = new Request.Builder() - .addHeader("User-Agent", "RedisBungee-ProxioDev") - .url(url) - .get() - .build(); - ResponseBody body = httpClient.newCall(request).execute().body(); - String response = body.string(); - body.close(); + public static String getName(UUID uuid) throws IOException { + String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString(); + Request request = new Request.Builder() + .addHeader("User-Agent", "RedisBungee-ProxioDev") + .url(url) + .get() + .build(); + ResponseBody body = httpClient.newCall(request).execute().body(); + String response = body.string(); + body.close(); - JsonObject json = gson.fromJson(response, JsonObject.class); - if (!json.has("success") || !json.get("success").getAsBoolean()) return null; - if (!json.has("data")) return null; - JsonObject data = json.getAsJsonObject("data"); - if (!data.has("player")) return null; - JsonObject player = data.getAsJsonObject("player"); - if (!player.has("username")) return null; + JsonObject json = gson.fromJson(response, JsonObject.class); + if (!json.has("success") || !json.get("success").getAsBoolean()) return null; + if (!json.has("data")) return null; + JsonObject data = json.getAsJsonObject("data"); + if (!data.has("player")) return null; + JsonObject player = data.getAsJsonObject("player"); + if (!player.has("username")) return null; - return player.get("username").getAsString(); - } + return player.get("username").getAsString(); + } } \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java index 7453074..acccf40 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java @@ -14,13 +14,15 @@ import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; - import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; import org.checkerframework.checker.nullness.qual.NonNull; import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.exceptions.JedisException; -import java.util.*; +import java.util.Calendar; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; diff --git a/RedisBungee-API/src/main/resources/messages.yml b/RedisBungee-API/src/main/resources/messages.yml index a1b1853..737b52c 100644 --- a/RedisBungee-API/src/main/resources/messages.yml +++ b/RedisBungee-API/src/main/resources/messages.yml @@ -1,2 +1,3 @@ logged-in-other-location: "§cYou logged in from another location!" -already-logged-in: "§cYou are already logged in!" \ No newline at end of file +already-logged-in: "§cYou are already logged in!" +error: "§cError has occurred" \ No newline at end of file diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 78cf66f..07e1cdb 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -40,11 +40,11 @@ tasks { options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) } runWaterfall { - waterfallVersion("1.19") + waterfallVersion("1.20") } compileJava { options.encoding = Charsets.UTF_8.name() - options.release.set(8) + options.release.set(17) } javadoc { options.encoding = Charsets.UTF_8.name() @@ -73,6 +73,7 @@ tasks { relocate("com.google.gson", "com.imaginarycode.minecraft.redisbungee.internal.com.google.gson") relocate("com.google.j2objc", "com.imaginarycode.minecraft.redisbungee.internal.com.google.j2objc") relocate("com.google.thirdparty", "com.imaginarycode.minecraft.redisbungee.internal.com.google.thirdparty") + relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") } } 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 deleted file mode 100644 index dea9185..0000000 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; -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; - -import java.util.UUID; - -public class BungeeDataManager extends AbstractDataManager 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()); - } - - @Override - public boolean handleKick(UUID target, String message) { - // check if the player is online on this proxy - ProxiedPlayer player = plugin.getPlayer(target); - if (player == null) return false; - player.disconnect(TextComponent.fromLegacyText(message)); - return true; - } -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java new file mode 100644 index 0000000..e312b7c --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.LoginEvent; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + + +public class BungeePlayerDataManager extends PlayerDataManager implements Listener { + public BungeePlayerDataManager(RedisBungeePlugin plugin) { + super(plugin); + } + + @Override + @EventHandler + public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) { + super.handleNetworkPlayerServerChange(event); + } + + @Override + @EventHandler + public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { + super.handleNetworkPlayerQuit(event); + } + + @Override + @EventHandler + public void onPubSubMessageEvent(PubSubMessageEvent event) { + super.handlePubSubMessageEvent(event); + } + + @Override + @EventHandler + public void onServerConnectedEvent(ServerConnectedEvent event) { + final String currentServer = event.getServer().getInfo().getName(); + final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName(); + super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); + } + + @EventHandler + public void onLoginEvent(LoginEvent event) { + event.registerIntent((Plugin) plugin); + // check if online + if (getLastOnline(event.getConnection().getUniqueId()) == 0) { + if (!plugin.configuration().restoreOldKickBehavior()) { + kickPlayer(event.getConnection().getUniqueId(), plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); + // wait 3 seconds before releasing the event + plugin.executeAsyncAfter(() -> event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); + } else { + event.setCancelled(true); + event.setCancelReason(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', Objects.requireNonNull(plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))))); + event.completeIntent((Plugin) plugin); + } + } else { + event.completeIntent((Plugin) plugin); + } + + } + + @Override + @EventHandler + public void onLoginEvent(PostLoginEvent event) { + super.addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getAddress().getAddress()); + } + + @Override + @EventHandler + public void onDisconnectEvent(PlayerDisconnectEvent event) { + super.removePlayer(event.getPlayer().getUniqueId()); + } + + +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java deleted file mode 100644 index f1a46c6..0000000 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import net.md_5.bungee.api.connection.PendingConnection; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import redis.clients.jedis.UnifiedJedis; -public class BungeePlayerUtils { - - public static void createBungeePlayer(ProxiedPlayer player, UnifiedJedis unifiedJedis, boolean fireEvent) { - String serverName = null; - if (player.getServer() != null) { - serverName = player.getServer().getInfo().getName(); - } - PendingConnection pendingConnection = player.getPendingConnection(); - PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, pendingConnection.getAddress().getAddress(), fireEvent); - } - -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index f540ab6..81988c4 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -10,131 +10,91 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.tasks.*; +import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; +import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.*; -import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; -import com.imaginarycode.minecraft.redisbungee.api.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.chat.TextComponent; 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 redis.clients.jedis.*; +import net.md_5.bungee.api.scheduler.ScheduledTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.JedisPool; -import java.io.*; +import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; -import java.util.*; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; public class RedisBungee extends Plugin implements RedisBungeePlugin, ConfigLoader { private static RedisBungeeAPI apiStatic; - private AbstractRedisBungeeAPI api; private RedisBungeeMode redisBungeeMode; - private PubSubListener psl = null; + private ProxyDataManager proxyDataManager; + private BungeePlayerDataManager playerDataManager; + private ScheduledTask heartbeatTask; + private ScheduledTask cleanupTask; private Summoner summoner; private UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; - private BungeeDataManager dataManager; private OkHttpClient httpClient; - private volatile List proxiesIds; - private final AtomicInteger globalPlayerCount = new AtomicInteger(); - private Future integrityCheck; - private Future heartbeatTask; - private static final Object SERVER_TO_PLAYERS_KEY = new Object(); - private final Cache> serverToPlayersCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.SECONDS) - .build(); + + private final Logger logger = LoggerFactory.getLogger("RedisBungee"); @Override - public RedisBungeeConfiguration getConfiguration() { + public RedisBungeeConfiguration configuration() { return this.configuration; } - @Override - public int getCount() { - return this.globalPlayerCount.get(); - } - - @Override - public Set getLocalPlayersAsUuidStrings() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (ProxiedPlayer player : getProxy().getPlayers()) { - builder.add(player.getUniqueId().toString()); - } - return builder.build(); - } - - @Override - public AbstractDataManager getDataManager() { - return this.dataManager; - } - @Override public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() { return this.api; } + @Override + public ProxyDataManager proxyDataManager() { + return this.proxyDataManager; + } + + @Override + public PlayerDataManager playerDataManager() { + return this.playerDataManager; + } + @Override public UUIDTranslator getUuidTranslator() { return this.uuidTranslator; } - @Override - public Multimap serverToPlayersCache() { - try { - return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getProxiesIds() { - return proxiesIds; - } - - @Override - public PubSubListener getPubSubListener() { - return this.psl; - } - - @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 fireEvent(Object event) { this.getProxy().getPluginManager().callEvent((Event) event); @@ -147,17 +107,32 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin getLocalOnlineUUIDs() { + HashSet uuids = new HashSet<>(); + ProxyServer.getInstance().getPlayers().forEach((proxiedPlayer) -> uuids.add(proxiedPlayer.getUniqueId())); + return uuids; + } + + @Override + protected void handlePlatformCommandExecution(String command) { + logInfo("Dispatching {}", command); + ProxyServer.getInstance().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), command); + } + }; + this.playerDataManager = new BungeePlayerDataManager(this); + + getProxy().getPluginManager().registerListener(this, this.playerDataManager); + getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this)); + // start listening + getProxy().getScheduler().runAsync(this, proxyDataManager); + // heartbeat + this.heartbeatTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS); + // cleanup + this.cleanupTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS); // init the http lib httpClient = new OkHttpClient(); Dispatcher dispatcher = new Dispatcher(getExecutorService()); @@ -226,29 +232,7 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin implements Listener { - - - public RedisBungeeBungeeListener(RedisBungeePlugin plugin, List exemptAddresses) { - super(plugin, exemptAddresses); - } - - @Override - @EventHandler(priority = HIGHEST) - public void onLogin(LoginEvent event) { - event.registerIntent((Plugin) plugin); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - if (event.isCancelled()) { - return null; - } - if (plugin.getConfiguration().restoreOldKickBehavior()) { - for (String s : plugin.getProxiesIds()) { - if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) { - event.setCancelled(true); - event.setCancelReason(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)); - return null; - } - } - } else if (api.isPlayerOnline(event.getConnection().getUniqueId())) { - PlayerUtils.setKickedOtherLocation(event.getConnection().getUniqueId().toString(), unifiedJedis); - api.kickPlayer(event.getConnection().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); - } - return null; - } finally { - event.completeIntent((Plugin) plugin); - } - } - }); - } - - @Override - @EventHandler - public void onPostLogin(PostLoginEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), unifiedJedis); - BungeePlayerUtils.createBungeePlayer(event.getPlayer(), unifiedJedis, true); - return null; - } - }); - } - - @Override - @EventHandler - public void onPlayerDisconnect(PlayerDisconnectEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true); - return null; - } - }); - - } - - @Override - @EventHandler - public void onServerChange(ServerConnectedEvent event) { - final String currentServer = event.getServer().getInfo().getName(); - final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName(); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName()); - PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer); - return null; - } - }); - } - - @Override - @EventHandler - public void onPing(ProxyPingEvent event) { - if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) { - return; - } - ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection()); - - if (forced != null && event.getConnection().getListener().isPingPassthrough()) { - return; - } - event.getResponse().getPlayers().setOnline(plugin.getCount()); - } - - @Override - @SuppressWarnings("UnstableApiUsage") - @EventHandler - public void onPluginMessage(PluginMessageEvent event) { - if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) { - final String currentChannel = event.getTag(); - final byte[] data = Arrays.copyOf(event.getData(), event.getData().length); - plugin.executeAsync(() -> { - ByteArrayDataInput in = ByteStreams.newDataInput(data); - - String subchannel = in.readUTF(); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - String type; - - switch (subchannel) { - case "PlayerList": - out.writeUTF("PlayerList"); - Set original = Collections.emptySet(); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - original = plugin.getPlayers(); - } else { - out.writeUTF(type); - try { - original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); - } catch (IllegalArgumentException ignored) { - } - } - Set players = new HashSet<>(); - for (UUID uuid : original) - players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false)); - out.writeUTF(Joiner.on(',').join(players)); - break; - case "PlayerCount": - out.writeUTF("PlayerCount"); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - out.writeInt(plugin.getCount()); - } else { - out.writeUTF(type); - try { - out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); - } catch (IllegalArgumentException e) { - out.writeInt(0); - } - } - break; - case "LastOnline": - String user = in.readUTF(); - out.writeUTF("LastOnline"); - out.writeUTF(user); - out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); - break; - case "ServerPlayers": - String type1 = in.readUTF(); - out.writeUTF("ServerPlayers"); - Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - - boolean includesUsers; - - switch (type1) { - case "COUNT": - includesUsers = false; - break; - case "PLAYERS": - includesUsers = true; - break; - default: - // TODO: Should I raise an error? - return; - } - - out.writeUTF(type1); - - if (includesUsers) { - Multimap human = HashMultimap.create(); - for (Map.Entry entry : multimap.entries()) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - serializeMultimap(human, true, out); - } else { - serializeMultiset(multimap.keys(), out); - } - break; - case "Proxy": - out.writeUTF("Proxy"); - out.writeUTF(plugin.getConfiguration().getProxyId()); - break; - case "PlayerProxy": - String username = in.readUTF(); - out.writeUTF("PlayerProxy"); - out.writeUTF(username); - out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); - break; - default: - return; - } - - ((Server) event.getSender()).sendData(currentChannel, out.toByteArray()); - }); - } - } - - @Override - @EventHandler - public void onPubSubMessage(PubSubMessageEvent event) { - if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) { - String message = event.getMessage(); - if (message.startsWith("/")) - message = message.substring(1); - plugin.logInfo("Invoking command via PubSub: /" + message); - ((Plugin) plugin).getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), message); - } - } -} 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 new file mode 100644 index 0000000..6d7b6a7 --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import net.md_5.bungee.api.AbstractReconnectHandler; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +import java.util.*; + +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap; +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset; + +public class RedisBungeeListener implements Listener { + + private final RedisBungeePlugin plugin; + + public RedisBungeeListener(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPing(ProxyPingEvent event) { + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getAddress().getAddress())) { + return; + } + ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection()); + + if (forced != null && event.getConnection().getListener().isPingPassthrough()) { + return; + } + event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers()); + } + + @SuppressWarnings("UnstableApiUsage") + @EventHandler + public void onPluginMessage(PluginMessageEvent event) { + if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) { + final String currentChannel = event.getTag(); + final byte[] data = Arrays.copyOf(event.getData(), event.getData().length); + plugin.executeAsync(() -> { + ByteArrayDataInput in = ByteStreams.newDataInput(data); + + String subchannel = in.readUTF(); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + String type; + + switch (subchannel) { + case "PlayerList" -> { + out.writeUTF("PlayerList"); + Set original = Collections.emptySet(); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + original = plugin.proxyDataManager().networkPlayers(); + } else { + out.writeUTF(type); + try { + original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); + } catch (IllegalArgumentException ignored) { + } + } + Set players = new HashSet<>(); + for (UUID uuid : original) + players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false)); + out.writeUTF(Joiner.on(',').join(players)); + } + case "PlayerCount" -> { + out.writeUTF("PlayerCount"); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + out.writeInt(plugin.proxyDataManager().totalNetworkPlayers()); + } else { + out.writeUTF(type); + try { + out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); + } catch (IllegalArgumentException e) { + out.writeInt(0); + } + } + } + case "LastOnline" -> { + String user = in.readUTF(); + out.writeUTF("LastOnline"); + out.writeUTF(user); + out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); + } + case "ServerPlayers" -> { + String type1 = in.readUTF(); + out.writeUTF("ServerPlayers"); + Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + boolean includesUsers; + switch (type1) { + case "COUNT" -> includesUsers = false; + case "PLAYERS" -> includesUsers = true; + default -> { + // TODO: Should I raise an error? + return; + } + } + out.writeUTF(type1); + if (includesUsers) { + Multimap human = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); + } + serializeMultimap(human, true, out); + } else { + serializeMultiset(multimap.keys(), out); + } + } + case "Proxy" -> { + out.writeUTF("Proxy"); + out.writeUTF(plugin.configuration().getProxyId()); + } + case "PlayerProxy" -> { + String username = in.readUTF(); + out.writeUTF("PlayerProxy"); + out.writeUTF(username); + out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); + } + default -> { + return; + } + } + + ((Server) event.getSender()).sendData(currentChannel, out.toByteArray()); + }); + } + } + + +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java index e5a9ea3..e6a7264 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java @@ -13,8 +13,8 @@ package com.imaginarycode.minecraft.redisbungee.commands; import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; -import com.imaginarycode.minecraft.redisbungee.RedisBungee; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.RedisBungee; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.BaseComponent; @@ -286,9 +286,10 @@ public class RedisBungeeCommands { public static class ServerIds extends Command { private final RedisBungee plugin; + public ServerIds(RedisBungee plugin) { super("serverids", "redisbungee.command.serverids"); - this.plugin =plugin; + this.plugin = plugin; } @Override @@ -313,8 +314,8 @@ public class RedisBungeeCommands { plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { @Override public void run() { - String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId(); - if (!plugin.getProxiesIds().contains(proxy)) { + String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); + if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create()); return; } diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 4c2bbb4..2415121 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -39,7 +39,7 @@ tasks { "https://jd.papermc.io/velocity/3.0.0/", // velocity api ) val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc") - options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) + options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) } runVelocity { velocityVersion("3.3.0-SNAPSHOT") @@ -61,6 +61,7 @@ tasks { relocate("com.squareup.okhttp", "com.imaginarycode.minecraft.redisbungee.internal.okhttp") relocate("okio", "com.imaginarycode.minecraft.redisbungee.internal.okio") relocate("org.json", "com.imaginarycode.minecraft.redisbungee.internal.json") + relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") } } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java new file mode 100644 index 0000000..3d7d6cf --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.proxy.ProxyPingEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.ServerPing; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap; +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset; + +public class RedisBungeeListener { + + private final RedisBungeePlugin plugin; + + public RedisBungeeListener(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + @Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last + public void onPing(ProxyPingEvent event) { + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) { + return; + } + ServerPing.Builder ping = event.getPing().asBuilder(); + ping.onlinePlayers(plugin.proxyDataManager().totalNetworkPlayers()); + event.setPing(ping.build()); + } + + @Subscribe + public void onPluginMessage(PluginMessageEvent event) { + if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) { + return; + } + + event.setResult(PluginMessageEvent.ForwardResult.handled()); + + plugin.executeAsync(() -> { + ByteArrayDataInput in = event.dataAsDataStream(); + + String subchannel = in.readUTF(); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + String type; + + switch (subchannel) { + case "PlayerList": + out.writeUTF("PlayerList"); + Set original = Collections.emptySet(); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + original = plugin.proxyDataManager().networkPlayers(); + } else { + out.writeUTF(type); + try { + original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); + } catch (IllegalArgumentException ignored) { + } + } + Set players = original.stream() + .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false)) + .collect(Collectors.toSet()); + out.writeUTF(Joiner.on(',').join(players)); + break; + case "PlayerCount": + out.writeUTF("PlayerCount"); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + out.writeInt(plugin.proxyDataManager().totalNetworkPlayers()); + } else { + out.writeUTF(type); + try { + out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); + } catch (IllegalArgumentException e) { + out.writeInt(0); + } + } + break; + case "LastOnline": + String user = in.readUTF(); + out.writeUTF("LastOnline"); + out.writeUTF(user); + out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); + break; + case "ServerPlayers": + String type1 = in.readUTF(); + out.writeUTF("ServerPlayers"); + Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + + boolean includesUsers; + + switch (type1) { + case "COUNT" -> includesUsers = false; + case "PLAYERS" -> includesUsers = true; + default -> { + // TODO: Should I raise an error? + return; + } + } + + out.writeUTF(type1); + + if (includesUsers) { + Multimap human = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); + } + serializeMultimap(human, true, out); + } else { + serializeMultiset(multimap.keys(), out); + } + break; + case "Proxy": + out.writeUTF("Proxy"); + out.writeUTF(plugin.configuration().getProxyId()); + break; + case "PlayerProxy": + String username = in.readUTF(); + out.writeUTF("PlayerProxy"); + out.writeUTF(username); + out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); + break; + default: + return; + } + + ((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); + }); + + } + + +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java deleted file mode 100644 index f73bf88..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.google.common.base.Joiner; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.io.ByteArrayDataInput; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; -import com.imaginarycode.minecraft.redisbungee.api.AbstractRedisBungeeListener; -import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils; -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.velocitypowered.api.event.Continuation; -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.ResultedEvent; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.LoginEvent; -import com.velocitypowered.api.event.connection.PluginMessageEvent; -import com.velocitypowered.api.event.connection.PostLoginEvent; -import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult; -import com.velocitypowered.api.event.player.ServerConnectedEvent; -import com.velocitypowered.api.event.proxy.ProxyPingEvent; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; -import com.velocitypowered.api.proxy.server.ServerPing; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.*; -import java.util.stream.Collectors; - -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap; -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset; - -public class RedisBungeeVelocityListener extends AbstractRedisBungeeListener { - // Some messages are using legacy characters - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); - - public RedisBungeeVelocityListener(RedisBungeePlugin plugin, List exemptAddresses) { - super(plugin, exemptAddresses); - } - - @Subscribe(order = PostOrder.LAST) - public void onLogin(LoginEvent event, Continuation continuation) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - if (!event.getResult().isAllowed()) { - return null; - } - if (plugin.getConfiguration().restoreOldKickBehavior()) { - - for (String s : plugin.getProxiesIds()) { - if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) { - event.setResult(ResultedEvent.ComponentResult.denied(serializer.deserialize(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)))); - return null; - } - } - - } else if (api.isPlayerOnline(event.getPlayer().getUniqueId())) { - PlayerUtils.setKickedOtherLocation(event.getPlayer().getUniqueId().toString(), unifiedJedis); - api.kickPlayer(event.getPlayer().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); - } - return null; - } finally { - continuation.resume(); - } - } - - }); - } - - @Override - @Subscribe - public void onPostLogin(PostLoginEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), unifiedJedis); - VelocityPlayerUtils.createVelocityPlayer(event.getPlayer(), unifiedJedis, true); - return null; - } - }); - } - - @Override - @Subscribe - public void onPlayerDisconnect(DisconnectEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true); - return null; - } - - }); - - } - - @Override - @Subscribe - public void onServerChange(ServerConnectedEvent event) { - final String currentServer = event.getServer().getServerInfo().getName(); - final String oldServer = event.getPreviousServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", currentServer); - PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer); - return null; - } - }); - } - - @Override - @Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last - public void onPing(ProxyPingEvent event) { - if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) { - return; - } - ServerPing.Builder ping = event.getPing().asBuilder(); - ping.onlinePlayers(plugin.getCount()); - event.setPing(ping.build()); - } - - @Override - @Subscribe - public void onPluginMessage(PluginMessageEvent event) { - if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) { - return; - } - - event.setResult(ForwardResult.handled()); - - plugin.executeAsync(() -> { - ByteArrayDataInput in = event.dataAsDataStream(); - - String subchannel = in.readUTF(); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - String type; - - switch (subchannel) { - case "PlayerList": - out.writeUTF("PlayerList"); - Set original = Collections.emptySet(); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - original = plugin.getPlayers(); - } else { - out.writeUTF(type); - try { - original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); - } catch (IllegalArgumentException ignored) { - } - } - Set players = original.stream() - .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false)) - .collect(Collectors.toSet()); - out.writeUTF(Joiner.on(',').join(players)); - break; - case "PlayerCount": - out.writeUTF("PlayerCount"); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - out.writeInt(plugin.getCount()); - } else { - out.writeUTF(type); - try { - out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); - } catch (IllegalArgumentException e) { - out.writeInt(0); - } - } - break; - case "LastOnline": - String user = in.readUTF(); - out.writeUTF("LastOnline"); - out.writeUTF(user); - out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); - break; - case "ServerPlayers": - String type1 = in.readUTF(); - out.writeUTF("ServerPlayers"); - Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - - boolean includesUsers; - - switch (type1) { - case "COUNT": - includesUsers = false; - break; - case "PLAYERS": - includesUsers = true; - break; - default: - // TODO: Should I raise an error? - return; - } - - out.writeUTF(type1); - - if (includesUsers) { - Multimap human = HashMultimap.create(); - for (Map.Entry entry : multimap.entries()) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - serializeMultimap(human, true, out); - } else { - serializeMultiset(multimap.keys(), out); - } - break; - case "Proxy": - out.writeUTF("Proxy"); - out.writeUTF(plugin.getConfiguration().getProxyId()); - break; - case "PlayerProxy": - String username = in.readUTF(); - out.writeUTF("PlayerProxy"); - out.writeUTF(username); - out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); - break; - default: - return; - } - - ((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); - }); - - } - - - @Override - @Subscribe - public void onPubSubMessage(PubSubMessageEvent event) { - if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) { - String message = event.getMessage(); - if (message.startsWith("/")) - message = message.substring(1); - plugin.logInfo("Invoking command via PubSub: /" + message); - ((RedisBungeeVelocityPlugin) plugin).getProxy().getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), message); - - } - } -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index fe0d6c6..db2c870 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -10,12 +10,11 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; import com.google.inject.Inject; -import com.imaginarycode.minecraft.redisbungee.api.*; +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; @@ -23,7 +22,7 @@ import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEv import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.tasks.*; +import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; @@ -46,17 +45,21 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.scheduler.ScheduledTask; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.slf4j.Logger; -import redis.clients.jedis.*; import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.io.*; +import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; @Plugin(id = "redisbungee", name = "RedisBungee", version = Constants.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"}) public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, ConfigLoader { @@ -64,29 +67,25 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con private final Logger logger; private final Path dataFolder; private final AbstractRedisBungeeAPI api; - private final PubSubListener psl; private Summoner jedisSummoner; private RedisBungeeMode redisBungeeMode; private final UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; - private final VelocityDataManager dataManager; private final OkHttpClient httpClient; - private volatile List proxiesIds; - private final AtomicInteger globalPlayerCount = new AtomicInteger(); - private ScheduledTask integrityCheck; + + private final ProxyDataManager proxyDataManager; + + private final VelocityPlayerDataManager playerDataManager; + + private ScheduledTask cleanUpTask; private ScheduledTask heartbeatTask; - private static final Object SERVER_TO_PLAYERS_KEY = new Object(); public static final List IDENTIFIERS = List.of( MinecraftChannelIdentifier.create("legacy", "redisbungee"), new LegacyChannelIdentifier("RedisBungee"), // This is needed for clients before 1.13 new LegacyChannelIdentifier("legacy:redisbungee") ); - private final Cache> serverToPlayersCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.SECONDS) - .build(); - @Inject public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { @@ -102,11 +101,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } this.api = new RedisBungeeAPI(this); InitialUtils.checkRedisVersion(this); - // check if this proxy is recovering from a crash and start heart the beat. - InitialUtils.checkIfRecovering(this, getDataFolder()); + this.proxyDataManager = new ProxyDataManager(this) { + @Override + public Set getLocalOnlineUUIDs() { + HashSet players = new HashSet<>(); + server.getAllPlayers().forEach(player -> players.add(player.getUniqueId())); + return players; + } + + @Override + protected void handlePlatformCommandExecution(String command) { + server.getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), command); + } + }; + this.playerDataManager = new VelocityPlayerDataManager(this); uuidTranslator = new UUIDTranslator(this); - dataManager = new VelocityDataManager(this); - psl = new PubSubListener(this); this.httpClient = new OkHttpClient(); Dispatcher dispatcher = new Dispatcher(Executors.newFixedThreadPool(6)); this.httpClient.setDispatcher(dispatcher); @@ -115,31 +124,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } - @Override - public RedisBungeeConfiguration getConfiguration() { - return this.configuration; - } - - @Override - public int getCount() { - return this.globalPlayerCount.get(); - } - - - @Override - public Set getLocalPlayersAsUuidStrings() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Player player : getProxy().getAllPlayers()) { - builder.add(player.getUniqueId().toString()); - } - return builder.build(); - } - - @Override - public AbstractDataManager getDataManager() { - return this.dataManager; - } - @Override public Summoner getSummoner() { return this.jedisSummoner; @@ -150,29 +134,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.api; } + @Override + public ProxyDataManager proxyDataManager() { + return this.proxyDataManager; + } + + @Override + public PlayerDataManager playerDataManager() { + return this.playerDataManager; + } + @Override public UUIDTranslator getUuidTranslator() { return this.uuidTranslator; } - @Override - public Multimap serverToPlayersCache() { - try { - return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getProxiesIds() { - return proxiesIds; - } - - @Override - public PubSubListener getPubSubListener() { - return this.psl; - } @Override public void executeAsync(Runnable runnable) { @@ -199,16 +175,36 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con this.getLogger().info(msg); } + @Override + public void logInfo(String format, Object... object) { + logger.info(format, object); + } + @Override public void logWarn(String msg) { this.getLogger().warn(msg); } + @Override + public void logWarn(String format, Object... object) { + logger.warn(format, object); + } + @Override public void logFatal(String msg) { this.getLogger().error(msg); } + @Override + public void logFatal(String format, Throwable throwable) { + logger.error(format, throwable); + } + + @Override + public RedisBungeeConfiguration configuration() { + return this.configuration; + } + @Override public Player getPlayer(UUID uuid) { return this.getProxy().getPlayer(uuid).orElse(null); @@ -229,6 +225,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null); } + private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); + + @Override + public boolean handlePlatformKick(UUID uuid, String message) { + Player player = getPlayer(uuid); + if (player == null) return false; + player.disconnect(serializer.deserialize(message)); + return true; + } + @Override public String getPlayerServerName(Player player) { return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null); @@ -247,25 +253,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con @Override public void initialize() { logInfo("Initializing RedisBungee....."); - updateProxiesIds(); // start heartbeat task - heartbeatTask = getProxy().getScheduler().buildTask(this, new HeartbeatTask(this, this.globalPlayerCount)).repeat(HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT).schedule(); + // heartbeat and clean up + this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule(); + this.cleanUpTask = server.getScheduler().buildTask(this, this.proxyDataManager::correctionTask).repeat(Duration.ofSeconds(60)).schedule(); - getProxy().getEventManager().register(this, new RedisBungeeVelocityListener(this, configuration.getExemptAddresses())); - getProxy().getEventManager().register(this, dataManager); - getProxy().getScheduler().buildTask(this, psl).schedule(); - - IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) { - @Override - public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) { - Player playerProxied = getProxy().getPlayer(UUID.fromString(player)).orElse(null); - if (playerProxied == null) - return; // We'll deal with it later. - VelocityPlayerUtils.createVelocityPlayer(playerProxied, unifiedJedis, false); - } - }; - integrityCheck = getProxy().getScheduler().buildTask(this, integrityCheckTask::execute).repeat(30, TimeUnit.SECONDS).schedule(); + server.getEventManager().register(this, this.playerDataManager); + server.getEventManager().register(this, new RedisBungeeListener(this)); + // subscribe + server.getScheduler().buildTask(this, this.proxyDataManager).schedule(); // register plugin messages IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register); @@ -292,19 +289,18 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con public void stop() { logInfo("Turning off redis connections....."); // Poison the PubSub listener - if (psl != null) { - psl.poison(); - } - if (integrityCheck != null) { - integrityCheck.cancel(); + if (cleanUpTask != null) { + cleanUpTask.cancel(); } if (heartbeatTask != null) { heartbeatTask.cancel(); } - ShutdownUtils.shutdownCleanup(this); + + try { + this.proxyDataManager.close(); this.jedisSummoner.close(); - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException(e); } @@ -331,10 +327,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.redisBungeeMode; } - @Override - public void updateProxiesIds() { - this.proxiesIds = this.getCurrentProxiesIds(false); - } @Subscribe(order = PostOrder.FIRST) public void onProxyInitializeEvent(ProxyInitializeEvent event) { diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java deleted file mode 100644 index 4ad9ef3..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.PostLoginEvent; -import com.velocitypowered.api.proxy.Player; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - -import java.util.UUID; - - -public class VelocityDataManager extends AbstractDataManager { - - public VelocityDataManager(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - @Subscribe - public void onPostLogin(PostLoginEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @Subscribe - public void onPlayerDisconnect(DisconnectEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @Subscribe - public void onPubSubMessage(PubSubMessageEvent event) { - handlePubSubMessage(event.getChannel(), event.getMessage()); - } - - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); - @Override - public boolean handleKick(UUID target, String message) { - Player player = plugin.getPlayer(target); - if (player == null) { - return false; - } - player.disconnect(serializer.deserialize(message)); - return true; - } -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java new file mode 100644 index 0000000..a2b6735 --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class VelocityPlayerDataManager extends PlayerDataManager { + public VelocityPlayerDataManager(RedisBungeePlugin plugin) { + super(plugin); + } + + @Override + @Subscribe + public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) { + handleNetworkPlayerServerChange(event); + } + + @Override + @Subscribe + public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { + handleNetworkPlayerQuit(event); + } + + @Override + @Subscribe + public void onPubSubMessageEvent(PubSubMessageEvent event) { + handlePubSubMessageEvent(event); + } + + @Override + @Subscribe + public void onServerConnectedEvent(ServerConnectedEvent event) { + final String currentServer = event.getServer().getServerInfo().getName(); + final String oldServer; + if (event.getPreviousServer().isPresent()) { + oldServer = event.getPreviousServer().get().getServerInfo().getName(); + } else { + oldServer = null; + } + super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); + } + + private static final LegacyComponentSerializer LEGACY_COMPONENT_SERIALIZER = LegacyComponentSerializer.builder().build(); + + @Subscribe + public void onLoginEvent(LoginEvent event, Continuation continuation) { + // check if online + if (getLastOnline(event.getPlayer().getUniqueId()) == 0) { + if (!plugin.configuration().restoreOldKickBehavior()) { + kickPlayer(event.getPlayer().getUniqueId(), plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); + // wait 3 seconds before releasing the event + plugin.executeAsyncAfter(continuation::resume, TimeUnit.SECONDS, 3); + } else { + event.setResult(ResultedEvent.ComponentResult.denied(LEGACY_COMPONENT_SERIALIZER.deserialize(Objects.requireNonNull(plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))))); + continuation.resume(); + } + } else { + continuation.resume(); + } + } + + @Override + @Subscribe + public void onLoginEvent(PostLoginEvent event) { + addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getRemoteAddress().getAddress()); + } + + @Override + @Subscribe + public void onDisconnectEvent(DisconnectEvent event) { + if (event.getLoginStatus() == DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN || event.getLoginStatus() == DisconnectEvent.LoginStatus.PRE_SERVER_JOIN) { + removePlayer(event.getPlayer().getUniqueId()); + } + } +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java deleted file mode 100644 index 2f43fc8..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; -import redis.clients.jedis.UnifiedJedis; - -import java.util.Optional; - -public class VelocityPlayerUtils { - protected static void createVelocityPlayer(Player player, UnifiedJedis unifiedJedis, boolean fireEvent) { - Optional optionalServerConnection = player.getCurrentServer(); - String serverName = null; - if (optionalServerConnection.isPresent()) { - serverName = optionalServerConnection.get().getServerInfo().getName(); - } - PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, player.getRemoteAddress().getAddress(), fireEvent); - } - - -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java index 3537981..bb9823e 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java @@ -10,12 +10,6 @@ package com.imaginarycode.minecraft.redisbungee.commands; -import java.net.InetAddress; -import java.text.SimpleDateFormat; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; @@ -25,11 +19,16 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; - import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; +import java.net.InetAddress; +import java.text.SimpleDateFormat; +import java.util.Set; +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. @@ -324,8 +323,8 @@ public class RedisBungeeCommands { CommandSource sender = invocation.source(); String[] args = invocation.arguments(); plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId(); - if (!plugin.getProxiesIds().contains(proxy)) { + String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); + if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED)); return; } diff --git a/gradle.properties b/gradle.properties index f1c10e0..016d2f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -group = com.imaginarycode.minecraft -version = 0.12.0-SNAPSHOT +group=com.imaginarycode.minecraft +version=0.12.0-SNAPSHOT