Add ServerCommand system for live cross-server commands,. Update Queuer to support Redis based database and functionality. Fix ServerStatus start up dates to synchronize properly with central Redis time. Add new DataRepository's for easier dynamic object storage in redis. Update Portal server-transfers to support new cross-server command system rather than database stored server transfers.

This commit is contained in:
MrTwiggy 2014-09-21 20:42:47 -04:00
parent 7ca4b6830b
commit 5c9ee3e79e
28 changed files with 1228 additions and 1085 deletions

Binary file not shown.

View File

@ -50,7 +50,7 @@ public class SendCommand extends CommandBase<Portal>
return; return;
} }
Plugin.AddTransferRecord(playerTarget, serverTarget); Portal.transferPlayer(playerTarget, serverTarget);
UtilPlayer.message(player, F.main(Plugin.GetName(), C.cGray + "You have sent player: " + C.cGold + playerTarget + C.cGray + " to server: " + C.cGold + serverTarget + C.cGray + "!")); UtilPlayer.message(player, F.main(Plugin.GetName(), C.cGray + "You have sent player: " + C.cGold + playerTarget + C.cGray + " to server: " + C.cGold + serverTarget + C.cGray + "!"));
return; return;

View File

@ -63,7 +63,7 @@ public class ServerCommand extends CommandBase<Portal>
else else
deniedAccess = true; deniedAccess = true;
} }
else if (servUp.contains("ULTRA") || servUp.contains("BETA") || servUp.contains("T_")) else if (servUp.contains("ULTRA") || servUp.contains("BETA"))
{ {
if (playerRank.Has(Rank.ULTRA)) if (playerRank.Has(Rank.ULTRA))
Plugin.SendPlayerToServerWithMessage(player, args[0]); Plugin.SendPlayerToServerWithMessage(player, args[0]);
@ -93,7 +93,7 @@ public class ServerCommand extends CommandBase<Portal>
{ {
UtilPlayer.message( UtilPlayer.message(
player, player,
F.main(Plugin.GetName(), C.cRed + "You don't have permission to join " + C.cGold + args[0] + " with /server")); F.main(Plugin.GetName(), C.cRed + "You don't have permission to join " + C.cGold + args[0]));
} }
} }
}); });

View File

@ -25,14 +25,18 @@ import mineplex.core.portal.Commands.*;
import mineplex.core.updater.UpdateType; import mineplex.core.updater.UpdateType;
import mineplex.core.updater.event.UpdateEvent; import mineplex.core.updater.event.UpdateEvent;
import mineplex.serverdata.Region; import mineplex.serverdata.Region;
import mineplex.serverdata.ServerCommandManager;
import mineplex.serverdata.ServerManager; import mineplex.serverdata.ServerManager;
public class Portal extends MiniPlugin public class Portal extends MiniPlugin
{ {
private HashSet<String> _connectingPlayers = new HashSet<String>();
//private PortalRepository _repositorysitory = new PortalRepository();
private PortalRepository _repository; // The singleton instance of Portal
private static Portal instance;
public static Portal getInstance() { return instance; }
private HashSet<String> _connectingPlayers = new HashSet<String>();
private Region _region; private Region _region;
private boolean _retrieve = true; private boolean _retrieve = true;
private String _serverName; private String _serverName;
@ -41,12 +45,15 @@ public class Portal extends MiniPlugin
{ {
super("Portal", plugin); super("Portal", plugin);
instance = this;
this._serverName = serverName; this._serverName = serverName;
this._region = plugin.getConfig().getBoolean("serverstatus.us") ? Region.US : Region.EU; this._region = plugin.getConfig().getBoolean("serverstatus.us") ? Region.US : Region.EU;
Bukkit.getMessenger().registerOutgoingPluginChannel(GetPlugin(), "BungeeCord"); Bukkit.getMessenger().registerOutgoingPluginChannel(GetPlugin(), "BungeeCord");
_repository = new PortalRepository(); // Register the server command type for future use
ServerCommandManager.getInstance().registerCommandType(TransferCommand.class);
} }
public void SendAllPlayers(String serverName) public void SendAllPlayers(String serverName)
@ -111,15 +118,11 @@ public class Portal extends MiniPlugin
}, 20L); }, 20L);
} }
public void AddTransferRecord(final String playerName, final String serverName) public static void transferPlayer(String playerName, String serverName)
{ {
Bukkit.getScheduler().runTaskAsynchronously(GetPlugin(), new Runnable() ServerTransfer serverTransfer = new ServerTransfer(playerName, serverName);
{ TransferCommand transferCommand = new TransferCommand(serverTransfer);
public void run() transferCommand.publish();
{
_repository.addServerTransfer(new ServerTransfer(playerName, serverName));
}
});
} }
public void DoesServerExist(final String serverName, final Callback<Boolean> callback) public void DoesServerExist(final String serverName, final Callback<Boolean> callback)
@ -149,51 +152,4 @@ public class Portal extends MiniPlugin
AddCommand(new ServerCommand(this)); AddCommand(new ServerCommand(this));
AddCommand(new SendCommand(this)); AddCommand(new SendCommand(this));
} }
@EventHandler
public void checkForServerTransfers(UpdateEvent event)
{
if (event.getType() != UpdateType.SEC || Bukkit.getOnlinePlayers().size() == 0)
return;
_retrieve = !_retrieve;
if (_retrieve)
{
Bukkit.getScheduler().runTaskAsynchronously(GetPlugin(), new Runnable()
{
public void run()
{
final Collection<ServerTransfer> serverTransfers = _repository.getServerTransfers();
for (Iterator<ServerTransfer> iterator = serverTransfers.iterator(); iterator.hasNext();)
{
ServerTransfer serverTransfer = iterator.next();
if (serverTransfer.getServerName().equalsIgnoreCase(_serverName))
{
_repository.removeServerTransfer(serverTransfer.getPlayerName());
iterator.remove();
}
}
Bukkit.getScheduler().runTask(GetPlugin(), new Runnable()
{
public void run()
{
for (ServerTransfer serverTransfer : serverTransfers)
{
Player player = GetPlugin().getServer().getPlayer(serverTransfer.getPlayerName());
if (player != null)
{
SendPlayerToServer(player, serverTransfer.getServerName());
}
}
}
});
}
});
}
}
} }

View File

