Refactor BungeeCord-related code

This commit is contained in:
filoghost 2021-03-19 19:48:13 +01:00
parent 9a56a032de
commit 57fd78eac5
14 changed files with 508 additions and 617 deletions

View File

@ -30,7 +30,6 @@ import me.filoghost.holographicdisplays.object.internal.InternalHologram;
import me.filoghost.holographicdisplays.object.internal.InternalHologramManager;
import me.filoghost.holographicdisplays.placeholder.AnimationsRegistry;
import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager;
import me.filoghost.holographicdisplays.task.BungeeCleanupTask;
import me.filoghost.holographicdisplays.task.WorldPlayerCounterTask;
import me.filoghost.holographicdisplays.util.NMSVersion;
import org.bstats.bukkit.MetricsLite;
@ -38,6 +37,7 @@ import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacketSettings {
@ -112,7 +112,6 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke
// Start repeating tasks.
placeholderManager.startRefreshTask(this);
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new BungeeCleanupTask(bungeeServerTracker), 5 * 60 * 20, 5 * 60 * 20);
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new WorldPlayerCounterTask(), 0L, 3 * 20);
HologramCommandManager commandManager = new HologramCommandManager(configManager, internalHologramManager, nmsManager);
@ -142,7 +141,6 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke
public void load(boolean deferHologramsCreation, ErrorCollector errorCollector) {
placeholderManager.untrackAll();
internalHologramManager.clearAll();
bungeeServerTracker.resetTrackedServers();
configManager.reloadCustomPlaceholders(errorCollector);
configManager.reloadMainConfig(errorCollector);
@ -153,7 +151,7 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke
errorCollector.add(e, "failed to load animation files");
}
bungeeServerTracker.restartTask(Configuration.bungeeRefreshSeconds);
bungeeServerTracker.restart(Configuration.bungeeRefreshSeconds, TimeUnit.SECONDS);
if (deferHologramsCreation) {
// For the initial load: holograms are loaded later, when the worlds are ready

View File

@ -1,97 +0,0 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord;
import me.filoghost.fcommons.logging.Log;
import me.filoghost.holographicdisplays.HolographicDisplays;
import me.filoghost.holographicdisplays.disk.Configuration;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.Collection;
public class BungeeChannel implements PluginMessageListener {
private static final String BUNGEECORD_CHANNEL = "BungeeCord";
private static final String REDISBUNGEE_CHANNEL = "legacy:redisbungee";
private final BungeeServerTracker bungeeServerTracker;
public BungeeChannel(BungeeServerTracker bungeeServerTracker) {
this.bungeeServerTracker = bungeeServerTracker;
}
public void register(Plugin plugin) {
Bukkit.getMessenger().registerOutgoingPluginChannel(plugin, BUNGEECORD_CHANNEL);
Bukkit.getMessenger().registerIncomingPluginChannel(plugin, BUNGEECORD_CHANNEL, this);
Bukkit.getMessenger().registerOutgoingPluginChannel(plugin, REDISBUNGEE_CHANNEL);
Bukkit.getMessenger().registerIncomingPluginChannel(plugin, REDISBUNGEE_CHANNEL, this);
}
private String getTargetChannel() {
if (Configuration.useRedisBungee) {
return REDISBUNGEE_CHANNEL;
} else {
return BUNGEECORD_CHANNEL;
}
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (channel.equals(getTargetChannel())) {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(message));
try {
String subChannel = in.readUTF();
if (subChannel.equals("PlayerCount")) {
String server = in.readUTF();
if (in.available() > 0) {
int online = in.readInt();
BungeeServerInfo serverInfo = bungeeServerTracker.getOrCreateServerInfo(server);
serverInfo.setOnlinePlayers(online);
}
}
} catch (EOFException e) {
// Do nothing.
} catch (IOException e) {
// This should never happen.
e.printStackTrace();
}
}
}
public void askPlayerCount(String server) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(b);
try {
out.writeUTF("PlayerCount");
out.writeUTF(server);
} catch (IOException e) {
// It should not happen.
Log.warning("I/O Exception while asking for player count on server '" + server + "'.", e);
}
// OR, if you don't need to send it to a specific player
Collection<? extends Player> players = Bukkit.getOnlinePlayers();
if (players.size() > 0) {
players.iterator().next().sendPluginMessage(HolographicDisplays.getInstance(), getTargetChannel(), b.toByteArray());
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord;
import me.filoghost.fcommons.logging.Log;
import me.filoghost.holographicdisplays.disk.Configuration;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collection;
public class BungeeMessenger implements PluginMessageListener {
private static final String BUNGEECORD_CHANNEL = "BungeeCord";
private static final String REDISBUNGEE_CHANNEL = "legacy:redisbungee";
private final Plugin plugin;
private final PlayerCountCallback playerCountCallback;
private BungeeMessenger(Plugin plugin, PlayerCountCallback playerCountCallback) {
this.plugin = plugin;
this.playerCountCallback = playerCountCallback;
}
public static BungeeMessenger registerNew(Plugin plugin, PlayerCountCallback playerCountCallback) {
BungeeMessenger bungeeMessenger = new BungeeMessenger(plugin, playerCountCallback);
Bukkit.getMessenger().registerOutgoingPluginChannel(plugin, BUNGEECORD_CHANNEL);
Bukkit.getMessenger().registerIncomingPluginChannel(plugin, BUNGEECORD_CHANNEL, bungeeMessenger);
Bukkit.getMessenger().registerOutgoingPluginChannel(plugin, REDISBUNGEE_CHANNEL);
Bukkit.getMessenger().registerIncomingPluginChannel(plugin, REDISBUNGEE_CHANNEL, bungeeMessenger);
return bungeeMessenger;
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (Configuration.pingerEnabled || !channel.equals(getTargetChannel())) {
return;
}
DataInputStream in = new DataInputStream(new ByteArrayInputStream(message));
try {
String subChannel = in.readUTF();
if (!subChannel.equals("PlayerCount")) {
return;
}
String server = in.readUTF();
int online = in.readInt();
playerCountCallback.onReceive(server, online);
} catch (IOException e) {
Log.warning("Error while decoding player count from BungeeCord.", e);
}
}
public void sendPlayerCountRequest(String server) {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(byteOut);
try {
out.writeUTF("PlayerCount");
out.writeUTF(server);
} catch (IOException e) {
Log.warning("Error while encoding player count message for server \"" + server + "\".", e);
}
// Send the message through a random player (BungeeCord will not forward it to them)
Collection<? extends Player> players = Bukkit.getOnlinePlayers();
if (players.size() > 0) {
Player player = players.iterator().next();
player.sendPluginMessage(plugin, getTargetChannel(), byteOut.toByteArray());
}
}
private String getTargetChannel() {
if (Configuration.useRedisBungee) {
return REDISBUNGEE_CHANNEL;
} else {
return BUNGEECORD_CHANNEL;
}
}
public interface PlayerCountCallback {
void onReceive(String serverName, int playerCount);
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord;
import me.filoghost.holographicdisplays.disk.Configuration;
public class BungeeServerInfo {
private volatile boolean isOnline;
private volatile int onlinePlayers;
private volatile int maxPlayers;
// The two lines of a motd
private volatile String motd1; // Should never be null
private volatile String motd2; // Should never be null
private volatile long lastRequest;
protected BungeeServerInfo() {
isOnline = false;
this.motd1 = "";
this.motd2 = "";
updateLastRequest();
}
public boolean isOnline() {
return isOnline;
}
public void setOnline(boolean isOnline) {
this.isOnline = isOnline;
}
public int getOnlinePlayers() {
return onlinePlayers;
}
public void setOnlinePlayers(int onlinePlayers) {
this.onlinePlayers = onlinePlayers;
}
public int getMaxPlayers() {
return maxPlayers;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public String getMotd1() {
return motd1;
}
public String getMotd2() {
return motd2;
}
public void setMotd(String motd) {
if (motd == null) {
this.motd1 = "";
this.motd2 = "";
return;
}
int separatorIndex = motd.indexOf("\n");
if (separatorIndex >= 0) {
String line1 = motd.substring(0, separatorIndex);
String line2 = motd.substring(separatorIndex + 1);
this.motd1 = Configuration.pingerTrimMotd ? line1.trim() : line1;
this.motd2 = Configuration.pingerTrimMotd ? line2.trim() : line2;
} else {
this.motd1 = Configuration.pingerTrimMotd ? motd.trim() : motd;
this.motd2 = "";
}
}
public long getLastRequest() {
return lastRequest;
}
public void updateLastRequest() {
this.lastRequest = System.currentTimeMillis();
}
}

View File

@ -7,8 +7,9 @@ package me.filoghost.holographicdisplays.bridge.bungeecord;
import me.filoghost.fcommons.logging.Log;
import me.filoghost.holographicdisplays.HolographicDisplays;
import me.filoghost.holographicdisplays.bridge.bungeecord.serverpinger.PingResponse;
import me.filoghost.holographicdisplays.bridge.bungeecord.serverpinger.ServerPinger;
import me.filoghost.holographicdisplays.bridge.bungeecord.pinger.PingResponse;
import me.filoghost.holographicdisplays.bridge.bungeecord.pinger.ServerPinger;
import me.filoghost.holographicdisplays.core.DebugLogger;
import me.filoghost.holographicdisplays.disk.Configuration;
import me.filoghost.holographicdisplays.disk.ServerAddress;
import org.bukkit.Bukkit;
@ -17,184 +18,122 @@ import org.bukkit.plugin.Plugin;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
public class BungeeServerTracker {
private static final String PINGER_NOT_ENABLED_ERROR = "[Please enable pinger]";
private static final long UNTRACK_AFTER_TIME_WITHOUT_REQUESTS = TimeUnit.MINUTES.toMillis(10);
private final BungeeChannel bungeeChannel;
private final Map<String, BungeeServerInfo> trackedServers;
private final ConcurrentMap<String, TrackedServer> trackedServers;
private final BungeeMessenger bungeeMessenger;
private int taskID = -1;
public BungeeServerTracker(Plugin plugin) {
bungeeChannel = new BungeeChannel(this);
bungeeChannel.register(plugin);
trackedServers = new ConcurrentHashMap<>();
bungeeMessenger = BungeeMessenger.registerNew(plugin, this::updateServerInfoFromBungee);
}
public void resetTrackedServers() {
public void restart(int updateInterval, TimeUnit timeUnit) {
trackedServers.clear();
}
public void track(String server) {
if (!trackedServers.containsKey(server)) {
BungeeServerInfo info = new BungeeServerInfo();
info.setMotd(Configuration.pingerOfflineMotd);
trackedServers.put(server, info);
if (!Configuration.pingerEnabled) {
bungeeChannel.askPlayerCount(server);
}
}
}
protected BungeeServerInfo getOrCreateServerInfo(String server) {
BungeeServerInfo info = trackedServers.get(server);
if (info == null) {
info = new BungeeServerInfo();
info.setMotd(Configuration.pingerOfflineMotd);
trackedServers.put(server, info);
}
return info;
}
public int getPlayersOnline(String server) {
BungeeServerInfo info = trackedServers.get(server);
if (info != null) {
info.updateLastRequest();
return info.getOnlinePlayers();
} else {
// It was not tracked, add it.
track(server);
return 0;
}
}
public String getMaxPlayers(String server) {
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
BungeeServerInfo info = trackedServers.get(server);
if (info != null) {
info.updateLastRequest();
return String.valueOf(info.getMaxPlayers());
} else {
// It was not tracked, add it.
track(server);
return "0";
}
}
public String getMotd1(String server) {
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
BungeeServerInfo info = trackedServers.get(server);
if (info != null) {
info.updateLastRequest();
return info.getMotd1();
} else {
// It was not tracked, add it.
track(server);
return Configuration.pingerOfflineMotd;
}
}
public String getMotd2(String server) {
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
BungeeServerInfo info = trackedServers.get(server);
if (info != null) {
info.updateLastRequest();
return info.getMotd2();
} else {
// It was not tracked, add it.
track(server);
return "";
}
}
public String getOnlineStatus(String server) {
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
BungeeServerInfo info = trackedServers.get(server);
if (info != null) {
info.updateLastRequest();
return info.isOnline() ? Configuration.pingerStatusOnline : Configuration.pingerStatusOffline;
} else {
// It was not tracked, add it.
track(server);
return Configuration.pingerStatusOffline;
}
}
public Map<String, BungeeServerInfo> getTrackedServers() {
return trackedServers;
}
public void restartTask(int refreshSeconds) {
if (taskID != -1) {
Bukkit.getScheduler().cancelTask(taskID);
}
taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(HolographicDisplays.getInstance(), () -> {
taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(HolographicDisplays.getInstance(),
this::runPeriodicUpdateTask, 1, timeUnit.toSeconds(updateInterval) * 20L);
}
public ServerInfo getCurrentServerInfo(String serverName) {
// If it wasn't already tracked, send an update request instantly
if (!Configuration.pingerEnabled && !trackedServers.containsKey(serverName)) {
bungeeMessenger.sendPlayerCountRequest(serverName);
}
TrackedServer trackedServer = trackedServers.computeIfAbsent(serverName, TrackedServer::new);
trackedServer.updateLastRequest();
return trackedServer.serverInfo;
}
private void runPeriodicUpdateTask() {
removeUnusedServers();
if (Configuration.pingerEnabled) {
runAsyncPinger();
} else {
for (String server : trackedServers.keySet()) {
bungeeChannel.askPlayerCount(server);
}
}
}, 1, refreshSeconds * 20L);
}
private void runAsyncPinger() {
Bukkit.getScheduler().runTaskAsynchronously(HolographicDisplays.getInstance(), () -> {
for (ServerAddress serverAddress : Configuration.pingerServers) {
BungeeServerInfo serverInfo = getOrCreateServerInfo(serverAddress.getName());
boolean displayOffline = false;
for (TrackedServer trackedServer : trackedServers.values()) {
updateServerInfoWithPinger(trackedServer);
}
});
} else {
for (String serverName : trackedServers.keySet()) {
bungeeMessenger.sendPlayerCountRequest(serverName);
}
}
}
private void updateServerInfoWithPinger(TrackedServer trackedServer) {
ServerAddress serverAddress = Configuration.pingerServerAddresses.get(trackedServer.serverName);
if (serverAddress != null) {
trackedServer.serverInfo = pingServer(serverAddress);
} else {
trackedServer.serverInfo = ServerInfo.offline("[Unknown server: " + trackedServer.serverName + "]");
}
}
private void updateServerInfoFromBungee(String serverName, int onlinePlayers) {
TrackedServer trackedServer = trackedServers.get(serverName);
if (trackedServer != null) {
trackedServer.serverInfo = ServerInfo.online(onlinePlayers, 0, "");
}
}
private ServerInfo pingServer(ServerAddress serverAddress) {
try {
PingResponse data = ServerPinger.fetchData(serverAddress, Configuration.pingerTimeout);
if (data.isOnline()) {
serverInfo.setOnline(true);
serverInfo.setOnlinePlayers(data.getOnlinePlayers());
serverInfo.setMaxPlayers(data.getMaxPlayers());
serverInfo.setMotd(data.getMotd());
} else {
displayOffline = true;
}
return ServerInfo.online(data.getOnlinePlayers(), data.getMaxPlayers(), data.getMotd());
} catch (SocketTimeoutException e) {
// Common error, avoid logging
displayOffline = true;
// Common error, do not log
} catch (UnknownHostException e) {
Log.warning("Couldn't fetch data from " + serverAddress + ": unknown host address.");
displayOffline = true;
} catch (IOException e) {
Log.warning("Couldn't fetch data from " + serverAddress + ".", e);
displayOffline = true;
}
if (displayOffline) {
serverInfo.setOnline(false);
serverInfo.setOnlinePlayers(0);
serverInfo.setMaxPlayers(0);
serverInfo.setMotd(Configuration.pingerOfflineMotd);
return ServerInfo.offline(Configuration.pingerOfflineMotd);
}
private void removeUnusedServers() {
long now = System.currentTimeMillis();
trackedServers.values().removeIf(trackedServer -> {
if (now - trackedServer.lastRequest > UNTRACK_AFTER_TIME_WITHOUT_REQUESTS) {
DebugLogger.info("Untracked unused server \"" + trackedServer.serverName + "\".");
return true;
} else {
return false;
}
});
}
private static class TrackedServer {
private final String serverName;
private volatile ServerInfo serverInfo;
private volatile long lastRequest;
private TrackedServer(String serverName) {
this.serverName = serverName;
this.serverInfo = ServerInfo.offline(Configuration.pingerOfflineMotd);
}
private void updateLastRequest() {
this.lastRequest = System.currentTimeMillis();
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord;
import me.filoghost.fcommons.Strings;
import me.filoghost.holographicdisplays.disk.Configuration;
public class ServerInfo {
private final boolean online;
private final int onlinePlayers;
private final int maxPlayers;
private final String motdLine1, motdLine2;
public static ServerInfo online(int onlinePlayers, int maxPlayers, String motd) {
return new ServerInfo(true, onlinePlayers, maxPlayers, motd);
}
public static ServerInfo offline(String motd) {
return new ServerInfo(false, 0, 0, motd);
}
private ServerInfo(boolean online, int onlinePlayers, int maxPlayers, String motd) {
this.online = online;
this.onlinePlayers = onlinePlayers;
this.maxPlayers = maxPlayers;
if (Strings.isEmpty(motd)) {
motdLine1 = "";
motdLine2 = "";
} else if (motd.contains("\n")) {
String[] lines = Strings.split(motd, "\n", 2);
if (Configuration.pingerTrimMotd) {
lines = Strings.trim(lines);
}
motdLine1 = lines[0];
motdLine2 = lines.length > 1 ? lines[1] : "";
} else {
if (Configuration.pingerTrimMotd) {
motd = motd.trim();
}
motdLine1 = motd;
motdLine2 = "";
}
}
public boolean isOnline() {
return online;
}
public int getOnlinePlayers() {
return onlinePlayers;
}
public int getMaxPlayers() {
return maxPlayers;
}
public String getMotdLine1() {
return motdLine1;
}
public String getMotdLine2() {
return motdLine2;
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord.pinger;
import me.filoghost.holographicdisplays.core.DebugLogger;
import me.filoghost.holographicdisplays.disk.ServerAddress;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
public class PingResponse {
private final String motd;
private final int onlinePlayers;
private final int maxPlayers;
protected static PingResponse fromJson(String jsonString, ServerAddress address) {
if (jsonString == null || jsonString.isEmpty()) {
logInvalidResponse(jsonString, address);
return errorResponse("Invalid ping response (null or empty)");
}
Object jsonObject = JSONValue.parse(jsonString);
if (!(jsonObject instanceof JSONObject)) {
logInvalidResponse(jsonString, address);
return errorResponse("Invalid ping response (wrong format)");
}
JSONObject json = (JSONObject) jsonObject;
Object descriptionObject = json.get("description");
String motd;
int onlinePlayers = 0;
int maxPlayers = 0;
if (descriptionObject == null) {
logInvalidResponse(jsonString, address);
return errorResponse("Invalid ping response (description not found)");
}
if (descriptionObject instanceof JSONObject) {
Object text = ((JSONObject) descriptionObject).get("text");
if (text == null) {
logInvalidResponse(jsonString, address);
return errorResponse("Invalid ping response (text not found)");
}
motd = text.toString();
} else {
motd = descriptionObject.toString();
}
Object playersObject = json.get("players");
if (playersObject instanceof JSONObject) {
JSONObject playersJson = (JSONObject) playersObject;
Object onlineObject = playersJson.get("online");
if (onlineObject instanceof Number) {
onlinePlayers = ((Number) onlineObject).intValue();
}
Object maxObject = playersJson.get("max");
if (maxObject instanceof Number) {
maxPlayers = ((Number) maxObject).intValue();
}
}
return new PingResponse(motd, onlinePlayers, maxPlayers);
}
private static void logInvalidResponse(String responseJsonString, ServerAddress address) {
DebugLogger.warning("Received invalid JSON response from IP \"" + address + "\": " + responseJsonString);
}
private static PingResponse errorResponse(String error) {
return new PingResponse(error, 0, 0);
}
private PingResponse(String motd, int onlinePlayers, int maxPlayers) {
this.motd = motd;
this.onlinePlayers = onlinePlayers;
this.maxPlayers = maxPlayers;
}
public String getMotd() {
return motd;
}
public int getOnlinePlayers() {
return onlinePlayers;
}
public int getMaxPlayers() {
return maxPlayers;
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord.pinger;
import me.filoghost.holographicdisplays.disk.ServerAddress;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class ServerPinger {
public static PingResponse fetchData(final ServerAddress serverAddress, int timeout) throws IOException {
try (Socket socket = openSocket(serverAddress)) {
socket.setSoTimeout(timeout);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
DataInputStream in = new DataInputStream(socket.getInputStream());
// Handshake packet
ByteArrayOutputStream handshakeBytes = new ByteArrayOutputStream();
DataOutputStream handshakeOut = new DataOutputStream(handshakeBytes);
handshakeOut.writeByte(0x00); // Packet ID
writeVarInt(handshakeOut, 4); // Protocol version
writeString(handshakeOut, serverAddress.getAddress());
handshakeOut.writeShort(serverAddress.getPort());
writeVarInt(handshakeOut, 1); // Next state: status request
writeByteArray(out, handshakeBytes.toByteArray());
// Status request packet
writeByteArray(out, new byte[]{ 0x00 }); // Packet ID
// Response packet
readVarInt(in); // Packet size
readVarInt(in); // Packet ID
String responseJson = readString(in);
return PingResponse.fromJson(responseJson, serverAddress);
}
}
private static Socket openSocket(ServerAddress serverAddress) throws IOException {
return new Socket(serverAddress.getAddress(), serverAddress.getPort());
}
private static String readString(DataInputStream in) throws IOException {
byte[] bytes = readByteArray(in);
return new String(bytes, StandardCharsets.UTF_8);
}
private static void writeString(DataOutputStream out, String s) throws IOException {
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
writeByteArray(out, bytes);
}
private static byte[] readByteArray(DataInputStream in) throws IOException {
int length = readVarInt(in);
byte[] bytes = new byte[length];
in.readFully(bytes);
return bytes;
}
private static void writeByteArray(DataOutputStream out, byte[] bytes) throws IOException {
writeVarInt(out, bytes.length);
out.write(bytes);
}
private static int readVarInt(DataInputStream in) throws IOException {
int i = 0;
int j = 0;
while (true) {
int k = in.readByte();
i |= (k & 0x7F) << j++ * 7;
if (j > 5) {
throw new RuntimeException("VarInt too big");
}
if ((k & 0x80) != 0x80) {
return i;
}
}
}
private static void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
while ((paramInt & 0xFFFFFF80) != 0x0) {
out.writeByte((paramInt & 0x7F) | 0x80);
paramInt >>>= 7;
}
out.writeByte(paramInt);
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord.serverpinger;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
class PacketUtils {
public static void writeString(final DataOutputStream out, final String s, final Charset charset) throws IOException {
if (charset == StandardCharsets.UTF_8) {
writeVarInt(out, s.length());
} else {
out.writeShort(s.length());
}
out.write(s.getBytes(charset));
}
public static int readVarInt(final DataInputStream in) throws IOException {
int i = 0;
int j = 0;
while (true) {
final int k = in.readByte();
i |= (k & 0x7F) << j++ * 7;
if (j > 5) {
throw new RuntimeException("VarInt too big");
}
if ((k & 0x80) != 0x80) {
return i;
}
}
}
public static void writeVarInt(final DataOutputStream out, int paramInt) throws IOException {
while ((paramInt & 0xFFFFFF80) != 0x0) {
out.write((paramInt & 0x7F) | 0x80);
paramInt >>>= 7;
}
out.write(paramInt);
}
public static void closeQuietly(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException ignored) {}
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord.serverpinger;
import me.filoghost.holographicdisplays.disk.ServerAddress;
import me.filoghost.holographicdisplays.core.DebugLogger;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
public class PingResponse
{
private boolean isOnline;
private String motd;
private int onlinePlayers;
private int maxPlayers;
public PingResponse(String jsonString, ServerAddress address) {
if (jsonString == null || jsonString.isEmpty()) {
motd = "Invalid ping response";
DebugLogger.warning("Received empty Json response from IP \"" + address.toString() + "\".");
return;
}
Object jsonObject = JSONValue.parse(jsonString);
if (!(jsonObject instanceof JSONObject)) {
motd = "Invalid ping response";
DebugLogger.warning("Received invalid Json response from IP \"" + address.toString() + "\": " + jsonString);
return;
}
JSONObject json = (JSONObject) jsonObject;
isOnline = true;
Object descriptionObject = json.get("description");
if (descriptionObject != null) {
if (descriptionObject instanceof JSONObject) {
Object text = ((JSONObject) descriptionObject).get("text");
if (text != null) {
motd = text.toString();
} else {
motd = "Invalid ping response (text not found)";
}
} else {
motd = descriptionObject.toString();
}
} else {
motd = "Invalid ping response (description not found)";
DebugLogger.warning("Received invalid Json response from IP \"" + address.toString() + "\": " + jsonString);
}
Object playersObject = json.get("players");
if (playersObject instanceof JSONObject) {
JSONObject playersJson = (JSONObject) playersObject;
Object onlineObject = playersJson.get("online");
if (onlineObject instanceof Number) {
onlinePlayers = ((Number) onlineObject).intValue();
}
Object maxObject = playersJson.get("max");
if (maxObject instanceof Number) {
maxPlayers = ((Number) maxObject).intValue();
}
}
}
public boolean isOnline() {
return isOnline;
}
public String getMotd() {
return motd;
}
public int getOnlinePlayers() {
return onlinePlayers;
}
public int getMaxPlayers() {
return maxPlayers;
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.bridge.bungeecord.serverpinger;
import me.filoghost.holographicdisplays.disk.ServerAddress;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class ServerPinger {
public static PingResponse fetchData(final ServerAddress serverAddress, int timeout) throws IOException {
Socket socket = null;
DataOutputStream dataOut = null;
DataInputStream dataIn = null;
try {
socket = new Socket(serverAddress.getAddress(), serverAddress.getPort());
socket.setSoTimeout(timeout);
dataOut = new DataOutputStream(socket.getOutputStream());
dataIn = new DataInputStream(socket.getInputStream());
final ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
final DataOutputStream handshake = new DataOutputStream(byteOut);
handshake.write(0);
PacketUtils.writeVarInt(handshake, 4);
PacketUtils.writeString(handshake, serverAddress.getAddress(), StandardCharsets.UTF_8);
handshake.writeShort(serverAddress.getPort());
PacketUtils.writeVarInt(handshake, 1);
byte[] bytes = byteOut.toByteArray();
PacketUtils.writeVarInt(dataOut, bytes.length);
dataOut.write(bytes);
bytes = new byte[] { 0 };
PacketUtils.writeVarInt(dataOut, bytes.length);
dataOut.write(bytes);
PacketUtils.readVarInt(dataIn);
PacketUtils.readVarInt(dataIn);
final byte[] responseData = new byte[PacketUtils.readVarInt(dataIn)];
dataIn.readFully(responseData);
final String jsonString = new String(responseData, StandardCharsets.UTF_8);
return new PingResponse(jsonString, serverAddress);
} finally {
PacketUtils.closeQuietly(dataOut);
PacketUtils.closeQuietly(dataIn);
PacketUtils.closeQuietly(socket);
}
}
}

View File

@ -19,8 +19,8 @@ import org.bukkit.ChatColor;
import java.time.DateTimeException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
public class Configuration {
@ -41,7 +41,7 @@ public class Configuration {
public static String pingerStatusOnline;
public static String pingerStatusOffline;
public static boolean pingerTrimMotd;
public static List<ServerAddress> pingerServers;
public static Map<String, ServerAddress> pingerServerAddresses;
public static void load(MainConfigModel config, ErrorCollector errorCollector) {
spaceBetweenLines = config.spaceBetweenLines;
@ -62,12 +62,12 @@ public class Configuration {
pingerStatusOffline = StringConverter.toReadableFormat(config.pingerStatusOffline);
pingerTrimMotd = config.pingerTrimMotd;
pingerServers = new ArrayList<>();
pingerServerAddresses = new HashMap<>();
if (pingerEnabled) {
for (String singleServer : config.pingerServers) {
ServerAddress serverAddress = parseServerAddress(singleServer, errorCollector);
if (serverAddress != null) {
pingerServers.add(serverAddress);
pingerServerAddresses.put(serverAddress.getName(), serverAddress);
}
}
}

View File

@ -5,12 +5,15 @@
*/
package me.filoghost.holographicdisplays.placeholder;
import me.filoghost.fcommons.Strings;
import me.filoghost.fcommons.logging.Log;
import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer;
import me.filoghost.holographicdisplays.bridge.bungeecord.ServerInfo;
import me.filoghost.holographicdisplays.bridge.bungeecord.BungeeServerTracker;
import me.filoghost.holographicdisplays.core.Utils;
import me.filoghost.holographicdisplays.core.hologram.StandardTextLine;
import me.filoghost.holographicdisplays.core.nms.entity.NMSArmorStand;
import me.filoghost.holographicdisplays.disk.Configuration;
import me.filoghost.holographicdisplays.task.WorldPlayerCounterTask;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
@ -26,6 +29,8 @@ import java.util.regex.Pattern;
public class PlaceholdersManager {
private static final String PINGER_NOT_ENABLED_ERROR = "[Please enable pinger]";
private static final Pattern BUNGEE_ONLINE_PATTERN = makePlaceholderWithArgsPattern("online");
private static final Pattern BUNGEE_MAX_PATTERN = makePlaceholderWithArgsPattern("max_players");
private static final Pattern BUNGEE_MOTD_PATTERN = makePlaceholderWithArgsPattern("motd");
@ -170,28 +175,22 @@ public class PlaceholdersManager {
}
final String serverName = extractArgumentFromPlaceholder(matcher);
bungeeServerTracker.track(serverName); // Track this server.
if (serverName.contains(",")) {
String[] split = serverName.split(",");
for (int i = 0; i < split.length; i++) {
split[i] = split[i].trim();
}
final String[] serversToTrack = split;
String[] serversToTrack = Strings.splitAndTrim(serverName, ",");
// Add it to tracked servers.
bungeeReplacers.put(matcher.group(), () -> {
int count = 0;
for (String serverToTrack : serversToTrack) {
count += bungeeServerTracker.getPlayersOnline(serverToTrack);
count += bungeeServerTracker.getCurrentServerInfo(serverToTrack).getOnlinePlayers();
}
return String.valueOf(count);
});
} else {
// Normal, single tracked server.
bungeeReplacers.put(matcher.group(), () -> {
return String.valueOf(bungeeServerTracker.getPlayersOnline(serverName));
return String.valueOf(bungeeServerTracker.getCurrentServerInfo(serverName).getOnlinePlayers());
});
}
}
@ -204,11 +203,14 @@ public class PlaceholdersManager {
}
final String serverName = extractArgumentFromPlaceholder(matcher);
bungeeServerTracker.track(serverName); // Track this server.
// Add it to tracked servers.
bungeeReplacers.put(matcher.group(), () -> {
return bungeeServerTracker.getMaxPlayers(serverName);
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
return String.valueOf(bungeeServerTracker.getCurrentServerInfo(serverName).getMaxPlayers());
});
}
@ -220,11 +222,14 @@ public class PlaceholdersManager {
}
final String serverName = extractArgumentFromPlaceholder(matcher);
bungeeServerTracker.track(serverName); // Track this server.
// Add it to tracked servers.
bungeeReplacers.put(matcher.group(), () -> {
return bungeeServerTracker.getMotd1(serverName);
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
return bungeeServerTracker.getCurrentServerInfo(serverName).getMotdLine1();
});
}
@ -236,11 +241,14 @@ public class PlaceholdersManager {
}
final String serverName = extractArgumentFromPlaceholder(matcher);
bungeeServerTracker.track(serverName); // Track this server.
// Add it to tracked servers.
bungeeReplacers.put(matcher.group(), () -> {
return bungeeServerTracker.getMotd2(serverName);
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
return bungeeServerTracker.getCurrentServerInfo(serverName).getMotdLine2();
});
}
@ -252,11 +260,19 @@ public class PlaceholdersManager {
}
final String serverName = extractArgumentFromPlaceholder(matcher);
bungeeServerTracker.track(serverName); // Track this server.
// Add it to tracked servers.
bungeeReplacers.put(matcher.group(), () -> {
return bungeeServerTracker.getOnlineStatus(serverName);
if (!Configuration.pingerEnabled) {
return PINGER_NOT_ENABLED_ERROR;
}
ServerInfo serverInfo = bungeeServerTracker.getCurrentServerInfo(serverName);
if (serverInfo.isOnline()) {
return Configuration.pingerStatusOnline;
} else {
return Configuration.pingerStatusOffline;
}
});
}

View File

@ -1,46 +0,0 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.task;
import me.filoghost.holographicdisplays.bridge.bungeecord.BungeeServerInfo;
import me.filoghost.holographicdisplays.bridge.bungeecord.BungeeServerTracker;
import me.filoghost.holographicdisplays.core.DebugLogger;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
/**
* A task to remove unused server data in the server tracker.
*/
public class BungeeCleanupTask implements Runnable {
private static final long MAX_INACTIVITY = TimeUnit.MINUTES.toMillis(10);
private final BungeeServerTracker bungeeServerTracker;
public BungeeCleanupTask(BungeeServerTracker bungeeServerTracker) {
this.bungeeServerTracker = bungeeServerTracker;
}
@Override
public void run() {
long now = System.currentTimeMillis();
Iterator<Entry<String, BungeeServerInfo>> iter = bungeeServerTracker.getTrackedServers().entrySet().iterator();
while (iter.hasNext()) {
Entry<String, BungeeServerInfo> next = iter.next();
long lastRequest = next.getValue().getLastRequest();
if (lastRequest != 0 && now - lastRequest > MAX_INACTIVITY) {
// Don't track that server anymore.
iter.remove();
DebugLogger.info("Removed bungee server \"" + next.getKey() + "\" from tracking due to inactivity.");
}
}
}
}