From 2a421cdd8dab340201ff7465bdbe5a65dfb9d79f Mon Sep 17 00:00:00 2001 From: md_5 Date: Sat, 1 Jun 2013 17:29:14 +1000 Subject: [PATCH] Close #306 use SQLite for reconnect locations --- .../net/md_5/bungee/api/ReconnectHandler.java | 14 ++- .../main/java/net/md_5/bungee/BungeeCord.java | 4 +- .../net/md_5/bungee/YamlReconnectHandler.java | 101 ------------------ .../reconnect/AbstractReconnectManager.java | 36 +++++++ .../bungee/reconnect/SQLReconnectHandler.java | 93 ++++++++++++++++ 5 files changed, 143 insertions(+), 105 deletions(-) delete mode 100644 proxy/src/main/java/net/md_5/bungee/YamlReconnectHandler.java create mode 100644 proxy/src/main/java/net/md_5/bungee/reconnect/AbstractReconnectManager.java create mode 100644 proxy/src/main/java/net/md_5/bungee/reconnect/SQLReconnectHandler.java diff --git a/api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java b/api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java index 0a90c123..8b1b48af 100644 --- a/api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java +++ b/api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java @@ -12,7 +12,7 @@ public interface ReconnectHandler * @param player the connecting player * @return the server to connect to */ - public ServerInfo getServer(ProxiedPlayer player); + ServerInfo getServer(ProxiedPlayer player); /** * Save the server of this player before they disconnect so it can be @@ -20,12 +20,20 @@ public interface ReconnectHandler * * @param player the player to save */ - public void setServer(ProxiedPlayer player); + void setServer(ProxiedPlayer player); // TOOD: String + String arguments? /** * Save all pending reconnect locations. Whilst not used for database * connections, this method will be called at a predefined interval to allow * the saving of reconnect files. */ - public void save(); + void save(); + + /** + * Close all connections indicating that the proxy is about to shutdown and + * all data should be saved. No new requests will be made after this method + * has been called. + * + */ + void close(); } diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 747a2f44..a7b492ea 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -1,5 +1,6 @@ package net.md_5.bungee; +import net.md_5.bungee.reconnect.SQLReconnectHandler; import net.md_5.bungee.scheduler.BungeeScheduler; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.ning.http.client.AsyncHttpClient; @@ -198,7 +199,7 @@ public class BungeeCord extends ProxyServer config.load(); if ( reconnectHandler == null ) { - reconnectHandler = new YamlReconnectHandler(); + reconnectHandler = new SQLReconnectHandler(); } isRunning = true; @@ -302,6 +303,7 @@ public class BungeeCord extends ProxyServer getLogger().info( "Saving reconnect locations" ); reconnectHandler.save(); + reconnectHandler.close(); saveThread.cancel(); metricsThread.cancel(); diff --git a/proxy/src/main/java/net/md_5/bungee/YamlReconnectHandler.java b/proxy/src/main/java/net/md_5/bungee/YamlReconnectHandler.java deleted file mode 100644 index 29f179df..00000000 --- a/proxy/src/main/java/net/md_5/bungee/YamlReconnectHandler.java +++ /dev/null @@ -1,101 +0,0 @@ -package net.md_5.bungee; - -import com.google.common.base.Preconditions; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.ReconnectHandler; -import net.md_5.bungee.api.config.ListenerInfo; -import net.md_5.bungee.api.config.ServerInfo; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import org.yaml.snakeyaml.Yaml; - -public class YamlReconnectHandler implements ReconnectHandler -{ - - private final Yaml yaml = new Yaml(); - private final File readFile = new File( "locations.yml" ); - private final File writeFile = new File( "locations.yml~" ); - /*========================================================================*/ - private Map data; - - @SuppressWarnings("unchecked") - public YamlReconnectHandler() - { - try - { - readFile.createNewFile(); - try ( FileReader rd = new FileReader( readFile ) ) - { - data = yaml.loadAs( rd, Map.class ); - } - } catch ( Exception ex ) - { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not load reconnect locations", ex ); - readFile.delete(); - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Reconnect locations have been reset, do NOT worry about this error" ); - } - - if ( data == null ) - { - data = new ConcurrentHashMap<>(); - } else - { - data = new ConcurrentHashMap<>( data ); - } - } - - @Override - public ServerInfo getServer(ProxiedPlayer player) - { - ListenerInfo listener = player.getPendingConnection().getListener(); - String name; - String forced = listener.getForcedHosts().get( player.getPendingConnection().getVirtualHost().getHostName().toLowerCase() ); - if ( forced == null && listener.isForceDefault() ) - { - forced = listener.getDefaultServer(); - } - - String server = ( forced == null ) ? data.get( key( player ) ) : forced; - name = ( server != null ) ? server : listener.getDefaultServer(); - ServerInfo info = ProxyServer.getInstance().getServerInfo( name ); - if ( info == null ) - { - info = ProxyServer.getInstance().getServerInfo( listener.getDefaultServer() ); - } - Preconditions.checkState( info != null, "Default server not defined" ); - return info; - } - - @Override - public void setServer(ProxiedPlayer player) - { - data.put( key( player ), player.getServer().getInfo().getName() ); - } - - private String key(ProxiedPlayer player) - { - InetSocketAddress host = player.getPendingConnection().getVirtualHost(); - return player.getName() + ";" + host.getHostString() + ":" + host.getPort(); - } - - @Override - public synchronized void save() - { - try ( FileWriter wr = new FileWriter( writeFile ) ) - { - yaml.dump( data, wr ); - readFile.delete(); - writeFile.renameTo( readFile ); - } catch ( IOException ex ) - { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not save reconnect locations", ex ); - } - } -} diff --git a/proxy/src/main/java/net/md_5/bungee/reconnect/AbstractReconnectManager.java b/proxy/src/main/java/net/md_5/bungee/reconnect/AbstractReconnectManager.java new file mode 100644 index 00000000..b33689fc --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/reconnect/AbstractReconnectManager.java @@ -0,0 +1,36 @@ +package net.md_5.bungee.reconnect; + +import com.google.common.base.Preconditions; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ReconnectHandler; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public abstract class AbstractReconnectManager implements ReconnectHandler +{ + + @Override + public ServerInfo getServer(ProxiedPlayer player) + { + ListenerInfo listener = player.getPendingConnection().getListener(); + String name; + String forced = listener.getForcedHosts().get( player.getPendingConnection().getVirtualHost().getHostName().toLowerCase() ); + if ( forced == null && listener.isForceDefault() ) + { + forced = listener.getDefaultServer(); + } + + String server = ( forced == null ) ? getStoredServer( player ) : forced; + name = ( server != null ) ? server : listener.getDefaultServer(); + ServerInfo info = ProxyServer.getInstance().getServerInfo( name ); + if ( info == null ) + { + info = ProxyServer.getInstance().getServerInfo( listener.getDefaultServer() ); + } + Preconditions.checkState( info != null, "Default server not defined" ); + return info; + } + + protected abstract String getStoredServer(ProxiedPlayer player); +} diff --git a/proxy/src/main/java/net/md_5/bungee/reconnect/SQLReconnectHandler.java b/proxy/src/main/java/net/md_5/bungee/reconnect/SQLReconnectHandler.java new file mode 100644 index 00000000..f362b044 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/reconnect/SQLReconnectHandler.java @@ -0,0 +1,93 @@ +package net.md_5.bungee.reconnect; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public class SQLReconnectHandler extends AbstractReconnectManager +{ + + private final Connection connection; + + public SQLReconnectHandler() throws ClassNotFoundException, SQLException + { + Class.forName( "org.sqlite.JDBC" ); + connection = DriverManager.getConnection( "jdbc:sqlite:bungee.sqlite" ); + + try ( PreparedStatement ps = connection.prepareStatement( + "CREATE TABLE IF NOT EXISTS players (" + + "playerId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + + "username TEXT NOT NULL UNIQUE COLLATE NOCASE," + + "seen INTEGER," + + "server TEXT" + + ");" ) ) + { + ps.executeUpdate(); + } + } + + @Override + protected String getStoredServer(ProxiedPlayer player) + { + String server = null; + try ( PreparedStatement ps = connection.prepareStatement( "SELECT server FROM players WHERE username = ?" ) ) + { + ps.setString( 1, player.getName() ); + try ( ResultSet rs = ps.executeQuery() ) + { + if ( rs.next() ) + { + server = rs.getString( 1 ); + } else + { + try ( PreparedStatement playerUpdate = connection.prepareStatement( "INSERT INTO players( username ) VALUES( ? )" ) ) + { + playerUpdate.setString( 1, player.getName() ); + playerUpdate.executeUpdate(); + } + } + } + } catch ( SQLException ex ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not load location for player " + player.getName(), ex ); + } + return server; + } + + @Override + public void setServer(ProxiedPlayer player) + { + + try ( PreparedStatement ps = connection.prepareStatement( "UPDATE players SET server = ? WHERE username = ?" ) ) + { + ps.setString( 1, player.getServer().getInfo().getName() ); + ps.setString( 2, player.getName() ); + ps.executeUpdate(); + } catch ( SQLException ex ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not save location for player " + player.getName() + " on server " + player.getServer().getInfo().getName(), ex ); + } + } + + @Override + public void save() + { + } + + @Override + public void close() + { + try + { + connection.close(); + } catch ( SQLException ex ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Error closing SQLite connection", ex ); + } + } +}