@ -1,177 +0,0 @@
package mineplex.core.portal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import mineplex.serverdata.ServerManager;
import mineplex.serverdata.Utility;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisConnectionException;
public class PortalRepository
{
// The delimiter character used for redis key paths
public final char KEY_DELIMITER = '.';
// The access portal for jedis resources
private JedisPool _jedisPool;
/**
* Class constructor
*/
public PortalRepository()
{
this._jedisPool = new JedisPool(new JedisPoolConfig(), ServerManager.DEFAULT_REDIS_HOST,
ServerManager.DEFAULT_REDIS_PORT);
}
/**
* @return the {@link Set} of all ongoing {@link ServerTransfer}s available in this repository.
*/
public Collection<ServerTransfer> getServerTransfers()
{
Set<ServerTransfer> serverTransfers = new HashSet<ServerTransfer>();
Jedis jedis = _jedisPool.getResource();
try
{
String setKey = "servertransfers";
Set<String> playerNames = jedis.smembers(setKey);
Pipeline pipeline = jedis.pipelined();
List<Response<String>> responses = new ArrayList<Response<String>>();
for (String playerName : playerNames)
{
String dataKey = concatenate(setKey, playerName);
responses.add(pipeline.get(dataKey));
}
pipeline.sync();
for (Response<String> response : responses)
{
String serializedData = response.get();
ServerTransfer serverTransfer = Utility.deserialize(serializedData, ServerTransfer.class);
serverTransfers.add(serverTransfer);
}
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
return serverTransfers;
}
/**
* Add a new {@link ServerTransfer} to this repository.
* @param serverTransfer - the {@link ServerTransfer} to be added in.
*/
public void addServerTransfer(ServerTransfer serverTransfer)
{
Jedis jedis = _jedisPool.getResource();
try
{
String setKey = "servertransfers";
String dataKey = concatenate(setKey, serverTransfer.getPlayerName());
String serializedTransfer = Utility.serialize(serverTransfer);
Transaction transaction = jedis.multi();
transaction.sadd(setKey, serverTransfer.getPlayerName());
transaction.set(dataKey, serializedTransfer);
transaction.exec();
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
}
/**
* Remove an existing {@link ServerTransfer} from this repository whose
* stored {@code playerName} matches the passed in name.
* @param playerName - the name of the player whose active {@link ServerTransfer}
* is to be removed.
* @return true, if the {@link ServerTransfer} belonging to player with matching
* {@code playerName} was successfully removed, false otherwise.
*/
public boolean removeServerTransfer(String playerName)
{
boolean removedTransfer = false;
Jedis jedis = _jedisPool.getResource();
try
{
String setKey = "servertransfers";
String dataKey = concatenate(setKey, playerName);
if (jedis.sismember(setKey, playerName))
{
Transaction transaction = jedis.multi();
transaction.srem(setKey, playerName);
transaction.del(dataKey);
transaction.exec();
removedTransfer = true;
}
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
return removedTransfer;
}
/**
* @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);
}
/*
* 'servertransfers' contains a set of player names for players with an active server transfer
* 'servertransfers.<player name>' contains the JSON serialized 'ServerTransfer' object holding info.
*/
}

View File

@ -0,0 +1,40 @@
package mineplex.core.portal;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import mineplex.serverdata.ServerCommand;
/**
* The TransferCommand is sent across the server network to notify
* servers to transfer players to other destinations.
* @author Ty
*
*/
public class TransferCommand extends ServerCommand
{
// The ServerTransfer to be sent to another server for enactment
private ServerTransfer transfer;
/**
* Class constructor
* @param transfer - the {@link ServerTransfer} to notify another server of
*/
public TransferCommand(ServerTransfer transfer)
{
this.transfer = transfer;
}
@Override
public void run()
{
Player player = Bukkit.getPlayer(transfer.getPlayerName());
if (player != null && player.isOnline())
{
Portal.getInstance().SendPlayerToServer(player, transfer.getServerName());
}
}
}

View File

@ -17,6 +17,7 @@ import mineplex.serverdata.MinecraftServer;
import mineplex.serverdata.Region; import mineplex.serverdata.Region;
import mineplex.serverdata.ServerManager; import mineplex.serverdata.ServerManager;
import mineplex.serverdata.ServerRepository; import mineplex.serverdata.ServerRepository;
import mineplex.serverdata.Utility;
public class ServerStatusManager extends MiniPlugin public class ServerStatusManager extends MiniPlugin
{ {
@ -31,10 +32,13 @@ public class ServerStatusManager extends MiniPlugin
private boolean _enabled = true; private boolean _enabled = true;
private long _startUpDate;
public ServerStatusManager(JavaPlugin plugin, LagMeter lagMeter) public ServerStatusManager(JavaPlugin plugin, LagMeter lagMeter)
{ {
super("Server Status Manager", plugin); super("Server Status Manager", plugin);
_startUpDate = Utility.currentTimeMillis();
_lagMeter = lagMeter; _lagMeter = lagMeter;
if (new File("IgnoreUpdates.dat").exists()) if (new File("IgnoreUpdates.dat").exists())
@ -145,7 +149,7 @@ public class ServerStatusManager extends MiniPlugin
int maxRam = (int) (Runtime.getRuntime().maxMemory() / 1048576); int maxRam = (int) (Runtime.getRuntime().maxMemory() / 1048576);
return new MinecraftServer(_name, group, motd, address, port, playerCount, return new MinecraftServer(_name, group, motd, address, port, playerCount,
maxPlayerCount, tps, ram, maxRam, 0L); maxPlayerCount, tps, ram, maxRam, _startUpDate);
} }
public String getCurrentServerName() public String getCurrentServerName()

View File

@ -12,7 +12,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/> <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value=""/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value=""/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/> <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${BUILD_FILES}\common.xml"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${build_files}${BUILD_FILES}\common.xml"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,clean"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,clean"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/> <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Mineplex.Hub}"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Mineplex.Hub}"/>

View File

@ -6,5 +6,8 @@
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Core.Common"/> <classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Core.Common"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpclient-4.2.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpclient-4.2.jar"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpcore-4.2.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpcore-4.2.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerData"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/jedis-2.4.2.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Core"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -1,29 +0,0 @@
package mineplex.queuer;
import java.util.Comparator;
import repository.PlayerMatchStatus;
public class EloPlayerSorter implements Comparator<PlayerMatchStatus>
{
public int EloMark = -1;
public EloPlayerSorter(int eloMark)
{
EloMark = eloMark;
}
public int compare(PlayerMatchStatus playerA, PlayerMatchStatus playerB)
{
if (playerA.Elo - EloMark < playerB.Elo - EloMark)
return -1;
if (playerB.Elo - EloMark < playerA.Elo - EloMark)
return 1;
if (playerA.Id < playerB.Id)
return -1;
return 1;
}
}

View File

@ -0,0 +1,75 @@
package mineplex.queuer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public class Match
{
private int _id;
public int getId() { return _id; }
private Set<QueueParty> _parties;
public Set<QueueParty> getParties() { return _parties; }
private boolean _waitingForInvites;
public boolean isWaitingForInvites() { return _waitingForInvites; }
private long _waitingStartTime;
public long getWaitDuration() { return System.currentTimeMillis() - _waitingStartTime; }
private int _averageElo;
public int getAverageElo() { return _averageElo; }
public Match(int id, int averageElo, QueueParty... parties)
{
this._id = id;
this._averageElo = averageElo;
for (QueueParty party : parties)
{
joinQueueParty(party);
}
}
/**
* Add a {@link QueueParty} to this match.
* @param queueParty
*/
public void joinQueueParty(QueueParty queueParty)
{
_parties.add(queueParty);
}
/**
* Remove a {@link QueueParty} from this match.
* @param queueParty
*/
public void quitQueueParty(QueueParty queueParty)
{
_parties.remove(queueParty);
}
public int getPlayerCount()
{
int playerCount = 0;
for (QueueParty party : _parties)
{
playerCount += party.getPlayerCount();
}
return playerCount;
}
public void setWaitingForInvites(boolean waitingForInvites)
{
this._waitingForInvites = waitingForInvites;
if (waitingForInvites)
{
this._waitingStartTime = System.currentTimeMillis();
}
}
}

View File

@ -1,42 +0,0 @@
package mineplex.queuer;
import java.util.Collection;
import java.util.HashMap;
import repository.PlayerMatchStatus;
public class PlayerMatch
{
private HashMap<Integer, PlayerMatchStatus> _players = new HashMap<Integer, PlayerMatchStatus>();
private int _playerCount = 0;
public boolean WaitingForInvites = false;
public long WaitingStarted = 0;
public int MatchId = -1;
public int Elo = 0;
public void addPlayerRecord(PlayerMatchStatus record)
{
_players.put(record.Id, record);
_playerCount += record.PlayerCount;
}
public void removePlayerRecord(PlayerMatchStatus record)
{
_players.remove(record.Id);
_playerCount -= record.PlayerCount;
}
public int getPlayerCount()
{
return _playerCount;
}
public Collection<PlayerMatchStatus> getPlayerRecords()
{
return _players.values();
}
}

View File

@ -0,0 +1,78 @@
package mineplex.queuer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import mineplex.serverdata.Data;
import mineplex.serverdata.Region;
public class QueueParty implements Data
{
private int _id;
public int getId() { return _id; }
private String _state;
public String getState() { return _state; }
public void setState(String state) { this._state = state; }
private Set<String> _players;
public Set<String> getPlayers() { return _players; }
private int _assignedMatch;
public int getAssignedMatch () { return _assignedMatch; }
public void setAssignedMatch(int assignedMatch) { this._assignedMatch = assignedMatch; }
private int _variance;
private String _gameType;
private int _averageElo;
public int getAverageElo() { return _averageElo; }
private int _playerCount;
public int getPlayerCount() { return _playerCount; }
private long _queueStartTime;
private boolean _prompted;
public boolean isPrompted() { return _prompted; }
public void setPrompted(boolean prompted) { this._prompted = prompted; }
private Region _region;
private Set<String> _otherPartyStates;
public Set<String> getOtherPartyStates() { return _otherPartyStates; }
public void setOtherPartyStates(Set<String> otherPartyStates) { this._otherPartyStates = otherPartyStates; }
public QueueParty()
{
this._id = -1;
this._state = "Awaiting Match";
this._assignedMatch = -1;
this._variance = 25;
this._prompted = false;
this._region = Region.US;
this._players = new HashSet<String>();
this._otherPartyStates = new HashSet<String>();
this._queueStartTime = System.currentTimeMillis();
}
public QueueParty(Collection<String> players, String gameType, int averageElo)
{
this._players.addAll(players);
this._gameType = gameType;
this._averageElo = averageElo;
}
public boolean hasAssignedMatch()
{
return _assignedMatch != -1;
}
@Override
public String getDataId()
{
return Integer.toString(_id);
}
}

View File

@ -0,0 +1,21 @@
package mineplex.queuer;
import java.util.Comparator;
public class QueuePartySorter implements Comparator<QueueParty>
{
public int compare(QueueParty party1, QueueParty party2)
{
if (party1.getAverageElo() < party2.getAverageElo())
return -1;
if (party2.getAverageElo() < party1.getAverageElo())
return 1;
if (party1.getId() < party2.getId())
return -1;
return 1;
}
}

View File

@ -0,0 +1,164 @@
package mineplex.queuer;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import mineplex.core.portal.Portal;
import mineplex.core.portal.ServerTransfer;
import mineplex.serverdata.DataRepository;
import mineplex.serverdata.MinecraftServer;
import mineplex.serverdata.RedisDataRepository;
import mineplex.serverdata.Region;
import mineplex.serverdata.ServerManager;
import mineplex.serverdata.ServerRepository;
public class QueueRepository
{
private DataRepository<QueueParty> _partyRepository;
/**
* Class constructor
* @param host - the host to connect the QueueRepository to
* @param port - the designated port of the QueueRepository database
* @param region - the region of server queues to manage
*/
public QueueRepository(String host, int port, Region region)
{
this._partyRepository = new RedisDataRepository<QueueParty>(host, port, region,
QueueParty.class, "queue-parties");
}
/**
* {@code host} defaults to {@value ServerManager#DEFAULT_REDIS_HOST} and
* {@code port} defaults to {@value ServerManager#DEFAULT_REDIS_PORT}
*
* @see #QueueRepository(String, int, Region)
*/
public QueueRepository(Region region)
{
this(ServerManager.DEFAULT_REDIS_HOST, ServerManager.DEFAULT_REDIS_PORT, region);
}
public QueueParty getQueueParty(int partyId)
{
return _partyRepository.getElement(Integer.toString(partyId));
}
public QueueParty createQueueParty(Collection<String> players, String gameType, int averageElo)
{
QueueParty queueParty = new QueueParty(players, gameType, averageElo);
updateQueueParty(queueParty);
return queueParty;
}
public void updateQueueParty(QueueParty queueParty)
{
_partyRepository.addElement(queueParty);
}
public void deleteQueueParty(int partyId)
{
_partyRepository.removeElement(Integer.toString(partyId));
}
public void deleteQueueParty(QueueParty party)
{
deleteQueueParty(party.getId());
}
public void deleteAssignedParties(int matchId)
{
for (QueueParty queueParty : getJoinedQueueParties(matchId))
{
deleteQueueParty(queueParty);
}
}
public Collection<QueueParty> getQueueParties()
{
return _partyRepository.getElements();
}
public Collection<QueueParty> getJoinedQueueParties(int matchId)
{
Collection<QueueParty> queueParties = new HashSet<QueueParty>();
for (QueueParty queueParty : getQueueParties())
{
if (queueParty.getAssignedMatch() == matchId)
{
queueParties.add(queueParty);
}
}
return queueParties;
}
public Map<Integer, QueueParty> getMappedQueueParties()
{
Map<Integer, QueueParty> queueParties = new HashMap<Integer, QueueParty>();
for (QueueParty queueParty : getQueueParties())
{
queueParties.put(queueParty.getId(), queueParty);
}
return queueParties;
}
public void assignMatch(QueueParty queueParty, Match match)
{
queueParty.setAssignedMatch(match.getId());
queueParty.setState("Awaiting Confirmation");
updateQueueParty(queueParty);
}
public void startMatch(int matchId)
{
MinecraftServer emptyServer = getEmptyServer();
if (emptyServer != null)
{
for (QueueParty queueParty : getJoinedQueueParties(matchId))
{
for (String playerName : queueParty.getPlayers())
{
Portal.transferPlayer(playerName, emptyServer.getName());
}
}
}
}
protected MinecraftServer getEmptyServer()
{
ServerRepository serverRepository = ServerManager.getServerRepository(Region.US);
Collection<MinecraftServer> servers = serverRepository.getServersByGroup("DominateElo");
for (MinecraftServer server : servers)
{
if (server.getPlayerCount() == 0)
{
return server;
}
}
return null;
}
public void deleteMatch(int matchId)
{
for (QueueParty queueParty : getJoinedQueueParties(matchId))
{
queueParty.setAssignedMatch(-1);
queueParty.setState("Awaiting Match");
updateQueueParty(queueParty);
}
}
}

View File

@ -2,29 +2,32 @@ package mineplex.queuer;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import repository.PlayerMatchStatus; import mineplex.serverdata.Region;
import repository.Repository;
public class Queuer public class Queuer
{ {
private static Repository _repository = new Repository(); private static QueueRepository _repo;
public static void main (String args[]) public static void main (String args[])
{ {
boolean us = !new File("eu.dat").exists(); Region region = (!new File("eu.dat").exists()) ? Region.US : Region.EU;
_repository.initialize(us); _repo = new QueueRepository(region);
HashMap<Integer, Integer> playerVarianceMap = new HashMap<Integer, Integer>(); HashMap<Integer, Integer> playerVarianceMap = new HashMap<Integer, Integer>();
HashMap<Integer, PlayerMatch> playerPrepMatchMap = new HashMap<Integer, PlayerMatch>(); HashMap<Integer, Match> playerPrepMatchMap = new HashMap<Integer, Match>();
List<PlayerMatch> matchList = new ArrayList<PlayerMatch>(); Set<Match> matches = new HashSet<Match>();
EloPlayerSorter playerSorter = new EloPlayerSorter(1250); QueuePartySorter partySorter = new QueuePartySorter();
int matchId = 1; int matchId = 1;
@ -34,135 +37,132 @@ public class Queuer
matchId %= 1500; matchId %= 1500;
List<Integer> assignedMatchIdChecked = new ArrayList<Integer>(); List<Integer> assignedMatchIdChecked = new ArrayList<Integer>();
HashMap<Integer, PlayerMatchStatus> queueRecords = _repository.retrieveQueuedRecords(); Map<Integer, QueueParty> queueParties = _repo.getMappedQueueParties();
int matchPlayerCount = 2; int matchPlayerCount = 2;
System.out.println("Checking " + queueRecords.size() + " queues..."); System.out.println("Checking " + queueParties.size() + " queues...");
for (PlayerMatchStatus queueRecord : queueRecords.values()) for (QueueParty queueParty : queueParties.values())
{ {
Integer keyId = queueRecord.Id; int partyId = queueParty.getId();
int variance = playerVarianceMap.containsKey(partyId) ? playerVarianceMap.get(partyId) : 0;
variance += 25;
playerVarianceMap.put(partyId, variance);
// Add or increase variance mapping if (queueParty.hasAssignedMatch())
if (playerVarianceMap.containsKey(keyId))
playerVarianceMap.put(keyId, playerVarianceMap.get(keyId) + 25);
else
playerVarianceMap.put(keyId, 25);
int playerVariance = playerVarianceMap.get(keyId);
if (queueRecord.AssignedMatch == -1)
{ {
for (PlayerMatch match : matchList) for (Match match : matches)
{ {
if (Math.abs(match.Elo - queueRecord.Elo) <= playerVariance) if (Math.abs(match.getAverageElo() - queueParty.getAverageElo()) <= variance)
{ {
if (playerPrepMatchMap.containsKey(keyId)) if (playerPrepMatchMap.containsKey(partyId))
{ {
if (playerPrepMatchMap.get(keyId) == match) if (playerPrepMatchMap.get(partyId) == match)
break; break;
playerPrepMatchMap.get(keyId).removePlayerRecord(queueRecord); playerPrepMatchMap.get(partyId).quitQueueParty(queueParty);
} }
match.addPlayerRecord(queueRecord); match.joinQueueParty(queueParty);
playerPrepMatchMap.put(keyId, match); playerPrepMatchMap.put(partyId, match);
System.out.println("Found prep match for '" + queueRecord.Id + "'");
log("Found prep match for '" + queueParty.getId() + "'");
break; break;
} }
} }
if (!playerPrepMatchMap.containsKey(keyId)) if (!playerPrepMatchMap.containsKey(partyId))
{ {
PlayerMatch match = new PlayerMatch(); Match match = new Match(matchId++, queueParty.getAverageElo(), queueParty);
match.Elo = queueRecord.Elo;
match.MatchId = matchId;
match.addPlayerRecord(queueRecord);
playerPrepMatchMap.put(keyId, match); playerPrepMatchMap.put(partyId, match);
matchList.add(match); matches.add(match);
matchId++;
} }
} }
else if (!assignedMatchIdChecked.contains(queueRecord.AssignedMatch)) else if (!assignedMatchIdChecked.contains(queueParty.getAssignedMatch()))
{ {
System.out.println("Checking if match '" + queueRecord.AssignedMatch + "' is ready."); int assignedMatchId = queueParty.getAssignedMatch();
List<String> matchStatuses = _repository.getMatchStatuses(queueRecord.AssignedMatch); log("Checking if match '" + assignedMatchId + "' is ready.");
//List<String> matchStatuses = _repo.getMatchStatuses(queueRecord.AssignedMatch);
Collection<QueueParty> joinedParties = _repo.getJoinedQueueParties(assignedMatchId);
boolean matchReady = true; boolean matchReady = true;
boolean matchDeny = false; boolean matchDeny = false;
for (String matchStatus : matchStatuses) for (QueueParty joinedParty : joinedParties)
{ {
if (matchStatus.equalsIgnoreCase("Deny")) String partyState = joinedParty.getState();
if (partyState.equalsIgnoreCase("Deny"))
{ {
matchDeny = true; matchDeny = true;
matchReady = false; matchReady = false;
break; break;
} }
else if (!matchStatus.equalsIgnoreCase("Ready")) else if (!partyState.equalsIgnoreCase("Ready"))
{
matchReady = false; matchReady = false;
} }
}
if (matchReady) if (matchReady)
{ {
_repository.startMatch(queueRecord.AssignedMatch); _repo.startMatch(assignedMatchId);
_repository.deleteQueuesByAssignedMatch(queueRecord.AssignedMatch); _repo.deleteAssignedParties(assignedMatchId);
System.out.println("Starting match '" + queueRecord.AssignedMatch + "'"); System.out.println("Starting match '" + assignedMatchId + "'");
} }
else if (matchDeny) else if (matchDeny)
{ {
_repository.removeAssignedMatch(queueRecord.AssignedMatch); _repo.deleteMatch(assignedMatchId);
} }
assignedMatchIdChecked.add(queueRecord.AssignedMatch); assignedMatchIdChecked.add(assignedMatchId);
} }
} }
System.out.println("Checking " + matchList.size() + " matches..."); System.out.println("Checking " + matches.size() + " matches...");
// Check for and kick off invites for ready matches // Check for and kick off invites for ready matches
for (Iterator<PlayerMatch> matchIterator = matchList.iterator(); matchIterator.hasNext();) for (Iterator<Match> matchIterator = matches.iterator(); matchIterator.hasNext();)
{ {
PlayerMatch match = matchIterator.next(); Match match = matchIterator.next();
// Don't give me crap about not using iterator...can't cuz of stupid thing. // Don't give me crap about not using iterator...can't cuz of stupid thing.
List<PlayerMatchStatus> matchStatusesToRemove = new ArrayList<PlayerMatchStatus>(); Set<QueueParty> partiesToRemove = new HashSet<QueueParty>();
for (PlayerMatchStatus matchStatus : match.getPlayerRecords()) for (QueueParty queueParty : match.getParties())
{ {
if (!queueRecords.containsKey(matchStatus.Id)) if (!queueParties.containsKey(queueParty.getId()))
{ {
System.out.println("Removing matchStatus : " + matchStatus.Id); log("Removing matchStatus : " + queueParty.getId());
matchStatusesToRemove.add(matchStatus); partiesToRemove.add(queueParty);
if (match.WaitingForInvites) if (match.isWaitingForInvites())
{ {
_repository.removeAssignedMatch(match.MatchId); _repo.deleteMatch(match.getId());
match.WaitingForInvites = false; match.setWaitingForInvites(false);
} }
} }
} }
for (PlayerMatchStatus matchStatus : matchStatusesToRemove) for (QueueParty party : partiesToRemove)
{ {
match.removePlayerRecord(matchStatus); match.quitQueueParty(party);
} }
if (match.WaitingForInvites) if (match.isWaitingForInvites())
{ {
if ((System.currentTimeMillis() - match.WaitingStarted) > 15000) if ((match.getWaitDuration()) > 15000)
{ {
for (PlayerMatchStatus matchStatus : match.getPlayerRecords()) for (QueueParty queueParty : match.getParties())
{ {
if (!matchStatus.State.equalsIgnoreCase("Ready")) if (!queueParty.getState().equalsIgnoreCase("Ready"))
_repository.deleteQueuesById(matchStatus.Id); {
_repo.deleteQueueParty(queueParty.getId());
} }
_repository.removeAssignedMatch(match.MatchId); }
match.WaitingForInvites = false;
_repo.deleteMatch(match.getId());
match.setWaitingForInvites(false);
} }
continue; continue;
@ -170,51 +170,42 @@ public class Queuer
if (match.getPlayerCount() >= matchPlayerCount) if (match.getPlayerCount() >= matchPlayerCount)
{ {
playerSorter.EloMark = match.Elo; List<QueueParty> partyList = new ArrayList<QueueParty>(match.getParties());
Collections.sort(partyList, partySorter);
List<PlayerMatchStatus> playerList = new ArrayList<PlayerMatchStatus>();
playerList.addAll(match.getPlayerRecords());
Collections.sort(playerList, playerSorter);
int playerCount = 0; int playerCount = 0;
for (QueueParty party : partyList)
for (int i = 0; i < playerList.size(); i++)
{ {
PlayerMatchStatus player = playerList.get(i); if (playerCount + party.getPlayerCount() > matchPlayerCount)
if (playerCount + player.PlayerCount > matchPlayerCount)
{ {
match.removePlayerRecord(player); match.quitQueueParty(party);
playerPrepMatchMap.remove(player.Id); playerPrepMatchMap.remove(party.getId());
log("Oops hit player cap, can't fit you in this match.");
System.out.println("Oops hit player cap, can't fit you in this match.");
continue; continue;
} }
playerCount += player.PlayerCount; playerCount += party.getPlayerCount();
} }
if (playerCount == matchPlayerCount) if (playerCount == matchPlayerCount)
{ {
System.out.println("Sent match invites for '" + match.MatchId + "'"); log("Sent match invites for '" + match.getId() + "'");
for (PlayerMatchStatus player : match.getPlayerRecords()) for (QueueParty party : match.getParties())
{ {
playerPrepMatchMap.remove(player.Id); playerPrepMatchMap.remove(party.getId());
_repository.assignMatch(player.Id, match.MatchId); _repo.assignMatch(party, match);
} }
match.WaitingForInvites = true; match.setWaitingForInvites(true);
match.WaitingStarted = System.currentTimeMillis(); matchesMade++;
matchesMade += 1;
} }
} }
else if (match.getPlayerCount() == 0) else if (match.getPlayerCount() == 0)
{
matchIterator.remove(); matchIterator.remove();
} }
}
_repository.clearOldServerTransfers();
try try
{ {
@ -228,5 +219,12 @@ public class Queuer
e.printStackTrace(); e.printStackTrace();
} }
} }
}
private static void log(String message)
{
System.out.println(message);
} }
} }

View File

@ -1,19 +0,0 @@
package repository;
public class PlayerMatchStatus
{
public int Id = -1;
public String State = "Awaiting Match";
public int AssignedMatch = -1;
public int Variance = 25;
public String GameType;
public int Elo;
public int PlayerCount;
public long QueuedStartTime;
public void printInfo()
{
System.out.println("PlayerMatchStatus: Id=" + Id + " State=" + State + " AssignedMatch=" + AssignedMatch + " Variance=" + Variance + " GameType=" + GameType + " Elo=" + Elo + " PlayerCount=" + PlayerCount + " QueuedStartTime=" + QueuedStartTime);
}
}

View File

@ -1,644 +0,0 @@
package repository;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class Repository
{
private static Object _connectionLock = new Object();
private String _connectionString = "jdbc:mysql://db.mineplex.com:3306/Queue?autoReconnect=true&failOverReadOnly=false&maxReconnects=10";
private String _serverStatusConnectionString = "jdbc:mysql://db.mineplex.com:3306/ServerStatus?autoReconnect=true&failOverReadOnly=false&maxReconnects=10";
private String _userName = "root";
private String _password = "tAbechAk3wR7tuTh";
private boolean _us = true;
private static String CREATE_TRANSFER_TABLE = "CREATE TABLE IF NOT EXISTS playerServerTransfer (id INT NOT NULL AUTO_INCREMENT, playerName VARCHAR(256), serverName VARCHAR(256), updated LONG, PRIMARY KEY (id));";
private static String INSERT_TRANSFER_RECORD = "INSERT INTO playerServerTransfer (playerName, serverName, updated) VALUES (?, ?, now());";
private static String DELETE_OLD_TRANSFER_RECORDS = "DELETE FROM playerServerTransfer WHERE TIME_TO_SEC(TIMEDIFF(now(), updated)) > 15;";
private static String CREATE_ELO_QUEUE_TABLE = "CREATE TABLE IF NOT EXISTS playerQueue (id INT NOT NULL AUTO_INCREMENT, playerList VARCHAR(256), gameType VARCHAR(256), elo INT, state VARCHAR(256), time LONG, assignedMatch INT, us BOOLEAN NOT NULL DEFAULT 'true', PRIMARY KEY (id));";
private static String SAVE_STATE_VALUE = "UPDATE playerQueue SET state = ? WHERE id = ?;";
private static String DELETE_ASSIGNED_QUEUE_RECORDS = "DELETE FROM playerQueue WHERE assignedMatch = ?;";
private static String DELETE_QUEUE_RECORD = "DELETE FROM playerQueue WHERE id = ?;";
private static String RESET_MATCH_RECORDS = "UPDATE playerQueue SET assignedMatch = -1, state = 'Awaiting Match' WHERE assignedMatch = ?;";
private static String RETRIEVE_QUEUE_RECORDS = "SELECT id, gameType, elo, playerCount, assignedMatch, time, us FROM playerQueue WHERE us = ?;";
private static String RETRIEVE_MATCH_STATUS = "SELECT state, playerList FROM playerQueue WHERE assignedMatch = ?;";
private static String UPDATE_MATCH_STATUS = "UPDATE playerQueue SET assignedMatch = ?, state = ? WHERE id = ?;";
private static String RETRIEVE_SERVERGROUP_STATUSES = "SELECT serverName, now(), updated FROM ServerStatus WHERE players = 0 AND serverGroup = ?";
private Connection _connection = null;
private Connection _serverStatusConnection = null;
public void initialize(boolean us)
{
_us = us;
PreparedStatement preparedStatement = null;
try
{
Class.forName("com.mysql.jdbc.Driver");
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
// Create table
preparedStatement = _connection.prepareStatement(CREATE_ELO_QUEUE_TABLE);
preparedStatement.execute();
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
if (preparedStatement != null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
try
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
// Create table
preparedStatement = _connection.prepareStatement(CREATE_TRANSFER_TABLE);
preparedStatement.execute();
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
if (preparedStatement != null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
}
public void updateState(PlayerMatchStatus matchStatus)
{
PreparedStatement preparedStatement = null;
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(SAVE_STATE_VALUE);
preparedStatement.setString(1, matchStatus.State);
preparedStatement.setInt(2, matchStatus.Id);
if (preparedStatement.executeUpdate() == 0)
{
System.out.println("Error updating state.");
}
}
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
if (preparedStatement != null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
}
public PlayerMatchStatus checkForAssignedMatch(int id)
{
ResultSet resultSet = null;
PreparedStatement preparedStatement = null;
PlayerMatchStatus matchStatus = new PlayerMatchStatus();
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(RETRIEVE_MATCH_STATUS);
preparedStatement.setInt(1, id);
resultSet = preparedStatement.getGeneratedKeys();
while (resultSet.next())
{
matchStatus.Id = id;
matchStatus.State = resultSet.getString(1);
matchStatus.AssignedMatch = resultSet.getInt(2);
}
}
}
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 matchStatus;
}
public HashMap<Integer, PlayerMatchStatus> retrieveQueuedRecords()
{
ResultSet resultSet = null;
PreparedStatement preparedStatement = null;
HashMap<Integer, PlayerMatchStatus> queueRecords = new HashMap<Integer, PlayerMatchStatus>();
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(RETRIEVE_QUEUE_RECORDS);
preparedStatement.setBoolean(1, _us);
resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
PlayerMatchStatus matchStatus = new PlayerMatchStatus();
matchStatus.Id = resultSet.getInt(1);
matchStatus.GameType = resultSet.getString(2);
matchStatus.Elo = resultSet.getInt(3);
matchStatus.PlayerCount = resultSet.getInt(4);
matchStatus.AssignedMatch = resultSet.getInt(5);
matchStatus.QueuedStartTime = resultSet.getLong(6);
queueRecords.put(matchStatus.Id, matchStatus);
}
}
}
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 queueRecords;
}
public List<String> getMatchStatuses(int assignedMatch)
{
ResultSet resultSet = null;
PreparedStatement preparedStatement = null;
List<String> matchStatuses = new ArrayList<String>();
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(RETRIEVE_MATCH_STATUS);
preparedStatement.setInt(1, assignedMatch);
resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
matchStatuses.add(resultSet.getString(1));
}
}
}
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 matchStatuses;
}
public void deleteQueuesByAssignedMatch(int matchId)
{
PreparedStatement preparedStatement = null;
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(DELETE_ASSIGNED_QUEUE_RECORDS);
preparedStatement.setInt(1, matchId);
if (preparedStatement.executeUpdate() == 0)
{
System.out.println("Error deleting queue records.");
}
}
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
if (preparedStatement != null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
}
public void deleteQueuesById(int id)
{
PreparedStatement preparedStatement = null;
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(DELETE_QUEUE_RECORD);
preparedStatement.setInt(1, id);
if (preparedStatement.executeUpdate() == 0)
{
System.out.println("Error deleting queue record.");
}
}
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
if (preparedStatement != null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
}
public void clearOldServerTransfers()
{
PreparedStatement preparedStatement = null;
try
{
if (_connection == null || _connection.isClosed())
_connection = DriverManager.getConnection(_serverStatusConnectionString, _userName, _password);
preparedStatement = _connection.prepareStatement(DELETE_OLD_TRANSFER_RECORDS);
preparedStatement.executeUpdate();
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
if (preparedStatement != null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
}
public void startMatch(int assignedMatch)
{
ResultSet resultSet = null;
PreparedStatement preparedStatement = null;
PreparedStatement addTransferStatement = null;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String serverName = "";
try
{
if (_serverStatusConnection == null || _serverStatusConnection.isClosed())
_serverStatusConnection = DriverManager.getConnection(_serverStatusConnectionString, _userName, _password);
preparedStatement = _serverStatusConnection.prepareStatement(RETRIEVE_SERVERGROUP_STATUSES);
preparedStatement.setString(1, "DominateElo");
resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
long current = dateFormat.parse(resultSet.getString(2)).getTime();
long updated = dateFormat.parse(resultSet.getString(3)).getTime();
if (current - updated < 15000)
{
serverName = resultSet.getString(1);
break;
}
}
resultSet.close();
preparedStatement.close();
if (_connection == null || _connection.isClosed())
_connection = DriverManager.getConnection(_serverStatusConnectionString, _userName, _password);
preparedStatement = _connection.prepareStatement(RETRIEVE_MATCH_STATUS);
preparedStatement.setInt(1, assignedMatch);
addTransferStatement = _connection.prepareStatement(INSERT_TRANSFER_RECORD);
resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
for (String name : Arrays.asList(resultSet.getString(2).split("\\s*,\\s*")))
{
addTransferStatement.setString(1, name);
addTransferStatement.setString(2, serverName);
addTransferStatement.addBatch();
}
}
addTransferStatement.executeBatch();
}
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();
}
}
}
}
public void assignMatch(int id, int matchId)
{
ResultSet resultSet = null;
PreparedStatement preparedStatement = null;
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(UPDATE_MATCH_STATUS);
preparedStatement.setInt(1, matchId);
preparedStatement.setString(2, "Awaiting Confirmation");
preparedStatement.setInt(3, id);
preparedStatement.executeUpdate();
}
}
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();
}
}
}
}
public void removeAssignedMatch(int assignedMatch)
{
System.out.println("Resetting Records for " + assignedMatch);
PreparedStatement preparedStatement = null;
try
{
synchronized (_connectionLock)
{
if (_connection.isClosed())
{
_connection = DriverManager.getConnection(_connectionString, _userName, _password);
}
preparedStatement = _connection.prepareStatement(RESET_MATCH_RECORDS);
preparedStatement.setInt(1, assignedMatch);
preparedStatement.executeUpdate();
}
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
if (preparedStatement != null)
{
try
{
preparedStatement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,9 @@
package mineplex.serverdata;
public interface Data
{
/**
* @return the unique id key representing this {@link Data} object in {@link DataRepository}s.
*/
public String getDataId();
}

View File

@ -0,0 +1,30 @@
package mineplex.serverdata;
import java.util.Collection;
/**
* 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 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();
}

View File

@ -0,0 +1,344 @@
package mineplex.serverdata;
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 redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisConnectionException;
public class RedisDataRepository<T extends Data> implements DataRepository<T>
{
// The delimiter character used for redis key paths
public final char KEY_DELIMITER = '.';
// The pool used to retrieve jedis instances.
private JedisPool _jedisPool;
public JedisPool getJedisPool() { return _jedisPool; }
// The geographical region of the servers stored by this ServerRepository
private Region _region;
private Class<T> elementType;
private String elementLabel;
/**
* Class constructor
* @param host
* @param port
* @param region
*/
public RedisDataRepository(String host, int port, Region region,
Class<T> elementType, String elementLabel)
{
this._jedisPool = new JedisPool(new JedisPoolConfig(), host, port);
this._region = region;
this.elementType = elementType;
this.elementLabel = elementLabel;
}
public RedisDataRepository(Region region, Class<T> elementType, String elementLabel)
{
this(ServerManager.DEFAULT_REDIS_HOST, ServerManager.DEFAULT_REDIS_PORT, region,
elementType, elementLabel);
}
public String getElementSetKey()
{
return concatenate("data", elementLabel, _region.toString());
}
public String generateKey(T element)
{
return generateKey(element.getDataId());
}
public String generateKey(String dataId)
{
return concatenate(getElementSetKey(), dataId);
}
@Override
public Collection<T> getElements()
{
Collection<T> elements = new HashSet<T>();
Jedis jedis = _jedisPool.getResource();
try
{
Pipeline pipeline = jedis.pipelined();
List<Response<String>> responses = new ArrayList<Response<String>>();
for (String dataId : getActiveElements())
{
responses.add(pipeline.get(generateKey(dataId)));
}
pipeline.sync();
for (Response<String> response : responses)
{
String serializedData = response.get();
T element = deserialize(serializedData);
if (element != null)
{
elements.add(element);
}
}
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
return elements;
}
@Override
public T getElement(String dataId)
{
T element = null;
Jedis jedis = _jedisPool.getResource();
try
{
String key = generateKey(dataId);
String serializedData = jedis.get(key);
element = deserialize(serializedData);
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
return element;
}
@Override
public void addElement(T element, int timeout)
{
Jedis jedis = _jedisPool.getResource();
try
{
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();
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
}
@Override
public void addElement(T element)
{
addElement(element, 1000 * 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)
{
Jedis jedis = _jedisPool.getResource();
try
{
String setKey = getElementSetKey();
String dataKey = generateKey(dataId);
Transaction transaction = jedis.multi();
transaction.set(dataKey, null);
transaction.zrem(setKey, dataId);
transaction.exec();
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
}
@Override
public boolean elementExists(String dataId)
{
return getElement(dataId) != null;
}
@Override
public int clean()
{
Jedis jedis = _jedisPool.getResource();
try
{
for (String dataId : getDeadElements())
{
String dataKey = generateKey(dataId);
Transaction transaction = jedis.multi();
transaction.del(dataKey);
transaction.zrem(getElementSetKey(), dataId);
transaction.exec();
}
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
return 0;
}
protected Set<String> getActiveElements()
{
Set<String> dataIds = new HashSet<String>();
Jedis jedis = _jedisPool.getResource();
try
{
String min = "(" + currentTime();
String max = "+inf";
dataIds = jedis.zrangeByScore(getElementSetKey(), min, max);
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
return dataIds;
}
protected Set<String> getDeadElements()
{
Set<String> dataIds = new HashSet<String>();
Jedis jedis = _jedisPool.getResource();
try
{
String min = "-inf";
String max = currentTime() + "";
dataIds = jedis.zrangeByScore(getElementSetKey(), min, max);
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
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.currentTimeMillis();
}
/**
* @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);
}
}

View File

@ -17,6 +17,9 @@ import redis.clients.jedis.Transaction;
import redis.clients.jedis.Tuple; import redis.clients.jedis.Tuple;
import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisConnectionException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/** /**
* RedisServerRepository offers a Redis-based implementation of {@link ServerRepository} * RedisServerRepository offers a Redis-based implementation of {@link ServerRepository}
* using a mixture of hash and JSON encoded storage. * using a mixture of hash and JSON encoded storage.
@ -69,12 +72,7 @@ public class RedisServerRepository implements ServerRepository
for (Response<String> response : responses) for (Response<String> response : responses)
{ {
String serializedData = response.get(); String serializedData = response.get();
MinecraftServer server = Utility.deserialize(serializedData, MinecraftServer.class); servers.add(Utility.deserialize(serializedData, MinecraftServer.class));
if (server != null)
{
servers.add(server);
}
} }
} }
catch (JedisConnectionException exception) catch (JedisConnectionException exception)
@ -94,6 +92,22 @@ public class RedisServerRepository implements ServerRepository
return servers; 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 @Override
public MinecraftServer getServerStatus(String serverName) public MinecraftServer getServerStatus(String serverName)
{ {
@ -135,7 +149,7 @@ public class RedisServerRepository implements ServerRepository
String serverName = serverData.getName(); String serverName = serverData.getName();
String setKey = concatenate("serverstatus", "minecraft", _region.toString()); String setKey = concatenate("serverstatus", "minecraft", _region.toString());
String dataKey = concatenate(setKey, serverName); String dataKey = concatenate(setKey, serverName);
long expiry = Long.parseLong(jedis.time().get(0)) + timeout; long expiry = Utility.currentTimeMillis() + timeout;
Transaction transaction = jedis.multi(); Transaction transaction = jedis.multi();
transaction.set(dataKey, serializedData); transaction.set(dataKey, serializedData);
@ -169,7 +183,7 @@ public class RedisServerRepository implements ServerRepository
String dataKey = concatenate(setKey, serverName); String dataKey = concatenate(setKey, serverName);
Transaction transaction = jedis.multi(); Transaction transaction = jedis.multi();
transaction.del(dataKey); transaction.set(dataKey, null);
transaction.zrem(setKey, serverName); transaction.zrem(setKey, serverName);
transaction.exec(); transaction.exec();
} }
@ -253,7 +267,7 @@ public class RedisServerRepository implements ServerRepository
{ {
for (MinecraftServer minecraftServer : getServerStatuses()) for (MinecraftServer minecraftServer : getServerStatuses())
{ {
if (serverGroups.containsKey(minecraftServer.getGroup()) && minecraftServer.getPublicAddress().equalsIgnoreCase(server.getPrivateAddress())) if (serverGroups.containsKey(minecraftServer.getGroup()))
{ {
ServerGroup serverGroup = serverGroups.get(minecraftServer.getGroup()); ServerGroup serverGroup = serverGroups.get(minecraftServer.getGroup());
server.incrementServerCount(serverGroup); server.incrementServerCount(serverGroup);
@ -311,7 +325,7 @@ public class RedisServerRepository implements ServerRepository
try try
{ {
String min = "(" + jedis.time().get(0); String min = "(" + Utility.currentTimeMillis();
String max = "+inf"; String max = "+inf";
names = jedis.zrangeByScore(key, min, max); names = jedis.zrangeByScore(key, min, max);
} }
@ -332,6 +346,38 @@ public class RedisServerRepository implements ServerRepository
return names; 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>();
Jedis jedis = _jedisPool.getResource();
try
{
String min = "-inf";
String max = Utility.currentTimeMillis() + "";
names = jedis.zrangeByScore(key, min, max);
}
catch (JedisConnectionException exception)
{
exception.printStackTrace();
_jedisPool.returnBrokenResource(jedis);
jedis = null;
}
finally
{
if (jedis != null)
{
_jedisPool.returnResource(jedis);
}
}
return names;
}
/** /**
* @param elements - the elements to concatenate together * @param elements - the elements to concatenate together
* @return the concatenated form of all {@code elements} * @return the concatenated form of all {@code elements}

View File

@ -10,4 +10,5 @@ public enum Region
{ {
US, US,
EU, EU,
ALL;
} }

View File

@ -0,0 +1,51 @@
package mineplex.serverdata;
public abstract class ServerCommand
{
// The names of servers targetted to receive this ServerCommand.
private String[] targetServers;
/**
* Class constructor
* @param targetServers
*/
public ServerCommand(String... targetServers)
{
this.targetServers = targetServers;
}
/**
* Run the command on it's destination target server.
*/
public abstract void run();
/**
* @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 (targetServers == null || targetServers.length == 0) // Targets all online servers
return true;
for (String targetServer : targetServers)
{
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);
}
}

View File

@ -0,0 +1,60 @@
package mineplex.serverdata;
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 = message.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)
{
}
}

View File

@ -0,0 +1,133 @@
package mineplex.serverdata;
import java.util.HashMap;
import java.util.Map;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class ServerCommandManager
{
// The singleton instance of ServerCommandManager
private static ServerCommandManager instance;
public final String SERVER_COMMANDS_CHANNEL = "commands.server";
private JedisPool _jedisPool;
private Map<String, Class<? extends ServerCommand>> commandTypes;
/**
* Private class constructor to prevent non-singleton instances.
*/
private ServerCommandManager()
{
this._jedisPool = new JedisPool(new JedisPoolConfig(), ServerManager.DEFAULT_REDIS_HOST,
ServerManager.DEFAULT_REDIS_PORT);
this.commandTypes = new HashMap<String, Class<? extends ServerCommand>>();
initialize();
}
/**
* Initialize the ServerCommandManager by subscribing to the
* redis network.
*/
private void initialize()
{
final Jedis jedis = _jedisPool.getResource();
// Spin up a new thread and subscribe to the Redis pubsub network
Thread thread = new Thread(new Runnable()
{
public void run()
{
try
{
jedis.psubscribe(new ServerCommandListener(), SERVER_COMMANDS_CHANNEL + ":*");
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
_jedisPool.returnResource(jedis);
}
}
});
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(ServerCommand serverCommand)
{
Jedis jedis = _jedisPool.getResource();
try
{
String commandType = serverCommand.getClass().toString();
String serializedCommand = Utility.serialize(serverCommand);
jedis.publish(SERVER_COMMANDS_CHANNEL + ":" + commandType, serializedCommand);
}
catch (Exception exception)
{
exception.printStackTrace();
}
finally
{
_jedisPool.returnResource(jedis);
}
}
/**
* Handle an incoming (serialized) {@link ServerCommand}.
* @param commandType - the type of command being received
* @param serializedCommand - the serialized {@link ServerCommand} data.
*/
public void handleCommand(String commandType, String serializedCommand)
{
if (commandTypes.containsKey(commandType))
{
ServerCommand serverCommand = Utility.deserialize(serializedCommand, commandTypes.get(commandType));
if (serverCommand.isTargetServer("THIS SERVER NAME HERE")) // TODO: Find server name ref
{
// TODO: Run synchronously?
serverCommand.run(); // Run the server command
}
}
}
/**
* Register a new type of {@link ServerCommand}.
* @param commandType - the {@link ServerCommand} type to register.
*/
public void registerCommandType(Class<? extends ServerCommand> commandType)
{
String commandName = commandType.toString();
if (!commandTypes.containsKey(commandName))
{
commandTypes.put(commandName, commandType);
}
}
/**
* @return the singleton instance of ServerCommandManager
*/
public static ServerCommandManager getInstance()
{
if (instance == null)
{
instance = new ServerCommandManager();
}
return instance;
}
}

View File

@ -1,7 +1,6 @@
package mineplex.serverdata; package mineplex.serverdata;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
/** /**
* The ServerRepository is used for storing/retrieving active sessions * The ServerRepository is used for storing/retrieving active sessions
@ -19,6 +18,8 @@ public interface ServerRepository
*/ */
public Collection<MinecraftServer> getServerStatuses(); public Collection<MinecraftServer> getServerStatuses();
public Collection<MinecraftServer> getServersByGroup(String serverGroup);
/** /**
* @param serverName - the name of the {@link MinecraftServer} to be fetched. * @param serverName - the name of the {@link MinecraftServer} to be fetched.
* @return the currently active {@link MinecraftServer} with a matching {@code serverName}, * @return the currently active {@link MinecraftServer} with a matching {@code serverName},
@ -62,5 +63,6 @@ public interface ServerRepository
*/ */
public Collection<ServerGroup> getServerGroups(); public Collection<ServerGroup> getServerGroups();
Collection<MinecraftServer> getDeadServers(); public Collection<MinecraftServer> getDeadServers();
} }

View File

@ -1,5 +1,9 @@
package mineplex.serverdata; package mineplex.serverdata;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@ -15,6 +19,9 @@ public class Utility
private static Gson _gson = new GsonBuilder().create(); private static Gson _gson = new GsonBuilder().create();
public static Gson getGson() { return _gson; } public static Gson getGson() { return _gson; }
// Public static jedis pool for interacting with central default jedis repo.
private static JedisPool _jedisPool;
/** /**
* @param object - the (non-null) object to serialize * @param object - the (non-null) object to serialize
* @return the serialized form of {@code object}. * @return the serialized form of {@code object}.
@ -51,4 +58,36 @@ public class Utility
return result; return result;
} }
/**
* @return the current timestamp (in milliseconds) fetched from the central jedis repository
* for synced timestamps.
*/
public static long currentTimeMillis()
{
long currentTime = 0;
Jedis jedis = getPool().getResource();
try
{
currentTime = Long.parseLong(jedis.time().get(0));
}
finally
{
getPool().returnResource(jedis);
}
return currentTime;
}
public static JedisPool getPool()
{
if (_jedisPool == null)
{
_jedisPool = new JedisPool(new JedisPoolConfig(),
ServerManager.DEFAULT_REDIS_HOST, ServerManager.DEFAULT_REDIS_PORT);
}
return _jedisPool;
}
} }