First commit
This commit is contained in:
BIN
Libraries/Bungeecord.jar
Normal file
BIN
Libraries/Bungeecord.jar
Normal file
Binary file not shown.
BIN
Libraries/commons-dbcp2-2.0.1.jar
Normal file
BIN
Libraries/commons-dbcp2-2.0.1.jar
Normal file
Binary file not shown.
BIN
Libraries/commons-pool2-2.9.0.jar
Normal file
BIN
Libraries/commons-pool2-2.9.0.jar
Normal file
Binary file not shown.
BIN
Libraries/craftbukkit.jar
Normal file
BIN
Libraries/craftbukkit.jar
Normal file
Binary file not shown.
BIN
Libraries/gson-2.2.1.jar
Normal file
BIN
Libraries/gson-2.2.1.jar
Normal file
Binary file not shown.
BIN
Libraries/httpclient-4.5.2.jar
Normal file
BIN
Libraries/httpclient-4.5.2.jar
Normal file
Binary file not shown.
BIN
Libraries/jd-gui-1.6.6.jar
Normal file
BIN
Libraries/jd-gui-1.6.6.jar
Normal file
Binary file not shown.
BIN
Libraries/jedis-2.8.1.jar
Normal file
BIN
Libraries/jedis-2.8.1.jar
Normal file
Binary file not shown.
BIN
Libraries/jooq-3.5.2.jar
Normal file
BIN
Libraries/jooq-3.5.2.jar
Normal file
Binary file not shown.
BIN
Libraries/jooq-codegen-3.5.2.jar
Normal file
BIN
Libraries/jooq-codegen-3.5.2.jar
Normal file
Binary file not shown.
3
Mineplexer/.idea/.gitignore
generated
vendored
Normal file
3
Mineplexer/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
9
Mineplexer/.idea/Mineplexer.iml
generated
Normal file
9
Mineplexer/.idea/Mineplexer.iml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
17
Mineplexer/.idea/artifacts/Mineplex_Bungee_Mineplexer_jar.xml
generated
Normal file
17
Mineplexer/.idea/artifacts/Mineplex_Bungee_Mineplexer_jar.xml
generated
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="Mineplex.Bungee.Mineplexer:jar">
|
||||||
|
<output-path>$PROJECT_DIR$/../</output-path>
|
||||||
|
<root id="archive" name="Mineplexer.jar">
|
||||||
|
<element id="module-output" name="Mineplex.Cache" />
|
||||||
|
<element id="module-output" name="Mineplex.ServerData" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/gson-2.2.1.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/commons-pool2-2.9.0.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/jedis-2.8.1.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/httpclient-4.5.2.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/commons-dbcp2-2.0.1.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/jooq-3.5.2.jar" path-in-jar="/" />
|
||||||
|
<element id="module-output" name="Mineplex.Bungee.Mineplexer" />
|
||||||
|
<element id="file-copy" path="$PROJECT_DIR$/Mineplex.Bungee.Mineplexer/plugin.yml" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
9
Mineplexer/.idea/artifacts/Mineplex_Cache_jar.xml
generated
Normal file
9
Mineplexer/.idea/artifacts/Mineplex_Cache_jar.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="Mineplex.Cache:jar">
|
||||||
|
<output-path>$PROJECT_DIR$/out/artifacts/Mineplex_Cache_jar</output-path>
|
||||||
|
<root id="archive" name="Mineplex.Cache.jar">
|
||||||
|
<element id="module-output" name="Mineplex.Cache" />
|
||||||
|
<element id="module-output" name="Mineplex.ServerData" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
8
Mineplexer/.idea/artifacts/Mineplex_ServerData_jar.xml
generated
Normal file
8
Mineplexer/.idea/artifacts/Mineplex_ServerData_jar.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="Mineplex.ServerData:jar">
|
||||||
|
<output-path>$PROJECT_DIR$/out/artifacts/Mineplex_ServerData_jar</output-path>
|
||||||
|
<root id="archive" name="Mineplex.ServerData.jar">
|
||||||
|
<element id="module-output" name="Mineplex.ServerData" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
6
Mineplexer/.idea/misc.xml
generated
Normal file
6
Mineplexer/.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8 (2)" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
11
Mineplexer/.idea/modules.xml
generated
Normal file
11
Mineplexer/.idea/modules.xml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/Mineplex.Bungee.Mineplexer/Mineplex.Bungee.Mineplexer.iml" filepath="$PROJECT_DIR$/Mineplex.Bungee.Mineplexer/Mineplex.Bungee.Mineplexer.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/Mineplex.Cache/Mineplex.Cache.iml" filepath="$PROJECT_DIR$/Mineplex.Cache/Mineplex.Cache.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/Mineplex.ServerData/Mineplex.ServerData.iml" filepath="$PROJECT_DIR$/Mineplex.ServerData/Mineplex.ServerData.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/Mineplexer.iml" filepath="$PROJECT_DIR$/.idea/Mineplexer.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module" module-name="Mineplex.Cache" />
|
||||||
|
<orderEntry type="module" module-name="Mineplex.ServerData" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/Bungeecord.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/commons-dbcp2-2.0.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/commons-pool2-2.9.0.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/craftbukkit.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/craftbukkit.jar!/" />
|
||||||
|
</SOURCES>
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/gson-2.2.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/httpclient-4.5.2.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/jd-gui-1.6.6.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/jedis-2.8.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/jooq-3.5.2.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
4
Mineplexer/Mineplex.Bungee.Mineplexer/plugin.yml
Normal file
4
Mineplexer/Mineplex.Bungee.Mineplexer/plugin.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
name: Mineplexer
|
||||||
|
main: mineplex.bungee.Mineplexer
|
||||||
|
version: 1
|
||||||
|
author: defek7
|
@ -0,0 +1,171 @@
|
|||||||
|
package mineplex.bungee;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import net.md_5.bungee.BungeeCord;
|
||||||
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
|
||||||
|
public class FileUpdater implements Runnable
|
||||||
|
{
|
||||||
|
private Plugin _plugin;
|
||||||
|
private HashMap<String, String> _jarMd5Map = new HashMap<String, String>();
|
||||||
|
|
||||||
|
private boolean _needUpdate;
|
||||||
|
private boolean _enabled = true;
|
||||||
|
private int _timeTilRestart = 5;
|
||||||
|
|
||||||
|
public FileUpdater(Plugin plugin)
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
|
||||||
|
getPluginMd5s();
|
||||||
|
|
||||||
|
if (new File("IgnoreUpdates.dat").exists())
|
||||||
|
_enabled = false;
|
||||||
|
|
||||||
|
_plugin.getProxy().getScheduler().schedule(_plugin, this, 2L, 2L, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkForNewFiles()
|
||||||
|
{
|
||||||
|
if (_needUpdate || !_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
boolean windows = System.getProperty("os.name").startsWith("Windows");
|
||||||
|
|
||||||
|
File updateDir = new File((windows ? "C:" : File.separator + "home" + File.separator + "mineplex") + File.separator + "update");
|
||||||
|
|
||||||
|
updateDir.mkdirs();
|
||||||
|
|
||||||
|
FilenameFilter statsFilter = new FilenameFilter()
|
||||||
|
{
|
||||||
|
public boolean accept(File paramFile, String paramString)
|
||||||
|
{
|
||||||
|
if (paramString.endsWith("jar"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (File f : updateDir.listFiles(statsFilter))
|
||||||
|
{
|
||||||
|
FileInputStream fis = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_jarMd5Map.containsKey(f.getName()))
|
||||||
|
{
|
||||||
|
fis = new FileInputStream(f);
|
||||||
|
String md5 = DigestUtils.md5Hex(fis);
|
||||||
|
|
||||||
|
if (!md5.equals(_jarMd5Map.get(f.getName())))
|
||||||
|
{
|
||||||
|
System.out.println(f.getName() + " old jar : " + _jarMd5Map.get(f.getName()));
|
||||||
|
System.out.println(f.getName() + " new jar : " + md5);
|
||||||
|
_needUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (fis != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fis.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getPluginMd5s()
|
||||||
|
{
|
||||||
|
File pluginDir = new File("plugins");
|
||||||
|
|
||||||
|
pluginDir.mkdirs();
|
||||||
|
|
||||||
|
FilenameFilter statsFilter = new FilenameFilter()
|
||||||
|
{
|
||||||
|
public boolean accept(File paramFile, String paramString)
|
||||||
|
{
|
||||||
|
if (paramString.endsWith("jar"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (File f : pluginDir.listFiles(statsFilter))
|
||||||
|
{
|
||||||
|
FileInputStream fis = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fis = new FileInputStream(f);
|
||||||
|
_jarMd5Map.put(f.getName(), DigestUtils.md5Hex(fis));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (fis != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fis.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
checkForNewFiles();
|
||||||
|
|
||||||
|
if (_needUpdate)
|
||||||
|
{
|
||||||
|
BungeeCord.getInstance().broadcast(ChatColor.RED + "Connection Node" + ChatColor.DARK_GRAY + ">" + ChatColor.YELLOW + "This connection node will be restarting in " + _timeTilRestart + " minutes.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_timeTilRestart -= 2;
|
||||||
|
|
||||||
|
if (_timeTilRestart < 0 || !_enabled)
|
||||||
|
{
|
||||||
|
BungeeCord.getInstance().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package mineplex.bungee;
|
||||||
|
|
||||||
|
import mineplex.bungee.lobbyBalancer.LobbyBalancer;
|
||||||
|
import mineplex.bungee.motd.MotdManager;
|
||||||
|
import mineplex.bungee.playerCount.PlayerCount;
|
||||||
|
import mineplex.bungee.playerStats.PlayerStats;
|
||||||
|
import mineplex.bungee.playerTracker.PlayerTracker;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
|
||||||
|
public class Mineplexer extends Plugin
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
new MotdManager(this);
|
||||||
|
new LobbyBalancer(this);
|
||||||
|
new PlayerCount(this);
|
||||||
|
//new FileUpdater(this);
|
||||||
|
//TODO: new PlayerStats(this);
|
||||||
|
new PlayerTracker(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
package mineplex.bungee.lobbyBalancer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||||
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.data.MinecraftServer;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
import mineplex.serverdata.servers.ServerRepository;
|
||||||
|
|
||||||
|
public class LobbyBalancer implements Listener, Runnable
|
||||||
|
{
|
||||||
|
private Plugin _plugin;
|
||||||
|
private ServerRepository _repository;
|
||||||
|
|
||||||
|
private final Map<LobbyType, List<MinecraftServer>> _sortedLobbyMap = new EnumMap<>(LobbyType.class);
|
||||||
|
private final Map<LobbyType, Integer> _nextIndexMap = new EnumMap<>(LobbyType.class);
|
||||||
|
private static final LobbySorter LOBBY_SORTER = new LobbySorter();
|
||||||
|
private static final Object _serverLock = new Object();
|
||||||
|
|
||||||
|
public LobbyBalancer(Plugin plugin)
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
|
||||||
|
Region region = !new File("eu.dat").exists() ? Region.US : Region.EU;
|
||||||
|
_repository = ServerManager.getServerRepository(region);
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||||
|
_plugin.getProxy().getScheduler().schedule(_plugin, this, 500L, 500L, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void playerConnect(ServerConnectEvent event)
|
||||||
|
{
|
||||||
|
Arrays.stream(LobbyType.values())
|
||||||
|
.filter(type -> type.getConnectName().equalsIgnoreCase(event.getTarget().getName()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(lobbyType ->
|
||||||
|
{
|
||||||
|
synchronized (_serverLock)
|
||||||
|
{
|
||||||
|
List<MinecraftServer> lobbies = _sortedLobbyMap.get(lobbyType);
|
||||||
|
/*
|
||||||
|
if(lobbies.size() == 0){
|
||||||
|
event.getPlayer().disconnect("Sorry! There aren't any servers that are currently active right now. Come back later :3");
|
||||||
|
System.out.println("NOTICE! The network doesn't have any active servers. Are we in development status?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
int nextIndex = _nextIndexMap.getOrDefault(lobbyType, 0);
|
||||||
|
if (nextIndex >= lobbies.size())
|
||||||
|
{
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
MinecraftServer server = lobbies.get(nextIndex);
|
||||||
|
event.setTarget(_plugin.getProxy().getServerInfo(server.getName()));
|
||||||
|
server.incrementPlayerCount(1);
|
||||||
|
System.out.println("Sending " + event.getPlayer().getName() + " to " + server.getName() + "(" + server.getPublicAddress() + ")");
|
||||||
|
_nextIndexMap.put(lobbyType, ++nextIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
loadServers();
|
||||||
|
|
||||||
|
for (LobbyType type : LobbyType.values())
|
||||||
|
{
|
||||||
|
if (!_plugin.getProxy().getServers().containsKey(type.getConnectName()))
|
||||||
|
{
|
||||||
|
_plugin.getProxy().getServers().put(type.getConnectName(), _plugin.getProxy().constructServerInfo(type.getConnectName(), new InetSocketAddress("lobby.mineplex.com", 25565), "LobbyBalancer", false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadServers()
|
||||||
|
{
|
||||||
|
Collection<MinecraftServer> servers = _repository.getServerStatuses();
|
||||||
|
synchronized (_serverLock)
|
||||||
|
{
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
_sortedLobbyMap.clear();
|
||||||
|
for (LobbyType type : LobbyType.values())
|
||||||
|
{
|
||||||
|
_sortedLobbyMap.put(type, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MinecraftServer server : servers)
|
||||||
|
{
|
||||||
|
if (server.getName() == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
InetSocketAddress socketAddress = new InetSocketAddress(server.getPublicAddress(), server.getPort());
|
||||||
|
_plugin.getProxy().getServers().put(server.getName(), _plugin.getProxy().constructServerInfo(server.getName(), socketAddress, "LobbyBalancer", false));
|
||||||
|
|
||||||
|
if (server.getMotd() != null && server.getMotd().contains("Restarting"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.stream(LobbyType.values())
|
||||||
|
.filter(type -> server.getName().toUpperCase().startsWith(type.getUppercasePrefix()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(type -> _sortedLobbyMap.get(type).add(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
_sortedLobbyMap.values().forEach(lobbies -> Collections.sort(lobbies, LOBBY_SORTER));
|
||||||
|
|
||||||
|
long timeSpentInLock = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
|
if (timeSpentInLock > 50)
|
||||||
|
System.out.println("[==] TIMING [==] Locked loading servers for " + timeSpentInLock + "ms");
|
||||||
|
|
||||||
|
_nextIndexMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package mineplex.bungee.lobbyBalancer;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import mineplex.serverdata.data.MinecraftServer;
|
||||||
|
|
||||||
|
public class LobbySorter implements Comparator<MinecraftServer>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public int compare(MinecraftServer first, MinecraftServer second)
|
||||||
|
{
|
||||||
|
if (second.getPlayerCount() == 999)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (first.getPlayerCount() == 999)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (first.getPlayerCount() < (first.getMaxPlayerCount() / 2) && second.getPlayerCount() >= (second.getMaxPlayerCount() / 2))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (second.getPlayerCount() < (second.getMaxPlayerCount() / 2) && first.getPlayerCount() >= (first.getMaxPlayerCount() / 2))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (first.getPlayerCount() < (first.getMaxPlayerCount() / 2))
|
||||||
|
{
|
||||||
|
if (first.getPlayerCount() > second.getPlayerCount())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (second.getPlayerCount() > first.getPlayerCount())
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (first.getPlayerCount() < second.getPlayerCount())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (second.getPlayerCount() < first.getPlayerCount())
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Integer.parseInt(first.getName().split("-")[1]) < Integer.parseInt(second.getName().split("-")[1]))
|
||||||
|
return -1;
|
||||||
|
else if (Integer.parseInt(second.getName().split("-")[1]) < Integer.parseInt(first.getName().split("-")[1]))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package mineplex.bungee.lobbyBalancer;
|
||||||
|
|
||||||
|
public enum LobbyType
|
||||||
|
{
|
||||||
|
NORMAL("Lobby", "LOBBY-", "MainMotd"),
|
||||||
|
CLANS("ClansHub", "CLANSHUB-", "ClansMotd"),
|
||||||
|
BETA("BetaHub","BETAHUB-", "BetaMotd"),
|
||||||
|
;
|
||||||
|
private final String _connectName; // The name of the server the player is connecting to
|
||||||
|
private final String _uppercasePrefix; // The (toUpperCase()) prefix given to servers of this lobby type
|
||||||
|
private final String _redisMotdKey;
|
||||||
|
|
||||||
|
LobbyType(String connectName, String uppercasePrefix, String redisMotdKey)
|
||||||
|
{
|
||||||
|
_connectName = connectName;
|
||||||
|
_uppercasePrefix = uppercasePrefix;
|
||||||
|
_redisMotdKey = redisMotdKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnectName()
|
||||||
|
{
|
||||||
|
return _connectName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUppercasePrefix()
|
||||||
|
{
|
||||||
|
return _uppercasePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRedisMotdKey()
|
||||||
|
{
|
||||||
|
return _redisMotdKey;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package mineplex.bungee.motd;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import mineplex.serverdata.data.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A GlobalMotd represents a set of MOTD packaged lines.
|
||||||
|
* @author MrTwiggy
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class GlobalMotd implements Data
|
||||||
|
{
|
||||||
|
// The unique name representing this MOTD set
|
||||||
|
private String _name;
|
||||||
|
|
||||||
|
private String _headline;
|
||||||
|
public String getHeadline() { return _headline; }
|
||||||
|
|
||||||
|
// List of lines describing the MOTD
|
||||||
|
private List<String> _motd;
|
||||||
|
public List<String> getMotd() { return _motd; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param name
|
||||||
|
* @param motd
|
||||||
|
*/
|
||||||
|
public GlobalMotd(String name, String headline, List<String> motd)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_headline = headline;
|
||||||
|
_motd = motd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique identifying ID associated with this {@link GlobalMotd}.
|
||||||
|
*/
|
||||||
|
public String getDataId()
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package mineplex.bungee.motd;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||||
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
|
||||||
|
import mineplex.bungee.lobbyBalancer.LobbyType;
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.data.DataRepository;
|
||||||
|
import mineplex.serverdata.redis.RedisDataRepository;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
|
||||||
|
public class MotdManager implements Listener, Runnable
|
||||||
|
{
|
||||||
|
private static final String DEFAULT_HEADLINE = " §b§l§m §8§l§m[ §r §9§lThieunang.club§r §f§lGames§r §8§l§m ]§b§l§m §r";
|
||||||
|
|
||||||
|
private final DataRepository<GlobalMotd> _repository;
|
||||||
|
private final Random _random = new Random();
|
||||||
|
private final Map<LobbyType, GlobalMotd> motds = new EnumMap<>(LobbyType.class);
|
||||||
|
|
||||||
|
public MotdManager(Plugin plugin)
|
||||||
|
{
|
||||||
|
plugin.getProxy().getScheduler().schedule(plugin, this, 5L, 30L, TimeUnit.SECONDS);
|
||||||
|
plugin.getProxy().getPluginManager().registerListener(plugin, this);
|
||||||
|
|
||||||
|
_repository = new RedisDataRepository<GlobalMotd>(ServerManager.getConnection(true, ServerManager.SERVER_STATUS_LABEL), ServerManager.getConnection(false, ServerManager.SERVER_STATUS_LABEL),
|
||||||
|
Region.ALL, GlobalMotd.class, "globalMotd");
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void serverPing(ProxyPingEvent event)
|
||||||
|
{
|
||||||
|
|
||||||
|
net.md_5.bungee.api.ServerPing serverPing = event.getResponse();
|
||||||
|
Optional<LobbyType> maybeType = Optional.empty();
|
||||||
|
|
||||||
|
if (event.getConnection().getListener() != null)
|
||||||
|
{
|
||||||
|
maybeType = Arrays.stream(LobbyType.values())
|
||||||
|
.filter(type -> event.getConnection().getListener().getDefaultServer().equalsIgnoreCase(type.getConnectName()))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
LobbyType lobbyType = maybeType.orElse(LobbyType.NORMAL);
|
||||||
|
GlobalMotd globalMotd = motds.get(lobbyType);
|
||||||
|
|
||||||
|
String motd = DEFAULT_HEADLINE;
|
||||||
|
if (globalMotd != null && globalMotd.getHeadline() != null)
|
||||||
|
{
|
||||||
|
motd = globalMotd.getHeadline() == null ? DEFAULT_HEADLINE : globalMotd.getHeadline();
|
||||||
|
if (globalMotd.getMotd() != null)
|
||||||
|
{
|
||||||
|
motd += "\n" + globalMotd.getMotd().get(_random.nextInt(globalMotd.getMotd().size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setResponse(new net.md_5.bungee.api.ServerPing(serverPing.getVersion(), serverPing.getPlayers(), motd, serverPing.getFaviconObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
for (LobbyType type : LobbyType.values())
|
||||||
|
{
|
||||||
|
GlobalMotd motd = _repository.getElement(type.getRedisMotdKey());
|
||||||
|
|
||||||
|
if (motd != null)
|
||||||
|
{
|
||||||
|
motds.put(type, motd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package mineplex.bungee.playerCount;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import mineplex.bungee.status.InternetStatus;
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.data.BungeeServer;
|
||||||
|
import mineplex.serverdata.data.DataRepository;
|
||||||
|
import mineplex.serverdata.redis.RedisDataRepository;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData.ConnectionType;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
import net.md_5.bungee.api.ServerPing.Players;
|
||||||
|
import net.md_5.bungee.api.config.ListenerInfo;
|
||||||
|
import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||||
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
|
||||||
|
public class PlayerCount implements Listener, Runnable
|
||||||
|
{
|
||||||
|
private DataRepository<BungeeServer> _repository;
|
||||||
|
private DataRepository<BungeeServer> _secondRepository;
|
||||||
|
private Region _region;
|
||||||
|
|
||||||
|
private ListenerInfo _listenerInfo;
|
||||||
|
private Plugin _plugin;
|
||||||
|
|
||||||
|
private int _totalPlayers = -1;
|
||||||
|
|
||||||
|
public PlayerCount(Plugin plugin)
|
||||||
|
{
|
||||||
|
_region = !new File("eu.dat").exists() ? Region.US : Region.EU;
|
||||||
|
_plugin = plugin;
|
||||||
|
|
||||||
|
_plugin.getProxy().getScheduler().schedule(_plugin, this, 4L, 4L, TimeUnit.SECONDS);
|
||||||
|
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||||
|
|
||||||
|
for (ListenerInfo info : _plugin.getProxy().getConfigurationAdapter().getListeners())
|
||||||
|
{
|
||||||
|
if (info.getDefaultServer().equalsIgnoreCase("Lobby"))
|
||||||
|
{
|
||||||
|
_listenerInfo = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_repository = new RedisDataRepository<BungeeServer>(ServerManager.getConnection(true, ServerManager.SERVER_STATUS_LABEL), ServerManager.getConnection(false, ServerManager.SERVER_STATUS_LABEL),
|
||||||
|
Region.ALL, BungeeServer.class, "bungeeServers");
|
||||||
|
|
||||||
|
if (_region == Region.US)
|
||||||
|
_secondRepository = new RedisDataRepository<BungeeServer>(new ConnectionData("127.0.0.1", 6379, ConnectionType.MASTER, "ServerStatus"), new ConnectionData("127.0.0.1", 6379, ConnectionType.SLAVE, "ServerStatus"),
|
||||||
|
Region.ALL, BungeeServer.class, "bungeeServers");
|
||||||
|
else
|
||||||
|
_secondRepository = new RedisDataRepository<BungeeServer>(new ConnectionData("127.0.0.1", 6379, ConnectionType.MASTER, "ServerStatus"), new ConnectionData("127.0.0.1", 6379, ConnectionType.SLAVE, "ServerStatus"),
|
||||||
|
Region.ALL, BungeeServer.class, "bungeeServers");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
BungeeServer snapshot = generateSnapshot();
|
||||||
|
if (snapshot != null)
|
||||||
|
{
|
||||||
|
_repository.addElement(snapshot, 15); // Update with a 15 second expiry on session
|
||||||
|
}
|
||||||
|
|
||||||
|
_totalPlayers = fetchPlayerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an up-to-date total player count across all active Bungee Servers.
|
||||||
|
*/
|
||||||
|
private int fetchPlayerCount()
|
||||||
|
{
|
||||||
|
int totalPlayers = 0;
|
||||||
|
for (BungeeServer server : _repository.getElements())
|
||||||
|
{
|
||||||
|
totalPlayers += server.getPlayerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BungeeServer server : _secondRepository.getElements())
|
||||||
|
{
|
||||||
|
totalPlayers += server.getPlayerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a newly instantiated {@link BungeeServer} snapshot of the current state of this server.
|
||||||
|
*/
|
||||||
|
private BungeeServer generateSnapshot()
|
||||||
|
{
|
||||||
|
if (_listenerInfo == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String name = _listenerInfo.getHost().getAddress().getHostAddress();
|
||||||
|
String host = _listenerInfo.getHost().getAddress().getHostAddress();
|
||||||
|
int port = _listenerInfo.getHost().getPort();
|
||||||
|
boolean connected = InternetStatus.isConnected();
|
||||||
|
int playerCount = _plugin.getProxy().getOnlineCount();
|
||||||
|
return new BungeeServer(name, _region, host, port, playerCount, connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void ServerPing(ProxyPingEvent event)
|
||||||
|
{
|
||||||
|
net.md_5.bungee.api.ServerPing serverPing = event.getResponse();
|
||||||
|
|
||||||
|
event.setResponse(new net.md_5.bungee.api.ServerPing(serverPing.getVersion(), new Players(_totalPlayers + 1, _totalPlayers, null), serverPing.getDescription(), serverPing.getFaviconObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalPlayers()
|
||||||
|
{
|
||||||
|
return _totalPlayers;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package mineplex.bungee.playerStats;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import mineplex.bungee.playerStats.data.IpInfo;
|
||||||
|
import mineplex.cache.player.PlayerCache;
|
||||||
|
import mineplex.cache.player.PlayerInfo;
|
||||||
|
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.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
|
||||||
|
public class PlayerStats implements Listener, Runnable
|
||||||
|
{
|
||||||
|
private Plugin _plugin;
|
||||||
|
private PlayerStatsRepository _repository;
|
||||||
|
|
||||||
|
private HashSet<UUID> _retrievingPlayerInfo = new HashSet<UUID>();
|
||||||
|
|
||||||
|
public PlayerStats(Plugin plugin)
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
|
||||||
|
_plugin.getProxy().getScheduler().schedule(_plugin, this, 5L, 5L, TimeUnit.MINUTES);
|
||||||
|
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||||
|
|
||||||
|
_repository = new PlayerStatsRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void playerConnect(final PostLoginEvent event)
|
||||||
|
{
|
||||||
|
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
String address = event.getPlayer().getPendingConnection().getAddress().getAddress().getHostAddress();
|
||||||
|
UUID uuid = event.getPlayer().getUniqueId();
|
||||||
|
String name = event.getPlayer().getName();
|
||||||
|
int version = event.getPlayer().getPendingConnection().getVersion();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PlayerInfo playerInfo = null;
|
||||||
|
IpInfo ipInfo = _repository.getIp(address);
|
||||||
|
|
||||||
|
boolean addOrUpdatePlayer = false;
|
||||||
|
|
||||||
|
playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
||||||
|
|
||||||
|
if (playerInfo == null)
|
||||||
|
{
|
||||||
|
addOrUpdatePlayer = true;
|
||||||
|
_retrievingPlayerInfo.add(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addOrUpdatePlayer)
|
||||||
|
{
|
||||||
|
if (playerInfo.getVersion() != version)
|
||||||
|
addOrUpdatePlayer = true;
|
||||||
|
else if (!playerInfo.getName().equalsIgnoreCase(name))
|
||||||
|
addOrUpdatePlayer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addOrUpdatePlayer)
|
||||||
|
{
|
||||||
|
// Just update? what about other properties?
|
||||||
|
PlayerInfo updatedPlayerInfo = _repository.getPlayer(uuid, name, version);
|
||||||
|
|
||||||
|
if (playerInfo != null)
|
||||||
|
{
|
||||||
|
playerInfo.setName(updatedPlayerInfo.getName());
|
||||||
|
playerInfo.setVersion(updatedPlayerInfo.getVersion());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
playerInfo = updatedPlayerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
playerInfo.setSessionId(_repository.updatePlayerStats(playerInfo.getId(), ipInfo.id));
|
||||||
|
playerInfo.updateLoginTime();
|
||||||
|
PlayerCache.getInstance().addPlayer(playerInfo);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_retrievingPlayerInfo.remove(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void playerDisconnect(final PlayerDisconnectEvent event)
|
||||||
|
{
|
||||||
|
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
UUID uuid = event.getPlayer().getUniqueId();
|
||||||
|
|
||||||
|
PlayerInfo playerInfo = null;
|
||||||
|
|
||||||
|
playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
||||||
|
|
||||||
|
int timeout = 5;
|
||||||
|
|
||||||
|
while (playerInfo == null && _retrievingPlayerInfo.contains(uuid) && timeout <= 5)
|
||||||
|
{
|
||||||
|
playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
||||||
|
|
||||||
|
if (playerInfo != null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
System.out.println("ERROR - Player disconnecting and isn't in cache... sleeping");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(500);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout++;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(playerInfo.getName() + ":" + playerInfo.getSessionId());
|
||||||
|
_repository.updatePlayerSession(playerInfo.getSessionId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
PlayerCache.getInstance().clean();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,295 @@
|
|||||||
|
package mineplex.bungee.playerStats;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import mineplex.bungee.playerStats.data.IpInfo;
|
||||||
|
import mineplex.cache.player.PlayerInfo;
|
||||||
|
import mineplex.serverdata.database.DBPool;
|
||||||
|
import mineplex.serverdata.database.RepositoryBase;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
public class PlayerStatsRepository extends RepositoryBase
|
||||||
|
{
|
||||||
|
private static String INSERT_PLAYERINFO = "INSERT INTO playerInfo (uuid, name, version) VALUES (?, ?, ?);";
|
||||||
|
private static String SELECT_PLAYERINFO = "SELECT id, name, version FROM playerInfo WHERE uuid = ?;";
|
||||||
|
private static String UPDATE_PLAYERINFO = "UPDATE playerInfo SET name = ?, version = ? WHERE id = ?;";
|
||||||
|
|
||||||
|
private static String INSERT_IPINFO = "INSERT INTO ipInfo (ipAddress) VALUES (?);";
|
||||||
|
private static String SELECT_IPINFO = "SELECT id FROM ipInfo WHERE ipAddress = ?;";
|
||||||
|
|
||||||
|
private static String UPDATE_PLAYERSTATS = "INSERT IGNORE INTO playerIps (playerInfoId, ipInfoId, date) VALUES (?, ?, curdate());"
|
||||||
|
+ "INSERT IGNORE INTO playerUniqueLogins (playerInfoId, day) values(?, curdate());"
|
||||||
|
+ "INSERT IGNORE INTO playerLoginSessions (playerInfoId, loginTime) values(?, now());";
|
||||||
|
|
||||||
|
private static String UPDATE_LOGINSESSION = "UPDATE playerLoginSessions SET timeInGame = TIME_TO_SEC(TIMEDIFF(now(), loginTime)) / 60 WHERE id = ?;";
|
||||||
|
|
||||||
|
public PlayerStatsRepository()
|
||||||
|
{
|
||||||
|
super(DBPool.getPlayerStats());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerInfo getPlayer(UUID uuid, String name, int version)
|
||||||
|
{
|
||||||
|
PlayerInfo playerInfo = null;
|
||||||
|
PreparedStatement preparedStatement = null;
|
||||||
|
ResultSet resultSet = null;
|
||||||
|
|
||||||
|
try(Connection connection = getConnection())
|
||||||
|
{
|
||||||
|
preparedStatement = connection.prepareStatement(SELECT_PLAYERINFO);
|
||||||
|
|
||||||
|
preparedStatement.setString(1, uuid.toString());
|
||||||
|
|
||||||
|
resultSet = preparedStatement.executeQuery();
|
||||||
|
|
||||||
|
while (resultSet.next())
|
||||||
|
{
|
||||||
|
playerInfo = new PlayerInfo(resultSet.getInt(1), uuid, resultSet.getString(2), resultSet.getInt(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSet.close();
|
||||||
|
preparedStatement.close();
|
||||||
|
|
||||||
|
if (playerInfo == null)
|
||||||
|
{
|
||||||
|
preparedStatement = connection.prepareStatement(INSERT_PLAYERINFO, Statement.RETURN_GENERATED_KEYS);
|
||||||
|
preparedStatement.setString(1, uuid.toString());
|
||||||
|
preparedStatement.setString(2, name);
|
||||||
|
preparedStatement.setInt(3, version);
|
||||||
|
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
|
||||||
|
resultSet = preparedStatement.getGeneratedKeys();
|
||||||
|
|
||||||
|
while (resultSet.next())
|
||||||
|
{
|
||||||
|
id = resultSet.getInt(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
playerInfo = new PlayerInfo(id, uuid, name, version);
|
||||||
|
|
||||||
|
resultSet.close();
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
else if (!playerInfo.getName().equalsIgnoreCase(name) || playerInfo.getVersion() != version)
|
||||||
|
{
|
||||||
|
preparedStatement = connection.prepareStatement(UPDATE_PLAYERINFO);
|
||||||
|
preparedStatement.setString(1, name);
|
||||||
|
preparedStatement.setInt(2, version);
|
||||||
|
preparedStatement.setInt(3, playerInfo.getId());
|
||||||
|
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (preparedStatement != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultSet != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resultSet.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpInfo getIp(String ipAddress)
|
||||||
|
{
|
||||||
|
IpInfo ipInfo = null;
|
||||||
|
PreparedStatement preparedStatement = null;
|
||||||
|
ResultSet resultSet = null;
|
||||||
|
|
||||||
|
try(Connection connection = getConnection())
|
||||||
|
{
|
||||||
|
preparedStatement = connection.prepareStatement(SELECT_IPINFO);
|
||||||
|
preparedStatement.setString(1, ipAddress);
|
||||||
|
|
||||||
|
resultSet = preparedStatement.executeQuery();
|
||||||
|
|
||||||
|
while (resultSet.next())
|
||||||
|
{
|
||||||
|
ipInfo = new IpInfo();
|
||||||
|
ipInfo.id = resultSet.getInt(1);
|
||||||
|
ipInfo.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSet.close();
|
||||||
|
preparedStatement.close();
|
||||||
|
|
||||||
|
if (ipInfo == null)
|
||||||
|
{
|
||||||
|
preparedStatement = connection.prepareStatement(INSERT_IPINFO, Statement.RETURN_GENERATED_KEYS);
|
||||||
|
preparedStatement.setString(1, ipAddress);
|
||||||
|
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
|
||||||
|
resultSet = preparedStatement.getGeneratedKeys();
|
||||||
|
|
||||||
|
while (resultSet.next())
|
||||||
|
{
|
||||||
|
id = resultSet.getInt(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipInfo = new IpInfo();
|
||||||
|
ipInfo.id = id;
|
||||||
|
ipInfo.ipAddress = ipAddress;
|
||||||
|
|
||||||
|
resultSet.close();
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (preparedStatement != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultSet != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resultSet.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int updatePlayerStats(int playerId, int ipId)
|
||||||
|
{
|
||||||
|
Statement statement = null;
|
||||||
|
ResultSet resultSet= null;
|
||||||
|
|
||||||
|
try(Connection connection = getConnection())
|
||||||
|
{
|
||||||
|
statement = connection.createStatement();
|
||||||
|
|
||||||
|
String queryString = UPDATE_PLAYERSTATS;
|
||||||
|
queryString = queryString.replaceFirst("\\?", playerId + "");
|
||||||
|
queryString = queryString.replaceFirst("\\?", ipId + "");
|
||||||
|
queryString = queryString.replaceFirst("\\?", playerId + "");
|
||||||
|
queryString = queryString.replaceFirst("\\?", playerId + "");
|
||||||
|
|
||||||
|
statement.executeUpdate(queryString, Statement.RETURN_GENERATED_KEYS);
|
||||||
|
|
||||||
|
statement.getMoreResults();
|
||||||
|
statement.getMoreResults();
|
||||||
|
resultSet = statement.getGeneratedKeys();
|
||||||
|
|
||||||
|
while (resultSet.next())
|
||||||
|
{
|
||||||
|
return resultSet.getInt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (statement != null)
|
||||||
|
statement.close();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (resultSet != null)
|
||||||
|
resultSet.close();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePlayerSession(int loginSessionId)
|
||||||
|
{
|
||||||
|
PreparedStatement preparedStatement = null;
|
||||||
|
|
||||||
|
try(Connection connection = getConnection())
|
||||||
|
{
|
||||||
|
preparedStatement = connection.prepareStatement(UPDATE_LOGINSESSION);
|
||||||
|
preparedStatement.setInt(1, loginSessionId);
|
||||||
|
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (preparedStatement != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
preparedStatement.close();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package mineplex.bungee.playerStats.data;
|
||||||
|
|
||||||
|
public class IpInfo
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public String ipAddress;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package mineplex.bungee.playerTracker;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import mineplex.serverdata.commands.CommandCallback;
|
||||||
|
import mineplex.serverdata.commands.PlayerJoinCommand;
|
||||||
|
import mineplex.serverdata.commands.ServerCommand;
|
||||||
|
|
||||||
|
public class PlayerJoinHandler implements CommandCallback
|
||||||
|
{
|
||||||
|
private PlayerTracker _playerTracker;
|
||||||
|
|
||||||
|
public PlayerJoinHandler(PlayerTracker playerTracker)
|
||||||
|
{
|
||||||
|
_playerTracker = playerTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ServerCommand command)
|
||||||
|
{
|
||||||
|
if (command instanceof PlayerJoinCommand)
|
||||||
|
{
|
||||||
|
PlayerJoinCommand joinCommand = (PlayerJoinCommand)command;
|
||||||
|
_playerTracker.kickPlayerIfOnline(UUID.fromString(joinCommand.getUuid()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package mineplex.bungee.playerTracker;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.commands.PlayerJoinCommand;
|
||||||
|
import mineplex.serverdata.commands.ServerCommandManager;
|
||||||
|
import mineplex.serverdata.data.DataRepository;
|
||||||
|
import mineplex.serverdata.data.PlayerStatus;
|
||||||
|
import mineplex.serverdata.redis.RedisDataRepository;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
public class PlayerTracker implements Listener
|
||||||
|
{
|
||||||
|
private static final int DEFAULT_STATUS_TIMEOUT = 60 * 60 * 8;
|
||||||
|
|
||||||
|
private DataRepository<PlayerStatus> _repository;
|
||||||
|
|
||||||
|
private Plugin _plugin;
|
||||||
|
|
||||||
|
private final List<UUID> _ignoreKick = Lists.newArrayList();
|
||||||
|
|
||||||
|
public PlayerTracker(Plugin plugin)
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
|
||||||
|
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||||
|
|
||||||
|
_repository = new RedisDataRepository<PlayerStatus>(ServerManager.getMasterConnection(), ServerManager.getSlaveConnection(),
|
||||||
|
Region.currentRegion(), PlayerStatus.class, "playerStatus");
|
||||||
|
|
||||||
|
ServerCommandManager.getInstance().initializeServer("BUNGEE ENABLE - " + System.currentTimeMillis(), new Gson());
|
||||||
|
ServerCommandManager.getInstance().registerCommandType(mineplex.serverdata.commands.PlayerJoinCommand.class, new PlayerJoinHandler(this));
|
||||||
|
|
||||||
|
System.out.println("Initialized PlayerTracker.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plugin getPlugin()
|
||||||
|
{
|
||||||
|
return _plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void playerConnect(final ServerConnectedEvent event)
|
||||||
|
{
|
||||||
|
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
PlayerStatus snapshot = new PlayerStatus(event.getPlayer().getUniqueId(), event.getPlayer().getName(), event.getServer().getInfo().getName());
|
||||||
|
_repository.addElement(snapshot, DEFAULT_STATUS_TIMEOUT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void playerDisconnect(final PlayerDisconnectEvent event)
|
||||||
|
{
|
||||||
|
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
_repository.removeElement(event.getPlayer().getUniqueId().toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void playerConnect(final PostLoginEvent event)
|
||||||
|
{
|
||||||
|
_ignoreKick.add(event.getPlayer().getUniqueId());
|
||||||
|
PlayerJoinCommand command = new PlayerJoinCommand(event.getPlayer().getUniqueId(), event.getPlayer().getName());
|
||||||
|
command.publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPlayerOnline(UUID uuid)
|
||||||
|
{
|
||||||
|
return _plugin.getProxy().getPlayer(uuid) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void kickPlayerIfOnline(UUID uuid)
|
||||||
|
{
|
||||||
|
if (_ignoreKick.remove(uuid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isPlayerOnline(uuid))
|
||||||
|
{
|
||||||
|
ProxiedPlayer player = _plugin.getProxy().getPlayer(uuid);
|
||||||
|
|
||||||
|
player.disconnect(new TextComponent("You have logged in from another location."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package mineplex.bungee.status;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.config.ListenerInfo;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
|
||||||
|
public class InternetStatus implements Runnable
|
||||||
|
{
|
||||||
|
// Current internet connectivity status
|
||||||
|
private static boolean _connected = true;
|
||||||
|
public static boolean isConnected() { return _connected; }
|
||||||
|
|
||||||
|
private Plugin _plugin;
|
||||||
|
|
||||||
|
public InternetStatus(Plugin plugin)
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
_plugin.getProxy().getScheduler().schedule(_plugin, this, 1L, 1L, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
System.out.println("Initialized InternetStatus.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
_connected = isOnline(); // Update _connected flag.
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnline()
|
||||||
|
{
|
||||||
|
return testUrl("www.google.com")
|
||||||
|
|| testUrl("www.espn.com")
|
||||||
|
|| testUrl("www.bing.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean testUrl(String url)
|
||||||
|
{
|
||||||
|
boolean reachable = false;
|
||||||
|
|
||||||
|
try (Socket socket = new Socket(url, 80))
|
||||||
|
{
|
||||||
|
reachable = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Meh i don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
return reachable;
|
||||||
|
}
|
||||||
|
}
|
12
Mineplexer/Mineplex.Cache/Mineplex.Cache.iml
Normal file
12
Mineplexer/Mineplex.Cache/Mineplex.Cache.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module" module-name="Mineplex.ServerData" />
|
||||||
|
</component>
|
||||||
|
</module>
|
110
Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerCache.java
vendored
Normal file
110
Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerCache.java
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package mineplex.cache.player;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.redis.RedisDataRepository;
|
||||||
|
import mineplex.serverdata.redis.atomic.RedisStringRepository;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
|
||||||
|
public enum PlayerCache
|
||||||
|
{
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public static PlayerCache getInstance()
|
||||||
|
{
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final RedisDataRepository<PlayerInfo> _playerInfoRepository;
|
||||||
|
private final RedisStringRepository _accountIdRepository;
|
||||||
|
|
||||||
|
PlayerCache()
|
||||||
|
{
|
||||||
|
_playerInfoRepository = new RedisDataRepository<PlayerInfo>(
|
||||||
|
ServerManager.getMasterConnection(),
|
||||||
|
ServerManager.getSlaveConnection(),
|
||||||
|
Region.ALL,
|
||||||
|
PlayerInfo.class,
|
||||||
|
"playercache");
|
||||||
|
|
||||||
|
_accountIdRepository = new RedisStringRepository(
|
||||||
|
ServerManager.getMasterConnection(),
|
||||||
|
ServerManager.getSlaveConnection(),
|
||||||
|
Region.ALL,
|
||||||
|
"accountid",
|
||||||
|
(int) TimeUnit.HOURS.toSeconds(6)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPlayer(PlayerInfo player)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_playerInfoRepository.addElement(player, 60 * 60 * 6); // 6 Hours
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.out.println("Error adding player info in PlayerCache : " + exception.getMessage());
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerInfo getPlayer(UUID uuid)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _playerInfoRepository.getElement(uuid.toString());
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.out.println("Error retrieving player info in PlayerCache : " + exception.getMessage());
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to grab a player's account ID from the cache
|
||||||
|
*
|
||||||
|
* @param uuid Minecraft Account UUID
|
||||||
|
* @return The account id of the player, or -1 if the player is not in the cache
|
||||||
|
*/
|
||||||
|
public int getAccountId(UUID uuid)
|
||||||
|
{
|
||||||
|
String accountIdStr = _accountIdRepository.get(uuid.toString());
|
||||||
|
|
||||||
|
if (accountIdStr == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int accountId = Integer.parseInt(accountIdStr);
|
||||||
|
if (accountId <= 0)
|
||||||
|
{
|
||||||
|
// remove invalid account id
|
||||||
|
_accountIdRepository.del(uuid.toString());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex)
|
||||||
|
{
|
||||||
|
// remove invalid account id
|
||||||
|
_accountIdRepository.del(uuid.toString());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateAccountId(UUID uuid, int newId)
|
||||||
|
{
|
||||||
|
_accountIdRepository.set(uuid.toString(), String.valueOf(newId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clean()
|
||||||
|
{
|
||||||
|
_playerInfoRepository.clean();
|
||||||
|
}
|
||||||
|
}
|
93
Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerInfo.java
vendored
Normal file
93
Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerInfo.java
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package mineplex.cache.player;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Utility;
|
||||||
|
import mineplex.serverdata.data.Data;
|
||||||
|
|
||||||
|
public class PlayerInfo implements Data
|
||||||
|
{
|
||||||
|
private int _id;
|
||||||
|
private int _accountId;
|
||||||
|
private UUID _uuid;
|
||||||
|
private String _name;
|
||||||
|
private boolean _online;
|
||||||
|
private long _lastUniqueLogin;
|
||||||
|
private long _loginTime;
|
||||||
|
private int _sessionId;
|
||||||
|
private int _version;
|
||||||
|
|
||||||
|
public PlayerInfo(int id, UUID uuid, String name, int version)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
_uuid = uuid;
|
||||||
|
_name = name;
|
||||||
|
_version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDataId()
|
||||||
|
{
|
||||||
|
return _uuid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId()
|
||||||
|
{
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUUID()
|
||||||
|
{
|
||||||
|
return _uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getOnline()
|
||||||
|
{
|
||||||
|
return _online;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastUniqueLogin()
|
||||||
|
{
|
||||||
|
return _lastUniqueLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLoginTime()
|
||||||
|
{
|
||||||
|
return _loginTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSessionId()
|
||||||
|
{
|
||||||
|
return _sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion()
|
||||||
|
{
|
||||||
|
return _version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionId(int sessionId)
|
||||||
|
{
|
||||||
|
_sessionId = sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(int version)
|
||||||
|
{
|
||||||
|
_version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLoginTime()
|
||||||
|
{
|
||||||
|
_loginTime = Utility.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
56
Mineplexer/Mineplex.ServerData/Mineplex.ServerData.iml
Normal file
56
Mineplexer/Mineplex.ServerData/Mineplex.ServerData.iml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/Bungeecord.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/jedis-2.8.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/commons-dbcp2-2.0.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/jooq-3.5.2.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../Libraries/commons-pool2-2.9.0.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -0,0 +1,25 @@
|
|||||||
|
package mineplex.serverdata;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Region enumerates the various geographical regions where Mineplex servers are
|
||||||
|
* hosted.
|
||||||
|
* @author Ty
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum Region
|
||||||
|
{
|
||||||
|
US,
|
||||||
|
EU,
|
||||||
|
ALL;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the geographical {@link Region} of the current running process.
|
||||||
|
*/
|
||||||
|
public static Region currentRegion()
|
||||||
|
{
|
||||||
|
return !new File("eu.dat").exists() ? Region.US : Region.EU;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
package mineplex.serverdata;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.connection.Server;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
import redis.clients.jedis.JedisPoolConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility offers various necessary utility-based methods for use in Mineplex.ServerData.
|
||||||
|
* @author Ty
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Utility
|
||||||
|
{
|
||||||
|
private static boolean _retrievedRedisTime = false;
|
||||||
|
private static long _millisTimeDifference;
|
||||||
|
|
||||||
|
// The Gson instance used to serialize/deserialize objects in JSON form.
|
||||||
|
private static Gson _gson = new Gson();
|
||||||
|
public static Gson getGson() { return _gson; }
|
||||||
|
|
||||||
|
// map of all instantiated connection pools, distinguished by their ip:port combination
|
||||||
|
private static final ConcurrentHashMap<String, JedisPool> _pools = new ConcurrentHashMap<String, JedisPool>();
|
||||||
|
|
||||||
|
// Public static jedis pool for interacting with central default jedis repo.
|
||||||
|
private static JedisPool _masterPool;
|
||||||
|
private static JedisPool _slavePool;
|
||||||
|
private static final Object _poolLock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object - the (non-null) object to serialize
|
||||||
|
* @return the serialized form of {@code object}.
|
||||||
|
*/
|
||||||
|
public static String serialize(Object object)
|
||||||
|
{
|
||||||
|
return _gson.toJson(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param serializedData - the serialized data to be deserialized
|
||||||
|
* @param type - the resulting class type of the object to be deserialized
|
||||||
|
* @return the deserialized form of {@code serializedData} for class {@code type}.
|
||||||
|
*/
|
||||||
|
public static <T> T deserialize(String serializedData, Class<T> type)
|
||||||
|
{
|
||||||
|
if (serializedData == null) return null;
|
||||||
|
|
||||||
|
return _gson.fromJson(serializedData, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param delimiter - the delimiter character used to separate the concatenated elements
|
||||||
|
* @param elements - the set of string elements to be concatenated and returned.
|
||||||
|
* @return the concatenated string of all {@code elements} separated by the {@code delimiter}.
|
||||||
|
*/
|
||||||
|
public static String concatenate(char delimiter, String... elements)
|
||||||
|
{
|
||||||
|
int length = elements.length;
|
||||||
|
String result = length > 0 ? elements[0] : new String();
|
||||||
|
|
||||||
|
for (int i = 1; i < length; i++)
|
||||||
|
{
|
||||||
|
result += delimiter + elements[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current timestamp (in seconds) fetched from the central jedis repository
|
||||||
|
* for synced timestamps.
|
||||||
|
*/
|
||||||
|
public static long currentTimeSeconds()
|
||||||
|
{
|
||||||
|
if (!_retrievedRedisTime)
|
||||||
|
setTimeDifference();
|
||||||
|
|
||||||
|
return (System.currentTimeMillis() + _millisTimeDifference) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current timestamp (in milliseconds) fetched from the central jedis repository
|
||||||
|
* for synced timestamps.
|
||||||
|
*/
|
||||||
|
public static long currentTimeMillis()
|
||||||
|
{
|
||||||
|
if (!_retrievedRedisTime)
|
||||||
|
setTimeDifference();
|
||||||
|
|
||||||
|
return System.currentTimeMillis() + _millisTimeDifference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param connData - the connection data specifying the database to be connected to.
|
||||||
|
* @return a newly instantiated {@link JedisPool} connected to the provided {@link ConnectionData} repository.
|
||||||
|
*/
|
||||||
|
public static JedisPool generatePool(ConnectionData connData)
|
||||||
|
{
|
||||||
|
synchronized(_poolLock)
|
||||||
|
{
|
||||||
|
String key = "127.0.0.1:6379";
|
||||||
|
if(connData != null){
|
||||||
|
key = getConnKey(connData);
|
||||||
|
}
|
||||||
|
JedisPool pool = _pools.get(key);
|
||||||
|
|
||||||
|
if (pool == null)
|
||||||
|
{
|
||||||
|
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
|
||||||
|
|
||||||
|
jedisPoolConfig.setMaxWaitMillis(1000);
|
||||||
|
jedisPoolConfig.setMinIdle(5);
|
||||||
|
jedisPoolConfig.setTestOnBorrow(true);
|
||||||
|
|
||||||
|
jedisPoolConfig.setMaxTotal(20);
|
||||||
|
jedisPoolConfig.setBlockWhenExhausted(true);
|
||||||
|
|
||||||
|
if(connData != null){
|
||||||
|
pool = new JedisPool(jedisPoolConfig, connData.getHost(), connData.getPort());
|
||||||
|
}else{
|
||||||
|
pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
|
||||||
|
}
|
||||||
|
_pools.put(key, pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param writeable - whether or not the Jedis connections returned should be writeable to.
|
||||||
|
* @return a globally available {@link JedisPool}
|
||||||
|
*/
|
||||||
|
public static JedisPool getPool(boolean writeable)
|
||||||
|
{
|
||||||
|
if (writeable)
|
||||||
|
{
|
||||||
|
if (_masterPool == null)
|
||||||
|
{
|
||||||
|
_masterPool = generatePool(ServerManager.getMasterConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
return _masterPool;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_slavePool == null)
|
||||||
|
{
|
||||||
|
ConnectionData slave = ServerManager.getSlaveConnection();
|
||||||
|
|
||||||
|
_slavePool = generatePool(slave);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _slavePool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getConnKey(ConnectionData connData)
|
||||||
|
{
|
||||||
|
return connData.getHost() + ":" + connData.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setTimeDifference()
|
||||||
|
{
|
||||||
|
long currentTime = 0;
|
||||||
|
JedisPool pool = getPool(false);
|
||||||
|
|
||||||
|
try (Jedis jedis = pool.getResource())
|
||||||
|
{
|
||||||
|
// Try multiple times in case one isn't valid
|
||||||
|
// Addresses an error in sentry
|
||||||
|
List<String> times = jedis.time();
|
||||||
|
for (String time : times.subList(0, Math.min(5, times.size())))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
currentTime = Long.parseLong(time);
|
||||||
|
break;
|
||||||
|
} catch (NumberFormatException ex) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_millisTimeDifference = (currentTime * 1000) - System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class AddPunishCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private final String _target;
|
||||||
|
private final String _category;
|
||||||
|
private final String _sentence;
|
||||||
|
private final String _reason;
|
||||||
|
private final long _duration;
|
||||||
|
private final String _admin;
|
||||||
|
private final String _adminUUID;
|
||||||
|
private final int _severity;
|
||||||
|
|
||||||
|
public AddPunishCommand(String finalPlayerName, int severity, String category, String sentence, String reason, long duration, String finalCallerName, String uuid)
|
||||||
|
{
|
||||||
|
this._target = finalPlayerName;
|
||||||
|
this._severity = severity;
|
||||||
|
this._category = category;
|
||||||
|
this._sentence = sentence;
|
||||||
|
this._reason = reason;
|
||||||
|
this._duration = duration;
|
||||||
|
this._admin = finalCallerName;
|
||||||
|
this._adminUUID = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
|
||||||
|
public class AnnouncementCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private boolean _displayTitle;
|
||||||
|
private String _rank;
|
||||||
|
private String _message;
|
||||||
|
|
||||||
|
public boolean getDisplayTitle() { return _displayTitle; }
|
||||||
|
public String getRank() { return _rank; }
|
||||||
|
public String getMessage() { return _message; }
|
||||||
|
|
||||||
|
public AnnouncementCommand(boolean displayTitle, String rank, String message)
|
||||||
|
{
|
||||||
|
_displayTitle = displayTitle;
|
||||||
|
_rank = rank;
|
||||||
|
_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
// Utilitizes a callback functionality to seperate dependencies
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
public interface CommandCallback<T extends ServerCommand>
|
||||||
|
{
|
||||||
|
void run(T command);
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
public class CommandType
|
||||||
|
{
|
||||||
|
private Class<? extends ServerCommand> _commandClazz;
|
||||||
|
public Class<? extends ServerCommand> getCommandType() { return _commandClazz; }
|
||||||
|
|
||||||
|
private CommandCallback<? extends ServerCommand> _commandCallback;
|
||||||
|
public CommandCallback<? extends ServerCommand> getCallback() { return _commandCallback; }
|
||||||
|
|
||||||
|
public CommandType(Class<? extends ServerCommand> commandClazz, CommandCallback<? extends ServerCommand> commandCallback)
|
||||||
|
{
|
||||||
|
_commandClazz = commandClazz;
|
||||||
|
_commandCallback = commandCallback;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PlayerJoinCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private String _uuid;
|
||||||
|
private String _name;
|
||||||
|
|
||||||
|
public PlayerJoinCommand(UUID uuid, String name)
|
||||||
|
{
|
||||||
|
_uuid = uuid.toString();
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
// Utilitizes a callback functionality to seperate dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUuid()
|
||||||
|
{
|
||||||
|
return _uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
|
||||||
|
public class PunishCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private String _playerName;
|
||||||
|
private boolean _ban;
|
||||||
|
private boolean _mute;
|
||||||
|
private String _message;
|
||||||
|
|
||||||
|
public String getPlayerName() { return _playerName; }
|
||||||
|
public boolean getBan() { return _ban; }
|
||||||
|
public boolean getMute() { return _mute; }
|
||||||
|
public String getMessage() { return _message; }
|
||||||
|
|
||||||
|
public PunishCommand(String playerName, boolean ban, boolean mute, String message)
|
||||||
|
{
|
||||||
|
_playerName = playerName;
|
||||||
|
_ban = ban;
|
||||||
|
_mute = mute;
|
||||||
|
_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
// Utilitizes a callback functionality to seperate dependencies
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
public class RemovePunishCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private final JsonObject _punishment;
|
||||||
|
private final String _target;
|
||||||
|
private final String _admin;
|
||||||
|
private final String _adminUUID;
|
||||||
|
private final String _reason;
|
||||||
|
|
||||||
|
public RemovePunishCommand(JsonObject punishment, String target, String admin, UUID adminUUID, String reason)
|
||||||
|
{
|
||||||
|
_punishment = punishment;
|
||||||
|
_target = target;
|
||||||
|
_admin = admin;
|
||||||
|
_adminUUID = adminUUID.toString();
|
||||||
|
_reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
|
||||||
|
public class RestartCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
|
||||||
|
private final String _server;
|
||||||
|
private final Region _region;
|
||||||
|
private final boolean _groupRestart;
|
||||||
|
|
||||||
|
public RestartCommand(String server, Region region, boolean groupRestart)
|
||||||
|
{
|
||||||
|
_server = server;
|
||||||
|
_region = region;
|
||||||
|
_groupRestart = groupRestart;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerName()
|
||||||
|
{
|
||||||
|
return _server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Region getRegion()
|
||||||
|
{
|
||||||
|
return _region;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGroupRestart()
|
||||||
|
{
|
||||||
|
return _groupRestart;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public abstract class ServerCommand
|
||||||
|
{
|
||||||
|
private final UUID _commandId = UUID.randomUUID();
|
||||||
|
private final String _fromServer = ServerCommandManager.getInstance().getServerName();
|
||||||
|
|
||||||
|
// The names of servers targetted to receive this ServerCommand.
|
||||||
|
private String[] _targetServers;
|
||||||
|
|
||||||
|
public ServerCommand()
|
||||||
|
{
|
||||||
|
_targetServers = new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerCommand(String... targetServers)
|
||||||
|
{
|
||||||
|
_targetServers = targetServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetServers(String... targetServers)
|
||||||
|
{
|
||||||
|
_targetServers = targetServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getTargetServers()
|
||||||
|
{
|
||||||
|
if (_targetServers == null)
|
||||||
|
{
|
||||||
|
_targetServers = new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _targetServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFromServer()
|
||||||
|
{
|
||||||
|
return this._fromServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the command on it's destination target server.
|
||||||
|
*/
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
// Not yet implemented in base
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param serverName - the name of the server to be checked for whether they are a target
|
||||||
|
* @return true, if {@code serverName} is one of the {@code targetServers} of this
|
||||||
|
* {@link ServerCommand}, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isTargetServer(String serverName)
|
||||||
|
{
|
||||||
|
if (getTargetServers().length == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (String targetServer : getTargetServers())
|
||||||
|
{
|
||||||
|
if (targetServer.equalsIgnoreCase(serverName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish the {@link ServerCommand} across the network to {@code targetServers}.
|
||||||
|
*/
|
||||||
|
public void publish()
|
||||||
|
{
|
||||||
|
ServerCommandManager.getInstance().publishCommand(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasSentFromThisServer()
|
||||||
|
{
|
||||||
|
return ServerCommandManager.getInstance().getServerName().equals(_fromServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSentToThisServer()
|
||||||
|
{
|
||||||
|
for (String targetServer : _targetServers)
|
||||||
|
{
|
||||||
|
if (ServerCommandManager.getInstance().getServerName().equals(targetServer))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getCommandId()
|
||||||
|
{
|
||||||
|
return _commandId;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import redis.clients.jedis.JedisPubSub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ServerCommandListener listens for published Redis network messages
|
||||||
|
* and deserializes them into their {@link ServerCommand} form, then registers
|
||||||
|
* it's arrival at the {@link ServerCommandManager}.
|
||||||
|
* @author Ty
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ServerCommandListener extends JedisPubSub
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPMessage(String pattern, String channelName, String message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String commandType = channelName.split(":")[1];
|
||||||
|
ServerCommandManager.getInstance().handleCommand(commandType, message);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String channelName, String message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPSubscribe(String pattern, int subscribedChannels)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPUnsubscribe(String pattern, int subscribedChannels)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(String channelName, int subscribedChannels)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnsubscribe(String channelName, int subscribedChannels)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Utility;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
|
||||||
|
public class ServerCommandManager
|
||||||
|
{
|
||||||
|
|
||||||
|
// The singleton instance of ServerCommandManager
|
||||||
|
private static ServerCommandManager _instance;
|
||||||
|
|
||||||
|
public final String SERVER_COMMANDS_CHANNEL = "commands.server";
|
||||||
|
|
||||||
|
private JedisPool _writePool;
|
||||||
|
private JedisPool _readPool;
|
||||||
|
private Map<String, CommandType> _commandTypes;
|
||||||
|
|
||||||
|
private String _localServerName;
|
||||||
|
private Gson _gson;
|
||||||
|
public void initializeServer(String serverName, Gson gson)
|
||||||
|
{
|
||||||
|
_localServerName = serverName;
|
||||||
|
_gson = gson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isServerInitialized() { return _localServerName != null; }
|
||||||
|
public String getServerName()
|
||||||
|
{
|
||||||
|
return _localServerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private class constructor to prevent non-singleton instances.
|
||||||
|
*/
|
||||||
|
private ServerCommandManager()
|
||||||
|
{
|
||||||
|
_writePool = Utility.generatePool(ServerManager.getMasterConnection()); // Publish to master instance
|
||||||
|
_readPool = Utility.generatePool(ServerManager.getSlaveConnection()); // Read from slave instance
|
||||||
|
|
||||||
|
_commandTypes = new HashMap<>();
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the ServerCommandManager by subscribing to the
|
||||||
|
* redis network.
|
||||||
|
*/
|
||||||
|
private void initialize()
|
||||||
|
{
|
||||||
|
// Spin up a new thread and subscribe to the Redis pubsub network
|
||||||
|
Thread thread = new Thread("Redis Manager")
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try (Jedis jedis = _readPool.getResource())
|
||||||
|
{
|
||||||
|
jedis.psubscribe(new ServerCommandListener(), SERVER_COMMANDS_CHANNEL + ":*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish a {@link ServerCommand} across the network to all live servers.
|
||||||
|
* @param serverCommand - the {@link ServerCommand} to issue to all servers.
|
||||||
|
*/
|
||||||
|
public void publishCommand(final ServerCommand serverCommand)
|
||||||
|
{
|
||||||
|
new Thread(new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
String commandType = serverCommand.getClass().getSimpleName();
|
||||||
|
String serializedCommand = _gson.toJson(serverCommand);
|
||||||
|
|
||||||
|
try(Jedis jedis = _writePool.getResource())
|
||||||
|
{
|
||||||
|
jedis.publish(SERVER_COMMANDS_CHANNEL + ":" + commandType, serializedCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming (serialized) {@link ServerCommand}.
|
||||||
|
* @param commandType - the type of command being received
|
||||||
|
* @param serializedCommand - the serialized {@link ServerCommand} data.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
public void handleCommand(final String commandType, String serializedCommand)
|
||||||
|
{
|
||||||
|
if (!isServerInitialized())
|
||||||
|
{
|
||||||
|
// TODO: Log un-initialized server receiving command?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_commandTypes.containsKey(commandType))
|
||||||
|
{
|
||||||
|
Class<? extends ServerCommand> commandClazz = _commandTypes.get(commandType).getCommandType();
|
||||||
|
final ServerCommand serverCommand = _gson.fromJson(serializedCommand, commandClazz);
|
||||||
|
|
||||||
|
if (serverCommand.isTargetServer(_localServerName))
|
||||||
|
{
|
||||||
|
// TODO: Run synchronously?
|
||||||
|
CommandCallback callback = _commandTypes.get(commandType).getCallback();
|
||||||
|
serverCommand.run(); // Run server command without callback
|
||||||
|
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback.run(serverCommand); // Run callback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new type of {@link ServerCommand}.
|
||||||
|
* @param commandType - the {@link ServerCommand} type to register.
|
||||||
|
*/
|
||||||
|
public <T extends ServerCommand> void registerCommandType(String commandName, Class<T> commandType, CommandCallback<T> callback)
|
||||||
|
{
|
||||||
|
if (_commandTypes.containsKey(commandName))
|
||||||
|
{
|
||||||
|
// Log overwriting of command type?
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandType cmdType = new CommandType(commandType, callback);
|
||||||
|
_commandTypes.put(commandName, cmdType);
|
||||||
|
System.out.println("Registered : " + commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends ServerCommand> void registerCommandType(Class<T> commandType, CommandCallback<T> callback)
|
||||||
|
{
|
||||||
|
registerCommandType(commandType.getSimpleName(), commandType, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerCommandType(String commandName, Class<? extends ServerCommand> commandType)
|
||||||
|
{
|
||||||
|
registerCommandType(commandName, commandType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the singleton instance of ServerCommandManager
|
||||||
|
*/
|
||||||
|
public static ServerCommandManager getInstance()
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
_instance = new ServerCommandManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
public class ServerTransfer
|
||||||
|
{
|
||||||
|
|
||||||
|
// The name of the player who is being transferred.
|
||||||
|
private String _playerName;
|
||||||
|
public String getPlayerName() { return _playerName; }
|
||||||
|
|
||||||
|
// The name of the destination server in this ServerTransfer.
|
||||||
|
private String _serverName;
|
||||||
|
public String getServerName() { return _serverName; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* @param playerName - the name of the player being transferred.
|
||||||
|
* @param serverName - the name of the server to transfer the player to (destination).
|
||||||
|
*/
|
||||||
|
public ServerTransfer(String playerName, String serverName)
|
||||||
|
{
|
||||||
|
_playerName = playerName;
|
||||||
|
_serverName = serverName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
|
||||||
|
public class SuicideCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private String _server;
|
||||||
|
public String getServerName() { return _server; }
|
||||||
|
|
||||||
|
private Region _region;
|
||||||
|
public Region getRegion() { return _region; }
|
||||||
|
|
||||||
|
public SuicideCommand(String server, Region region)
|
||||||
|
{
|
||||||
|
_server = server;
|
||||||
|
_region = region;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
// Utilitizes a callback functionality to seperate dependencies
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
|
||||||
|
public class TransferCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private final String _playerName;
|
||||||
|
private final String _targetServer;
|
||||||
|
|
||||||
|
public TransferCommand(String playerName, String targetServer)
|
||||||
|
{
|
||||||
|
_playerName = playerName;
|
||||||
|
_targetServer = targetServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlayerName()
|
||||||
|
{
|
||||||
|
return _playerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetServer()
|
||||||
|
{
|
||||||
|
return _targetServer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class TransferUUIDCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private final UUID _playerUUID;
|
||||||
|
private final String _targetServer;
|
||||||
|
|
||||||
|
public TransferUUIDCommand(UUID playerUUID, String targetServer)
|
||||||
|
{
|
||||||
|
_playerUUID = playerUUID;
|
||||||
|
_targetServer = targetServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getPlayerUUID()
|
||||||
|
{
|
||||||
|
return _playerUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetServer()
|
||||||
|
{
|
||||||
|
return _targetServer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package mineplex.serverdata.commands;
|
||||||
|
|
||||||
|
public class TwoFactorResetCommand extends ServerCommand
|
||||||
|
{
|
||||||
|
private String _adminName;
|
||||||
|
private String _adminUUID;
|
||||||
|
private String _targetName;
|
||||||
|
private String _targetUUID;
|
||||||
|
|
||||||
|
public TwoFactorResetCommand(String adminName, String adminUUID, String targetName, String targetUUID)
|
||||||
|
{
|
||||||
|
_adminName = adminName;
|
||||||
|
_adminUUID = adminUUID;
|
||||||
|
_targetName = targetName;
|
||||||
|
_targetUUID = targetUUID;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.data.Data;
|
||||||
|
|
||||||
|
public class BungeeServer implements Data
|
||||||
|
{
|
||||||
|
// The name of this server.
|
||||||
|
private String _name;
|
||||||
|
public String getName() { return _name; }
|
||||||
|
|
||||||
|
// The geographical region of this Bungee Server.
|
||||||
|
private Region _region;
|
||||||
|
public Region getRegion() { return _region; }
|
||||||
|
|
||||||
|
// The number of players currently online.
|
||||||
|
private int _playerCount;
|
||||||
|
public int getPlayerCount() { return _playerCount; }
|
||||||
|
|
||||||
|
// The public I.P address used by players to connect to the server.
|
||||||
|
private String _publicAddress;
|
||||||
|
public String getPublicAddress() { return _publicAddress; }
|
||||||
|
|
||||||
|
// The port the server is currently running/listening on.
|
||||||
|
private int _port;
|
||||||
|
public int getPort() { return _port; }
|
||||||
|
|
||||||
|
// Whether the Bungee server can connect to the internet.
|
||||||
|
private boolean _connected;
|
||||||
|
public boolean isConnected() { return _connected; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* @param name
|
||||||
|
* @param publicAddress
|
||||||
|
* @param port
|
||||||
|
* @param playerCount
|
||||||
|
* @param connected
|
||||||
|
*/
|
||||||
|
public BungeeServer(String name, Region region, String publicAddress, int port, int playerCount, boolean connected)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_region = region;
|
||||||
|
_playerCount = playerCount;
|
||||||
|
_publicAddress = publicAddress;
|
||||||
|
_port = port;
|
||||||
|
_connected = connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique identifying ID for this Bungee Server.
|
||||||
|
*/
|
||||||
|
public String getDataId()
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
public interface Data
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return the unique id key representing this {@link Data} object in {@link DataRepository}s.
|
||||||
|
*/
|
||||||
|
public String getDataId();
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataRepository is used to store {@link Data} objects in a central database
|
||||||
|
* for real-time fetching/modification.
|
||||||
|
* @author Ty
|
||||||
|
*
|
||||||
|
* @param <T> - the type of {@link Data} object stored in this repository.
|
||||||
|
*/
|
||||||
|
public interface DataRepository<T extends Data>
|
||||||
|
{
|
||||||
|
|
||||||
|
public Collection<T> getElements();
|
||||||
|
|
||||||
|
public T getElement(String dataId);
|
||||||
|
|
||||||
|
public Collection<T> getElements(Collection<String> dataIds);
|
||||||
|
|
||||||
|
public Map<String,T> getElementsMap(List<String> dataIds);
|
||||||
|
|
||||||
|
public void addElement(T element, int timeout);
|
||||||
|
|
||||||
|
public void addElement(T element);
|
||||||
|
|
||||||
|
public void removeElement(T element);
|
||||||
|
|
||||||
|
public void removeElement(String dataId);
|
||||||
|
|
||||||
|
public boolean elementExists(String dataId);
|
||||||
|
|
||||||
|
public int clean();
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
|
||||||
|
public class DedicatedServer
|
||||||
|
{
|
||||||
|
|
||||||
|
// The default amount of available CPU usage.
|
||||||
|
public static final int DEFAULT_CPU = 32;
|
||||||
|
|
||||||
|
// The default amount of available ram usage.
|
||||||
|
public static final int DEFAULT_RAM = 14000;
|
||||||
|
|
||||||
|
// The unique name representing this server
|
||||||
|
private String _name;
|
||||||
|
public String getName() { return _name; }
|
||||||
|
|
||||||
|
// The public I.P address used to connect to this server
|
||||||
|
private String _publicAddress;
|
||||||
|
public String getPublicAddress() { return _publicAddress; }
|
||||||
|
|
||||||
|
// The private I.P address of this server
|
||||||
|
private String _privateAddress;
|
||||||
|
public String getPrivateAddress() { return _privateAddress; }
|
||||||
|
|
||||||
|
// The geographical region that this dedicated server is located in
|
||||||
|
private Region _region;
|
||||||
|
public Region getRegion() { return _region; }
|
||||||
|
public boolean isUsRegion() { return _region == Region.US; }
|
||||||
|
|
||||||
|
// The amount of available CPU usage on this server box.
|
||||||
|
private int _availableCpu;
|
||||||
|
public int getAvailableCpu() { return _availableCpu; }
|
||||||
|
|
||||||
|
// The amount of available ram usage on this server box.
|
||||||
|
private int _availableRam;
|
||||||
|
public int getAvailableRam() { return _availableRam; }
|
||||||
|
|
||||||
|
// The amount of available CPU usage on this server box.
|
||||||
|
private int _maxCpu;
|
||||||
|
public int getMaxCpu() { return _maxCpu; }
|
||||||
|
|
||||||
|
// The amount of available ram usage on this server box.
|
||||||
|
private int _maxRam;
|
||||||
|
public int getMaxRam() { return _maxRam; }
|
||||||
|
|
||||||
|
// A mapping of server group names (Key) to the number of server instances (Value)
|
||||||
|
private Map<String, Integer> _serverCounts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* @param data - the set of serialized data values representing
|
||||||
|
* the internal state of this DedicatedServer.
|
||||||
|
*/
|
||||||
|
public DedicatedServer(Map<String, String> data)
|
||||||
|
{
|
||||||
|
_name = data.get("name");
|
||||||
|
_publicAddress = data.get("publicAddress");
|
||||||
|
_privateAddress = data.get("privateAddress");
|
||||||
|
_region = Region.valueOf(data.get("region").toUpperCase());
|
||||||
|
_availableCpu = Integer.valueOf(data.get("cpu"));
|
||||||
|
_availableRam = Integer.valueOf(data.get("ram"));
|
||||||
|
_maxCpu = Integer.valueOf(data.get("cpu"));
|
||||||
|
_maxRam = Integer.valueOf(data.get("ram"));
|
||||||
|
_serverCounts = new HashMap<String, Integer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of {@link MinecraftServer} instances on this server
|
||||||
|
* for a specific {@link ServerGroup} type.
|
||||||
|
* @param serverGroup - the {@link ServerGroup} whose server instance count is being set.
|
||||||
|
* @param serverCount - the number of {@link MinecraftServer} instances active on this server.
|
||||||
|
*/
|
||||||
|
public void setServerCount(ServerGroup serverGroup, int serverCount)
|
||||||
|
{
|
||||||
|
if (_serverCounts.containsKey(serverGroup.getName()))
|
||||||
|
{
|
||||||
|
int currentAmount = _serverCounts.get(serverGroup.getName());
|
||||||
|
_availableCpu += serverGroup.getRequiredCpu() * currentAmount;
|
||||||
|
_availableRam += serverGroup.getRequiredRam() * currentAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
_serverCounts.put(serverGroup.getName(), serverCount);
|
||||||
|
_availableCpu -= serverGroup.getRequiredCpu() * serverCount;
|
||||||
|
_availableRam -= serverGroup.getRequiredRam() * serverCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param serverGroup - the server group whose server count on this dedicated server is being fetched.
|
||||||
|
* @return the number of active {@link MinecraftServer}s on this dedicated server
|
||||||
|
* that belong to {@code serverGroup}.
|
||||||
|
*/
|
||||||
|
public int getServerCount(ServerGroup serverGroup)
|
||||||
|
{
|
||||||
|
String groupName = serverGroup.getName();
|
||||||
|
return _serverCounts.containsKey(groupName) ? _serverCounts.get(groupName) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the number of {@link MinecraftServer} instances on this server
|
||||||
|
* for a specific {@link ServerGroup} type by 1.
|
||||||
|
* @param serverGroup - the {@link ServerGroup} whose server instance count is being incremented
|
||||||
|
*/
|
||||||
|
public void incrementServerCount(ServerGroup serverGroup)
|
||||||
|
{
|
||||||
|
setServerCount(serverGroup, getServerCount(serverGroup) + 1);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
public class MinecraftServer
|
||||||
|
{
|
||||||
|
// The name of this server.
|
||||||
|
private String _name;
|
||||||
|
public String getName() { return _name; }
|
||||||
|
|
||||||
|
// The ServerGroup that this MinecraftServer belongs to.
|
||||||
|
private String _group;
|
||||||
|
public String getGroup() { return _group; }
|
||||||
|
|
||||||
|
// The current message of the day (MOTD) of the server.
|
||||||
|
private String _motd;
|
||||||
|
public String getMotd() { return _motd; }
|
||||||
|
|
||||||
|
// The number of players currently online.
|
||||||
|
private int _playerCount;
|
||||||
|
public int getPlayerCount() { return _playerCount; }
|
||||||
|
public void incrementPlayerCount(int amount) { this._playerCount += amount; }
|
||||||
|
|
||||||
|
// The maximum number of players allowed on the server.
|
||||||
|
private int _maxPlayerCount;
|
||||||
|
public int getMaxPlayerCount() { return _maxPlayerCount; }
|
||||||
|
|
||||||
|
// The ticks per second (TPS) of the server.
|
||||||
|
private int _tps;
|
||||||
|
public int getTps() { return _tps; }
|
||||||
|
|
||||||
|
// The current amount of RAM allocated to the server.
|
||||||
|
private int _ram;
|
||||||
|
public int getRam() { return _ram; }
|
||||||
|
|
||||||
|
// The maximum amount of available RAM that can be allocated to the server.
|
||||||
|
private int _maxRam;
|
||||||
|
public int getMaxRam() { return _maxRam; }
|
||||||
|
|
||||||
|
// The public I.P address used by players to connect to the server.
|
||||||
|
private String _publicAddress;
|
||||||
|
public String getPublicAddress() { return _publicAddress; }
|
||||||
|
|
||||||
|
// The port the server is currently running/listening on.
|
||||||
|
private int _port;
|
||||||
|
public int getPort() { return _port; }
|
||||||
|
|
||||||
|
private int _donorsOnline;
|
||||||
|
public int getDonorsOnline() { return _donorsOnline; }
|
||||||
|
|
||||||
|
private long _startUpDate;
|
||||||
|
|
||||||
|
private long _currentTime;
|
||||||
|
public long getCurrentTime()
|
||||||
|
{
|
||||||
|
return this._currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* @param name
|
||||||
|
* @param group
|
||||||
|
* @param motd
|
||||||
|
* @param publicAddress
|
||||||
|
* @param port
|
||||||
|
* @param playerCount
|
||||||
|
* @param maxPlayerCount
|
||||||
|
* @param tps
|
||||||
|
* @param ram
|
||||||
|
* @param maxRam
|
||||||
|
*/
|
||||||
|
public MinecraftServer(String name, String group, String motd, String publicAddress, int port,
|
||||||
|
int playerCount, int maxPlayerCount, int tps, int ram, int maxRam, long startUpDate, int donorsOnline)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_group = group;
|
||||||
|
_motd = motd;
|
||||||
|
_playerCount = playerCount;
|
||||||
|
_maxPlayerCount = maxPlayerCount;
|
||||||
|
_tps = tps;
|
||||||
|
_ram = ram;
|
||||||
|
_maxRam = maxRam;
|
||||||
|
_publicAddress = publicAddress;
|
||||||
|
_port = port;
|
||||||
|
_donorsOnline = donorsOnline;
|
||||||
|
_startUpDate = startUpDate;
|
||||||
|
_currentTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true, if {@value _playerCount} equals 0, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty()
|
||||||
|
{
|
||||||
|
return _playerCount == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the amount of time (in seconds) that this {@link MinecraftServer} has been online for.
|
||||||
|
*/
|
||||||
|
public double getUptime()
|
||||||
|
{
|
||||||
|
return (System.currentTimeMillis() / 1000d - _startUpDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true, if this server is currently joinable by players, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isJoinable()
|
||||||
|
{
|
||||||
|
if (_motd == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is super dodgy, this is the only way around monitor not killing game servers with the new MOTD system
|
||||||
|
if (_motd.isEmpty() || _motd.contains("VOTING") || _motd.contains("STARTING") || _motd.contains("WAITING") || _motd.contains("ALWAYS_OPEN"))
|
||||||
|
{
|
||||||
|
if (_playerCount < _maxPlayerCount)
|
||||||
|
{
|
||||||
|
int availableSlots = _maxPlayerCount - _playerCount;
|
||||||
|
|
||||||
|
return !_motd.isEmpty() || (availableSlots > 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroup(String group)
|
||||||
|
{
|
||||||
|
_group = group;
|
||||||
|
}
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
public class PlayerServerInfo implements Data
|
||||||
|
{
|
||||||
|
|
||||||
|
private String _playerName;
|
||||||
|
public String getPlayerName() { return _playerName; }
|
||||||
|
|
||||||
|
private String _lastServer;
|
||||||
|
public String getLastServer() { return _lastServer; }
|
||||||
|
public void setLastServer(String lastServer) { _lastServer = lastServer; }
|
||||||
|
|
||||||
|
public PlayerServerInfo(String playerName, String lastServer)
|
||||||
|
{
|
||||||
|
_playerName = playerName;
|
||||||
|
_lastServer = lastServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDataId()
|
||||||
|
{
|
||||||
|
return _playerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PlayerStatus implements Data
|
||||||
|
{
|
||||||
|
// The uuid of this player.
|
||||||
|
private UUID _uuid;
|
||||||
|
public UUID getUUID() { return _uuid; }
|
||||||
|
|
||||||
|
// The name of this player.
|
||||||
|
private String _name;
|
||||||
|
public String getName() { return _name; }
|
||||||
|
|
||||||
|
// The current server occupied by this player.
|
||||||
|
private String _server;
|
||||||
|
public String getServer() { return _server; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* @param name
|
||||||
|
* @param server
|
||||||
|
*/
|
||||||
|
public PlayerStatus(UUID uuid, String name, String server)
|
||||||
|
{
|
||||||
|
_uuid = uuid;
|
||||||
|
_name = name;
|
||||||
|
_server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique identifying String ID associated with this {@link PlayerStatus}.
|
||||||
|
*/
|
||||||
|
public String getDataId()
|
||||||
|
{
|
||||||
|
return _uuid.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,417 @@
|
|||||||
|
package mineplex.serverdata.data;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
|
||||||
|
public class ServerGroup
|
||||||
|
{
|
||||||
|
private HashMap<String, String> _dataMap = null;
|
||||||
|
|
||||||
|
private String _name;
|
||||||
|
private String _host;
|
||||||
|
private String _prefix;
|
||||||
|
|
||||||
|
private int _minPlayers;
|
||||||
|
private int _maxPlayers;
|
||||||
|
|
||||||
|
private int _requiredRam;
|
||||||
|
private int _requiredCpu;
|
||||||
|
private int _requiredTotalServers;
|
||||||
|
private int _requiredJoinableServers;
|
||||||
|
|
||||||
|
private String _uptimes = "";
|
||||||
|
|
||||||
|
private boolean _arcadeGroup;
|
||||||
|
private String _worldZip;
|
||||||
|
private String _plugin;
|
||||||
|
private String _configPath;
|
||||||
|
private int _portSection;
|
||||||
|
|
||||||
|
private boolean _pvp;
|
||||||
|
private boolean _tournament;
|
||||||
|
private boolean _tournamentPoints;
|
||||||
|
private boolean _teamRejoin;
|
||||||
|
private boolean _teamAutoJoin;
|
||||||
|
private boolean _teamForceBalance;
|
||||||
|
|
||||||
|
private boolean _gameAutoStart;
|
||||||
|
private boolean _gameTimeout;
|
||||||
|
private boolean _gameVoting, _mapVoting;
|
||||||
|
private boolean _rewardGems;
|
||||||
|
private boolean _rewardItems;
|
||||||
|
private boolean _rewardStats;
|
||||||
|
private boolean _rewardAchievements;
|
||||||
|
|
||||||
|
private boolean _hotbarInventory;
|
||||||
|
private boolean _hotbarHubClock;
|
||||||
|
private boolean _playerKickIdle;
|
||||||
|
private boolean _hardMaxPlayerCap;
|
||||||
|
|
||||||
|
private String _games;
|
||||||
|
private String _modes;
|
||||||
|
private String _boosterGroup;
|
||||||
|
private String _serverType;
|
||||||
|
private boolean _addNoCheat;
|
||||||
|
private boolean _addWorldEdit;
|
||||||
|
private boolean _whitelist;
|
||||||
|
private boolean _staffOnly;
|
||||||
|
private String _resourcePack = "";
|
||||||
|
|
||||||
|
private String _npcName = "";
|
||||||
|
private String _portalBottomCornerLocation = "";
|
||||||
|
private String _portalTopCornerLocation = "";
|
||||||
|
|
||||||
|
private String _teamServerKey = "";
|
||||||
|
|
||||||
|
private Region _region;
|
||||||
|
|
||||||
|
private Set<MinecraftServer> _servers;
|
||||||
|
|
||||||
|
public ServerGroup(Map<String, String> data, Collection<MinecraftServer> serverStatuses)
|
||||||
|
{
|
||||||
|
_name = data.get("name");
|
||||||
|
_prefix = data.get("prefix");
|
||||||
|
_requiredRam = Integer.valueOf(data.get("ram"));
|
||||||
|
_requiredCpu = Integer.valueOf(data.get("cpu"));
|
||||||
|
_requiredTotalServers = Integer.valueOf(data.get("totalServers"));
|
||||||
|
_requiredJoinableServers = Integer.valueOf(data.get("joinableServers"));
|
||||||
|
_portSection = Integer.valueOf(data.get("portSection"));
|
||||||
|
_uptimes = data.getOrDefault("uptimes", "");
|
||||||
|
_arcadeGroup = Boolean.valueOf(data.get("arcadeGroup"));
|
||||||
|
_worldZip = data.get("worldZip");
|
||||||
|
_plugin = data.get("plugin");
|
||||||
|
_configPath = data.get("configPath");
|
||||||
|
_minPlayers = Integer.valueOf(data.get("minPlayers"));
|
||||||
|
_maxPlayers = Integer.valueOf(data.get("maxPlayers"));
|
||||||
|
_pvp = Boolean.valueOf(data.get("pvp"));
|
||||||
|
_tournament = Boolean.valueOf(data.get("tournament"));
|
||||||
|
_tournamentPoints = Boolean.valueOf(data.get("tournamentPoints"));
|
||||||
|
_hardMaxPlayerCap = Boolean.valueOf(data.get("hardMaxPlayerCap"));
|
||||||
|
_games = data.get("games");
|
||||||
|
_modes = data.get("modes");
|
||||||
|
_boosterGroup = data.get("boosterGroup");
|
||||||
|
_serverType = data.get("serverType");
|
||||||
|
_addNoCheat = Boolean.valueOf(data.get("addNoCheat"));
|
||||||
|
_addWorldEdit = Boolean.valueOf(data.get("addWorldEdit"));
|
||||||
|
_teamRejoin = Boolean.valueOf(data.get("teamRejoin"));
|
||||||
|
_teamAutoJoin = Boolean.valueOf(data.get("teamAutoJoin"));
|
||||||
|
_teamForceBalance = Boolean.valueOf(data.get("teamForceBalance"));
|
||||||
|
_gameAutoStart = Boolean.valueOf(data.get("gameAutoStart"));
|
||||||
|
_gameTimeout = Boolean.valueOf(data.get("gameTimeout"));
|
||||||
|
_gameVoting = Boolean.valueOf(data.get("gameVoting"));
|
||||||
|
_mapVoting = Boolean.valueOf(data.get("mapVoting"));
|
||||||
|
_rewardGems = Boolean.valueOf(data.get("rewardGems"));
|
||||||
|
_rewardItems = Boolean.valueOf(data.get("rewardItems"));
|
||||||
|
_rewardStats = Boolean.valueOf(data.get("rewardStats"));
|
||||||
|
_rewardAchievements = Boolean.valueOf(data.get("rewardAchievements"));
|
||||||
|
_hotbarInventory = Boolean.valueOf(data.get("hotbarInventory"));
|
||||||
|
_hotbarHubClock = Boolean.valueOf(data.get("hotbarHubClock"));
|
||||||
|
_playerKickIdle = Boolean.valueOf(data.get("playerKickIdle"));
|
||||||
|
_staffOnly = Boolean.valueOf(data.get("staffOnly"));
|
||||||
|
_whitelist = Boolean.valueOf(data.get("whitelist"));
|
||||||
|
_resourcePack = data.getOrDefault("resourcePack", "");
|
||||||
|
_host = data.get("host");
|
||||||
|
_region = data.containsKey("region") ? Region.valueOf(data.get("region")) : Region.ALL;
|
||||||
|
_teamServerKey = data.getOrDefault("teamServerKey", "");
|
||||||
|
_portalBottomCornerLocation = data.getOrDefault("portalBottomCornerLocation", "");
|
||||||
|
_portalTopCornerLocation = data.getOrDefault("portalTopCornerLocation", "");
|
||||||
|
_npcName = data.getOrDefault("npcName", "");
|
||||||
|
|
||||||
|
if (serverStatuses != null)
|
||||||
|
parseServers(serverStatuses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerGroup(String name, String prefix, String host, int ram, int cpu, int totalServers, int joinable, int portSection, String uptimes, boolean arcade, String worldZip, String plugin, String configPath
|
||||||
|
, int minPlayers, int maxPlayers, boolean pvp, boolean tournament, boolean tournamentPoints, String games, String modes, String boosterGroup, String serverType, boolean noCheat, boolean worldEdit, boolean teamRejoin
|
||||||
|
, boolean teamAutoJoin, boolean teamForceBalance, boolean gameAutoStart, boolean gameTimeout, boolean gameVoting, boolean mapVoting, boolean rewardGems, boolean rewardItems, boolean rewardStats
|
||||||
|
, boolean rewardAchievements, boolean hotbarInventory, boolean hotbarHubClock, boolean playerKickIdle, boolean hardMaxPlayerCap, boolean staffOnly, boolean whitelist, String resourcePack, Region region
|
||||||
|
, String teamServerKey, String portalBottomCornerLocation, String portalTopCornerLocation, String npcName)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_prefix = prefix;
|
||||||
|
_host = host;
|
||||||
|
_requiredRam = ram;
|
||||||
|
_requiredCpu = cpu;
|
||||||
|
_requiredTotalServers = totalServers;
|
||||||
|
_requiredJoinableServers = joinable;
|
||||||
|
_portSection = portSection;
|
||||||
|
_uptimes = uptimes;
|
||||||
|
_arcadeGroup = arcade;
|
||||||
|
_worldZip = worldZip;
|
||||||
|
_plugin = plugin;
|
||||||
|
_configPath = configPath;
|
||||||
|
_minPlayers = minPlayers;
|
||||||
|
_maxPlayers = maxPlayers;
|
||||||
|
_pvp = pvp;
|
||||||
|
_tournament = tournament;
|
||||||
|
_tournamentPoints = tournamentPoints;
|
||||||
|
_games = games;
|
||||||
|
_modes = modes;
|
||||||
|
_boosterGroup = boosterGroup;
|
||||||
|
_serverType = serverType;
|
||||||
|
_addNoCheat = noCheat;
|
||||||
|
_addWorldEdit = worldEdit;
|
||||||
|
_teamRejoin = teamRejoin;
|
||||||
|
_teamAutoJoin = teamAutoJoin;
|
||||||
|
_teamForceBalance = teamForceBalance;
|
||||||
|
_gameAutoStart = gameAutoStart;
|
||||||
|
_gameTimeout = gameTimeout;
|
||||||
|
_gameVoting = gameVoting;
|
||||||
|
_mapVoting = mapVoting;
|
||||||
|
_rewardGems = rewardGems;
|
||||||
|
_rewardItems = rewardItems;
|
||||||
|
_rewardStats = rewardStats;
|
||||||
|
_rewardAchievements = rewardAchievements;
|
||||||
|
_hotbarInventory = hotbarInventory;
|
||||||
|
_hotbarHubClock = hotbarHubClock;
|
||||||
|
_playerKickIdle = playerKickIdle;
|
||||||
|
_hardMaxPlayerCap = hardMaxPlayerCap;
|
||||||
|
_staffOnly = staffOnly;
|
||||||
|
_whitelist = whitelist;
|
||||||
|
_resourcePack = resourcePack;
|
||||||
|
_region = region;
|
||||||
|
_teamServerKey = teamServerKey;
|
||||||
|
_portalBottomCornerLocation = portalBottomCornerLocation;
|
||||||
|
_portalTopCornerLocation = portalTopCornerLocation;
|
||||||
|
_npcName = npcName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerGroup(String name, String npcName, String prefix)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_npcName = npcName;
|
||||||
|
_prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() { return _name; }
|
||||||
|
public String getHost() { return _host; }
|
||||||
|
public String getPrefix() { return _prefix; }
|
||||||
|
|
||||||
|
public int getMinPlayers() { return _minPlayers; }
|
||||||
|
public int getMaxPlayers() { return _maxPlayers; }
|
||||||
|
|
||||||
|
public int getRequiredRam() { return _requiredRam; }
|
||||||
|
public int getRequiredCpu() { return _requiredCpu; }
|
||||||
|
public int getRequiredTotalServers() { return _requiredTotalServers; }
|
||||||
|
public int getRequiredJoinableServers() { return _requiredJoinableServers; }
|
||||||
|
public int getPortSection() { return _portSection; }
|
||||||
|
|
||||||
|
public boolean getArcadeGroup() { return _arcadeGroup; }
|
||||||
|
public String getWorldZip() { return _worldZip; }
|
||||||
|
public String getPlugin() { return _plugin; }
|
||||||
|
public String getConfigPath() { return _configPath; }
|
||||||
|
|
||||||
|
public boolean getPvp() { return _pvp; }
|
||||||
|
public boolean getTournament() { return _tournament; }
|
||||||
|
public boolean getTournamentPoints() { return _tournamentPoints; }
|
||||||
|
public boolean getTeamRejoin() { return _teamRejoin; }
|
||||||
|
public boolean getTeamAutoJoin() { return _teamAutoJoin; }
|
||||||
|
|
||||||
|
public boolean getTeamForceBalance() { return _teamForceBalance; }
|
||||||
|
public boolean getGameAutoStart() { return _gameAutoStart; }
|
||||||
|
public boolean getGameTimeout() { return _gameTimeout; }
|
||||||
|
public boolean getGameVoting() { return _gameVoting; }
|
||||||
|
public boolean getMapVoting() { return _mapVoting; }
|
||||||
|
public boolean getRewardGems() { return _rewardGems; }
|
||||||
|
public boolean getRewardItems() { return _rewardItems; }
|
||||||
|
public boolean getRewardStats() { return _rewardStats; }
|
||||||
|
public boolean getRewardAchievements() { return _rewardAchievements; }
|
||||||
|
|
||||||
|
public boolean getHotbarInventory() { return _hotbarInventory; }
|
||||||
|
public boolean getHotbarHubClock() { return _hotbarHubClock; }
|
||||||
|
public boolean getPlayerKickIdle() { return _playerKickIdle; }
|
||||||
|
public boolean getHardMaxPlayerCap() { return _hardMaxPlayerCap; }
|
||||||
|
|
||||||
|
public String getGames() { return _games; }
|
||||||
|
public String getModes() { return _modes; }
|
||||||
|
public String getBoosterGroup() { return _boosterGroup; }
|
||||||
|
|
||||||
|
public String getServerType() { return _serverType; }
|
||||||
|
public boolean getAddNoCheat() { return _addNoCheat; }
|
||||||
|
public boolean getAddWorldEdit() { return _addWorldEdit; }
|
||||||
|
public boolean getWhitelist() { return _whitelist; }
|
||||||
|
public boolean getStaffOnly() { return _staffOnly; }
|
||||||
|
public String getResourcePack() { return _resourcePack; }
|
||||||
|
public Region getRegion() { return _region; }
|
||||||
|
|
||||||
|
public String getTeamServerKey() { return _teamServerKey; }
|
||||||
|
|
||||||
|
public String getServerNpcName() { return _npcName; }
|
||||||
|
public String getPortalBottomCornerLocation() { return _portalBottomCornerLocation; }
|
||||||
|
public String getPortalTopCornerLocation() { return _portalTopCornerLocation; }
|
||||||
|
public String getUptimes() { return _uptimes; }
|
||||||
|
|
||||||
|
public Set<MinecraftServer> getServers() { return _servers; }
|
||||||
|
|
||||||
|
public int getServerCount()
|
||||||
|
{
|
||||||
|
return _servers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getJoinableCount()
|
||||||
|
{
|
||||||
|
int joinable = 0;
|
||||||
|
|
||||||
|
for (MinecraftServer server : _servers)
|
||||||
|
{
|
||||||
|
if (server.isJoinable())
|
||||||
|
{
|
||||||
|
joinable++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayerCount()
|
||||||
|
{
|
||||||
|
int playerCount = 0;
|
||||||
|
|
||||||
|
for (MinecraftServer server : _servers)
|
||||||
|
{
|
||||||
|
playerCount += server.getPlayerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxPlayerCount()
|
||||||
|
{
|
||||||
|
int maxPlayerCount = 0;
|
||||||
|
|
||||||
|
for (MinecraftServer server : _servers)
|
||||||
|
{
|
||||||
|
maxPlayerCount += server.getMaxPlayerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxPlayerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<MinecraftServer> getEmptyServers()
|
||||||
|
{
|
||||||
|
Collection<MinecraftServer> emptyServers = new HashSet<MinecraftServer>();
|
||||||
|
|
||||||
|
for (MinecraftServer server : _servers)
|
||||||
|
{
|
||||||
|
if (server.isEmpty() && server.getUptime() >= 150) // Only return empty servers that have been online for >150 seconds
|
||||||
|
{
|
||||||
|
emptyServers.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseServers(Collection<MinecraftServer> servers)
|
||||||
|
{
|
||||||
|
_servers = new HashSet<>();
|
||||||
|
|
||||||
|
for (MinecraftServer server : servers)
|
||||||
|
{
|
||||||
|
if (_name.equalsIgnoreCase(server.getGroup()))
|
||||||
|
{
|
||||||
|
_servers.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int generateUniqueId(int startId)
|
||||||
|
{
|
||||||
|
int id = startId;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
boolean uniqueId = true;
|
||||||
|
|
||||||
|
for (MinecraftServer server : _servers)
|
||||||
|
{
|
||||||
|
String serverName = server.getName();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String[] nameArgs = serverName.split("-");
|
||||||
|
int serverNum = Integer.parseInt(nameArgs[nameArgs.length - 1]);
|
||||||
|
|
||||||
|
if (serverNum == id)
|
||||||
|
{
|
||||||
|
uniqueId = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniqueId)
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, String> getDataMap()
|
||||||
|
{
|
||||||
|
if (_dataMap == null)
|
||||||
|
{
|
||||||
|
_dataMap = new HashMap<>();
|
||||||
|
|
||||||
|
_dataMap.put("name", _name);
|
||||||
|
_dataMap.put("prefix", _prefix);
|
||||||
|
_dataMap.put("ram", _requiredRam + "");
|
||||||
|
_dataMap.put("cpu", _requiredCpu + "");
|
||||||
|
_dataMap.put("totalServers", _requiredTotalServers + "");
|
||||||
|
_dataMap.put("joinableServers", _requiredJoinableServers + "");
|
||||||
|
_dataMap.put("portSection", _portSection + "");
|
||||||
|
_dataMap.put("uptimes", _uptimes);
|
||||||
|
_dataMap.put("arcadeGroup", _arcadeGroup + "");
|
||||||
|
_dataMap.put("worldZip", _worldZip);
|
||||||
|
_dataMap.put("plugin", _plugin);
|
||||||
|
_dataMap.put("configPath", _configPath);
|
||||||
|
_dataMap.put("minPlayers", _minPlayers + "");
|
||||||
|
_dataMap.put("maxPlayers", _maxPlayers + "");
|
||||||
|
_dataMap.put("pvp", _pvp + "");
|
||||||
|
_dataMap.put("tournament", _tournament + "");
|
||||||
|
_dataMap.put("tournamentPoints", _tournamentPoints + "");
|
||||||
|
_dataMap.put("games", _games);
|
||||||
|
_dataMap.put("modes", _modes);
|
||||||
|
_dataMap.put("serverType", _serverType);
|
||||||
|
_dataMap.put("addNoCheat", _addNoCheat + "");
|
||||||
|
_dataMap.put("teamRejoin", _teamRejoin + "");
|
||||||
|
_dataMap.put("teamAutoJoin", _teamAutoJoin + "");
|
||||||
|
_dataMap.put("teamForceBalance", _teamForceBalance + "");
|
||||||
|
_dataMap.put("gameAutoStart", _gameAutoStart + "");
|
||||||
|
_dataMap.put("gameTimeout", _gameTimeout + "");
|
||||||
|
_dataMap.put("gameVoting", String.valueOf(_gameVoting));
|
||||||
|
_dataMap.put("mapVoting", String.valueOf(_mapVoting));
|
||||||
|
_dataMap.put("rewardGems", _rewardGems + "");
|
||||||
|
_dataMap.put("rewardItems", _rewardItems + "");
|
||||||
|
_dataMap.put("rewardStats", _rewardStats + "");
|
||||||
|
_dataMap.put("rewardAchievements", _rewardAchievements + "");
|
||||||
|
_dataMap.put("hotbarInventory", _hotbarInventory + "");
|
||||||
|
_dataMap.put("hotbarHubClock", _hotbarHubClock + "");
|
||||||
|
_dataMap.put("playerKickIdle", _playerKickIdle + "");
|
||||||
|
_dataMap.put("staffOnly", _staffOnly + "");
|
||||||
|
_dataMap.put("whitelist", _whitelist + "");
|
||||||
|
_dataMap.put("resourcePack", _resourcePack);
|
||||||
|
_dataMap.put("host", _host);
|
||||||
|
_dataMap.put("region", _region.name());
|
||||||
|
_dataMap.put("teamServerKey", _teamServerKey);
|
||||||
|
_dataMap.put("boosterGroup", _boosterGroup);
|
||||||
|
_dataMap.put("portalBottomCornerLocation", _portalBottomCornerLocation);
|
||||||
|
_dataMap.put("portalTopCornerLocation", _portalTopCornerLocation);
|
||||||
|
_dataMap.put("npcName", _npcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _dataMap;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,179 @@
|
|||||||
|
package mineplex.serverdata.database;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.dbcp2.BasicDataSource;
|
||||||
|
|
||||||
|
public final class DBPool
|
||||||
|
{
|
||||||
|
private static DataSource ACCOUNT;
|
||||||
|
private static DataSource QUEUE;
|
||||||
|
private static DataSource MINEPLEX;
|
||||||
|
private static DataSource MINEPLEX_STATS;
|
||||||
|
private static DataSource PLAYER_STATS;
|
||||||
|
private static DataSource SERVER_STATS;
|
||||||
|
private static DataSource MSSQL_MOCK;
|
||||||
|
|
||||||
|
private static DataSource openDataSource(String url, String username, String password)
|
||||||
|
{
|
||||||
|
BasicDataSource source = new BasicDataSource();
|
||||||
|
source.addConnectionProperty("autoReconnect", "true");
|
||||||
|
source.addConnectionProperty("allowMultiQueries", "true");
|
||||||
|
source.addConnectionProperty("zeroDateTimeBehavior", "convertToNull");
|
||||||
|
source.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
|
||||||
|
source.setDriverClassName("com.mysql.jdbc.Driver");
|
||||||
|
source.setUrl(url);
|
||||||
|
source.setUsername(username);
|
||||||
|
source.setPassword(password);
|
||||||
|
source.setMaxTotal(5);
|
||||||
|
source.setMaxIdle(5);
|
||||||
|
source.setTimeBetweenEvictionRunsMillis(180 * 1000);
|
||||||
|
source.setSoftMinEvictableIdleTimeMillis(180 * 1000);
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getMssqlMock()
|
||||||
|
{
|
||||||
|
if (MSSQL_MOCK == null)
|
||||||
|
loadDataSources();
|
||||||
|
|
||||||
|
return MSSQL_MOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getAccount()
|
||||||
|
{
|
||||||
|
if (ACCOUNT == null)
|
||||||
|
loadDataSources();
|
||||||
|
|
||||||
|
return ACCOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getQueue()
|
||||||
|
{
|
||||||
|
if (QUEUE == null)
|
||||||
|
loadDataSources();
|
||||||
|
|
||||||
|
return QUEUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getMineplex()
|
||||||
|
{
|
||||||
|
if (MINEPLEX == null)
|
||||||
|
loadDataSources();
|
||||||
|
|
||||||
|
return MINEPLEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getMineplexStats()
|
||||||
|
{
|
||||||
|
if (MINEPLEX_STATS == null)
|
||||||
|
loadDataSources();
|
||||||
|
|
||||||
|
return MINEPLEX_STATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getPlayerStats()
|
||||||
|
{
|
||||||
|
if (PLAYER_STATS == null)
|
||||||
|
loadDataSources();
|
||||||
|
|
||||||
|
return PLAYER_STATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getServerStats()
|
||||||
|
{
|
||||||
|
if (SERVER_STATS == null)
|
||||||
|
loadDataSources();
|
||||||
|
|
||||||
|
return SERVER_STATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadDataSources()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File configFile = new File("database-config.dat");
|
||||||
|
|
||||||
|
if (configFile.exists())
|
||||||
|
{
|
||||||
|
List<String> lines = Files.readAllLines(configFile.toPath(), Charset.defaultCharset());
|
||||||
|
|
||||||
|
for (String line : lines)
|
||||||
|
{
|
||||||
|
deserializeConnection(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.println("database-config.dat not found at " + configFile.toPath().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
System.out.println("---Unable To Parse DBPOOL Configuration File---");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deserializeConnection(String line)
|
||||||
|
{
|
||||||
|
String[] args = line.split(" ");
|
||||||
|
if (args.length == 3)
|
||||||
|
{
|
||||||
|
String dbHost = args[0];
|
||||||
|
String userName = args[1];
|
||||||
|
String password = args[2];
|
||||||
|
ACCOUNT = openDataSource("jdbc:mysql://" + dbHost + "/account", userName, password);
|
||||||
|
QUEUE = openDataSource("jdbc:mysql://" + dbHost + "/queue", userName, password);
|
||||||
|
MINEPLEX = openDataSource("jdbc:mysql://" + dbHost + "/mineplex", userName, password);
|
||||||
|
MINEPLEX_STATS = openDataSource("jdbc:mysql://" + dbHost + "/mineplex_stats", userName, password);
|
||||||
|
PLAYER_STATS = openDataSource("jdbc:mysql://" + dbHost + "/player_stats", userName, password);
|
||||||
|
SERVER_STATS = openDataSource("jdbc:mysql://" + dbHost + "/server_stats", userName, password);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
String[] args = line.split(" ");
|
||||||
|
|
||||||
|
if (args.length == 4)
|
||||||
|
{
|
||||||
|
String dbHost = args[0];
|
||||||
|
String userName = args[1];
|
||||||
|
String password = args[2];
|
||||||
|
|
||||||
|
System.out.println(userName.toString());
|
||||||
|
System.out.println(dbHost.toString());
|
||||||
|
System.out.println(password.toString());
|
||||||
|
|
||||||
|
ACCOUNT = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
QUEUE = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
MINEPLEX = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
MINEPLEX_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
PLAYER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
SERVER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
MSSQL_MOCK = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
|
||||||
|
if (dbSource.toUpperCase().equalsIgnoreCase("ACCOUNT"))
|
||||||
|
ACCOUNT = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
else if (dbSource.toUpperCase().equalsIgnoreCase("QUEUE"))
|
||||||
|
QUEUE = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
else if (dbSource.toUpperCase().equalsIgnoreCase("MINEPLEX"))
|
||||||
|
MINEPLEX = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
else if (dbSource.toUpperCase().equalsIgnoreCase("MINEPLEX_STATS"))
|
||||||
|
MINEPLEX_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
else if (dbSource.toUpperCase().equalsIgnoreCase("PLAYER_STATS"))
|
||||||
|
PLAYER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
else if (dbSource.toUpperCase().equalsIgnoreCase("SERVER_STATS"))
|
||||||
|
SERVER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
else if (dbSource.toUpperCase().equalsIgnoreCase("MSSQL_MOCK"))
|
||||||
|
MSSQL_MOCK = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package mineplex.serverdata.database;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.omg.CORBA.ACTIVITY_REQUIRED;
|
||||||
|
|
||||||
|
public class DatabaseRunnable
|
||||||
|
{
|
||||||
|
private Runnable _runnable;
|
||||||
|
private String _errorMessage;
|
||||||
|
private AtomicInteger _failedAttempts = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public DatabaseRunnable(Runnable runnable, String error)
|
||||||
|
{
|
||||||
|
_runnable = runnable;
|
||||||
|
_errorMessage = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
_runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage()
|
||||||
|
{
|
||||||
|
return _errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incrementFailCount()
|
||||||
|
{
|
||||||
|
_failedAttempts.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFailedCounts()
|
||||||
|
{
|
||||||
|
return _failedAttempts.get();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,314 @@
|
|||||||
|
package mineplex.serverdata.database;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException;
|
||||||
|
|
||||||
|
import org.jooq.DSLContext;
|
||||||
|
import org.jooq.SQLDialect;
|
||||||
|
import org.jooq.impl.DSL;
|
||||||
|
|
||||||
|
import mineplex.serverdata.database.column.Column;
|
||||||
|
|
||||||
|
public abstract class RepositoryBase
|
||||||
|
{
|
||||||
|
static
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
|
} catch (ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSource _dataSource; // Connection pool
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param dataSource - the {@link DataSource} responsible for providing the connection pool to this repository.
|
||||||
|
*/
|
||||||
|
public RepositoryBase(DataSource dataSource)
|
||||||
|
{
|
||||||
|
_dataSource = dataSource;
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
initialize();
|
||||||
|
update();
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initialize()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void update()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link DataSource} used by the repository for connection pooling.
|
||||||
|
*/
|
||||||
|
protected DataSource getConnectionPool()
|
||||||
|
{
|
||||||
|
return _dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requirements: {@link Connection}s must be closed after usage so they may be returned to the pool!
|
||||||
|
* @see Connection#close()
|
||||||
|
* @return a newly fetched {@link Connection} from the connection pool, if a connection can be made, null otherwise.
|
||||||
|
*/
|
||||||
|
protected Connection getConnection()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _dataSource.getConnection();
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
// TODO: Log connection failures?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int executeUpdate(Connection connection, String query, Runnable onSQLError, Column<?>...columns)
|
||||||
|
{
|
||||||
|
return executeInsert(connection, query, null, onSQLError, columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int executeUpdate(String query, Runnable onSQLError, Column<?>...columns)
|
||||||
|
{
|
||||||
|
return executeInsert(query, null, onSQLError, columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a query against the repository.
|
||||||
|
* @param query - the concatenated query to execute in string form.
|
||||||
|
* @param columns - the column data values used for insertion into the query.
|
||||||
|
* @return the number of rows affected by this query in the repository.
|
||||||
|
*/
|
||||||
|
protected int executeUpdate(String query, Column<?>...columns)
|
||||||
|
{
|
||||||
|
return executeInsert(query, null, columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int executeInsert(Connection connection, String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||||
|
{
|
||||||
|
int affectedRows = 0;
|
||||||
|
|
||||||
|
// Automatic resource management for handling/closing objects.
|
||||||
|
try (
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
for (int i=0; i < columns.length; i++)
|
||||||
|
{
|
||||||
|
columns[i].setValue(preparedStatement, i+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
affectedRows = preparedStatement.executeUpdate();
|
||||||
|
|
||||||
|
if (callable != null)
|
||||||
|
{
|
||||||
|
callable.processResultSet(preparedStatement.getGeneratedKeys());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
if (onSQLError != null)
|
||||||
|
{
|
||||||
|
onSQLError.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return affectedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int executeInsert(String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||||
|
{
|
||||||
|
int affectedRows = 0;
|
||||||
|
|
||||||
|
// Automatic resource management for handling/closing objects.
|
||||||
|
try (
|
||||||
|
Connection connection = getConnection();
|
||||||
|
)
|
||||||
|
{
|
||||||
|
affectedRows = executeInsert(connection, query, callable, onSQLError, columns);
|
||||||
|
}
|
||||||
|
catch (SQLException exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
if (onSQLError != null)
|
||||||
|
{
|
||||||
|
onSQLError.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return affectedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int executeInsert(String query, ResultSetCallable callable, Column<?>...columns)
|
||||||
|
{
|
||||||
|
int affectedRows = 0;
|
||||||
|
|
||||||
|
// Automatic resource management for handling/closing objects.
|
||||||
|
try (
|
||||||
|
Connection connection = getConnection();
|
||||||
|
)
|
||||||
|
{
|
||||||
|
affectedRows = executeInsert(connection, query, callable, null, columns);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return affectedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeQuery(PreparedStatement statement, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i=0; i < columns.length; i++)
|
||||||
|
{
|
||||||
|
columns[i].setValue(statement, i+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ResultSet resultSet = statement.executeQuery())
|
||||||
|
{
|
||||||
|
if (callable != null)
|
||||||
|
{
|
||||||
|
callable.processResultSet(resultSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
if (onSQLError != null)
|
||||||
|
{
|
||||||
|
onSQLError.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeQuery(PreparedStatement statement, ResultSetCallable callable, Column<?>...columns)
|
||||||
|
{
|
||||||
|
executeQuery(statement, callable, null, columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeQuery(Connection connection, String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||||
|
{
|
||||||
|
// Automatic resource management for handling/closing objects.
|
||||||
|
try (
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(query)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
executeQuery(preparedStatement, callable, onSQLError, columns);
|
||||||
|
}
|
||||||
|
catch (SQLException exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
if (onSQLError != null)
|
||||||
|
{
|
||||||
|
onSQLError.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeQuery(Connection connection, String query, ResultSetCallable callable, Column<?>...columns)
|
||||||
|
{
|
||||||
|
// Automatic resource management for handling/closing objects.
|
||||||
|
try (
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(query)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
executeQuery(preparedStatement, callable, columns);
|
||||||
|
}
|
||||||
|
catch (MySQLSyntaxErrorException syntaxException)
|
||||||
|
{
|
||||||
|
System.err.println("Query \"" + query + "\" contained a syntax error:");
|
||||||
|
syntaxException.printStackTrace();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeQuery(String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||||
|
{
|
||||||
|
// Automatic resource management for handling/closing objects.
|
||||||
|
try (
|
||||||
|
Connection connection = getConnection();
|
||||||
|
)
|
||||||
|
{
|
||||||
|
executeQuery(connection, query, callable, onSQLError, columns);
|
||||||
|
}
|
||||||
|
catch (SQLException exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
if (onSQLError != null)
|
||||||
|
{
|
||||||
|
onSQLError.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeQuery(String query, ResultSetCallable callable, Column<?>...columns)
|
||||||
|
{
|
||||||
|
// Automatic resource management for handling/closing objects.
|
||||||
|
try (
|
||||||
|
Connection connection = getConnection();
|
||||||
|
)
|
||||||
|
{
|
||||||
|
executeQuery(connection, query, callable, columns);
|
||||||
|
}
|
||||||
|
catch (SQLException exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DSLContext jooq()
|
||||||
|
{
|
||||||
|
return DSL.using(DBPool.getAccount(), SQLDialect.MYSQL);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package mineplex.serverdata.database;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public interface ResultSetCallable
|
||||||
|
{
|
||||||
|
public void processResultSet(ResultSet resultSet) throws SQLException;
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public abstract class Column<Type>
|
||||||
|
{
|
||||||
|
public String Name;
|
||||||
|
public Type Value;
|
||||||
|
|
||||||
|
public Column(String name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Column(String name, Type value)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getCreateString();
|
||||||
|
|
||||||
|
public abstract Type getValue(ResultSet resultSet) throws SQLException;
|
||||||
|
|
||||||
|
public abstract void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException;
|
||||||
|
|
||||||
|
public abstract Column<Type> clone();
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ColumnBoolean extends Column<Boolean>
|
||||||
|
{
|
||||||
|
public ColumnBoolean(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnBoolean(String name, boolean value)
|
||||||
|
{
|
||||||
|
super(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreateString()
|
||||||
|
{
|
||||||
|
return Name + " BOOLEAN";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getValue(ResultSet resultSet) throws SQLException
|
||||||
|
{
|
||||||
|
return resultSet.getBoolean(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||||
|
{
|
||||||
|
preparedStatement.setBoolean(columnNumber, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnBoolean clone()
|
||||||
|
{
|
||||||
|
return new ColumnBoolean(Name, Value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ColumnByte extends Column<Byte>
|
||||||
|
{
|
||||||
|
public ColumnByte(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
Value = (byte)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnByte(String name, Byte value)
|
||||||
|
{
|
||||||
|
super(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreateString()
|
||||||
|
{
|
||||||
|
return Name + " TINYINT";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Byte getValue(ResultSet resultSet) throws SQLException
|
||||||
|
{
|
||||||
|
return resultSet.getByte(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||||
|
{
|
||||||
|
preparedStatement.setLong(columnNumber, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnByte clone()
|
||||||
|
{
|
||||||
|
return new ColumnByte(Name, Value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ColumnDouble extends Column<Double>
|
||||||
|
{
|
||||||
|
public ColumnDouble(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
Value = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnDouble(String name, Double value)
|
||||||
|
{
|
||||||
|
super(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreateString()
|
||||||
|
{
|
||||||
|
return Name + " DOUBLE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getValue(ResultSet resultSet) throws SQLException
|
||||||
|
{
|
||||||
|
return resultSet.getDouble(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||||
|
{
|
||||||
|
preparedStatement.setDouble(columnNumber, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnDouble clone()
|
||||||
|
{
|
||||||
|
return new ColumnDouble(Name, Value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ColumnInt extends Column<Integer>
|
||||||
|
{
|
||||||
|
public ColumnInt(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
Value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnInt(String name, int value)
|
||||||
|
{
|
||||||
|
super(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreateString()
|
||||||
|
{
|
||||||
|
return Name + " INT";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue(ResultSet resultSet) throws SQLException
|
||||||
|
{
|
||||||
|
return resultSet.getInt(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||||
|
{
|
||||||
|
preparedStatement.setInt(columnNumber, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnInt clone()
|
||||||
|
{
|
||||||
|
return new ColumnInt(Name, Value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ColumnLong extends Column<Long>
|
||||||
|
{
|
||||||
|
public ColumnLong(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
Value = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnLong(String name, Long value)
|
||||||
|
{
|
||||||
|
super(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreateString()
|
||||||
|
{
|
||||||
|
return Name + " LONG";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getValue(ResultSet resultSet) throws SQLException
|
||||||
|
{
|
||||||
|
return resultSet.getLong(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||||
|
{
|
||||||
|
preparedStatement.setLong(columnNumber, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnLong clone()
|
||||||
|
{
|
||||||
|
return new ColumnLong(Name, Value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
|
public class ColumnTimestamp extends Column<Timestamp>
|
||||||
|
{
|
||||||
|
public ColumnTimestamp(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnTimestamp(String name, Timestamp value)
|
||||||
|
{
|
||||||
|
super(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreateString()
|
||||||
|
{
|
||||||
|
return Name + " TIMESTAMP";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timestamp getValue(ResultSet resultSet) throws SQLException
|
||||||
|
{
|
||||||
|
return resultSet.getTimestamp(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||||
|
{
|
||||||
|
preparedStatement.setTimestamp(columnNumber, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnTimestamp clone()
|
||||||
|
{
|
||||||
|
return new ColumnTimestamp(Name, Value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package mineplex.serverdata.database.column;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ColumnVarChar extends Column<String>
|
||||||
|
{
|
||||||
|
public int Length = 25;
|
||||||
|
|
||||||
|
public ColumnVarChar(String name, int length)
|
||||||
|
{
|
||||||
|
this(name, length, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColumnVarChar(String name, int length, String value)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
|
||||||
|
Length = length;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateString()
|
||||||
|
{
|
||||||
|
return Name + " VARCHAR(" + Length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue(ResultSet resultSet) throws SQLException
|
||||||
|
{
|
||||||
|
return resultSet.getString(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||||
|
{
|
||||||
|
preparedStatement.setString(columnNumber, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnVarChar clone()
|
||||||
|
{
|
||||||
|
return new ColumnVarChar(Name, Length, Value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package mineplex.serverdata.redis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData.ConnectionType;
|
||||||
|
|
||||||
|
public class RedisConfig
|
||||||
|
{
|
||||||
|
// Failsafe values in case configuration is not provided
|
||||||
|
private static final String DEFAULT_IP = "127.0.0.1";
|
||||||
|
private static final int DEFAULT_PORT = 6379;
|
||||||
|
private static Random random = new Random(); // Utility random
|
||||||
|
|
||||||
|
// The connections managed by this configuration
|
||||||
|
private List<ConnectionData> _connections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* @param connections
|
||||||
|
*/
|
||||||
|
public RedisConfig(List<ConnectionData> connections)
|
||||||
|
{
|
||||||
|
_connections = connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* Produces a default-value based RedisConfig.
|
||||||
|
*/
|
||||||
|
public RedisConfig()
|
||||||
|
{
|
||||||
|
_connections = new ArrayList<ConnectionData>();
|
||||||
|
_connections.add(new ConnectionData(DEFAULT_IP, DEFAULT_PORT, ConnectionType.MASTER, "DefaultConnection"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code writeable} defaults to {@literal true}.
|
||||||
|
*/
|
||||||
|
public ConnectionData getConnection()
|
||||||
|
{
|
||||||
|
return getConnection(true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param writeable - whether the returned connection reference can receive write-requests.
|
||||||
|
* @return a {@link ConnectionData} referencing a valid redis-connection from this configuration.
|
||||||
|
*/
|
||||||
|
public ConnectionData getConnection(boolean writeable, String name)
|
||||||
|
{
|
||||||
|
List<ConnectionData> connections = getConnections(writeable, name);
|
||||||
|
|
||||||
|
if (connections.size() > 0)
|
||||||
|
{
|
||||||
|
int index = random.nextInt(connections.size());
|
||||||
|
return connections.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConnectionData> getConnections(boolean writeable, String name)
|
||||||
|
{
|
||||||
|
List<ConnectionData> connections = new ArrayList<ConnectionData>();
|
||||||
|
ConnectionType type = (writeable) ? ConnectionType.MASTER : ConnectionType.SLAVE;
|
||||||
|
|
||||||
|
for (ConnectionData connection : _connections)
|
||||||
|
{
|
||||||
|
if (connection.getType() == type && connection.nameMatches(name))
|
||||||
|
{
|
||||||
|
connections.add(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,268 @@
|
|||||||
|
package mineplex.serverdata.redis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.Utility;
|
||||||
|
import mineplex.serverdata.data.Data;
|
||||||
|
import mineplex.serverdata.data.DataRepository;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.Pipeline;
|
||||||
|
import redis.clients.jedis.Response;
|
||||||
|
import redis.clients.jedis.Transaction;
|
||||||
|
|
||||||
|
public class RedisDataRepository<T extends Data> extends RedisRepository implements DataRepository<T>
|
||||||
|
{
|
||||||
|
// The class type of the elements stored in this repository
|
||||||
|
private Class<T> _elementType;
|
||||||
|
// A unique label designating the elements and this repository.
|
||||||
|
private String _elementLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
* @param writeConn
|
||||||
|
* @param readConn
|
||||||
|
* @param region
|
||||||
|
*/
|
||||||
|
public RedisDataRepository(ConnectionData writeConn, ConnectionData readConn, Region region,
|
||||||
|
Class<T> elementType, String elementLabel)
|
||||||
|
{
|
||||||
|
super(writeConn, readConn, region);
|
||||||
|
_elementType = elementType;
|
||||||
|
_elementLabel = elementLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedisDataRepository(ConnectionData conn, Region region, Class<T> elementType, String elementLabel)
|
||||||
|
{
|
||||||
|
this(conn, conn, region, elementType, elementLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedisDataRepository(Region region, Class<T> elementType, String elementLabel)
|
||||||
|
{
|
||||||
|
this(ServerManager.getMasterConnection(), ServerManager.getSlaveConnection(), region,
|
||||||
|
elementType, elementLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getElementSetKey()
|
||||||
|
{
|
||||||
|
return concatenate("data", _elementLabel, getRegion().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateKey(T element)
|
||||||
|
{
|
||||||
|
return generateKey(element.getDataId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateKey(String dataId)
|
||||||
|
{
|
||||||
|
return concatenate(getElementSetKey(), dataId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<T> getElements()
|
||||||
|
{
|
||||||
|
return getElements(getActiveElements());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<T> getElements(Collection<String> dataIds)
|
||||||
|
{
|
||||||
|
Collection<T> elements = new HashSet<T>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
|
||||||
|
List<Response<String>> responses = new ArrayList<Response<String>>();
|
||||||
|
for (String dataId : dataIds)
|
||||||
|
{
|
||||||
|
responses.add(pipeline.get(generateKey(dataId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block until all requests have received pipelined responses
|
||||||
|
pipeline.sync();
|
||||||
|
|
||||||
|
for (Response<String> response : responses)
|
||||||
|
{
|
||||||
|
String serializedData = response.get();
|
||||||
|
T element = deserialize(serializedData);
|
||||||
|
|
||||||
|
if (element != null)
|
||||||
|
{
|
||||||
|
elements.add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String,T> getElementsMap(List<String> dataIds)
|
||||||
|
{
|
||||||
|
Map<String,T> elements = new HashMap<>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
|
||||||
|
List<Response<String>> responses = new ArrayList<>();
|
||||||
|
for (String dataId : dataIds)
|
||||||
|
{
|
||||||
|
responses.add(pipeline.get(generateKey(dataId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block until all requests have received pipelined responses
|
||||||
|
pipeline.sync();
|
||||||
|
|
||||||
|
for (int i = 0; i < responses.size(); i++)
|
||||||
|
{
|
||||||
|
String key = dataIds.get(i);
|
||||||
|
|
||||||
|
Response<String> response = responses.get(i);
|
||||||
|
String serializedData = response.get();
|
||||||
|
T element = deserialize(serializedData);
|
||||||
|
|
||||||
|
elements.put(key, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getElement(String dataId)
|
||||||
|
{
|
||||||
|
T element = null;
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String key = generateKey(dataId);
|
||||||
|
String serializedData = jedis.get(key);
|
||||||
|
element = deserialize(serializedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addElement(T element, int timeout)
|
||||||
|
{
|
||||||
|
try(Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
String serializedData = serialize(element);
|
||||||
|
String dataId = element.getDataId();
|
||||||
|
String setKey = getElementSetKey();
|
||||||
|
String dataKey = generateKey(element);
|
||||||
|
long expiry = currentTime() + timeout;
|
||||||
|
|
||||||
|
Transaction transaction = jedis.multi();
|
||||||
|
transaction.set(dataKey, serializedData);
|
||||||
|
transaction.zadd(setKey, expiry, dataId.toString());
|
||||||
|
transaction.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addElement(T element)
|
||||||
|
{
|
||||||
|
addElement(element, 60 * 60 * 24 * 7 * 4 * 12 * 10); // Set the timeout to 10 years
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeElement(T element)
|
||||||
|
{
|
||||||
|
removeElement(element.getDataId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeElement(String dataId)
|
||||||
|
{
|
||||||
|
try(Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
String setKey = getElementSetKey();
|
||||||
|
String dataKey = generateKey(dataId);
|
||||||
|
|
||||||
|
Transaction transaction = jedis.multi();
|
||||||
|
transaction.del(dataKey);
|
||||||
|
transaction.zrem(setKey, dataId);
|
||||||
|
transaction.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean elementExists(String dataId)
|
||||||
|
{
|
||||||
|
return getElement(dataId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int clean()
|
||||||
|
{
|
||||||
|
try(Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
for (String dataId : getDeadElements())
|
||||||
|
{
|
||||||
|
String dataKey = generateKey(dataId);
|
||||||
|
|
||||||
|
Transaction transaction = jedis.multi();
|
||||||
|
transaction.del(dataKey);
|
||||||
|
transaction.zrem(getElementSetKey(), dataId);
|
||||||
|
transaction.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<String> getActiveElements()
|
||||||
|
{
|
||||||
|
Set<String> dataIds = new HashSet<String>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String min = "(" + currentTime();
|
||||||
|
String max = "+inf";
|
||||||
|
dataIds = jedis.zrangeByScore(getElementSetKey(), min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<String> getDeadElements()
|
||||||
|
{
|
||||||
|
Set<String> dataIds = new HashSet<String>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String min = "-inf";
|
||||||
|
String max = currentTime() + "";
|
||||||
|
dataIds = jedis.zrangeByScore(getElementSetKey(), min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T deserialize(String serializedData)
|
||||||
|
{
|
||||||
|
return Utility.deserialize(serializedData, _elementType);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String serialize(T element)
|
||||||
|
{
|
||||||
|
return Utility.serialize(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Long currentTime()
|
||||||
|
{
|
||||||
|
return Utility.currentTimeSeconds();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package mineplex.serverdata.redis;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.Utility;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
import mineplex.serverdata.servers.ServerManager;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository for managing Redis Connections
|
||||||
|
* @author Shaun Bennett
|
||||||
|
*/
|
||||||
|
public class RedisRepository
|
||||||
|
{
|
||||||
|
protected static final char KEY_DELIMITER = '.';
|
||||||
|
|
||||||
|
private JedisPool _writePool;
|
||||||
|
private JedisPool _readPool;
|
||||||
|
private Region _region;
|
||||||
|
|
||||||
|
public RedisRepository(ConnectionData writeConn, ConnectionData readConn, Region region)
|
||||||
|
{
|
||||||
|
_writePool = Utility.generatePool(writeConn);
|
||||||
|
_readPool = (writeConn == readConn) ? _writePool : Utility.generatePool(readConn);
|
||||||
|
_region = region;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedisRepository(Region region)
|
||||||
|
{
|
||||||
|
this(ServerManager.getMasterConnection(), ServerManager.getSlaveConnection(), region);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Jedis Resource from the pool. This Jedis instance needs to be closed when you are done with using it.
|
||||||
|
* Call jedis.close() or use try with resources when using getResource()
|
||||||
|
*
|
||||||
|
* @param writeable If we need to be able to write to redis. Trying to write to a non writeable jedis instance will
|
||||||
|
* throw an error.
|
||||||
|
* @return {@link Jedis} instance from pool
|
||||||
|
*/
|
||||||
|
protected Jedis getResource(boolean writeable)
|
||||||
|
{
|
||||||
|
return (writeable ? _writePool : _readPool).getResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the server region that this redis repository is for. The region will affect the keys for redis
|
||||||
|
* @return server region
|
||||||
|
*/
|
||||||
|
public Region getRegion()
|
||||||
|
{
|
||||||
|
return _region;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getKey(String dataKey)
|
||||||
|
{
|
||||||
|
return concatenate("minecraft", "data", _region.name(), dataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param elements - the elements to concatenate together
|
||||||
|
* @return the concatenated form of all {@code elements}
|
||||||
|
* separated by the delimiter {@value KEY_DELIMITER}.
|
||||||
|
*/
|
||||||
|
protected String concatenate(String... elements)
|
||||||
|
{
|
||||||
|
return Utility.concatenate(KEY_DELIMITER, elements);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,394 @@
|
|||||||
|
package mineplex.serverdata.redis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.Utility;
|
||||||
|
import mineplex.serverdata.data.DedicatedServer;
|
||||||
|
import mineplex.serverdata.data.MinecraftServer;
|
||||||
|
import mineplex.serverdata.data.ServerGroup;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
import mineplex.serverdata.servers.ServerRepository;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
import redis.clients.jedis.Pipeline;
|
||||||
|
import redis.clients.jedis.Response;
|
||||||
|
import redis.clients.jedis.Transaction;
|
||||||
|
import redis.clients.jedis.Tuple;
|
||||||
|
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RedisServerRepository offers a Redis-based implementation of {@link ServerRepository}
|
||||||
|
* using a mixture of hash and JSON encoded storage.
|
||||||
|
* @author Ty
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class RedisServerRepository extends RedisRepository implements ServerRepository
|
||||||
|
{
|
||||||
|
public RedisServerRepository(ConnectionData writeConn, ConnectionData readConn, Region region)
|
||||||
|
{
|
||||||
|
super(writeConn, readConn, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MinecraftServer> getServerStatuses()
|
||||||
|
{
|
||||||
|
return getServerStatusesByPrefix("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MinecraftServer> getServerStatusesByPrefix(String prefix)
|
||||||
|
{
|
||||||
|
Collection<MinecraftServer> servers = new HashSet<MinecraftServer>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
List<Response<String>> responses = new ArrayList<Response<String>>();
|
||||||
|
for (String serverName : getActiveNames(setKey))
|
||||||
|
{
|
||||||
|
if (prefix.isEmpty() || serverName.startsWith(prefix))
|
||||||
|
{
|
||||||
|
String dataKey = concatenate(setKey, serverName);
|
||||||
|
responses.add(pipeline.get(dataKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.sync();
|
||||||
|
|
||||||
|
for (Response<String> response : responses)
|
||||||
|
{
|
||||||
|
String serializedData = response.get();
|
||||||
|
MinecraftServer server = Utility.deserialize(serializedData, MinecraftServer.class);
|
||||||
|
|
||||||
|
if (server != null)
|
||||||
|
{
|
||||||
|
servers.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MinecraftServer> getServersByGroup(String serverGroup)
|
||||||
|
{
|
||||||
|
Collection<MinecraftServer> servers = new HashSet<MinecraftServer>();
|
||||||
|
|
||||||
|
for (MinecraftServer server : getServerStatuses())
|
||||||
|
{
|
||||||
|
if (server.getGroup().equalsIgnoreCase(serverGroup))
|
||||||
|
{
|
||||||
|
servers.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MinecraftServer getServerStatus(String serverName)
|
||||||
|
{
|
||||||
|
MinecraftServer server = null;
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||||
|
String dataKey = concatenate(setKey, serverName);
|
||||||
|
String serializedData = jedis.get(dataKey);
|
||||||
|
server = Utility.deserialize(serializedData, MinecraftServer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updataServerStatus(MinecraftServer serverData, int timeout)
|
||||||
|
{
|
||||||
|
try(Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
String serializedData = Utility.serialize(serverData);
|
||||||
|
String serverName = serverData.getName();
|
||||||
|
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||||
|
String dataKey = concatenate(setKey, serverName);
|
||||||
|
long expiry = Utility.currentTimeSeconds() + timeout;
|
||||||
|
|
||||||
|
Transaction transaction = jedis.multi();
|
||||||
|
transaction.set(dataKey, serializedData);
|
||||||
|
transaction.zadd(setKey, expiry, serverName);
|
||||||
|
transaction.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeServerStatus(MinecraftServer serverData)
|
||||||
|
{
|
||||||
|
try(Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
String serverName = serverData.getName();
|
||||||
|
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||||
|
String dataKey = concatenate(setKey, serverName);
|
||||||
|
|
||||||
|
Transaction transaction = jedis.multi();
|
||||||
|
transaction.del(dataKey);
|
||||||
|
transaction.zrem(setKey, serverName);
|
||||||
|
transaction.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean serverExists(String serverName)
|
||||||
|
{
|
||||||
|
return getServerStatus(serverName) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<DedicatedServer> getDedicatedServers()
|
||||||
|
{
|
||||||
|
Collection<DedicatedServer> servers = new HashSet<DedicatedServer>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String key = concatenate("serverstatus", "dedicated");
|
||||||
|
Set<String> serverNames = jedis.smembers(key);
|
||||||
|
HashMap<String, Response<Map<String, String>>> serverDatas = new HashMap<String, Response<Map<String, String>>>();
|
||||||
|
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
|
||||||
|
for (String serverName : serverNames)
|
||||||
|
{
|
||||||
|
String dataKey = concatenate(key, serverName);
|
||||||
|
serverDatas.put(serverName, pipeline.hgetAll(dataKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.sync();
|
||||||
|
|
||||||
|
for (Entry<String, Response<Map<String, String>>> responseEntry : serverDatas.entrySet())
|
||||||
|
{
|
||||||
|
Map<String, String> data = responseEntry.getValue().get();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DedicatedServer server = new DedicatedServer(data);
|
||||||
|
|
||||||
|
if (server.getRegion() == getRegion())
|
||||||
|
servers.add(server);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.out.println(responseEntry.getKey() + " Errored");
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ServerGroup> getServerGroups(Collection<MinecraftServer> serverStatuses)
|
||||||
|
{
|
||||||
|
Collection<ServerGroup> servers = new HashSet<ServerGroup>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String key = "servergroups";
|
||||||
|
Set<String> names = jedis.smembers(key);
|
||||||
|
Set<Response<Map<String, String>>> serverDatas = new HashSet<Response<Map<String, String>>>();
|
||||||
|
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
|
||||||
|
for (String serverName : names)
|
||||||
|
{
|
||||||
|
String dataKey = concatenate(key, serverName);
|
||||||
|
serverDatas.add(pipeline.hgetAll(dataKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.sync();
|
||||||
|
|
||||||
|
for (Response<Map<String, String>> response : serverDatas)
|
||||||
|
{
|
||||||
|
Map<String, String> data = response.get();
|
||||||
|
|
||||||
|
if (data.entrySet().size() == 0)
|
||||||
|
{
|
||||||
|
// please no
|
||||||
|
// System.out.println("Encountered empty map! Skipping...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ServerGroup serverGroup = new ServerGroup(data, serverStatuses);
|
||||||
|
|
||||||
|
if (serverGroup.getRegion() == Region.ALL || serverGroup.getRegion() == getRegion())
|
||||||
|
servers.add(serverGroup);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.out.println("Error parsing ServerGroup : " + data.get("name"));
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key - the key where the sorted set of server sessions is stored
|
||||||
|
* @return the {@link Set} of active server names stored at {@code key} for non-expired
|
||||||
|
* servers.
|
||||||
|
*/
|
||||||
|
protected Set<String> getActiveNames(String key)
|
||||||
|
{
|
||||||
|
Set<String> names = new HashSet<String>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String min = "(" + Utility.currentTimeSeconds();
|
||||||
|
String max = "+inf";
|
||||||
|
names = jedis.zrangeByScore(key, min, max);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key - the key where the sorted set of server sessions is stored
|
||||||
|
* @return the {@link Set} of dead (expired) server names stored at {@code key}.
|
||||||
|
*/
|
||||||
|
protected Set<String> getDeadNames(String key)
|
||||||
|
{
|
||||||
|
Set<String> names = new HashSet<String>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String min = "-inf";
|
||||||
|
String max = Utility.currentTimeSeconds() + "";
|
||||||
|
names = jedis.zrangeByScore(key, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MinecraftServer> getDeadServers()
|
||||||
|
{
|
||||||
|
Set<MinecraftServer> servers = new HashSet<MinecraftServer>();
|
||||||
|
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
Pipeline pipeline = jedis.pipelined();
|
||||||
|
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||||
|
String min = "-inf";
|
||||||
|
String max = Utility.currentTimeSeconds() + "";
|
||||||
|
|
||||||
|
List<Response<String>> responses = new ArrayList<Response<String>>();
|
||||||
|
for (Tuple serverName : jedis.zrangeByScoreWithScores(setKey, min, max))
|
||||||
|
{
|
||||||
|
String dataKey = concatenate(setKey, serverName.getElement());
|
||||||
|
responses.add(pipeline.get(dataKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.sync();
|
||||||
|
|
||||||
|
for (Response<String> response : responses)
|
||||||
|
{
|
||||||
|
String serializedData = response.get();
|
||||||
|
MinecraftServer server = Utility.deserialize(serializedData, MinecraftServer.class);
|
||||||
|
|
||||||
|
if (server != null)
|
||||||
|
servers.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateServerGroup(ServerGroup serverGroup)
|
||||||
|
{
|
||||||
|
try(Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
HashMap<String, String> serializedData = serverGroup.getDataMap();
|
||||||
|
System.out.println(serializedData);
|
||||||
|
String serverGroupName = serverGroup.getName();
|
||||||
|
String key = "servergroups";
|
||||||
|
String dataKey = concatenate(key, serverGroupName);
|
||||||
|
|
||||||
|
Transaction transaction = jedis.multi();
|
||||||
|
transaction.hmset(dataKey, serializedData);
|
||||||
|
transaction.sadd(key, serverGroupName);
|
||||||
|
transaction.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeServerGroup(ServerGroup serverGroup)
|
||||||
|
{
|
||||||
|
try(Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
String serverName = serverGroup.getName();
|
||||||
|
String setKey = "servergroups";
|
||||||
|
String dataKey = concatenate(setKey, serverName);
|
||||||
|
|
||||||
|
Transaction transaction = jedis.multi();
|
||||||
|
transaction.del(dataKey);
|
||||||
|
transaction.srem(setKey, serverName);
|
||||||
|
transaction.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerGroup getServerGroup(String serverGroup)
|
||||||
|
{
|
||||||
|
ServerGroup server = null;
|
||||||
|
try(Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
String key = concatenate("servergroups", serverGroup);
|
||||||
|
Map<String, String> data = jedis.hgetAll(key);
|
||||||
|
|
||||||
|
server = new ServerGroup(data, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <region> = "US" or "EU"
|
||||||
|
* serverstatus.minecraft.<region>.<name> stores the JSON encoded information of an active MinecraftServer instance.
|
||||||
|
* serverstatus.minecraft.<region> stores a sorted set with the set of name's for MinecraftServers
|
||||||
|
* with a value of their expiry date (in ms)
|
||||||
|
*
|
||||||
|
* -----------------------
|
||||||
|
*
|
||||||
|
* serverstatus.dedicated.<name> stores the hash containing information of an active dedicated server instance
|
||||||
|
* serverstatus.dedicated stores the set of active dedicated server names.
|
||||||
|
* serverstatus.dedicated uses a hash with the following keys:
|
||||||
|
* name, publicAddress, privateAddress, region, cpu, ram
|
||||||
|
*
|
||||||
|
* Example commands for adding/creating a new dedicated server:
|
||||||
|
* 1. HMSET serverstatus.dedicated.<name> name <?> publicAddress <?> privateAddress <?> region <?> cpu <?> ram <?>
|
||||||
|
* 2. SADD serverstatus.dedicated <name>
|
||||||
|
*
|
||||||
|
* ------------------------
|
||||||
|
*
|
||||||
|
* servergroups.<name> stores the hash-set containing information for the server group type.
|
||||||
|
* servergroups stores the set of active server group names.
|
||||||
|
* servergroups.<name> stores a hash of the following key name/values
|
||||||
|
* name, prefix, scriptName, ram, cpu, totalServers, joinableServers
|
||||||
|
*
|
||||||
|
* Example commands for adding/creating a new server group:
|
||||||
|
*
|
||||||
|
* 1. HMSET servergroups.<name> name <?> prefix <?> scriptName <?> ram <?> cpu <?> totalServers <?> joinableServers <?>
|
||||||
|
* 2. SADD servergroups <name>
|
||||||
|
*/
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package mineplex.serverdata.redis.atomic;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.redis.RedisRepository;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
|
||||||
|
public class RedisStringRepository extends RedisRepository
|
||||||
|
{
|
||||||
|
private final String _dataKey;
|
||||||
|
private final int _expiration;
|
||||||
|
|
||||||
|
public RedisStringRepository(ConnectionData writeConn, ConnectionData readConn, Region region, String dataKey, int expiryInSeconds)
|
||||||
|
{
|
||||||
|
super(writeConn, readConn, region);
|
||||||
|
this._dataKey = dataKey;
|
||||||
|
this._expiration = expiryInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedisStringRepository(ConnectionData writeConn, ConnectionData readConn, Region region, String dataKey)
|
||||||
|
{
|
||||||
|
this(writeConn, readConn, region, dataKey, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String key, String value)
|
||||||
|
{
|
||||||
|
try (Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
if (_expiration == -1)
|
||||||
|
{
|
||||||
|
jedis.set(generateKey(key), value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jedis.setex(generateKey(key), _expiration, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key)
|
||||||
|
{
|
||||||
|
String element;
|
||||||
|
|
||||||
|
try (Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
element = jedis.get(generateKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void del(String key)
|
||||||
|
{
|
||||||
|
try (Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
jedis.del(generateKey(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getElementSetKey()
|
||||||
|
{
|
||||||
|
return concatenate("data", _dataKey, getRegion().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateKey(String dataId)
|
||||||
|
{
|
||||||
|
return concatenate(getElementSetKey(), dataId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package mineplex.serverdata.redis.counter;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A counter represents an incrementing atomic number that is stored and updated through redis. This allows for
|
||||||
|
* multiple servers to share and update the same counter
|
||||||
|
*
|
||||||
|
* @author Shaun Bennett
|
||||||
|
*/
|
||||||
|
public class Counter
|
||||||
|
{
|
||||||
|
// Cached count of the counter
|
||||||
|
private final AtomicLong _count = new AtomicLong(0);
|
||||||
|
// The System.currentTimeMillis() when cached count was last updated
|
||||||
|
private volatile long _lastUpdated;
|
||||||
|
// The unique key to reference this counter
|
||||||
|
private final String _dataKey;
|
||||||
|
|
||||||
|
// Redis repository to store the count
|
||||||
|
private final CounterRedisRepository _redisRepository;
|
||||||
|
|
||||||
|
public Counter(ConnectionData writeConnection, ConnectionData readConnection, Region region, String dataKey)
|
||||||
|
{
|
||||||
|
_dataKey = dataKey;
|
||||||
|
_redisRepository = new CounterRedisRepository(writeConnection, readConnection, region, dataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Counter(String dataKey)
|
||||||
|
{
|
||||||
|
_dataKey = dataKey;
|
||||||
|
_redisRepository = new CounterRedisRepository(dataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a value to the counter and return the new counter value. This method is thread-safe and interacts
|
||||||
|
* directly with the atomic value stored in redis. The value returned from redis is then returned
|
||||||
|
*
|
||||||
|
* addAndGet will also update the cached counter value so we don't need to make extra trips to redis
|
||||||
|
*
|
||||||
|
* @param amount the amount to add to the counter
|
||||||
|
* @return the updated value of the counter from redis repository
|
||||||
|
*/
|
||||||
|
public long addAndGet(long amount)
|
||||||
|
{
|
||||||
|
long newCount = _redisRepository.incrementCount(amount);
|
||||||
|
updateCount(newCount);
|
||||||
|
return newCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest cached count from the counter. This value will not be changed until {@link #addAndGet(long)}
|
||||||
|
* or {@link #updateCount} is called.
|
||||||
|
*
|
||||||
|
* @return The counter count
|
||||||
|
*/
|
||||||
|
public long getCount()
|
||||||
|
{
|
||||||
|
return _count.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the cached count to reflect the count in redis. This should be called async
|
||||||
|
*/
|
||||||
|
public void updateCount()
|
||||||
|
{
|
||||||
|
updateCount(_redisRepository.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the counter back to 0. Immediately updates the redis repository.
|
||||||
|
*
|
||||||
|
* @return The value of the counter before it was reset
|
||||||
|
*/
|
||||||
|
public long reset()
|
||||||
|
{
|
||||||
|
updateCount(0);
|
||||||
|
return _redisRepository.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data key for this counter. The data key is used as the redis repository key
|
||||||
|
* @return The data key for this counter
|
||||||
|
*/
|
||||||
|
public String getDataKey()
|
||||||
|
{
|
||||||
|
return _dataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the cached count with a new value
|
||||||
|
* @param newCount updated count
|
||||||
|
*/
|
||||||
|
protected void updateCount(long newCount)
|
||||||
|
{
|
||||||
|
_count.set(newCount);
|
||||||
|
_lastUpdated = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
package mineplex.serverdata.redis.counter;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.redis.RedisRepository;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis repository to store the count for {@link Counter}
|
||||||
|
* @author Shaun Bennett
|
||||||
|
*/
|
||||||
|
public class CounterRedisRepository extends RedisRepository
|
||||||
|
{
|
||||||
|
private String _dataKey;
|
||||||
|
|
||||||
|
public CounterRedisRepository(ConnectionData writeConnection, ConnectionData readConnection, Region region, String dataKey)
|
||||||
|
{
|
||||||
|
super(writeConnection, readConnection, region);
|
||||||
|
|
||||||
|
_dataKey = dataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CounterRedisRepository(String dataKey)
|
||||||
|
{
|
||||||
|
super(Region.ALL);
|
||||||
|
|
||||||
|
_dataKey = dataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current count inside the fountain
|
||||||
|
* @return The current count for the fountain
|
||||||
|
*/
|
||||||
|
public long getCount()
|
||||||
|
{
|
||||||
|
long count = 0;
|
||||||
|
|
||||||
|
try (Jedis jedis = getResource(false))
|
||||||
|
{
|
||||||
|
count = Long.parseLong(jedis.get(getKey()));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex)
|
||||||
|
{
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the current count by {@code increment} and then return the latest
|
||||||
|
* count from redis. This is handled in an atomic process
|
||||||
|
* @param increment Amount to increment counter by
|
||||||
|
* @return The updated count from redis
|
||||||
|
*/
|
||||||
|
public long incrementCount(long increment)
|
||||||
|
{
|
||||||
|
long count = 0;
|
||||||
|
|
||||||
|
try (Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
count = jedis.incrBy(getKey(), increment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the counter back to 0
|
||||||
|
* @return the value of the counter before it was reset
|
||||||
|
*/
|
||||||
|
public long reset()
|
||||||
|
{
|
||||||
|
long count = -1;
|
||||||
|
|
||||||
|
try (Jedis jedis = getResource(true))
|
||||||
|
{
|
||||||
|
count = Long.parseLong(jedis.getSet(getKey(), "0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the key for this counter
|
||||||
|
* @return The key is used to store the value in redis
|
||||||
|
*/
|
||||||
|
private String getKey()
|
||||||
|
{
|
||||||
|
return getKey(_dataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// private void setNX()
|
||||||
|
// {
|
||||||
|
// try (Jedis jedis = getResource(true))
|
||||||
|
// {
|
||||||
|
// jedis.setnx(getKey(), "0");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package mineplex.serverdata.redis.counter;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A redis counter that is aiming to reach a goal
|
||||||
|
*
|
||||||
|
* @author Shaun Bennett
|
||||||
|
*/
|
||||||
|
public class GoalCounter extends Counter
|
||||||
|
{
|
||||||
|
private int _lastMilestone;
|
||||||
|
// Has the goal been completed?
|
||||||
|
private boolean _completed;
|
||||||
|
// The goal we are trying to reach
|
||||||
|
private long _goal;
|
||||||
|
|
||||||
|
private List<GoalCounterListener> _listeners;
|
||||||
|
|
||||||
|
public GoalCounter(ConnectionData writeConnection, ConnectionData readConnection, Region region, String dataKey, long goal)
|
||||||
|
{
|
||||||
|
super(writeConnection, readConnection, region, dataKey);
|
||||||
|
|
||||||
|
init(goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GoalCounter(String dataKey, long goal)
|
||||||
|
{
|
||||||
|
super(dataKey);
|
||||||
|
|
||||||
|
init(goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(long goal)
|
||||||
|
{
|
||||||
|
_completed = false;
|
||||||
|
_goal = goal;
|
||||||
|
_listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
updateCount();
|
||||||
|
|
||||||
|
_lastMilestone = (int) getFillPercent();
|
||||||
|
|
||||||
|
updateMilestone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the progress towards the goal as a percent ranging from 0 to 1.
|
||||||
|
* @return the percent progress towards goal
|
||||||
|
*/
|
||||||
|
public double getFillPercent()
|
||||||
|
{
|
||||||
|
return (((double) getCount()) / _goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the goal been completed?
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isCompleted()
|
||||||
|
{
|
||||||
|
return _completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the goal for this GoalCounter
|
||||||
|
* @return the goal of this counter
|
||||||
|
*/
|
||||||
|
public long getGoal()
|
||||||
|
{
|
||||||
|
return _goal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener to this GoalCounter
|
||||||
|
* @param listener the listener to be added
|
||||||
|
*/
|
||||||
|
public void addListener(GoalCounterListener listener)
|
||||||
|
{
|
||||||
|
_listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all the listeners
|
||||||
|
*/
|
||||||
|
public void clearListeners()
|
||||||
|
{
|
||||||
|
_listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update {@link #_completed} and notify listeners if it has completed
|
||||||
|
*/
|
||||||
|
private void updateMilestone()
|
||||||
|
{
|
||||||
|
int currentMilestone = (int) getFillPercent();
|
||||||
|
|
||||||
|
if (currentMilestone != _lastMilestone && currentMilestone > _lastMilestone)
|
||||||
|
{
|
||||||
|
_listeners.forEach(listener -> listener.onMilestone(this, currentMilestone));
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastMilestone = currentMilestone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateCount(long newCount)
|
||||||
|
{
|
||||||
|
super.updateCount(newCount);
|
||||||
|
|
||||||
|
updateMilestone();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package mineplex.serverdata.redis.counter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shaun Bennett
|
||||||
|
*/
|
||||||
|
public interface GoalCounterListener
|
||||||
|
{
|
||||||
|
public void onMilestone(GoalCounter counter, int milestone);
|
||||||
|
}
|
@ -0,0 +1,356 @@
|
|||||||
|
package mineplex.serverdata.redis.messaging;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Utility;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData;
|
||||||
|
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
import redis.clients.jedis.JedisPubSub;
|
||||||
|
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||||
|
import redis.clients.jedis.exceptions.JedisDataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A subscription to a Redis pub/sub system through a Jedis client. Includes a publishing mechanism
|
||||||
|
* as well.
|
||||||
|
* <p>
|
||||||
|
* Subscribes to Jedis and offers a variety of methods to edit the channels that this listens to
|
||||||
|
* over time. Does not support pattern-matching, even though Jedis can. Takes a single subscriber to
|
||||||
|
* inform of incoming messages (on all channels this is subscribed to).
|
||||||
|
* <p>
|
||||||
|
* For the sake of internal efficiency, this does not protect the sanity or unchangeability of
|
||||||
|
* arguments passed into its methods. Clients should not generally interact with this directly.
|
||||||
|
* <p>
|
||||||
|
* The Jedis pub/sub interface is a little confusing, especially when it comes to multithreading. At
|
||||||
|
* any given time, if this class is subscribed to any channels at all, it will have a single thread
|
||||||
|
* that is listening for incoming messages from redis with a blocking method. After that listening
|
||||||
|
* thread is created, we can add and remove subscriptions as desired, but the initial subscription
|
||||||
|
* and actual listening must be done on its own thread. When all channels are unsubscribed from, the
|
||||||
|
* listening thread returns. Note that this is stated with about 70% certainty, as the internals of
|
||||||
|
* the pub/sub mechanism are not entirely clear to me.
|
||||||
|
* <p>
|
||||||
|
* This class maintains a constant connection to its redis server by subscribing to a base channel.
|
||||||
|
* This makes it much easier to protect its operation from potentially insane client commands.
|
||||||
|
* <p>
|
||||||
|
* If the connection to the given redis instance fails or is interrupted, will keep attempting to
|
||||||
|
* reconnect periodically until destroyed. Publishers and subscribers are not informed of failure in
|
||||||
|
* any way.
|
||||||
|
* <p>
|
||||||
|
* When {@link #unsubscribe()} or {@link #destroy()} is called, this class ceases operation.
|
||||||
|
*/
|
||||||
|
public class PubSubJedisClient extends JedisPubSub implements PubSubLibraryClient
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final long RECONNECT_PERIOD_MILLIS = 800;
|
||||||
|
private static final String BASE_CHANNEL = "pG8n5jp#";
|
||||||
|
|
||||||
|
private static final String BOLD = "\u001B[1m";
|
||||||
|
private static final String RESET = "\u001B[0m";
|
||||||
|
|
||||||
|
private final String _id;
|
||||||
|
private JedisPool _writePool;
|
||||||
|
private final ConnectionData _readConn;
|
||||||
|
private JedisPool _readPool;
|
||||||
|
private final ExecutorService _threadPool = Executors.newCachedThreadPool();
|
||||||
|
private volatile Subscriber _sub;
|
||||||
|
|
||||||
|
private final Set<String> _channels = Collections
|
||||||
|
.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||||
|
private final Map<String, SettableFuture<Boolean>> _pendingFutures = new ConcurrentHashMap<String, SettableFuture<Boolean>>();
|
||||||
|
|
||||||
|
private volatile boolean _subscribed; // Is there a base subscription yet?
|
||||||
|
private volatile boolean _destroyed; // has this been deliberately destroyed?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param writeTo The connection info for the redis instance this client should publish to.
|
||||||
|
* @param readFrom The connection info for the redis instance this client to subscribe to.
|
||||||
|
*/
|
||||||
|
public PubSubJedisClient(ConnectionData writeTo, ConnectionData readFrom)
|
||||||
|
{
|
||||||
|
if (writeTo == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("redis connection info cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
_id = writeTo.getName();
|
||||||
|
_writePool = Utility.generatePool(writeTo);
|
||||||
|
|
||||||
|
_readConn = readFrom;
|
||||||
|
_readPool = Utility.generatePool(readFrom);
|
||||||
|
|
||||||
|
createSubscription(BASE_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final synchronized void setSubscriber(Subscriber sub)
|
||||||
|
{
|
||||||
|
_sub = sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ListenableFuture<Boolean> addChannel(String channel)
|
||||||
|
{
|
||||||
|
SettableFuture<Boolean> ret = _pendingFutures.get(channel);
|
||||||
|
if (ret == null)
|
||||||
|
{
|
||||||
|
ret = SettableFuture.create();
|
||||||
|
_pendingFutures.put(channel, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_channels.add(channel);
|
||||||
|
|
||||||
|
if (_subscribed)
|
||||||
|
{ // Already has a subscription thread and can just add a new channel to it.
|
||||||
|
subscribe(channel);
|
||||||
|
}
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
log("Encountered issue subscribing to a channel.");
|
||||||
|
ex.printStackTrace();
|
||||||
|
ret.setException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void removeChannel(String channel)
|
||||||
|
{
|
||||||
|
if (BASE_CHANNEL.equals(channel))
|
||||||
|
{ // Protects the base subscription
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_channels.remove(channel);
|
||||||
|
|
||||||
|
if (_subscribed)
|
||||||
|
{
|
||||||
|
unsubscribe(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void unsubscribe()
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void destroy()
|
||||||
|
{
|
||||||
|
_destroyed = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
super.unsubscribe();
|
||||||
|
} catch (NullPointerException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SettableFuture<Boolean> fut : _pendingFutures.values())
|
||||||
|
{
|
||||||
|
fut.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onMessage(String channel, String message)
|
||||||
|
{
|
||||||
|
if (_sub == null || BASE_CHANNEL.equals(channel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_sub.onMessage(channel, message);
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ListenableFuture<Boolean> publish(final String channel, final String message)
|
||||||
|
{
|
||||||
|
final SettableFuture<Boolean> ret = SettableFuture.create();
|
||||||
|
_threadPool.execute(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
Jedis bJedis = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bJedis = _writePool.getResource();
|
||||||
|
bJedis.publish(channel, message);
|
||||||
|
_writePool.returnResource((Jedis) bJedis);
|
||||||
|
ret.set(true);
|
||||||
|
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
log("Encountered issue while publishing a message.");
|
||||||
|
e.printStackTrace();
|
||||||
|
if (bJedis != null)
|
||||||
|
{
|
||||||
|
_writePool.returnBrokenResource((Jedis) bJedis);
|
||||||
|
}
|
||||||
|
ret.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirms successful subscriptions/unsubscriptions.
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(String channel, int subscribedChannels)
|
||||||
|
{
|
||||||
|
|
||||||
|
// informs subscriber that this subscription completed successfully
|
||||||
|
SettableFuture<Boolean> fut = _pendingFutures.remove(channel);
|
||||||
|
if (fut != null)
|
||||||
|
{
|
||||||
|
fut.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_subscribed)
|
||||||
|
{
|
||||||
|
for (String subscribeTo : _channels)
|
||||||
|
{
|
||||||
|
subscribe(subscribeTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_subscribed = true;
|
||||||
|
|
||||||
|
log("Subscribed to channel: " + channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnsubscribe(String channel, int subscribedChannels)
|
||||||
|
{
|
||||||
|
log("Unsubscribed from channel: " + channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the initial listening thread which blocks as it polls redis for new messages.
|
||||||
|
* Subsequent subscriptions can simply be added using {@link #subscribe(String...)} after the
|
||||||
|
* subscription thread has been created.
|
||||||
|
*
|
||||||
|
* @param firstChannel The first channel to initially subscribe to. If you do not have a first
|
||||||
|
* channel, there is no reason to create a subscriber thread yet.
|
||||||
|
*/
|
||||||
|
private void createSubscription(final String firstChannel)
|
||||||
|
{
|
||||||
|
|
||||||
|
final JedisPubSub pubsub = this;
|
||||||
|
|
||||||
|
new Thread(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
|
||||||
|
while (!_destroyed)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
log("Jedis connection to " + _readConn.getHost() + ":"
|
||||||
|
+ _readConn.getPort()
|
||||||
|
+ " failed or was interrupted, attempting to reconnect");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
Jedis jedisInstance = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// gets a non-thread-safe jedis instance from the thread-safe pool.
|
||||||
|
jedisInstance = _readPool.getResource();
|
||||||
|
|
||||||
|
log("Creating initial jedis subscription to channel " + firstChannel);
|
||||||
|
// this will block as long as there are subscriptions
|
||||||
|
jedisInstance.subscribe(pubsub, firstChannel);
|
||||||
|
|
||||||
|
log("jedisInstance.subscribe() returned, subscription over.");
|
||||||
|
|
||||||
|
// when the subscription ends (subscribe() returns), returns the instance to
|
||||||
|
// the pool
|
||||||
|
_readPool.returnResource(jedisInstance);
|
||||||
|
|
||||||
|
} catch (JedisConnectionException e)
|
||||||
|
{
|
||||||
|
log("Jedis connection encountered an issue.");
|
||||||
|
e.printStackTrace();
|
||||||
|
if (jedisInstance != null)
|
||||||
|
{
|
||||||
|
_readPool.returnBrokenResource((Jedis) jedisInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (JedisDataException e)
|
||||||
|
{
|
||||||
|
log("Jedis connection encountered an issue.");
|
||||||
|
e.printStackTrace();
|
||||||
|
if (jedisInstance != null)
|
||||||
|
{
|
||||||
|
_readPool.returnBrokenResource((Jedis) jedisInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_subscribed = false;
|
||||||
|
|
||||||
|
// sleeps for a short pause, rather than constantly retrying connection
|
||||||
|
if (!_destroyed)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(RECONNECT_PERIOD_MILLIS);
|
||||||
|
} catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
_destroyed = true;
|
||||||
|
log("Reconnection pause thread was interrupted.");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This implementation does not support pattern-matching subscriptions
|
||||||
|
@Override
|
||||||
|
public void onPMessage(String pattern, String channel, String message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPSubscribe(String pattern, int subscribedChannels)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPUnsubscribe(String pattern, int subscribedChannels)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String msg)
|
||||||
|
{
|
||||||
|
System.out.println(BOLD + "[" + getClass().getSimpleName()
|
||||||
|
+ (_id != null && !_id.isEmpty() ? " " + _id : "") + "] " + RESET + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package mineplex.serverdata.redis.messaging;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A multi-channel subscription and publisher to a pub/sub messaging implementation. An interface to
|
||||||
|
* the actual low-level pub/sub library, whatever it may be.
|
||||||
|
*
|
||||||
|
* For the sake of internal efficiency, this makes no guarantees for the sanity or unchangeability
|
||||||
|
* of arguments passed into its methods. Clients should not generally interact with this directly.
|
||||||
|
*/
|
||||||
|
public interface PubSubLibraryClient
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes a message to all subscribers of a given channel.
|
||||||
|
*
|
||||||
|
* @param channel The channel to publish the message on.
|
||||||
|
* @param message The message to send.
|
||||||
|
* @return A future object that will complete after an unknown amount of time with
|
||||||
|
* <code>false</code> if for some locally known reason the message definitely could not
|
||||||
|
* be published, else completes with <code>true</code>.
|
||||||
|
*/
|
||||||
|
ListenableFuture<Boolean> publish(String channel, String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a channel to this subscription.
|
||||||
|
*
|
||||||
|
* @param channel The channel to add. Should not change after being passed in.
|
||||||
|
* @return The asynchronous, future result of this attempt to add the channel. Will have
|
||||||
|
* <code>true</code> when the subscription starts successfully.
|
||||||
|
*/
|
||||||
|
ListenableFuture<Boolean> addChannel(String channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a channel from this subscription.
|
||||||
|
*
|
||||||
|
* @param channel The channel to remove. Should not change after being passed in.
|
||||||
|
*/
|
||||||
|
void removeChannel(String channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all channels from this subscription, kills its connections, and relinquishes any
|
||||||
|
* resources it was occupying.
|
||||||
|
* <p>
|
||||||
|
* Depending on the implementation, once a subscription has been destroyed, it may not be
|
||||||
|
* reusable and it may be necessary to construct a new one in order to resume.
|
||||||
|
* <p>
|
||||||
|
* Call this when the subscription is no longer being used. Holding unnecessary connections can
|
||||||
|
* cause serious performance and other issues on both ends.
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the subscriber to inform of messages received by this subscription.
|
||||||
|
*
|
||||||
|
* @param sub The listener for this subscription.
|
||||||
|
*/
|
||||||
|
void setSubscriber(Subscriber sub);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package mineplex.serverdata.redis.messaging;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Messager for standard pub/sub model. Handles multiple publishers and subscribers.
|
||||||
|
* <p>
|
||||||
|
* All messaging is asynchronous and non-blocking, even to local subscribers.
|
||||||
|
* <p>
|
||||||
|
* For more about the pub/sub messaging paradigm, see <a
|
||||||
|
* href="http://en.wikipedia.org/wiki/Publish%E2
|
||||||
|
* %80%93subscribe_pattern">http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern</a>
|
||||||
|
*/
|
||||||
|
public interface PubSubMessager
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes a message to all subscribers of a given channel.
|
||||||
|
* <p>
|
||||||
|
* Publishes to all connected subscribers, including local ones.
|
||||||
|
*
|
||||||
|
* @param channel The channel to publish the message on.
|
||||||
|
* @param message The message to send.
|
||||||
|
* @return A future object that will complete after an unknown amount of time with
|
||||||
|
* <code>false</code> if for some locally known reason the message definitely could not
|
||||||
|
* be published, else completes with <code>true</code> (which does not guarantee it
|
||||||
|
* succeeded 100%).
|
||||||
|
*/
|
||||||
|
ListenableFuture<Boolean> publish(String channel, String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to a messaging channel.
|
||||||
|
* <p>
|
||||||
|
* When incoming messages arrive, the subscriber is called from an arbitrary new thread.
|
||||||
|
*
|
||||||
|
* @param channel The channel to subscribe to.
|
||||||
|
* @param sub The subscriber to inform of incoming messages.
|
||||||
|
* @return The asynchronous, future result of this attempt to subscribe to the channel. Will
|
||||||
|
* have <code>true</code> when the subscription starts successfully.
|
||||||
|
*/
|
||||||
|
ListenableFuture<Boolean> subscribe(String channel, Subscriber sub);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes from a messaging channel.
|
||||||
|
*
|
||||||
|
* @param channel The channel to unsubscribe from.
|
||||||
|
* @param sub The subscriber to stop informing of incoming messages.
|
||||||
|
*/
|
||||||
|
void unsubscribe(String channel, Subscriber sub);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to gracefully shut down this messager. Generally irreversible.
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
package mineplex.serverdata.redis.messaging;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pub/sub messager that simply routes messages to some underlying pub/sub implementation, which
|
||||||
|
* is in turn represented by a multi-channel subscription and a publishing mechanism.
|
||||||
|
* <p>
|
||||||
|
* This class handles:
|
||||||
|
* <ol>
|
||||||
|
* <li>Providing a modular messaging interface that is thread-safe.
|
||||||
|
* <li>Protecting pub/sub implementations from some bad client behavior/data.
|
||||||
|
* <li>Routing messages for multiple subscribers to the same channel(s).
|
||||||
|
* </ol>
|
||||||
|
*/
|
||||||
|
public class PubSubRouter implements PubSubMessager, Subscriber
|
||||||
|
{
|
||||||
|
|
||||||
|
private final PubSubLibraryClient _pubSubClient;
|
||||||
|
private final Map<String, Set<Subscriber>> _subscribers;
|
||||||
|
|
||||||
|
private final ExecutorService _threadPool;
|
||||||
|
|
||||||
|
public PubSubRouter(PubSubLibraryClient client)
|
||||||
|
{
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("pubsub client cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pubSubClient = client;
|
||||||
|
this._pubSubClient.setSubscriber(this);
|
||||||
|
this._subscribers = new ConcurrentHashMap<String, Set<Subscriber>>();
|
||||||
|
|
||||||
|
this._threadPool = Executors.newCachedThreadPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ListenableFuture<Boolean> publish(String channel, String message)
|
||||||
|
{
|
||||||
|
if (channel == null || channel.isEmpty())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("channel cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// messages of 0 length are allowed. Null messages are treated as messages of 0 length.
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
message = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return _pubSubClient.publish(channel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ListenableFuture<Boolean> subscribe(String channel, Subscriber sub)
|
||||||
|
{
|
||||||
|
if (channel == null || channel.isEmpty() || sub == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("params cannot be null and channel cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
ListenableFuture<Boolean> ret = null;
|
||||||
|
|
||||||
|
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||||
|
if (channelSubs == null)
|
||||||
|
{
|
||||||
|
// uses CopyOnWriteArraySet for fast and consistent iteration (forwarding messages to
|
||||||
|
// subscribers) but slow writes (adding/removing subscribers).
|
||||||
|
// See a discussion of the issue here:
|
||||||
|
// http://stackoverflow.com/questions/6720396/different-types-of-thread-safe-sets-in-java
|
||||||
|
channelSubs = new CopyOnWriteArraySet<Subscriber>();
|
||||||
|
_subscribers.put(channel, channelSubs);
|
||||||
|
|
||||||
|
// starts a jedis subscription to the channel if there were no subscribers before
|
||||||
|
ret = _pubSubClient.addChannel(channel);
|
||||||
|
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ret = SettableFuture.create();
|
||||||
|
((SettableFuture<Boolean>) ret).set(true); // already subscribed, calls back immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
channelSubs.add(sub);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void unsubscribe(String channel, Subscriber sub)
|
||||||
|
{
|
||||||
|
if (channel == null || channel.isEmpty() || sub == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("params cannot be null and channel cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||||
|
if (channelSubs == null)
|
||||||
|
{ // no subscribers for the channel to begin with.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelSubs.remove(sub);
|
||||||
|
|
||||||
|
// stops the subscription to this channel if the unsubscribed was the last subscriber
|
||||||
|
if (channelSubs.isEmpty())
|
||||||
|
{
|
||||||
|
_subscribers.remove(channel);
|
||||||
|
_pubSubClient.removeChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onMessage(final String channel, final String message)
|
||||||
|
{
|
||||||
|
if (channel == null || message == null || channel.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||||
|
|
||||||
|
if (channelSubs == null)
|
||||||
|
{ // We should not still be listening
|
||||||
|
_pubSubClient.removeChannel(channel);
|
||||||
|
return;
|
||||||
|
} else if (channelSubs.isEmpty())
|
||||||
|
{
|
||||||
|
_subscribers.remove(channel);
|
||||||
|
_pubSubClient.removeChannel(channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Subscriber sub : channelSubs)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Gives subscribers their own thread from the thread pool in which to react to the
|
||||||
|
// message.
|
||||||
|
// Avoids interruptions and other problems while iterating over the subscriber set.
|
||||||
|
_threadPool.execute(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
sub.onMessage(channel, message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
_pubSubClient.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package mineplex.serverdata.redis.messaging;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A subscriber to a pub/sub channel.
|
||||||
|
*/
|
||||||
|
public interface Subscriber
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a message is sent on a channel that this is subscribed to.
|
||||||
|
* <p>
|
||||||
|
* No guarantees are made about what thread this will be called from.
|
||||||
|
*
|
||||||
|
* @param channel The channel that the message was sent on.
|
||||||
|
* @param message The message that was received.
|
||||||
|
*/
|
||||||
|
void onMessage(String channel, String message);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package mineplex.serverdata.serialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.Streams;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapts values whose runtime type may differ from their declaration type. This
|
||||||
|
* is necessary when a field's type is not the same type that GSON should create
|
||||||
|
* when deserializing that field. For example, consider these types:
|
||||||
|
* <pre> {@code
|
||||||
|
* abstract class Shape {
|
||||||
|
* int x;
|
||||||
|
* int y;
|
||||||
|
* }
|
||||||
|
* class Circle extends Shape {
|
||||||
|
* int radius;
|
||||||
|
* }
|
||||||
|
* class Rectangle extends Shape {
|
||||||
|
* int width;
|
||||||
|
* int height;
|
||||||
|
* }
|
||||||
|
* class Diamond extends Shape {
|
||||||
|
* int width;
|
||||||
|
* int height;
|
||||||
|
* }
|
||||||
|
* class Drawing {
|
||||||
|
* Shape bottomShape;
|
||||||
|
* Shape topShape;
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
* <p>Without additional type information, the serialized JSON is ambiguous. Is
|
||||||
|
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
|
||||||
|
* {
|
||||||
|
* "bottomShape": {
|
||||||
|
* "width": 10,
|
||||||
|
* "height": 5,
|
||||||
|
* "x": 0,
|
||||||
|
* "y": 0
|
||||||
|
* },
|
||||||
|
* "topShape": {
|
||||||
|
* "radius": 2,
|
||||||
|
* "x": 4,
|
||||||
|
* "y": 1
|
||||||
|
* }
|
||||||
|
* }}</pre>
|
||||||
|
* This class addresses this problem by adding type information to the
|
||||||
|
* serialized JSON and honoring that type information when the JSON is
|
||||||
|
* deserialized: <pre> {@code
|
||||||
|
* {
|
||||||
|
* "bottomShape": {
|
||||||
|
* "type": "Diamond",
|
||||||
|
* "width": 10,
|
||||||
|
* "height": 5,
|
||||||
|
* "x": 0,
|
||||||
|
* "y": 0
|
||||||
|
* },
|
||||||
|
* "topShape": {
|
||||||
|
* "type": "Circle",
|
||||||
|
* "radius": 2,
|
||||||
|
* "x": 4,
|
||||||
|
* "y": 1
|
||||||
|
* }
|
||||||
|
* }}</pre>
|
||||||
|
* Both the type field name ({@code "type"}) and the type labels ({@code
|
||||||
|
* "Rectangle"}) are configurable.
|
||||||
|
*
|
||||||
|
* <h3>Registering Types</h3>
|
||||||
|
* Create a {@code RuntimeTypeAdapter} by passing the base type and type field
|
||||||
|
* name to the {@link #of} factory method. If you don't supply an explicit type
|
||||||
|
* field name, {@code "type"} will be used. <pre> {@code
|
||||||
|
* RuntimeTypeAdapter<Shape> shapeAdapter
|
||||||
|
* = RuntimeTypeAdapter.of(Shape.class, "type");
|
||||||
|
* }</pre>
|
||||||
|
* Next register all of your subtypes. Every subtype must be explicitly
|
||||||
|
* registered. This protects your application from injection attacks. If you
|
||||||
|
* don't supply an explicit type label, the type's simple name will be used.
|
||||||
|
* <pre> {@code
|
||||||
|
* shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
|
||||||
|
* shapeAdapter.registerSubtype(Circle.class, "Circle");
|
||||||
|
* shapeAdapter.registerSubtype(Diamond.class, "Diamond");
|
||||||
|
* }</pre>
|
||||||
|
* Finally, register the type adapter in your application's GSON builder:
|
||||||
|
* <pre> {@code
|
||||||
|
* Gson gson = new GsonBuilder()
|
||||||
|
* .registerTypeAdapter(Shape.class, shapeAdapter)
|
||||||
|
* .create();
|
||||||
|
* }</pre>
|
||||||
|
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
|
||||||
|
* RuntimeTypeAdapter<Shape> shapeAdapter = RuntimeTypeAdapterFactory.of(Shape.class)
|
||||||
|
* .registerSubtype(Rectangle.class)
|
||||||
|
* .registerSubtype(Circle.class)
|
||||||
|
* .registerSubtype(Diamond.class);
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||||
|
private final Class<?> baseType;
|
||||||
|
private final String typeFieldName;
|
||||||
|
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
|
||||||
|
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
|
||||||
|
|
||||||
|
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
|
||||||
|
if (typeFieldName == null || baseType == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
this.baseType = baseType;
|
||||||
|
this.typeFieldName = typeFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new runtime type adapter using for {@code baseType} using {@code
|
||||||
|
* typeFieldName} as the type field name. Type field names are case sensitive.
|
||||||
|
*/
|
||||||
|
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
|
||||||
|
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
|
||||||
|
* the type field name.
|
||||||
|
*/
|
||||||
|
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
|
||||||
|
return new RuntimeTypeAdapterFactory<T>(baseType, "type");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers {@code type} identified by {@code label}. Labels are case
|
||||||
|
* sensitive.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if either {@code type} or {@code label}
|
||||||
|
* have already been registered on this type adapter.
|
||||||
|
*/
|
||||||
|
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
|
||||||
|
if (type == null || label == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
|
||||||
|
throw new IllegalArgumentException("types and labels must be unique");
|
||||||
|
}
|
||||||
|
labelToSubtype.put(label, type);
|
||||||
|
subtypeToLabel.put(type, label);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers {@code type} identified by its {@link Class#getSimpleName simple
|
||||||
|
* name}. Labels are case sensitive.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if either {@code type} or its simple name
|
||||||
|
* have already been registered on this type adapter.
|
||||||
|
*/
|
||||||
|
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
|
||||||
|
return registerSubtype(type, type.getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
|
||||||
|
if (type.getRawType() != baseType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, TypeAdapter<?>> labelToDelegate
|
||||||
|
= new LinkedHashMap<String, TypeAdapter<?>>();
|
||||||
|
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
|
||||||
|
= new LinkedHashMap<Class<?>, TypeAdapter<?>>();
|
||||||
|
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
|
||||||
|
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
|
||||||
|
labelToDelegate.put(entry.getKey(), delegate);
|
||||||
|
subtypeToDelegate.put(entry.getValue(), delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TypeAdapter<R>() {
|
||||||
|
@Override public R read(JsonReader in) throws IOException {
|
||||||
|
JsonElement jsonElement = Streams.parse(in);
|
||||||
|
JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
|
||||||
|
if (labelJsonElement == null) {
|
||||||
|
throw new JsonParseException("cannot deserialize " + baseType
|
||||||
|
+ " because it does not define a field named " + typeFieldName);
|
||||||
|
}
|
||||||
|
String label = labelJsonElement.getAsString();
|
||||||
|
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||||
|
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
|
||||||
|
if (delegate == null) {
|
||||||
|
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
|
||||||
|
+ label + "; did you forget to register a subtype?");
|
||||||
|
}
|
||||||
|
return delegate.fromJsonTree(jsonElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void write(JsonWriter out, R value) throws IOException {
|
||||||
|
if(value!=null) {
|
||||||
|
Class<?> srcType = value.getClass();
|
||||||
|
String label = subtypeToLabel.get(srcType);
|
||||||
|
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||||
|
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
|
||||||
|
if (delegate == null) {
|
||||||
|
throw new JsonParseException("cannot serialize " + srcType.getName()
|
||||||
|
+ "; did you forget to register a subtype?");
|
||||||
|
}
|
||||||
|
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
|
||||||
|
if (jsonObject.has(typeFieldName)) {
|
||||||
|
throw new JsonParseException("cannot serialize " + srcType.getName()
|
||||||
|
+ " because it already defines a field named " + typeFieldName);
|
||||||
|
}
|
||||||
|
JsonObject clone = new JsonObject();
|
||||||
|
clone.add(typeFieldName, new JsonPrimitive(label));
|
||||||
|
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
|
||||||
|
clone.add(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
Streams.write(clone, out);
|
||||||
|
}else{
|
||||||
|
out.nullValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package mineplex.serverdata.servers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectionData stores information relevant for initiating a connection to a repository.
|
||||||
|
* @author MrTwiggy
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ConnectionData
|
||||||
|
{
|
||||||
|
|
||||||
|
public enum ConnectionType
|
||||||
|
{
|
||||||
|
MASTER,
|
||||||
|
SLAVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionType _type; // The type of connection available
|
||||||
|
public ConnectionType getType() { return _type; }
|
||||||
|
|
||||||
|
private String _name; // The name associated with this connection
|
||||||
|
public String getName() { return _name; }
|
||||||
|
|
||||||
|
private String _host; // The host URL to connect to repository
|
||||||
|
public String getHost() { return _host; }
|
||||||
|
|
||||||
|
private int _port; // The port to connect to repository
|
||||||
|
public int getPort() { return _port; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param host - the host URL defining the repository
|
||||||
|
* @param port - the port used for connection to repository
|
||||||
|
* @param type - the type of connection referenced by this ConnectionData
|
||||||
|
* @param name - the name associated with ConnectionData
|
||||||
|
*/
|
||||||
|
public ConnectionData(String host, int port, ConnectionType type, String name)
|
||||||
|
{
|
||||||
|
_host = host;
|
||||||
|
_port = port;
|
||||||
|
_type = type;
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name
|
||||||
|
* @return true, if {@code name} is null or it matches (case-insensitive) the {@code _name} associated
|
||||||
|
* with this ConnectionData, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean nameMatches(String name)
|
||||||
|
{
|
||||||
|
return (name == null || name.equalsIgnoreCase(_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
|||||||
|
package mineplex.serverdata.servers;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import mineplex.serverdata.data.DedicatedServer;
|
||||||
|
|
||||||
|
public class DedicatedServerSorter implements Comparator<DedicatedServer>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public int compare(DedicatedServer first, DedicatedServer second)
|
||||||
|
{
|
||||||
|
if (second.getAvailableRam() <= 1024) return -1;
|
||||||
|
else if (first.getAvailableRam() <= 1024) return 1;
|
||||||
|
else if (first.getAvailableRam() > second.getAvailableRam()) return -1;
|
||||||
|
else if (second.getAvailableRam() > first.getAvailableRam()) return 1;
|
||||||
|
else if (first.getAvailableCpu() > second.getAvailableCpu()) return -1;
|
||||||
|
else if (second.getAvailableCpu() > first.getAvailableCpu()) return 1;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,170 @@
|
|||||||
|
package mineplex.serverdata.servers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import mineplex.serverdata.Region;
|
||||||
|
import mineplex.serverdata.redis.RedisConfig;
|
||||||
|
import mineplex.serverdata.redis.RedisServerRepository;
|
||||||
|
import mineplex.serverdata.servers.ConnectionData.ConnectionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServerManager handles the creation/management of {@link ServerRepository}s for use.
|
||||||
|
* @author Ty
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ServerManager
|
||||||
|
{
|
||||||
|
public static final String SERVER_STATUS_LABEL = "ServerStatus"; // Label differentiating ServerStatus related servers
|
||||||
|
private static final String DEFAULT_CONFIG = "redis-config.dat";
|
||||||
|
|
||||||
|
// Configuration determining connection information
|
||||||
|
private static RedisConfig _config;
|
||||||
|
|
||||||
|
// The cached repository instances
|
||||||
|
private static Map<Region, ServerRepository> repositories = new HashMap<Region, ServerRepository>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param host - the host url used to connect to the database
|
||||||
|
* @param port - the port to connect to the repository
|
||||||
|
* @param region - the geographical region of the {@link ServerRepository}.
|
||||||
|
* @return a newly instanced (or cached) {@link ServerRepository} for the specified {@code region}.
|
||||||
|
*/
|
||||||
|
private static ServerRepository getServerRepository(ConnectionData writeConn, ConnectionData readConn, Region region)
|
||||||
|
{
|
||||||
|
if (repositories.containsKey(region)) return repositories.get(region);
|
||||||
|
|
||||||
|
ServerRepository repository = new RedisServerRepository(writeConn, readConn, region);
|
||||||
|
repositories.put(region, repository);
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code host} defaults to {@value DEFAULT_REDIS_HOST} and
|
||||||
|
* {@code port} defaults to {@value DEFAULT_REDIS_PORT}.
|
||||||
|
*
|
||||||
|
* @see #getServerRepository(String, int, Region)
|
||||||
|
*/
|
||||||
|
public static ServerRepository getServerRepository(Region region)
|
||||||
|
{
|
||||||
|
return getServerRepository(getConnection(true, SERVER_STATUS_LABEL), getConnection(false, SERVER_STATUS_LABEL), region);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link ConnectionData} associated with the master instance connection.
|
||||||
|
*/
|
||||||
|
public static ConnectionData getMasterConnection()
|
||||||
|
{
|
||||||
|
return getConnection(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-Deterministic: Generates random slave instance connection.
|
||||||
|
* @return the {@link ConnectionData} associated with a random slave connection.
|
||||||
|
*/
|
||||||
|
public static ConnectionData getSlaveConnection()
|
||||||
|
{
|
||||||
|
return getConnection(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConnectionData getConnection(boolean writeable, String name)
|
||||||
|
{
|
||||||
|
return getDefaultConfig().getConnection(writeable, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param writeable - whether the connection referenced in return can receive write-requests
|
||||||
|
* @return a newly generated {@code ConnectionData} pointing to a valid connection.
|
||||||
|
*/
|
||||||
|
public static ConnectionData getConnection(boolean writeable)
|
||||||
|
{
|
||||||
|
return getConnection(writeable, "DefaultConnection");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the default {@link RedisConfig} associated with this manager, providing appropriate connections.
|
||||||
|
*/
|
||||||
|
public static RedisConfig getDefaultConfig()
|
||||||
|
{
|
||||||
|
return getConfig(DEFAULT_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link RedisConfig} associated with this manager, providing appropriate connections.
|
||||||
|
*/
|
||||||
|
public static RedisConfig getConfig(String fileName)
|
||||||
|
{
|
||||||
|
if (_config == null)
|
||||||
|
_config = loadConfig(fileName);
|
||||||
|
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RedisConfig loadConfig(String fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File configFile = new File(fileName);
|
||||||
|
|
||||||
|
if (configFile.exists())
|
||||||
|
{
|
||||||
|
List<ConnectionData> connections = new ArrayList<ConnectionData>();
|
||||||
|
List<String> lines = Files.readAllLines(configFile.toPath(), Charset.defaultCharset());
|
||||||
|
|
||||||
|
for (String line : lines)
|
||||||
|
{
|
||||||
|
ConnectionData connection = deserializeConnection(line);
|
||||||
|
connections.add(connection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RedisConfig(connections);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log(fileName + " not found at " + configFile.toPath().toString());
|
||||||
|
return new RedisConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.printStackTrace();
|
||||||
|
log("---Unable To Parse Redis Configuration File---");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param line - the serialized line representing a valid {@link ConnectionData} object.
|
||||||
|
* @return a deserialized {@link ConnectionData} referenced by the {@code line} passed in.
|
||||||
|
*/
|
||||||
|
private static ConnectionData deserializeConnection(String line)
|
||||||
|
{
|
||||||
|
String[] args = line.split(" ");
|
||||||
|
|
||||||
|
if (args.length >= 2)
|
||||||
|
{
|
||||||
|
String ip = args[0];
|
||||||
|
int port = Integer.parseInt(args[1]);
|
||||||
|
String typeName = (args.length >= 3) ? args[2].toUpperCase() : "MASTER"; // Defaults to MASTER if omitted.
|
||||||
|
ConnectionType type = ConnectionType.valueOf(typeName);
|
||||||
|
String name = (args.length >= 4) ? args[3] : "DefaultConnection"; // Defaults to DefaultConnection if omitted.
|
||||||
|
|
||||||
|
return new ConnectionData(ip, port, type, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(String message)
|
||||||
|
{
|
||||||
|
System.out.println(String.format("[ServerManager] %s", message));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package mineplex.serverdata.servers;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import mineplex.serverdata.data.DedicatedServer;
|
||||||
|
import mineplex.serverdata.data.MinecraftServer;
|
||||||
|
import mineplex.serverdata.data.ServerGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ServerRepository is used for storing/retrieving active sessions
|
||||||
|
* for {@link MinecraftServer}s, {@link DedicatedServer}s, and {@link ServerGroup}s
|
||||||
|
* from a persistent database/repoistory.
|
||||||
|
* @author Ty
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface ServerRepository
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a newly instanced snapshot {@link Collection} of all currently active
|
||||||
|
* {@link MinecraftServer}s in the repository.
|
||||||
|
*/
|
||||||
|
public Collection<MinecraftServer> getServerStatuses();
|
||||||
|
|
||||||
|
public Collection<MinecraftServer> getServerStatusesByPrefix(String prefix);
|
||||||
|
|
||||||
|
public Collection<MinecraftServer> getServersByGroup(String serverGroup);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param serverName - the name of the {@link MinecraftServer} to be fetched.
|
||||||
|
* @return the currently active {@link MinecraftServer} with a matching {@code serverName},
|
||||||
|
* if an active one exists, null otherwise.
|
||||||
|
*/
|
||||||
|
public MinecraftServer getServerStatus(String serverName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update (or add, if it doesn't already exist) a {@link MinecraftServer}s data
|
||||||
|
* in the repository.
|
||||||
|
*
|
||||||
|
* A {@link MinecraftServer} must be updated within {@code timeout} milliseconds before
|
||||||
|
* it expires and is removed from the repository.
|
||||||
|
* @param serverData - the {@link MinecraftServer} to add/update in the repository.
|
||||||
|
* @param timeout - the timeout (in milliseconds) before the {@link MinecraftServer} session expires.
|
||||||
|
*/
|
||||||
|
public void updataServerStatus(MinecraftServer serverData, int timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an active {@link MinecraftServer} from the repository.
|
||||||
|
* @param serverData - the {@link MinecraftServer} to be removed.
|
||||||
|
*/
|
||||||
|
public void removeServerStatus(MinecraftServer serverData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param serverName - the name of the server whose existence is being checked.
|
||||||
|
* @return true, if there exists an active {@link MinecraftServer} session with a
|
||||||
|
* matching {@code serverName}, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean serverExists(String serverName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a newly instanced snapshot {@link Collection} of all the
|
||||||
|
* currently active {@link DedicatedServer}s in the repository.
|
||||||
|
*/
|
||||||
|
public Collection<DedicatedServer> getDedicatedServers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a newly instanced snapshot {@link Collection} of all the
|
||||||
|
* currently active {@link ServerGroup}s in the repository.
|
||||||
|
*/
|
||||||
|
public Collection<ServerGroup> getServerGroups(Collection<MinecraftServer> servers);
|
||||||
|
|
||||||
|
public ServerGroup getServerGroup(String serverGroup);
|
||||||
|
|
||||||
|
public Collection<MinecraftServer> getDeadServers();
|
||||||
|
|
||||||
|
void updateServerGroup(ServerGroup serverGroup);
|
||||||
|
|
||||||
|
public void removeServerGroup(ServerGroup serverGroup);
|
||||||
|
}
|
139
sqldumps/player_stats.sql
Normal file
139
sqldumps/player_stats.sql
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
-- MariaDB dump 10.17 Distrib 10.4.14-MariaDB, for Win64 (AMD64)
|
||||||
|
--
|
||||||
|
-- Host: localhost Database: player_stats
|
||||||
|
-- ------------------------------------------------------
|
||||||
|
-- Server version 10.4.14-MariaDB
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ipinfo`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ipinfo`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ipinfo` (
|
||||||
|
`id` int(11) NOT NULL,
|
||||||
|
`ipAddress` text COLLATE latin1_bin NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `ipinfo`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `ipinfo` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `ipinfo` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `ipinfo` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `playerinfo`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `playerinfo`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `playerinfo` (
|
||||||
|
`uuid` varchar(100) NOT NULL,
|
||||||
|
`name` varchar(40) NOT NULL,
|
||||||
|
`version` text NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `playerinfo`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `playerinfo` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `playerinfo` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `playerinfo` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `playerips`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `playerips`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `playerips` (
|
||||||
|
`playerInfoId` int(11) NOT NULL,
|
||||||
|
`ipInfoId` int(11) NOT NULL,
|
||||||
|
`date` text NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `playerips`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `playerips` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `playerips` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `playerips` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `playerloginsessions`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `playerloginsessions`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `playerloginsessions` (
|
||||||
|
`playerInfoId` int(11) NOT NULL,
|
||||||
|
`loginTime` text NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `playerloginsessions`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `playerloginsessions` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `playerloginsessions` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `playerloginsessions` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `playeruniquelogins`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `playeruniquelogins`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `playeruniquelogins` (
|
||||||
|
`playerInfoId` int(11) NOT NULL,
|
||||||
|
`day` text NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `playeruniquelogins`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `playeruniquelogins` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `playeruniquelogins` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `playeruniquelogins` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
|
-- Dump completed on 2021-05-16 10:39:44
|
Reference in New Issue
Block a user