소스 검색

Abstract our database operations into a single class.

This allows the logic between SQL and Flatfile to remain more hidden in
most cases and makes the code easier to read.
GJ 12 년 전
부모
커밋
eea5784527
18개의 변경된 파일1189개의 추가작업 그리고 1384개의 파일을 삭제
  1. 4 14
      src/main/java/com/gmail/nossr50/commands/database/McpurgeCommand.java
  2. 4 16
      src/main/java/com/gmail/nossr50/commands/database/McremoveCommand.java
  3. 5 55
      src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java
  4. 10 18
      src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java
  5. 33 613
      src/main/java/com/gmail/nossr50/database/DatabaseManager.java
  6. 50 85
      src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java
  7. 628 0
      src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java
  8. 411 500
      src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java
  9. 3 15
      src/main/java/com/gmail/nossr50/mcMMO.java
  10. 4 2
      src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandAsyncTask.java
  11. 10 14
      src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandDisplayTask.java
  12. 3 3
      src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandAsyncTask.java
  13. 12 12
      src/main/java/com/gmail/nossr50/runnables/database/SQLConversionTask.java
  14. 2 2
      src/main/java/com/gmail/nossr50/runnables/database/SQLReconnectTask.java
  15. 4 14
      src/main/java/com/gmail/nossr50/runnables/database/UserPurgeTask.java
  16. 5 2
      src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileSaveTask.java
  17. 0 16
      src/main/java/com/gmail/nossr50/util/player/UserManager.java
  18. 1 3
      src/main/java/net/shatteredlands/shatt/backup/ZipLibrary.java

+ 4 - 14
src/main/java/com/gmail/nossr50/commands/database/McpurgeCommand.java

@@ -6,9 +6,8 @@ import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
 import org.bukkit.command.TabExecutor;
 
+import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.DatabaseManager;
-import com.gmail.nossr50.database.LeaderboardManager;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.Permissions;
 
@@ -24,19 +23,10 @@ public class McpurgeCommand implements TabExecutor {
 
         switch (args.length) {
             case 0:
-                if (Config.getInstance().getUseMySQL()) {
-                    DatabaseManager.purgePowerlessSQL();
+                mcMMO.databaseManager.purgePowerlessUsers();
 
-                    if (Config.getInstance().getOldUsersCutoff() != -1) {
-                        DatabaseManager.purgeOldSQL();
-                    }
-                }
-                else {
-                    LeaderboardManager.purgePowerlessFlatfile();
-
-                    if (Config.getInstance().getOldUsersCutoff() != -1) {
-                        LeaderboardManager.purgeOldFlatfile();
-                    }
+                if (Config.getInstance().getOldUsersCutoff() != -1) {
+                    mcMMO.databaseManager.purgeOldUsers();
                 }
 
                 sender.sendMessage(LocaleLoader.getString("Commands.mcpurge.Success"));

+ 4 - 16
src/main/java/com/gmail/nossr50/commands/database/McremoveCommand.java

@@ -9,12 +9,9 @@ import org.bukkit.command.CommandSender;
 import org.bukkit.command.TabExecutor;
 import org.bukkit.util.StringUtil;
 
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.DatabaseManager;
-import com.gmail.nossr50.database.LeaderboardManager;
+import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.commands.CommandUtils;
 import com.gmail.nossr50.util.player.UserManager;
@@ -35,20 +32,11 @@ public class McremoveCommand implements TabExecutor {
                     return true;
                 }
 
-                /* MySQL */
-                if (Config.getInstance().getUseMySQL()) {
-                    String tablePrefix = Config.getInstance().getMySQLTablePrefix();
-
-                    if (DatabaseManager.update("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.user = '" + args[0] + "'") != 0) {
-                        Misc.profileCleanup(args[0]);
-                        sender.sendMessage(LocaleLoader.getString("Commands.mcremove.Success", args[0]));
-                    }
+                if (mcMMO.databaseManager.removeUser(args[0])) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.mcremove.Success", args[0]));
                 }
                 else {
-                    if (LeaderboardManager.removeFlatFileUser(args[0])) {
-                        Misc.profileCleanup(args[0]);
-                        sender.sendMessage(LocaleLoader.getString("Commands.mcremove.Success", args[0]));
-                    }
+                    sender.sendMessage(args[0] + " could not be removed from the database."); // Pretty sure this should NEVER happen.
                 }
 
                 return true;

+ 5 - 55
src/main/java/com/gmail/nossr50/commands/player/McrankCommand.java

@@ -10,18 +10,12 @@ import org.bukkit.command.TabExecutor;
 import org.bukkit.util.StringUtil;
 
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.LeaderboardManager;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
-import com.gmail.nossr50.datatypes.skills.SkillType;
-import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.runnables.commands.McrankCommandAsyncTask;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.commands.CommandUtils;
 import com.gmail.nossr50.util.player.UserManager;
-import com.gmail.nossr50.util.skills.SkillUtils;
-
 import com.google.common.collect.ImmutableList;
 
 public class McrankCommand implements TabExecutor {
@@ -38,13 +32,7 @@ public class McrankCommand implements TabExecutor {
                     return true;
                 }
 
-                if (Config.getInstance().getUseMySQL()) {
-                    sqlDisplay(sender, sender.getName());
-                }
-                else {
-                    flatfileDisplay(sender, sender.getName());
-                }
-
+                display(sender, sender.getName());
                 return true;
 
             case 1:
@@ -62,18 +50,12 @@ public class McrankCommand implements TabExecutor {
                     if (CommandUtils.tooFar(sender, mcMMOPlayer.getPlayer(), Permissions.mcrankFar(sender))) {
                         return true;
                     }
-
-                } else if (CommandUtils.inspectOffline(sender, new PlayerProfile(playerName, false), Permissions.mcrankOffline(sender))) {
-                    return true;
-                }
-
-                if (Config.getInstance().getUseMySQL()) {
-                    sqlDisplay(sender, playerName);
                 }
-                else {
-                    flatfileDisplay(sender, playerName);
+                else if (CommandUtils.inspectOffline(sender, new PlayerProfile(playerName, false), Permissions.mcrankOffline(sender))) {
+                    return true;
                 }
 
+                display(sender, playerName);
                 return true;
 
             default:
@@ -92,39 +74,7 @@ public class McrankCommand implements TabExecutor {
         }
     }
 
-    private void flatfileDisplay(CommandSender sender, String playerName) {
-        LeaderboardManager.updateLeaderboards(); // Make sure the information is up to date
-
-        sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Heading"));
-        sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Player", playerName));
-
-        for (SkillType skillType : SkillType.values()) {
-            int[] rankInts = LeaderboardManager.getPlayerRank(playerName, skillType);
-
-            if (!Permissions.skillEnabled(sender, skillType) || skillType.isChildSkill()) {
-                continue;
-            }
-
-            if (rankInts[1] == 0) {
-                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillUtils.getSkillName(skillType), LocaleLoader.getString("Commands.mcrank.Unranked"))); // Don't bother showing ranking for players without skills
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillUtils.getSkillName(skillType), rankInts[0]));
-            }
-        }
-
-        // Show the powerlevel ranking
-        int[] rankInts = LeaderboardManager.getPlayerRank(playerName);
-
-        if (rankInts[1] == 0) {
-            sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Overall", LocaleLoader.getString("Commands.mcrank.Unranked"))); // Don't bother showing ranking for players without skills
-        }
-        else {
-            sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Overall", rankInts[0]));
-        }
-    }
-
-    private void sqlDisplay(CommandSender sender, String playerName) {
+    private void display(CommandSender sender, String playerName) {
         new McrankCommandAsyncTask(playerName, sender).runTaskAsynchronously(mcMMO.p);
     }
 }

+ 10 - 18
src/main/java/com/gmail/nossr50/commands/player/MctopCommand.java

@@ -11,7 +11,7 @@ import org.bukkit.util.StringUtil;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.LeaderboardManager;
+import com.gmail.nossr50.database.FlatfileDatabaseManager;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.runnables.commands.MctopCommandAsyncTask;
@@ -26,17 +26,14 @@ public class MctopCommand implements TabExecutor {
 
     @Override
     public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        boolean useMySQL = Config.getInstance().getUseMySQL();
-
-
         switch (args.length) {
             case 0:
-                display(1, "ALL", sender, useMySQL, command);
+                display(1, "ALL", sender, command);
                 return true;
 
             case 1:
                 if (StringUtils.isInt(args[0])) {
-                    display(Math.abs(Integer.parseInt(args[0])), "ALL", sender, useMySQL, command);
+                    display(Math.abs(Integer.parseInt(args[0])), "ALL", sender, command);
                     return true;
                 }
 
@@ -44,7 +41,7 @@ public class MctopCommand implements TabExecutor {
                     return true;
                 }
 
-                display(1, skill.toString(), sender, useMySQL, command);
+                display(1, skill.toString(), sender, command);
                 return true;
 
             case 2:
@@ -56,7 +53,7 @@ public class MctopCommand implements TabExecutor {
                     return true;
                 }
 
-                display(Math.abs(Integer.parseInt(args[1])), skill.toString(), sender, useMySQL, command);
+                display(Math.abs(Integer.parseInt(args[1])), skill.toString(), sender, command);
                 return true;
 
             default:
@@ -74,19 +71,14 @@ public class MctopCommand implements TabExecutor {
         }
     }
 
-    private void display(int page, String skill, CommandSender sender, boolean sql, Command command) {
+    private void display(int page, String skill, CommandSender sender, Command command) {
         if (!skill.equalsIgnoreCase("all") && !Permissions.mctop(sender, this.skill)) {
             sender.sendMessage(command.getPermissionMessage());
             return;
         }
 
-        if (sql) {
-            if (skill.equalsIgnoreCase("all")) {
-                sqlDisplay(page, "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing", sender);
-            }
-            else {
-                sqlDisplay(page, skill, sender);
-            }
+        if (Config.getInstance().getUseMySQL()) {
+            sqlDisplay(page, skill, sender);
         }
         else {
             flatfileDisplay(page, skill, sender);
@@ -94,7 +86,7 @@ public class MctopCommand implements TabExecutor {
     }
 
     private void flatfileDisplay(int page, String skill, CommandSender sender) {
-        LeaderboardManager.updateLeaderboards(); // Make sure we have the latest information
+        FlatfileDatabaseManager.updateLeaderboards(); // Make sure we have the latest information
 
         if (skill.equalsIgnoreCase("all")) {
             sender.sendMessage(LocaleLoader.getString("Commands.PowerLevel.Leaderboard"));
@@ -105,7 +97,7 @@ public class MctopCommand implements TabExecutor {
 
         int position = (page * 10) - 9;
 
-        for (String playerStat : LeaderboardManager.retrieveInfo(skill, page)) {
+        for (String playerStat : FlatfileDatabaseManager.retrieveInfo(skill, page)) {
             if (playerStat == null) {
                 continue;
             }

+ 33 - 613
src/main/java/com/gmail/nossr50/database/DatabaseManager.java

@@ -1,643 +1,63 @@
 package com.gmail.nossr50.database;
 
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
+import java.io.File;
+import java.io.IOException;
 
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.datatypes.database.DatabaseUpdateType;
-import com.gmail.nossr50.datatypes.skills.SkillType;
-import com.gmail.nossr50.runnables.database.SQLReconnectTask;
 import com.gmail.nossr50.util.Misc;
 
-public final class DatabaseManager {
-    private static String connectionString;
+public class DatabaseManager {
+    private final mcMMO plugin;
+    private final boolean isUsingSQL;
+    private File usersFile;
 
-    private static String tablePrefix = Config.getInstance().getMySQLTablePrefix();
-    private static Connection connection = null;
+    public DatabaseManager(final mcMMO plugin, final boolean isUsingSQL) {
+        this.plugin = plugin;
+        this.isUsingSQL = isUsingSQL;
 
-    // Scale waiting time by this much per failed attempt
-    private static final double SCALING_FACTOR = 40;
-
-    // Minimum wait in nanoseconds (default 500ms)
-    private static final long MIN_WAIT = 500L * 1000000L;
-
-    // Maximum time to wait between reconnects (default 5 minutes)
-    private static final long MAX_WAIT = 5L * 60L * 1000L * 1000000L;
-
-    // How long to wait when checking if connection is valid (default 3 seconds)
-    private static final int VALID_TIMEOUT = 3;
-
-    // When next to try connecting to Database in nanoseconds
-    private static long nextReconnectTimestamp = 0L;
-
-    // How many connection attempts have failed
-    private static int reconnectAttempt = 0;
-
-    private static final long ONE_MONTH = 2630000000L;
-
-    private DatabaseManager() {}
-
-    /**
-     * Attempt to connect to the mySQL database.
-     */
-    public static void connect() {
-        Config configInstance = Config.getInstance();
-        connectionString = "jdbc:mysql://" + configInstance.getMySQLServerName() + ":" + configInstance.getMySQLServerPort() + "/" + configInstance.getMySQLDatabaseName();
-
-        try {
-            mcMMO.p.getLogger().info("Attempting connection to MySQL...");
-
-            // Force driver to load if not yet loaded
-            Class.forName("com.mysql.jdbc.Driver");
-            Properties connectionProperties = new Properties();
-            connectionProperties.put("user", configInstance.getMySQLUserName());
-            connectionProperties.put("password", configInstance.getMySQLUserPassword());
-            connectionProperties.put("autoReconnect", "false");
-            connectionProperties.put("maxReconnects", "0");
-            connection = DriverManager.getConnection(connectionString, connectionProperties);
-
-            mcMMO.p.getLogger().info("Connection to MySQL was a success!");
+        if (isUsingSQL) {
+            SQLDatabaseManager.checkConnected();
+            SQLDatabaseManager.createStructure();
         }
-        catch (SQLException ex) {
-            connection = null;
-
-            if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
-                mcMMO.p.getLogger().info("Connection to MySQL failed!");
-            }
-        }
-        catch (ClassNotFoundException ex) {
-            connection = null;
-
-            if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
-                mcMMO.p.getLogger().info("MySQL database driver not found!");
-            }
+        else {
+            usersFile = new File(mcMMO.getUsersFilePath());
+            createFlatfileDatabase();
+            FlatfileDatabaseManager.updateLeaderboards();
         }
     }
 
-    /**
-     * Attempt to create the database structure.
-     */
-    public static void createStructure() {
-        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` ("
-                + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
-                + "`user` varchar(40) NOT NULL,"
-                + "`lastlogin` int(32) unsigned NOT NULL,"
-                + "PRIMARY KEY (`id`),"
-                + "UNIQUE KEY `user` (`user`)) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;");
-        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "huds` ("
-                + "`user_id` int(10) unsigned NOT NULL,"
-                + "`hudtype` varchar(50) NOT NULL DEFAULT 'STANDARD',"
-                + "`mobhealthbar` varchar(50) NOT NULL DEFAULT 'HEARTS',"
-                + "PRIMARY KEY (`user_id`),"
-                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
-                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
-        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "cooldowns` ("
-                + "`user_id` int(10) unsigned NOT NULL,"
-                + "`taming` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`mining` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`woodcutting` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`repair` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`unarmed` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`herbalism` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`excavation` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`archery` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`swords` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`axes` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`acrobatics` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "`blast_mining` int(32) unsigned NOT NULL DEFAULT '0',"
-                + "PRIMARY KEY (`user_id`),"
-                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
-                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
-        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "skills` ("
-                + "`user_id` int(10) unsigned NOT NULL,"
-                + "`taming` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`mining` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`woodcutting` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`repair` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`unarmed` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`herbalism` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`excavation` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`archery` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`swords` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`axes` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`acrobatics` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "PRIMARY KEY (`user_id`),"
-                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
-                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
-        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "experience` ("
-                + "`user_id` int(10) unsigned NOT NULL,"
-                + "`taming` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`mining` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`woodcutting` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`repair` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`unarmed` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`herbalism` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`excavation` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`archery` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`swords` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`axes` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "`acrobatics` int(10) unsigned NOT NULL DEFAULT '0',"
-                + "PRIMARY KEY (`user_id`),"
-                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
-                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
-
-        checkDatabaseStructure(DatabaseUpdateType.FISHING);
-        checkDatabaseStructure(DatabaseUpdateType.BLAST_MINING);
-        checkDatabaseStructure(DatabaseUpdateType.CASCADE_DELETE);
-        checkDatabaseStructure(DatabaseUpdateType.INDEX);
-        checkDatabaseStructure(DatabaseUpdateType.MOB_HEALTHBARS);
-    }
-
-    /**
-     * Attempt to write the SQL query.
-     *
-     * @param sql Query to write.
-     * @return true if the query was successfully written, false otherwise.
-     */
-    public static boolean write(String sql) {
-        if (!checkConnected()) {
-            return false;
-        }
-
-        PreparedStatement statement = null;
-        try {
-            statement = connection.prepareStatement(sql);
-            statement.executeUpdate();
-            return true;
-        }
-        catch (SQLException ex) {
-            printErrors(ex);
-            return false;
-        }
-        finally {
-            if (statement != null) {
-                try {
-                    statement.close();
-                }
-                catch (SQLException e) {
-                    printErrors(e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the number of rows affected by either a DELETE or UPDATE query
-     *
-     * @param sql SQL query to execute
-     * @return the number of rows affected
-     */
-    public static int update(String sql) {
-        if (!checkConnected()) {
-            return 0;
-        }
-
-        int rows = 0;
-
-        PreparedStatement statement = null;
-        try {
-            statement = connection.prepareStatement(sql);
-            rows = statement.executeUpdate();
-        }
-        catch (SQLException ex) {
-            printErrors(ex);
-        }
-        finally {
-            if (statement != null) {
-                try {
-                    statement.close();
-                }
-                catch (SQLException e) {
-                    printErrors(e);
-                }
-            }
-        }
-
-        return rows;
+    public void purgePowerlessUsers() {
+        plugin.getLogger().info("Purging powerless users...");
+        plugin.getLogger().info("Purged " + (isUsingSQL ? SQLDatabaseManager.purgePowerlessSQL() : FlatfileDatabaseManager.purgePowerlessFlatfile()) + " users from the database.");
     }
 
-    /**
-     * Get the Integer. Only return first row / first field.
-     *
-     * @param sql SQL query to execute
-     * @return the value in the first row / first field
-     */
-    public static int getInt(String sql) {
-        if (!checkConnected()) {
-            return 0;
-        }
-
-        int result = 0;
-
-        PreparedStatement statement = null;
-
-        try {
-            statement = connection.prepareStatement(sql);
-            ResultSet resultSet = statement.executeQuery();
-
-            if (resultSet.next()) {
-                result = resultSet.getInt(1);
-            }
-        }
-        catch (SQLException ex) {
-            printErrors(ex);
-        }
-        finally {
-            if (statement != null) {
-                try {
-                    statement.close();
-                }
-                catch (SQLException e) {
-                    printErrors(e);
-                }
-            }
-        }
-
-        return result;
+    public void purgeOldUsers() {
+        plugin.getLogger().info("Purging old users...");
+        plugin.getLogger().info("Purged " + (isUsingSQL ? SQLDatabaseManager.purgeOldSQL() : FlatfileDatabaseManager.removeOldFlatfileUsers()) + " users from the database.");
     }
 
-    /**
-     * Check connection status and re-establish if dead or stale.
-     *
-     * If the very first immediate attempt fails, further attempts
-     * will be made in progressively larger intervals up to MAX_WAIT
-     * intervals.
-     *
-     * This allows for MySQL to time out idle connections as needed by
-     * server operator, without affecting McMMO, while still providing
-     * protection against a database outage taking down Bukkit's tick
-     * processing loop due to attemping a database connection each
-     * time McMMO needs the database.
-     *
-     * @return the boolean value for whether or not we are connected
-     */
-    public static boolean checkConnected() {
-        boolean isClosed = true;
-        boolean isValid = false;
-        boolean exists = (connection != null);
-
-        // If we're waiting for server to recover then leave early
-        if (nextReconnectTimestamp > 0 && nextReconnectTimestamp > System.nanoTime()) {
-            return false;
-        }
-
-        if (exists) {
-            try {
-                isClosed = connection.isClosed();
-            }
-            catch (SQLException e) {
-                isClosed = true;
-                e.printStackTrace();
-                printErrors(e);
-            }
-
-            if (!isClosed) {
-                try {
-                    isValid = connection.isValid(VALID_TIMEOUT);
-                }
-                catch (SQLException e) {
-                    // Don't print stack trace because it's valid to lose idle connections to the server and have to restart them.
-                    isValid = false;
-                }
-            }
-        }
-
-        // Leave if all ok
-        if (exists && !isClosed && isValid) {
-            // Housekeeping
-            nextReconnectTimestamp = 0;
-            reconnectAttempt = 0;
+    public boolean removeUser(String playerName) {
+        if (isUsingSQL ? SQLDatabaseManager.removeUserSQL(playerName) : FlatfileDatabaseManager.removeFlatFileUser(playerName)) {
+            Misc.profileCleanup(playerName);
             return true;
         }
 
-        // Cleanup after ourselves for GC and MySQL's sake
-        if (exists && !isClosed) {
-            try {
-                connection.close();
-            }
-            catch (SQLException ex) {
-                // This is a housekeeping exercise, ignore errors
-            }
-        }
-
-        // Try to connect again
-        connect();
-
-        // Leave if connection is good
-        try {
-            if (connection != null && !connection.isClosed()) {
-                // Schedule a database save if we really had an outage
-                if (reconnectAttempt > 1) {
-                    new SQLReconnectTask().runTaskLater(mcMMO.p, 5);
-                }
-                nextReconnectTimestamp = 0;
-                reconnectAttempt = 0;
-                return true;
-            }
-        }
-        catch (SQLException e) {
-            // Failed to check isClosed, so presume connection is bad and attempt later
-            e.printStackTrace();
-            printErrors(e);
-        }
-
-        reconnectAttempt++;
-        nextReconnectTimestamp = (long)(System.nanoTime() + Math.min(MAX_WAIT, (reconnectAttempt * SCALING_FACTOR * MIN_WAIT)));
         return false;
     }
 
-    /**
-     * Read SQL query.
-     *
-     * @param sql SQL query to read
-     * @return the rows in this SQL query
-     */
-    public static HashMap<Integer, ArrayList<String>> read(String sql) {
-        ResultSet resultSet;
-        HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
-
-        if (checkConnected()) {
-            PreparedStatement statement = null;
-
-            try {
-                statement = connection.prepareStatement(sql);
-                resultSet = statement.executeQuery();
-
-                while (resultSet.next()) {
-                    ArrayList<String> column = new ArrayList<String>();
-
-                    for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
-                        column.add(resultSet.getString(i));
-                    }
-
-                    rows.put(resultSet.getRow(), column);
-                }
-            }
-            catch (SQLException ex) {
-                printErrors(ex);
-            }
-            finally {
-                if (statement != null) {
-                    try {
-                        statement.close();
-                    }
-                    catch (SQLException e) {
-                        printErrors(e);
-                    }
-                }
-            }
-        }
-
-        return rows;
-    }
-
-    public static Map<String, Integer> readSQLRank(String playerName) {
-        ResultSet resultSet;
-        Map<String, Integer> skills = new HashMap<String, Integer>();
-
-        if (checkConnected()) {
-            try {
-                for (SkillType skillType : SkillType.values()) {
-                    if (skillType.isChildSkill()) {
-                        continue;
-                    }
-
-                    String skillName = skillType.name().toLowerCase();
-                    String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " +
-                                 "AND " + skillName + " > (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
-                                 "WHERE user = ?)";
-
-                    PreparedStatement statement = connection.prepareStatement(sql);
-                    statement.setString(1, playerName);
-                    resultSet = statement.executeQuery();
-
-                    resultSet.next();
-
-                    int rank = resultSet.getInt("rank");
-
-                    sql = "SELECT user, " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " +
-                          "AND " + skillName + " = (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
-                          "WHERE user = '" + playerName + "') ORDER BY user";
-
-                    statement = connection.prepareStatement(sql);
-                    resultSet = statement.executeQuery();
-
-                    while (resultSet.next()) {
-                        if (resultSet.getString("user").equalsIgnoreCase(playerName)) {
-                            skills.put(skillType.name(), rank + resultSet.getRow());
-                            break;
-                        }
-                    }
-
-                    statement.close();
-                }
-
-                String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
-                             "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 " +
-                             "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > " +
-                             "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
-                             "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?)";
-
-                PreparedStatement statement = connection.prepareStatement(sql);
-                statement.setString(1, playerName);
-                resultSet = statement.executeQuery();
-
-                resultSet.next();
-
-                int rank = resultSet.getInt("rank");
-
-                sql = "SELECT user, taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
-                      "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
-                      "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 " +
-                      "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing = " +
-                      "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
-                      "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?) ORDER BY user";
-
-                statement = connection.prepareStatement(sql);
-                statement.setString(1, playerName);
-                resultSet = statement.executeQuery();
-
-                while (resultSet.next()) {
-                    if (resultSet.getString("user").equalsIgnoreCase(playerName)) {
-                        skills.put("ALL", rank + resultSet.getRow());
-                        break;
-                    }
-                }
-
-                statement.close();
-            }
-            catch (SQLException ex) {
-                printErrors(ex);
-            }
-        }
-
-        return skills;
-    }
-
-    public static void purgePowerlessSQL() {
-        mcMMO.p.getLogger().info("Purging powerless users...");
-        HashMap<Integer, ArrayList<String>> usernames;
-
-        usernames = read("SELECT u.user FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u " + "WHERE s.user_id = u.id AND " +
-                "(s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0");
-
-        write("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.id IN (SELECT * FROM " +
-                "(SELECT u.id FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u " + "WHERE s.user_id = u.id " +
-                "AND (s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0) AS p)");
-
-        int purgedUsers = 0;
-        for (int i = 1; i <= usernames.size(); i++) {
-            String playerName = usernames.get(i).get(0);
-
-            if (playerName == null || mcMMO.p.getServer().getOfflinePlayer(playerName).isOnline()) {
-                continue;
-            }
-
-            Misc.profileCleanup(playerName);
-            purgedUsers++;
-        }
-
-        mcMMO.p.getLogger().info("Purged " + purgedUsers + " users from the database.");
-    }
-
-    public static void purgeOldSQL() {
-        mcMMO.p.getLogger().info("Purging old users...");
-        long currentTime = System.currentTimeMillis();
-        long purgeTime = ONE_MONTH * Config.getInstance().getOldUsersCutoff();
-        HashMap<Integer, ArrayList<String>> usernames = read("SELECT user FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin*1000) > " + purgeTime + ")");
-        write("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.id IN (SELECT * FROM (SELECT id FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin*1000) > " + purgeTime + ")) AS p)");
-
-        int purgedUsers = 0;
-        for (int i = 1; i <= usernames.size(); i++) {
-            String playerName = usernames.get(i).get(0);
-
-            if (playerName == null) {
-                continue;
-            }
-
-            Misc.profileCleanup(playerName);
-            purgedUsers++;
+    private void createFlatfileDatabase() {
+        if (usersFile.exists()) {
+            return;
         }
 
-        mcMMO.p.getLogger().info("Purged " + purgedUsers + " users from the database.");
-    }
-
-    /**
-     * Check database structure for missing values.
-     *
-     * @param update Type of data to check updates for
-     */
-    private static void checkDatabaseStructure(DatabaseUpdateType update) {
-        String sql = null;
-        ResultSet resultSet = null;
-        HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
-
-        switch (update) {
-            case BLAST_MINING:
-                sql = "SELECT * FROM  `" + tablePrefix + "cooldowns` ORDER BY  `" + tablePrefix + "cooldowns`.`blast_mining` ASC LIMIT 0 , 30";
-                break;
-
-            case CASCADE_DELETE:
-                write("ALTER TABLE `" + tablePrefix + "huds` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-                write("ALTER TABLE `" + tablePrefix + "experience` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-                write("ALTER TABLE `" + tablePrefix + "cooldowns` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-                write("ALTER TABLE `" + tablePrefix + "skills` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
-                break;
-
-            case FISHING:
-                sql = "SELECT * FROM  `" + tablePrefix + "experience` ORDER BY  `" + tablePrefix + "experience`.`fishing` ASC LIMIT 0 , 30";
-                break;
+        usersFile.getParentFile().mkdir();
 
-            case INDEX:
-                if (read("SHOW INDEX FROM " + tablePrefix + "skills").size() != 13 && checkConnected()) {
-                    mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
-                    write("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_taming` (`taming`) USING BTREE, "
-                            + "ADD INDEX `idx_mining` (`mining`) USING BTREE, "
-                            + "ADD INDEX `idx_woodcutting` (`woodcutting`) USING BTREE, "
-                            + "ADD INDEX `idx_repair` (`repair`) USING BTREE, "
-                            + "ADD INDEX `idx_unarmed` (`unarmed`) USING BTREE, "
-                            + "ADD INDEX `idx_herbalism` (`herbalism`) USING BTREE, "
-                            + "ADD INDEX `idx_excavation` (`excavation`) USING BTREE, "
-                            + "ADD INDEX `idx_archery` (`archery`) USING BTREE, "
-                            + "ADD INDEX `idx_swords` (`swords`) USING BTREE, "
-                            + "ADD INDEX `idx_axes` (`axes`) USING BTREE, "
-                            + "ADD INDEX `idx_acrobatics` (`acrobatics`) USING BTREE, "
-                            + "ADD INDEX `idx_fishing` (`fishing`) USING BTREE;");
-                }
-                break;
-
-            case MOB_HEALTHBARS:
-                sql = "SELECT * FROM  `" + tablePrefix + "huds` ORDER BY  `" + tablePrefix + "huds`.`mobhealthbar` ASC LIMIT 0 , 30";
-                break;
-
-            default:
-                break;
-        }
-
-        PreparedStatement statement = null;
         try {
-            if (!checkConnected()) {
-                return;
-            }
-
-            statement = connection.prepareStatement(sql);
-            resultSet = statement.executeQuery();
-
-            while (resultSet.next()) {
-                ArrayList<String> column = new ArrayList<String>();
-
-                for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
-                    column.add(resultSet.getString(i));
-                }
-
-                rows.put(resultSet.getRow(), column);
-            }
+            plugin.debug("Creating mcmmo.users file...");
+            new File(mcMMO.getUsersFilePath()).createNewFile();
         }
-        catch (SQLException ex) {
-            switch (update) {
-                case BLAST_MINING:
-                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
-                    write("ALTER TABLE `"+tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0' ;");
-                    break;
-
-                case FISHING:
-                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
-                    write("ALTER TABLE `"+tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
-                    write("ALTER TABLE `"+tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
-                    break;
-
-                case MOB_HEALTHBARS:
-                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for mob healthbars...");
-                    write("ALTER TABLE `" + tablePrefix + "huds` ADD `mobhealthbar` varchar(50) NOT NULL DEFAULT 'HEARTS' ;");
-                    break;
-
-                default:
-                    break;
-            }
-        }
-        finally {
-            if (statement != null) {
-                try {
-                    statement.close();
-                }
-                catch (SQLException e) {
-                    // Ignore the error, we're leaving
-                }
-            }
+        catch (IOException e) {
+            e.printStackTrace();
         }
     }
-
-    private static void printErrors(SQLException ex) {
-        mcMMO.p.getLogger().severe("SQLException: " + ex.getMessage());
-        mcMMO.p.getLogger().severe("SQLState: " + ex.getSQLState());
-        mcMMO.p.getLogger().severe("VendorError: " + ex.getErrorCode());
-    }
 }

+ 50 - 85
src/main/java/com/gmail/nossr50/database/LeaderboardManager.java → src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java

@@ -9,6 +9,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
@@ -16,7 +17,7 @@ import com.gmail.nossr50.datatypes.database.PlayerStat;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.util.StringUtils;
 
-public final class LeaderboardManager {
+public final class FlatfileDatabaseManager {
     private static HashMap<SkillType, List<PlayerStat>> playerStatHash = new HashMap<SkillType, List<PlayerStat>>();
     private static List<PlayerStat> powerLevels = new ArrayList<PlayerStat>();
     private static long lastUpdate = 0;
@@ -24,7 +25,7 @@ public final class LeaderboardManager {
     private static final long UPDATE_WAIT_TIME = 600000L; // 10 minutes
     private static final long ONE_MONTH = 2630000000L;
 
-    private LeaderboardManager() {}
+    private FlatfileDatabaseManager() {}
 
     /**
      * Update the leader boards.
@@ -35,6 +36,7 @@ public final class LeaderboardManager {
             return;
         }
 
+        String usersFilePath = mcMMO.getUsersFilePath();
         lastUpdate = System.currentTimeMillis(); // Log when the last update was run
         powerLevels.clear(); // Clear old values from the power levels
 
@@ -54,8 +56,7 @@ public final class LeaderboardManager {
 
         // Read from the FlatFile database and fill our arrays with information
         try {
-            FileReader file = new FileReader(mcMMO.getUsersFilePath());
-            BufferedReader in = new BufferedReader(file);
+            BufferedReader in = new BufferedReader(new FileReader(usersFilePath));
             String line = "";
             ArrayList<String> players = new ArrayList<String>();
 
@@ -72,72 +73,25 @@ public final class LeaderboardManager {
 
                 players.add(playerName);
 
-                if (data.length > 1 && StringUtils.isInt(data[1])) {
-                    mining.add(new PlayerStat(playerName, Integer.parseInt(data[1])));
-                    powerLevel += Integer.parseInt(data[1]);
-                }
-
-                if (data.length > 5 && StringUtils.isInt(data[5])) {
-                    woodcutting.add(new PlayerStat(playerName, Integer.parseInt(data[5])));
-                    powerLevel += Integer.parseInt(data[5]);
-                }
-
-                if (data.length > 7 && StringUtils.isInt(data[7])) {
-                    repair.add(new PlayerStat(playerName, Integer.parseInt(data[7])));
-                    powerLevel += Integer.parseInt(data[7]);
-                }
-
-                if (data.length > 8 && StringUtils.isInt(data[8])) {
-                    unarmed.add(new PlayerStat(playerName, Integer.parseInt(data[8])));
-                    powerLevel += Integer.parseInt(data[8]);
-                }
-
-                if (data.length > 9 && StringUtils.isInt(data[9])) {
-                    herbalism.add(new PlayerStat(playerName, Integer.parseInt(data[9])));
-                    powerLevel += Integer.parseInt(data[9]);
-                }
-
-                if (data.length > 10 && StringUtils.isInt(data[10])) {
-                    excavation.add(new PlayerStat(playerName, Integer.parseInt(data[10])));
-                    powerLevel += Integer.parseInt(data[10]);
-                }
-
-                if (data.length > 11 && StringUtils.isInt(data[11])) {
-                    archery.add(new PlayerStat(playerName, Integer.parseInt(data[11])));
-                    powerLevel += Integer.parseInt(data[11]);
-                }
-
-                if (data.length > 12 && StringUtils.isInt(data[12])) {
-                    swords.add(new PlayerStat(playerName, Integer.parseInt(data[12])));
-                    powerLevel += Integer.parseInt(data[12]);
-                }
-
-                if (data.length > 13 && StringUtils.isInt(data[13])) {
-                    axes.add(new PlayerStat(playerName, Integer.parseInt(data[13])));
-                    powerLevel += Integer.parseInt(data[13]);
-                }
-
-                if (data.length > 14 && StringUtils.isInt(data[14])) {
-                    acrobatics.add(new PlayerStat(playerName, Integer.parseInt(data[14])));
-                    powerLevel += Integer.parseInt(data[14]);
-                }
-
-                if (data.length > 24 && StringUtils.isInt(data[24])) {
-                    taming.add(new PlayerStat(playerName, Integer.parseInt(data[24])));
-                    powerLevel += Integer.parseInt(data[24]);
-                }
-
-                if (data.length > 34 && StringUtils.isInt(data[34])) {
-                    fishing.add(new PlayerStat(playerName, Integer.parseInt(data[34])));
-                    powerLevel += Integer.parseInt(data[34]);
-                }
+                powerLevel += loadStat(mining, playerName, data, 1);
+                powerLevel += loadStat(woodcutting, playerName, data, 5);
+                powerLevel += loadStat(repair, playerName, data, 7);
+                powerLevel += loadStat(unarmed, playerName, data, 8);
+                powerLevel += loadStat(herbalism, playerName, data, 9);
+                powerLevel += loadStat(excavation, playerName, data, 10);
+                powerLevel += loadStat(archery, playerName, data, 11);
+                powerLevel += loadStat(swords, playerName, data, 12);
+                powerLevel += loadStat(axes, playerName, data, 13);
+                powerLevel += loadStat(acrobatics, playerName, data, 14);
+                powerLevel += loadStat(taming, playerName, data, 24);
+                powerLevel += loadStat(fishing, playerName, data, 34);
 
                 powerLevels.add(new PlayerStat(playerName, powerLevel));
             }
             in.close();
         }
         catch (Exception e) {
-            mcMMO.p.getLogger().severe("Exception while reading " + mcMMO.getUsersFilePath() + " (Are you sure you formatted it correctly?)" + e.toString());
+            mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
         }
 
         SkillComparator c = new SkillComparator();
@@ -203,14 +157,6 @@ public final class LeaderboardManager {
         return info;
     }
 
-    public static int[] getPlayerRank(String playerName) {
-        return getPlayerRank(playerName, powerLevels);
-    }
-
-    public static int[] getPlayerRank(String playerName, SkillType skillType) {
-        return getPlayerRank(playerName, playerStatHash.get(skillType));
-    }
-
     public static boolean removeFlatFileUser(String playerName) {
         boolean worked = false;
 
@@ -266,7 +212,7 @@ public final class LeaderboardManager {
         return worked;
     }
 
-    public static void purgePowerlessFlatfile() {
+    public static int purgePowerlessFlatfile() {
         mcMMO.p.getLogger().info("Purging powerless users...");
 
         int purgedUsers = 0;
@@ -277,16 +223,10 @@ public final class LeaderboardManager {
             }
         }
 
-        mcMMO.p.getLogger().info("Purged " + purgedUsers + " users from the database.");
-    }
-
-    public static void purgeOldFlatfile() {
-        mcMMO.p.getLogger().info("Purging old users...");
-        int purgedUsers = removeOldFlatfileUsers();
-        mcMMO.p.getLogger().info("Purged " + purgedUsers + " users from the database.");
+        return purgedUsers;
     }
 
-    private static int removeOldFlatfileUsers() {
+    public static int removeOldFlatfileUsers() {
         int removedPlayers = 0;
         long currentTime = System.currentTimeMillis();
         long purgeTime = ONE_MONTH * Config.getInstance().getOldUsersCutoff();
@@ -350,22 +290,47 @@ public final class LeaderboardManager {
         return removedPlayers;
     }
 
-    private static int[] getPlayerRank(String playerName, List<PlayerStat> statsList) {
+    private static Integer getPlayerRank(String playerName, List<PlayerStat> statsList) {
         int currentPos = 1;
 
         if (statsList == null) {
-            return new int[] {0, 0};
+            return null;
         }
 
         for (PlayerStat stat : statsList) {
             if (stat.name.equalsIgnoreCase(playerName)) {
-                return new int[] {currentPos, stat.statVal};
+                return currentPos;
             }
 
             currentPos++;
         }
 
-        return new int[] {0, 0};
+        return null;
+    }
+
+    public static Map<String, Integer> getPlayerRanks(String playerName) {
+        updateLeaderboards();
+
+        Map<String, Integer> skills = new HashMap<String, Integer>();
+
+        for (SkillType skill : SkillType.values()) {
+            skills.put(playerName, getPlayerRank(playerName, playerStatHash.get(skill)));
+        }
+
+        skills.put("ALL", getPlayerRank(playerName, powerLevels));
+
+        return skills;
+    }
+
+    private static int loadStat(List<PlayerStat> statList, String playerName, String[] data, int dataIndex) {
+        if (data.length > dataIndex) {
+            int statValue = Integer.parseInt(data[dataIndex]);
+
+            statList.add(new PlayerStat(playerName, statValue));
+            return statValue;
+        }
+
+        return 0;
     }
 
     private static class SkillComparator implements Comparator<PlayerStat> {

+ 628 - 0
src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java

@@ -0,0 +1,628 @@
+package com.gmail.nossr50.database;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.database.DatabaseUpdateType;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.runnables.database.SQLReconnectTask;
+import com.gmail.nossr50.util.Misc;
+
+public final class SQLDatabaseManager {
+    private static String connectionString;
+
+    private static String tablePrefix = Config.getInstance().getMySQLTablePrefix();
+    private static Connection connection = null;
+
+    // Scale waiting time by this much per failed attempt
+    private static final double SCALING_FACTOR = 40;
+
+    // Minimum wait in nanoseconds (default 500ms)
+    private static final long MIN_WAIT = 500L * 1000000L;
+
+    // Maximum time to wait between reconnects (default 5 minutes)
+    private static final long MAX_WAIT = 5L * 60L * 1000L * 1000000L;
+
+    // How long to wait when checking if connection is valid (default 3 seconds)
+    private static final int VALID_TIMEOUT = 3;
+
+    // When next to try connecting to Database in nanoseconds
+    private static long nextReconnectTimestamp = 0L;
+
+    // How many connection attempts have failed
+    private static int reconnectAttempt = 0;
+
+    private static final long ONE_MONTH = 2630000000L;
+
+    private SQLDatabaseManager() {}
+
+    /**
+     * Attempt to connect to the mySQL database.
+     */
+    public static void connect() {
+        Config configInstance = Config.getInstance();
+        connectionString = "jdbc:mysql://" + configInstance.getMySQLServerName() + ":" + configInstance.getMySQLServerPort() + "/" + configInstance.getMySQLDatabaseName();
+
+        try {
+            mcMMO.p.getLogger().info("Attempting connection to MySQL...");
+
+            // Force driver to load if not yet loaded
+            Class.forName("com.mysql.jdbc.Driver");
+            Properties connectionProperties = new Properties();
+            connectionProperties.put("user", configInstance.getMySQLUserName());
+            connectionProperties.put("password", configInstance.getMySQLUserPassword());
+            connectionProperties.put("autoReconnect", "false");
+            connectionProperties.put("maxReconnects", "0");
+            connection = DriverManager.getConnection(connectionString, connectionProperties);
+
+            mcMMO.p.getLogger().info("Connection to MySQL was a success!");
+        }
+        catch (SQLException ex) {
+            connection = null;
+
+            if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
+                mcMMO.p.getLogger().info("Connection to MySQL failed!");
+            }
+        }
+        catch (ClassNotFoundException ex) {
+            connection = null;
+
+            if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
+                mcMMO.p.getLogger().info("MySQL database driver not found!");
+            }
+        }
+    }
+
+    /**
+     * Attempt to create the database structure.
+     */
+    public static void createStructure() {
+        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` ("
+                + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
+                + "`user` varchar(40) NOT NULL,"
+                + "`lastlogin` int(32) unsigned NOT NULL,"
+                + "PRIMARY KEY (`id`),"
+                + "UNIQUE KEY `user` (`user`)) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;");
+        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "huds` ("
+                + "`user_id` int(10) unsigned NOT NULL,"
+                + "`hudtype` varchar(50) NOT NULL DEFAULT 'STANDARD',"
+                + "`mobhealthbar` varchar(50) NOT NULL DEFAULT 'HEARTS',"
+                + "PRIMARY KEY (`user_id`),"
+                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
+                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
+        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "cooldowns` ("
+                + "`user_id` int(10) unsigned NOT NULL,"
+                + "`taming` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`mining` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`woodcutting` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`repair` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`unarmed` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`herbalism` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`excavation` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`archery` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`swords` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`axes` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`acrobatics` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "`blast_mining` int(32) unsigned NOT NULL DEFAULT '0',"
+                + "PRIMARY KEY (`user_id`),"
+                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
+                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
+        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "skills` ("
+                + "`user_id` int(10) unsigned NOT NULL,"
+                + "`taming` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`mining` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`woodcutting` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`repair` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`unarmed` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`herbalism` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`excavation` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`archery` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`swords` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`axes` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`acrobatics` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "PRIMARY KEY (`user_id`),"
+                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
+                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
+        write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "experience` ("
+                + "`user_id` int(10) unsigned NOT NULL,"
+                + "`taming` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`mining` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`woodcutting` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`repair` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`unarmed` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`herbalism` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`excavation` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`archery` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`swords` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`axes` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "`acrobatics` int(10) unsigned NOT NULL DEFAULT '0',"
+                + "PRIMARY KEY (`user_id`),"
+                + "FOREIGN KEY (`user_id`) REFERENCES `" + tablePrefix + "users` (`id`) "
+                + "ON DELETE CASCADE) ENGINE=MyISAM DEFAULT CHARSET=latin1;");
+
+        checkDatabaseStructure(DatabaseUpdateType.FISHING);
+        checkDatabaseStructure(DatabaseUpdateType.BLAST_MINING);
+        checkDatabaseStructure(DatabaseUpdateType.CASCADE_DELETE);
+        checkDatabaseStructure(DatabaseUpdateType.INDEX);
+        checkDatabaseStructure(DatabaseUpdateType.MOB_HEALTHBARS);
+    }
+
+    /**
+     * Attempt to write the SQL query.
+     *
+     * @param sql Query to write.
+     * @return true if the query was successfully written, false otherwise.
+     */
+    public static boolean write(String sql) {
+        if (!checkConnected()) {
+            return false;
+        }
+
+        PreparedStatement statement = null;
+        try {
+            statement = connection.prepareStatement(sql);
+            statement.executeUpdate();
+            return true;
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+            return false;
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    printErrors(e);
+                }
+            }
+        }
+    }
+
+    public static boolean removeUserSQL(String playerName) {
+        return SQLDatabaseManager.update("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.user = '" + playerName + "'") != 0;
+    }
+
+    /**
+     * Returns the number of rows affected by either a DELETE or UPDATE query
+     *
+     * @param sql SQL query to execute
+     * @return the number of rows affected
+     */
+    public static int update(String sql) {
+        if (!checkConnected()) {
+            return 0;
+        }
+
+        int rows = 0;
+
+        PreparedStatement statement = null;
+        try {
+            statement = connection.prepareStatement(sql);
+            rows = statement.executeUpdate();
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    printErrors(e);
+                }
+            }
+        }
+
+        return rows;
+    }
+
+    /**
+     * Get the Integer. Only return first row / first field.
+     *
+     * @param sql SQL query to execute
+     * @return the value in the first row / first field
+     */
+    public static int getInt(String sql) {
+        if (!checkConnected()) {
+            return 0;
+        }
+
+        int result = 0;
+
+        PreparedStatement statement = null;
+
+        try {
+            statement = connection.prepareStatement(sql);
+            ResultSet resultSet = statement.executeQuery();
+
+            if (resultSet.next()) {
+                result = resultSet.getInt(1);
+            }
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    printErrors(e);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Check connection status and re-establish if dead or stale.
+     *
+     * If the very first immediate attempt fails, further attempts
+     * will be made in progressively larger intervals up to MAX_WAIT
+     * intervals.
+     *
+     * This allows for MySQL to time out idle connections as needed by
+     * server operator, without affecting McMMO, while still providing
+     * protection against a database outage taking down Bukkit's tick
+     * processing loop due to attemping a database connection each
+     * time McMMO needs the database.
+     *
+     * @return the boolean value for whether or not we are connected
+     */
+    public static boolean checkConnected() {
+        boolean isClosed = true;
+        boolean isValid = false;
+        boolean exists = (connection != null);
+
+        // If we're waiting for server to recover then leave early
+        if (nextReconnectTimestamp > 0 && nextReconnectTimestamp > System.nanoTime()) {
+            return false;
+        }
+
+        if (exists) {
+            try {
+                isClosed = connection.isClosed();
+            }
+            catch (SQLException e) {
+                isClosed = true;
+                e.printStackTrace();
+                printErrors(e);
+            }
+
+            if (!isClosed) {
+                try {
+                    isValid = connection.isValid(VALID_TIMEOUT);
+                }
+                catch (SQLException e) {
+                    // Don't print stack trace because it's valid to lose idle connections to the server and have to restart them.
+                    isValid = false;
+                }
+            }
+        }
+
+        // Leave if all ok
+        if (exists && !isClosed && isValid) {
+            // Housekeeping
+            nextReconnectTimestamp = 0;
+            reconnectAttempt = 0;
+            return true;
+        }
+
+        // Cleanup after ourselves for GC and MySQL's sake
+        if (exists && !isClosed) {
+            try {
+                connection.close();
+            }
+            catch (SQLException ex) {
+                // This is a housekeeping exercise, ignore errors
+            }
+        }
+
+        // Try to connect again
+        connect();
+
+        // Leave if connection is good
+        try {
+            if (connection != null && !connection.isClosed()) {
+                // Schedule a database save if we really had an outage
+                if (reconnectAttempt > 1) {
+                    new SQLReconnectTask().runTaskLater(mcMMO.p, 5);
+                }
+                nextReconnectTimestamp = 0;
+                reconnectAttempt = 0;
+                return true;
+            }
+        }
+        catch (SQLException e) {
+            // Failed to check isClosed, so presume connection is bad and attempt later
+            e.printStackTrace();
+            printErrors(e);
+        }
+
+        reconnectAttempt++;
+        nextReconnectTimestamp = (long)(System.nanoTime() + Math.min(MAX_WAIT, (reconnectAttempt * SCALING_FACTOR * MIN_WAIT)));
+        return false;
+    }
+
+    /**
+     * Read SQL query.
+     *
+     * @param sql SQL query to read
+     * @return the rows in this SQL query
+     */
+    public static HashMap<Integer, ArrayList<String>> read(String sql) {
+        ResultSet resultSet;
+        HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
+
+        if (checkConnected()) {
+            PreparedStatement statement = null;
+
+            try {
+                statement = connection.prepareStatement(sql);
+                resultSet = statement.executeQuery();
+
+                while (resultSet.next()) {
+                    ArrayList<String> column = new ArrayList<String>();
+
+                    for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
+                        column.add(resultSet.getString(i));
+                    }
+
+                    rows.put(resultSet.getRow(), column);
+                }
+            }
+            catch (SQLException ex) {
+                printErrors(ex);
+            }
+            finally {
+                if (statement != null) {
+                    try {
+                        statement.close();
+                    }
+                    catch (SQLException e) {
+                        printErrors(e);
+                    }
+                }
+            }
+        }
+
+        return rows;
+    }
+
+    public static Map<String, Integer> readSQLRank(String playerName) {
+        ResultSet resultSet;
+        Map<String, Integer> skills = new HashMap<String, Integer>();
+
+        if (checkConnected()) {
+            try {
+                for (SkillType skillType : SkillType.values()) {
+                    if (skillType.isChildSkill()) {
+                        continue;
+                    }
+
+                    String skillName = skillType.name().toLowerCase();
+                    String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " +
+                                 "AND " + skillName + " > (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
+                                 "WHERE user = ?)";
+
+                    PreparedStatement statement = connection.prepareStatement(sql);
+                    statement.setString(1, playerName);
+                    resultSet = statement.executeQuery();
+
+                    resultSet.next();
+
+                    int rank = resultSet.getInt("rank");
+
+                    sql = "SELECT user, " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " +
+                          "AND " + skillName + " = (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
+                          "WHERE user = '" + playerName + "') ORDER BY user";
+
+                    statement = connection.prepareStatement(sql);
+                    resultSet = statement.executeQuery();
+
+                    while (resultSet.next()) {
+                        if (resultSet.getString("user").equalsIgnoreCase(playerName)) {
+                            skills.put(skillType.name(), rank + resultSet.getRow());
+                            break;
+                        }
+                    }
+
+                    statement.close();
+                }
+
+                String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
+                             "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 " +
+                             "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > " +
+                             "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
+                             "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?)";
+
+                PreparedStatement statement = connection.prepareStatement(sql);
+                statement.setString(1, playerName);
+                resultSet = statement.executeQuery();
+
+                resultSet.next();
+
+                int rank = resultSet.getInt("rank");
+
+                sql = "SELECT user, taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
+                      "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
+                      "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing > 0 " +
+                      "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing = " +
+                      "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing " +
+                      "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?) ORDER BY user";
+
+                statement = connection.prepareStatement(sql);
+                statement.setString(1, playerName);
+                resultSet = statement.executeQuery();
+
+                while (resultSet.next()) {
+                    if (resultSet.getString("user").equalsIgnoreCase(playerName)) {
+                        skills.put("ALL", rank + resultSet.getRow());
+                        break;
+                    }
+                }
+
+                statement.close();
+            }
+            catch (SQLException ex) {
+                printErrors(ex);
+            }
+        }
+
+        return skills;
+    }
+
+    public static int purgePowerlessSQL() {
+        HashMap<Integer, ArrayList<String>> usernames = read("SELECT u.user FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u WHERE s.user_id = u.id AND (s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0");
+        write("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.id IN (SELECT * FROM (SELECT u.id FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u WHERE s.user_id = u.id AND (s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0) AS p)");
+
+        return processPurge(usernames.values());
+    }
+
+    public static int purgeOldSQL() {
+        long currentTime = System.currentTimeMillis();
+        long purgeTime = ONE_MONTH * Config.getInstance().getOldUsersCutoff();
+
+        HashMap<Integer, ArrayList<String>> usernames = read("SELECT user FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin*1000) > " + purgeTime + ")");
+        write("DELETE FROM " + tablePrefix + "users WHERE " + tablePrefix + "users.id IN (SELECT * FROM (SELECT id FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin*1000) > " + purgeTime + ")) AS p)");
+
+        return processPurge(usernames.values());
+    }
+
+    private static int processPurge(Collection<ArrayList<String>> usernames) {
+        int purgedUsers = 0;
+
+        for (ArrayList<String> user : usernames) {
+            Misc.profileCleanup(user.get(0));
+            purgedUsers++;
+        }
+
+        return purgedUsers;
+    }
+
+    /**
+     * Check database structure for missing values.
+     *
+     * @param update Type of data to check updates for
+     */
+    private static void checkDatabaseStructure(DatabaseUpdateType update) {
+        String sql = null;
+        ResultSet resultSet = null;
+        HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
+
+        switch (update) {
+            case BLAST_MINING:
+                sql = "SELECT * FROM  `" + tablePrefix + "cooldowns` ORDER BY  `" + tablePrefix + "cooldowns`.`blast_mining` ASC LIMIT 0 , 30";
+                break;
+
+            case CASCADE_DELETE:
+                write("ALTER TABLE `" + tablePrefix + "huds` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                write("ALTER TABLE `" + tablePrefix + "experience` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                write("ALTER TABLE `" + tablePrefix + "cooldowns` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                write("ALTER TABLE `" + tablePrefix + "skills` ADD FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE;");
+                break;
+
+            case FISHING:
+                sql = "SELECT * FROM  `" + tablePrefix + "experience` ORDER BY  `" + tablePrefix + "experience`.`fishing` ASC LIMIT 0 , 30";
+                break;
+
+            case INDEX:
+                if (read("SHOW INDEX FROM " + tablePrefix + "skills").size() != 13 && checkConnected()) {
+                    mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
+                    write("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_taming` (`taming`) USING BTREE, "
+                            + "ADD INDEX `idx_mining` (`mining`) USING BTREE, "
+                            + "ADD INDEX `idx_woodcutting` (`woodcutting`) USING BTREE, "
+                            + "ADD INDEX `idx_repair` (`repair`) USING BTREE, "
+                            + "ADD INDEX `idx_unarmed` (`unarmed`) USING BTREE, "
+                            + "ADD INDEX `idx_herbalism` (`herbalism`) USING BTREE, "
+                            + "ADD INDEX `idx_excavation` (`excavation`) USING BTREE, "
+                            + "ADD INDEX `idx_archery` (`archery`) USING BTREE, "
+                            + "ADD INDEX `idx_swords` (`swords`) USING BTREE, "
+                            + "ADD INDEX `idx_axes` (`axes`) USING BTREE, "
+                            + "ADD INDEX `idx_acrobatics` (`acrobatics`) USING BTREE, "
+                            + "ADD INDEX `idx_fishing` (`fishing`) USING BTREE;");
+                }
+                break;
+
+            case MOB_HEALTHBARS:
+                sql = "SELECT * FROM  `" + tablePrefix + "huds` ORDER BY  `" + tablePrefix + "huds`.`mobhealthbar` ASC LIMIT 0 , 30";
+                break;
+
+            default:
+                break;
+        }
+
+        PreparedStatement statement = null;
+        try {
+            if (!checkConnected()) {
+                return;
+            }
+
+            statement = connection.prepareStatement(sql);
+            resultSet = statement.executeQuery();
+
+            while (resultSet.next()) {
+                ArrayList<String> column = new ArrayList<String>();
+
+                for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
+                    column.add(resultSet.getString(i));
+                }
+
+                rows.put(resultSet.getRow(), column);
+            }
+        }
+        catch (SQLException ex) {
+            switch (update) {
+                case BLAST_MINING:
+                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
+                    write("ALTER TABLE `"+tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0' ;");
+                    break;
+
+                case FISHING:
+                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
+                    write("ALTER TABLE `"+tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
+                    write("ALTER TABLE `"+tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0' ;");
+                    break;
+
+                case MOB_HEALTHBARS:
+                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for mob healthbars...");
+                    write("ALTER TABLE `" + tablePrefix + "huds` ADD `mobhealthbar` varchar(50) NOT NULL DEFAULT 'HEARTS' ;");
+                    break;
+
+                default:
+                    break;
+            }
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    // Ignore the error, we're leaving
+                }
+            }
+        }
+    }
+
+    private static void printErrors(SQLException ex) {
+        mcMMO.p.getLogger().severe("SQLException: " + ex.getMessage());
+        mcMMO.p.getLogger().severe("SQLState: " + ex.getSQLState());
+        mcMMO.p.getLogger().severe("VendorError: " + ex.getErrorCode());
+    }
+}

+ 411 - 500
src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java

@@ -12,7 +12,7 @@ import java.util.Set;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.spout.SpoutConfig;
-import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.database.SQLDatabaseManager;
 import com.gmail.nossr50.datatypes.MobHealthbarType;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
@@ -20,512 +20,54 @@ import com.gmail.nossr50.datatypes.spout.huds.HudType;
 import com.gmail.nossr50.datatypes.spout.huds.McMMOHud;
 import com.gmail.nossr50.skills.child.FamilyTree;
 import com.gmail.nossr50.util.Misc;
-import com.gmail.nossr50.util.StringUtils;
 
 public class PlayerProfile {
-    private String playerName;
-
-    // HUD
-    private McMMOHud spoutHud;
-    private HudType  hudType;
-    private MobHealthbarType mobHealthbarType;
-
-    // mySQL Stuff
+    private final String playerName;
     private int userId;
-
     private boolean loaded;
 
-    private Map<SkillType, Integer>   skills     = new HashMap<SkillType, Integer>(); // Skills and Levels
-    private Map<SkillType, Integer>   skillsXp   = new HashMap<SkillType, Integer>(); // Skills and Xp
-    private Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>();
+    /* HUDs */
+    private HudType hudType;
+    private MobHealthbarType mobHealthbarType;
+    private McMMOHud spoutHud;
 
-    private final static String location = mcMMO.getUsersFilePath();
+    /* Skill Data */
+    private final Map<SkillType, Integer>   skills     = new HashMap<SkillType, Integer>();   // Skill & Level
+    private final Map<SkillType, Integer>   skillsXp   = new HashMap<SkillType, Integer>();   // Skill & XP
+    private final Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
 
     public PlayerProfile(String playerName, boolean addNew) {
         this.playerName = playerName;
-        mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
 
-        if (mcMMO.spoutEnabled) {
-            hudType = SpoutConfig.getInstance().getDefaultHudType();
-        }
-        else {
-            hudType = HudType.DISABLED;
-        }
+        hudType = mcMMO.spoutEnabled ? SpoutConfig.getInstance().getDefaultHudType() : HudType.DISABLED;
+        mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
 
         for (AbilityType abilityType : AbilityType.values()) {
-            skillsDATS.put(abilityType, 0);
+            skillsDATS.put(abilityType, 0); 
         }
 
         for (SkillType skillType : SkillType.values()) {
-            if (!skillType.isChildSkill()) {
-                skills.put(skillType, 0);
-                skillsXp.put(skillType, 0);
-            }
-        }
-
-        if (Config.getInstance().getUseMySQL()) {
-            if (!loadMySQL() && addNew) {
-                addMySQLPlayer();
-                loaded = true;
-            }
-        }
-        else if (!load() && addNew) {
-            addPlayer();
-            loaded = true;
-        }
-    }
-
-    public String getPlayerName() {
-        return playerName;
-    }
-
-    public boolean loadMySQL() {
-        String tablePrefix = Config.getInstance().getMySQLTablePrefix();
-
-        userId = DatabaseManager.getInt("SELECT id FROM " + tablePrefix + "users WHERE user = '" + playerName + "'");
-
-        if (userId == 0) {
-            return false;
-        }
-
-        HashMap<Integer, ArrayList<String>> huds = DatabaseManager.read("SELECT hudtype FROM " + tablePrefix + "huds WHERE user_id = " + userId);
-
-        if (huds.get(1) == null) {
-            DatabaseManager.write("INSERT INTO " + tablePrefix + "huds (user_id) VALUES (" + userId + ")");
-        }
-        else {
-            hudType = HudType.valueOf(huds.get(1).get(0));
-            mobHealthbarType = MobHealthbarType.valueOf(DatabaseManager.read("SELECT mobhealthbar FROM " + tablePrefix + "huds WHERE user_id = " + userId).get(1).get(0));
-        }
-
-        HashMap<Integer, ArrayList<String>> cooldowns = DatabaseManager.read("SELECT mining, woodcutting, unarmed, herbalism, excavation, swords, axes, blast_mining FROM " + tablePrefix + "cooldowns WHERE user_id = " + userId);
-        ArrayList<String> cooldownValues = cooldowns.get(1);
-
-        if (cooldownValues == null) {
-            DatabaseManager.write("INSERT INTO " + tablePrefix + "cooldowns (user_id) VALUES (" + userId + ")");
-            mcMMO.p.getLogger().warning(playerName + "does not exist in the cooldown table. Their cooldowns will be reset.");
-        }
-        else {
-            skillsDATS.put(AbilityType.SUPER_BREAKER, Integer.valueOf(cooldownValues.get(0)));
-            skillsDATS.put(AbilityType.TREE_FELLER, Integer.valueOf(cooldownValues.get(1)));
-            skillsDATS.put(AbilityType.BERSERK, Integer.valueOf(cooldownValues.get(2)));
-            skillsDATS.put(AbilityType.GREEN_TERRA, Integer.valueOf(cooldownValues.get(3)));
-            skillsDATS.put(AbilityType.GIGA_DRILL_BREAKER, Integer.valueOf(cooldownValues.get(4)));
-            skillsDATS.put(AbilityType.SERRATED_STRIKES, Integer.valueOf(cooldownValues.get(5)));
-            skillsDATS.put(AbilityType.SKULL_SPLITTER, Integer.valueOf(cooldownValues.get(6)));
-            skillsDATS.put(AbilityType.BLAST_MINING, Integer.valueOf(cooldownValues.get(7)));
-        }
-
-        HashMap<Integer, ArrayList<String>> stats = DatabaseManager.read("SELECT taming, mining, repair, woodcutting, unarmed, herbalism, excavation, archery, swords, axes, acrobatics, fishing FROM " + tablePrefix + "skills WHERE user_id = " + userId);
-        ArrayList<String> statValues = stats.get(1);
-
-        if (statValues == null) {
-            DatabaseManager.write("INSERT INTO " + tablePrefix + "skills (user_id) VALUES (" + userId + ")");
-            mcMMO.p.getLogger().warning(playerName + "does not exist in the skills table. Their stats will be reset.");
-        }
-        else {
-            skills.put(SkillType.TAMING, Integer.valueOf(statValues.get(0)));
-            skills.put(SkillType.MINING, Integer.valueOf(statValues.get(1)));
-            skills.put(SkillType.REPAIR, Integer.valueOf(statValues.get(2)));
-            skills.put(SkillType.WOODCUTTING, Integer.valueOf(statValues.get(3)));
-            skills.put(SkillType.UNARMED, Integer.valueOf(statValues.get(4)));
-            skills.put(SkillType.HERBALISM, Integer.valueOf(statValues.get(5)));
-            skills.put(SkillType.EXCAVATION, Integer.valueOf(statValues.get(6)));
-            skills.put(SkillType.ARCHERY, Integer.valueOf(statValues.get(7)));
-            skills.put(SkillType.SWORDS, Integer.valueOf(statValues.get(8)));
-            skills.put(SkillType.AXES, Integer.valueOf(statValues.get(9)));
-            skills.put(SkillType.ACROBATICS, Integer.valueOf(statValues.get(10)));
-            skills.put(SkillType.FISHING, Integer.valueOf(statValues.get(11)));
-        }
-
-        HashMap<Integer, ArrayList<String>> experience = DatabaseManager.read("SELECT taming, mining, repair, woodcutting, unarmed, herbalism, excavation, archery, swords, axes, acrobatics, fishing FROM " + tablePrefix + "experience WHERE user_id = " + userId);
-        ArrayList<String> experienceValues = experience.get(1);
-
-        if (experienceValues == null) {
-            DatabaseManager.write("INSERT INTO " + tablePrefix + "experience (user_id) VALUES (" + userId + ")");
-            mcMMO.p.getLogger().warning(playerName + "does not exist in the experience table. Their experience will be reset.");
-        }
-        else {
-            skillsXp.put(SkillType.TAMING, Integer.valueOf(experienceValues.get(0)));
-            skillsXp.put(SkillType.MINING, Integer.valueOf(experienceValues.get(1)));
-            skillsXp.put(SkillType.REPAIR, Integer.valueOf(experienceValues.get(2)));
-            skillsXp.put(SkillType.WOODCUTTING, Integer.valueOf(experienceValues.get(3)));
-            skillsXp.put(SkillType.UNARMED, Integer.valueOf(experienceValues.get(4)));
-            skillsXp.put(SkillType.HERBALISM, Integer.valueOf(experienceValues.get(5)));
-            skillsXp.put(SkillType.EXCAVATION, Integer.valueOf(experienceValues.get(6)));
-            skillsXp.put(SkillType.ARCHERY, Integer.valueOf(experienceValues.get(7)));
-            skillsXp.put(SkillType.SWORDS, Integer.valueOf(experienceValues.get(8)));
-            skillsXp.put(SkillType.AXES, Integer.valueOf(experienceValues.get(9)));
-            skillsXp.put(SkillType.ACROBATICS, Integer.valueOf(experienceValues.get(10)));
-            skillsXp.put(SkillType.FISHING, Integer.valueOf(experienceValues.get(11)));
-        }
-
-        loaded = true;
-        return true;
-    }
-
-    public void addMySQLPlayer() {
-        String tablePrefix = Config.getInstance().getMySQLTablePrefix();
-
-        DatabaseManager.write("INSERT INTO " + tablePrefix + "users (user, lastlogin) VALUES ('" + playerName + "'," + System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR + ")");
-        userId = DatabaseManager.getInt("SELECT id FROM " + tablePrefix + "users WHERE user = '" + playerName + "'");
-
-        DatabaseManager.write("INSERT INTO " + tablePrefix + "cooldowns (user_id) VALUES (" + userId + ")");
-        DatabaseManager.write("INSERT INTO " + tablePrefix + "skills (user_id) VALUES (" + userId + ")");
-        DatabaseManager.write("INSERT INTO " + tablePrefix + "experience (user_id) VALUES (" + userId + ")");
-    }
-
-    public boolean load() {
-        try {
-            // Open the user file
-            FileReader file = new FileReader(location);
-            BufferedReader in = new BufferedReader(file);
-            String line;
-
-            while ((line = in.readLine()) != null) {
-                // Find if the line contains the player we want.
-                String[] character = line.split(":");
-
-                if (!character[0].equalsIgnoreCase(playerName)) {
-                    continue;
-                }
-
-                if (character.length > 1 && StringUtils.isInt(character[1])) {
-                    skills.put(SkillType.MINING, Integer.valueOf(character[1]));
-                }
-
-                if (character.length > 4 && StringUtils.isInt(character[4])) {
-                    skillsXp.put(SkillType.MINING, Integer.valueOf(character[4]));
-                }
-
-                if (character.length > 5 && StringUtils.isInt(character[5])) {
-                    skills.put(SkillType.WOODCUTTING, Integer.valueOf(character[5]));
-                }
-
-                if (character.length > 6 && StringUtils.isInt(character[6])) {
-                    skillsXp.put(SkillType.WOODCUTTING, Integer.valueOf(character[6]));
-                }
-
-                if (character.length > 7 && StringUtils.isInt(character[7])) {
-                    skills.put(SkillType.REPAIR, Integer.valueOf(character[7]));
-                }
-
-                if (character.length > 8 && StringUtils.isInt(character[8])) {
-                    skills.put(SkillType.UNARMED,  Integer.valueOf(character[8]));
-                }
-
-                if (character.length > 9 && StringUtils.isInt(character[9])) {
-                    skills.put(SkillType.HERBALISM, Integer.valueOf(character[9]));
-                }
-
-                if (character.length > 10 && StringUtils.isInt(character[10])) {
-                    skills.put(SkillType.EXCAVATION, Integer.valueOf(character[10]));
-                }
-
-                if (character.length > 11 && StringUtils.isInt(character[11])) {
-                    skills.put(SkillType.ARCHERY, Integer.valueOf(character[11]));
-                }
-
-                if (character.length > 12 && StringUtils.isInt(character[12])) {
-                    skills.put(SkillType.SWORDS, Integer.valueOf(character[12]));
-                }
-
-                if (character.length > 13 && StringUtils.isInt(character[13])) {
-                    skills.put(SkillType.AXES, Integer.valueOf(character[13]));
-                }
-
-                if (character.length > 14 && StringUtils.isInt(character[14])) {
-                    skills.put(SkillType.ACROBATICS, Integer.valueOf(character[14]));
-                }
-
-                if (character.length > 15 && StringUtils.isInt(character[15])) {
-                    skillsXp.put(SkillType.REPAIR, Integer.valueOf(character[15]));
-                }
-
-                if (character.length > 16 && StringUtils.isInt(character[16])) {
-                    skillsXp.put(SkillType.UNARMED, Integer.valueOf(character[16]));
-                }
-
-                if (character.length > 17 && StringUtils.isInt(character[17])) {
-                    skillsXp.put(SkillType.HERBALISM, Integer.valueOf(character[17]));
-                }
-
-                if (character.length > 18 && StringUtils.isInt(character[18])) {
-                    skillsXp.put(SkillType.EXCAVATION, Integer.valueOf(character[18]));
-                }
-
-                if (character.length > 19 && StringUtils.isInt(character[19])) {
-                    skillsXp.put(SkillType.ARCHERY, Integer.valueOf(character[19]));
-                }
-
-                if (character.length > 20 && StringUtils.isInt(character[20])) {
-                    skillsXp.put(SkillType.SWORDS, Integer.valueOf(character[20]));
-                }
-
-                if (character.length > 21 && StringUtils.isInt(character[21])) {
-                    skillsXp.put(SkillType.AXES, Integer.valueOf(character[21]));
-                }
-
-                if (character.length > 22 && StringUtils.isInt(character[22])) {
-                    skillsXp.put(SkillType.ACROBATICS, Integer.valueOf(character[22]));
-                }
-
-                if (character.length > 24 && StringUtils.isInt(character[24])) {
-                    skills.put(SkillType.TAMING, Integer.valueOf(character[24]));
-                }
-
-                if (character.length > 25 && StringUtils.isInt(character[25])) {
-                    skillsXp.put(SkillType.TAMING, Integer.valueOf(character[25]));
-                }
-
-                if (character.length > 26) {
-                    skillsDATS.put(AbilityType.BERSERK, Integer.valueOf(character[26]));
-                }
-
-                if (character.length > 27) {
-                    skillsDATS.put(AbilityType.GIGA_DRILL_BREAKER, Integer.valueOf(character[27]));
-                }
-
-                if (character.length > 28) {
-                    skillsDATS.put(AbilityType.TREE_FELLER, Integer.valueOf(character[28]));
-                }
-
-                if (character.length > 29) {
-                    skillsDATS.put(AbilityType.GREEN_TERRA, Integer.valueOf(character[29]));
-                }
-
-                if (character.length > 30) {
-                    skillsDATS.put(AbilityType.SERRATED_STRIKES, Integer.valueOf(character[30]));
-                }
-
-                if (character.length > 31) {
-                    skillsDATS.put(AbilityType.SKULL_SPLITTER, Integer.valueOf(character[31]));
-                }
-
-                if (character.length > 32) {
-                    skillsDATS.put(AbilityType.SUPER_BREAKER, Integer.valueOf(character[32]));
-                }
-
-                if (character.length > 33) {
-                    hudType = HudType.valueOf(character[33]);
-                }
-
-                if (character.length > 34) {
-                    skills.put(SkillType.FISHING, Integer.valueOf(character[34]));
-                }
-
-                if (character.length > 35) {
-                    skillsXp.put(SkillType.FISHING, Integer.valueOf(character[35]));
-                }
-
-                if (character.length > 36) {
-                    skillsDATS.put(AbilityType.BLAST_MINING, Integer.valueOf(character[36]));
-                }
-
-                if (character.length > 38) {
-                    mobHealthbarType = MobHealthbarType.valueOf(character[38]);
-                }
-
-                loaded = true;
-
-                in.close();
-                return true;
+            if (skillType.isChildSkill()) {
+                continue;
             }
 
-            in.close();
-        }
-        catch (Exception e) {
-            e.printStackTrace();
-        }
-        return false;
-    }
-
-    public void save() {
-        Long timestamp = System.currentTimeMillis();
-
-        // If we are using mysql save to database
-        if (Config.getInstance().getUseMySQL()) {
-            String tablePrefix = Config.getInstance().getMySQLTablePrefix();
-
-            DatabaseManager.write("UPDATE " + tablePrefix + "huds SET hudtype = '" + hudType.toString() + "' WHERE user_id = " + userId);
-            DatabaseManager.write("UPDATE " + tablePrefix + "huds SET mobhealthbar = '" + mobHealthbarType.toString() + "' WHERE user_id = " + userId);
-            DatabaseManager.write("UPDATE " + tablePrefix + "users SET lastlogin = " + ((int) (timestamp / Misc.TIME_CONVERSION_FACTOR)) + " WHERE id = " + userId);
-            DatabaseManager.write("UPDATE " + tablePrefix + "cooldowns SET "
-                    + " mining = " + skillsDATS.get(AbilityType.SUPER_BREAKER)
-                    + ", woodcutting = " + skillsDATS.get(AbilityType.TREE_FELLER)
-                    + ", unarmed = " + skillsDATS.get(AbilityType.BERSERK)
-                    + ", herbalism = " + skillsDATS.get(AbilityType.GREEN_TERRA)
-                    + ", excavation = " + skillsDATS.get(AbilityType.GIGA_DRILL_BREAKER)
-                    + ", swords = " + skillsDATS.get(AbilityType.SERRATED_STRIKES)
-                    + ", axes = " + skillsDATS.get(AbilityType.SKULL_SPLITTER)
-                    + ", blast_mining = " + skillsDATS.get(AbilityType.BLAST_MINING)
-                    + " WHERE user_id = " + userId);
-            DatabaseManager.write("UPDATE " + tablePrefix + "skills SET "
-                    + " taming = " + skills.get(SkillType.TAMING)
-                    + ", mining = " + skills.get(SkillType.MINING)
-                    + ", repair = " + skills.get(SkillType.REPAIR)
-                    + ", woodcutting = " + skills.get(SkillType.WOODCUTTING)
-                    + ", unarmed = " + skills.get(SkillType.UNARMED)
-                    + ", herbalism = " + skills.get(SkillType.HERBALISM)
-                    + ", excavation = " + skills.get(SkillType.EXCAVATION)
-                    + ", archery = " + skills.get(SkillType.ARCHERY)
-                    + ", swords = " + skills.get(SkillType.SWORDS)
-                    + ", axes = " + skills.get(SkillType.AXES)
-                    + ", acrobatics = " + skills.get(SkillType.ACROBATICS)
-                    + ", fishing = " + skills.get(SkillType.FISHING)
-                    + " WHERE user_id = " + userId);
-            DatabaseManager.write("UPDATE " + tablePrefix + "experience SET "
-                    + "  taming = " + skillsXp.get(SkillType.TAMING)
-                    + ", mining = " + skillsXp.get(SkillType.MINING)
-                    + ", repair = " + skillsXp.get(SkillType.REPAIR)
-                    + ", woodcutting = " + skillsXp.get(SkillType.WOODCUTTING)
-                    + ", unarmed = " + skillsXp.get(SkillType.UNARMED)
-                    + ", herbalism = " + skillsXp.get(SkillType.HERBALISM)
-                    + ", excavation = " + skillsXp.get(SkillType.EXCAVATION)
-                    + ", archery = " + skillsXp.get(SkillType.ARCHERY)
-                    + ", swords = " + skillsXp.get(SkillType.SWORDS)
-                    + ", axes = " + skillsXp.get(SkillType.AXES)
-                    + ", acrobatics = " + skillsXp.get(SkillType.ACROBATICS)
-                    + ", fishing = " + skillsXp.get(SkillType.FISHING)
-                    + " WHERE user_id = " + userId);
-        }
-        else {
-            // Otherwise save to flatfile
-            try {
-                // Open the file
-                FileReader file = new FileReader(location);
-                BufferedReader in = new BufferedReader(file);
-                StringBuilder writer = new StringBuilder();
-                String line;
-
-                // While not at the end of the file
-                while ((line = in.readLine()) != null) {
-                    // Read the line in and copy it to the output it's not the player we want to edit
-                    if (!line.split(":")[0].equalsIgnoreCase(playerName)) {
-                        writer.append(line).append("\r\n");
-                    }
-                    else {
-                        // Otherwise write the new player information
-                        writer.append(playerName).append(":");
-                        writer.append(skills.get(SkillType.MINING)).append(":");
-                        writer.append(":");
-                        writer.append(":");
-                        writer.append(skillsXp.get(SkillType.MINING)).append(":");
-                        writer.append(skills.get(SkillType.WOODCUTTING)).append(":");
-                        writer.append(skillsXp.get(SkillType.WOODCUTTING)).append(":");
-                        writer.append(skills.get(SkillType.REPAIR)).append(":");
-                        writer.append(skills.get(SkillType.UNARMED)).append(":");
-                        writer.append(skills.get(SkillType.HERBALISM)).append(":");
-                        writer.append(skills.get(SkillType.EXCAVATION)).append(":");
-                        writer.append(skills.get(SkillType.ARCHERY)).append(":");
-                        writer.append(skills.get(SkillType.SWORDS)).append(":");
-                        writer.append(skills.get(SkillType.AXES)).append(":");
-                        writer.append(skills.get(SkillType.ACROBATICS)).append(":");
-                        writer.append(skillsXp.get(SkillType.REPAIR)).append(":");
-                        writer.append(skillsXp.get(SkillType.UNARMED)).append(":");
-                        writer.append(skillsXp.get(SkillType.HERBALISM)).append(":");
-                        writer.append(skillsXp.get(SkillType.EXCAVATION)).append(":");
-                        writer.append(skillsXp.get(SkillType.ARCHERY)).append(":");
-                        writer.append(skillsXp.get(SkillType.SWORDS)).append(":");
-                        writer.append(skillsXp.get(SkillType.AXES)).append(":");
-                        writer.append(skillsXp.get(SkillType.ACROBATICS)).append(":");
-                        writer.append(":");
-                        writer.append(skills.get(SkillType.TAMING)).append(":");
-                        writer.append(skillsXp.get(SkillType.TAMING)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.BERSERK)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.GIGA_DRILL_BREAKER)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.TREE_FELLER)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.GREEN_TERRA)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.SERRATED_STRIKES)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.SKULL_SPLITTER)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.SUPER_BREAKER)).append(":");
-                        writer.append(hudType.toString()).append(":");
-                        writer.append(skills.get(SkillType.FISHING)).append(":");
-                        writer.append(skillsXp.get(SkillType.FISHING)).append(":");
-                        writer.append(skillsDATS.get(AbilityType.BLAST_MINING)).append(":");
-                        writer.append(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR).append(":");
-                        writer.append(mobHealthbarType.toString()).append(":");
-                        writer.append("\r\n");
-                    }
-                }
-
-                in.close();
-
-                // Write the new file
-                FileWriter out = new FileWriter(location);
-                out.write(writer.toString());
-                out.flush();
-                out.close();
-            }
-            catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    public void addPlayer() {
-        try {
-            // Open the file to write the player
-            FileWriter file = new FileWriter(location, true);
-            BufferedWriter out = new BufferedWriter(file);
-
-            // Add the player to the end
-            out.append(playerName).append(":");
-            out.append("0:"); // Mining
-            out.append(":");
-            out.append(":");
-            out.append("0:"); // Xp
-            out.append("0:"); // Woodcutting
-            out.append("0:"); // WoodCuttingXp
-            out.append("0:"); // Repair
-            out.append("0:"); // Unarmed
-            out.append("0:"); // Herbalism
-            out.append("0:"); // Excavation
-            out.append("0:"); // Archery
-            out.append("0:"); // Swords
-            out.append("0:"); // Axes
-            out.append("0:"); // Acrobatics
-            out.append("0:"); // RepairXp
-            out.append("0:"); // UnarmedXp
-            out.append("0:"); // HerbalismXp
-            out.append("0:"); // ExcavationXp
-            out.append("0:"); // ArcheryXp
-            out.append("0:"); // SwordsXp
-            out.append("0:"); // AxesXp
-            out.append("0:"); // AcrobaticsXp
-            out.append(":");
-            out.append("0:"); // Taming
-            out.append("0:"); // TamingXp
-            out.append("0:"); // DATS
-            out.append("0:"); // DATS
-            out.append("0:"); // DATS
-            out.append("0:"); // DATS
-            out.append("0:"); // DATS
-            out.append("0:"); // DATS
-            out.append("0:"); // DATS
-            out.append(hudType.toString()).append(":"); // HUD
-            out.append("0:"); // Fishing
-            out.append("0:"); // FishingXp
-            out.append("0:"); // Blast Mining
-            out.append(String.valueOf(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)).append(":"); // LastLogin
-            out.append(mobHealthbarType.toString()).append(":"); // Mob Healthbar HUD
-
-            // Add more in the same format as the line above
-
-            out.newLine();
-            out.close();
-        }
-        catch (Exception e) {
-            e.printStackTrace();
+            skills.put(skillType, 0);
+            skillsXp.put(skillType, 0);
         }
-    }
 
-    /*
-     * mySQL Stuff
-     */
+        if (!loadPlayer() && addNew) {
+            addPlayer();
+            loaded = true;
+        }
+    }
 
-    public int getMySQLuserId() {
-        return userId;
+    public void save() {
+        if (Config.getInstance().getUseMySQL()) {
+            saveMySQL();
+        }
+        else {
+            saveFlatfile();
+        }
     }
 
     public boolean isLoaded() {
@@ -594,8 +136,8 @@ public class PlayerProfile {
      * Reset all skill cooldowns.
      */
     public void resetCooldowns() {
-        for (AbilityType x : skillsDATS.keySet()) {
-            skillsDATS.put(x, 0);
+        for (AbilityType ability : skillsDATS.keySet()) {
+            skillsDATS.put(ability, 0);
         }
     }
 
@@ -611,17 +153,6 @@ public class PlayerProfile {
         return skills.get(skillType);
     }
 
-    public int getChildSkillLevel(SkillType skillType) {
-        Set<SkillType> parents = FamilyTree.getParents(skillType);
-        int sum = 0;
-
-        for (SkillType parent : parents) {
-            sum += Math.min(getSkillLevel(parent), 1000);
-        }
-
-        return sum / parents.size();
-    }
-
     public int getSkillXpLevel(SkillType skillType) {
         return skillsXp.get(skillType);
     }
@@ -705,4 +236,384 @@ public class PlayerProfile {
     public int getXpToLevel(SkillType skillType) {
         return 1020 + (skills.get(skillType) *  Config.getInstance().getFormulaMultiplierCurve());
     }
+
+    private int getChildSkillLevel(SkillType skillType) {
+        Set<SkillType> parents = FamilyTree.getParents(skillType);
+        int sum = 0;
+
+        for (SkillType parent : parents) {
+            sum += Math.min(getSkillLevel(parent), 1000);
+        }
+
+        return sum / parents.size();
+    }
+
+    private boolean loadPlayer() {
+        return Config.getInstance().getUseMySQL() ? loadMySQL() : loadFlatfile();
+    }
+
+    private void addPlayer() {
+        if (Config.getInstance().getUseMySQL()) {
+            addMySQLPlayer();
+        }
+        else {
+            addFlatfilePlayer();
+        }
+    }
+
+    private boolean loadMySQL() {
+        String tablePrefix = Config.getInstance().getMySQLTablePrefix();
+
+        userId = SQLDatabaseManager.getInt("SELECT id FROM " + tablePrefix + "users WHERE user = '" + playerName + "'");
+
+        if (userId == 0) {
+            return false;
+        }
+
+        ArrayList<String> hudValues = SQLDatabaseManager.read("SELECT hudtype, mobhealthbar FROM " + tablePrefix + "huds WHERE user_id = " + userId).get(1);
+
+        if (hudValues == null) {
+            SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "huds (user_id, mobhealthbar) VALUES (" + userId + "," + mobHealthbarType.name() + ")");
+            mcMMO.p.getLogger().warning(playerName + "does not exist in the HUD table. Their HUDs will be reset.");
+        }
+        else {
+            hudType = HudType.valueOf(hudValues.get(0));
+            mobHealthbarType = MobHealthbarType.valueOf(hudValues.get(1));
+        }
+
+        ArrayList<String> cooldownValues = SQLDatabaseManager.read("SELECT mining, woodcutting, unarmed, herbalism, excavation, swords, axes, blast_mining FROM " + tablePrefix + "cooldowns WHERE user_id = " + userId).get(1);
+
+        if (cooldownValues == null) {
+            SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "cooldowns (user_id) VALUES (" + userId + ")");
+            mcMMO.p.getLogger().warning(playerName + "does not exist in the cooldown table. Their cooldowns will be reset.");
+        }
+        else {
+            skillsDATS.put(AbilityType.SUPER_BREAKER, Integer.valueOf(cooldownValues.get(0)));
+            skillsDATS.put(AbilityType.TREE_FELLER, Integer.valueOf(cooldownValues.get(1)));
+            skillsDATS.put(AbilityType.BERSERK, Integer.valueOf(cooldownValues.get(2)));
+            skillsDATS.put(AbilityType.GREEN_TERRA, Integer.valueOf(cooldownValues.get(3)));
+            skillsDATS.put(AbilityType.GIGA_DRILL_BREAKER, Integer.valueOf(cooldownValues.get(4)));
+            skillsDATS.put(AbilityType.SERRATED_STRIKES, Integer.valueOf(cooldownValues.get(5)));
+            skillsDATS.put(AbilityType.SKULL_SPLITTER, Integer.valueOf(cooldownValues.get(6)));
+            skillsDATS.put(AbilityType.BLAST_MINING, Integer.valueOf(cooldownValues.get(7)));
+        }
+
+        ArrayList<String> statValues = SQLDatabaseManager.read("SELECT taming, mining, repair, woodcutting, unarmed, herbalism, excavation, archery, swords, axes, acrobatics, fishing FROM " + tablePrefix + "skills WHERE user_id = " + userId).get(1);
+
+        if (statValues == null) {
+            SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "skills (user_id) VALUES (" + userId + ")");
+            mcMMO.p.getLogger().warning(playerName + "does not exist in the skills table. Their stats will be reset.");
+        }
+        else {
+            skills.put(SkillType.TAMING, Integer.valueOf(statValues.get(0)));
+            skills.put(SkillType.MINING, Integer.valueOf(statValues.get(1)));
+            skills.put(SkillType.REPAIR, Integer.valueOf(statValues.get(2)));
+            skills.put(SkillType.WOODCUTTING, Integer.valueOf(statValues.get(3)));
+            skills.put(SkillType.UNARMED, Integer.valueOf(statValues.get(4)));
+            skills.put(SkillType.HERBALISM, Integer.valueOf(statValues.get(5)));
+            skills.put(SkillType.EXCAVATION, Integer.valueOf(statValues.get(6)));
+            skills.put(SkillType.ARCHERY, Integer.valueOf(statValues.get(7)));
+            skills.put(SkillType.SWORDS, Integer.valueOf(statValues.get(8)));
+            skills.put(SkillType.AXES, Integer.valueOf(statValues.get(9)));
+            skills.put(SkillType.ACROBATICS, Integer.valueOf(statValues.get(10)));
+            skills.put(SkillType.FISHING, Integer.valueOf(statValues.get(11)));
+        }
+
+        ArrayList<String> experienceValues = SQLDatabaseManager.read("SELECT taming, mining, repair, woodcutting, unarmed, herbalism, excavation, archery, swords, axes, acrobatics, fishing FROM " + tablePrefix + "experience WHERE user_id = " + userId).get(1);
+
+        if (experienceValues == null) {
+            SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "experience (user_id) VALUES (" + userId + ")");
+            mcMMO.p.getLogger().warning(playerName + "does not exist in the experience table. Their experience will be reset.");
+        }
+        else {
+            skillsXp.put(SkillType.TAMING, Integer.valueOf(experienceValues.get(0)));
+            skillsXp.put(SkillType.MINING, Integer.valueOf(experienceValues.get(1)));
+            skillsXp.put(SkillType.REPAIR, Integer.valueOf(experienceValues.get(2)));
+            skillsXp.put(SkillType.WOODCUTTING, Integer.valueOf(experienceValues.get(3)));
+            skillsXp.put(SkillType.UNARMED, Integer.valueOf(experienceValues.get(4)));
+            skillsXp.put(SkillType.HERBALISM, Integer.valueOf(experienceValues.get(5)));
+            skillsXp.put(SkillType.EXCAVATION, Integer.valueOf(experienceValues.get(6)));
+            skillsXp.put(SkillType.ARCHERY, Integer.valueOf(experienceValues.get(7)));
+            skillsXp.put(SkillType.SWORDS, Integer.valueOf(experienceValues.get(8)));
+            skillsXp.put(SkillType.AXES, Integer.valueOf(experienceValues.get(9)));
+            skillsXp.put(SkillType.ACROBATICS, Integer.valueOf(experienceValues.get(10)));
+            skillsXp.put(SkillType.FISHING, Integer.valueOf(experienceValues.get(11)));
+        }
+
+        loaded = true;
+        return true;
+    }
+
+    private void addMySQLPlayer() {
+        String tablePrefix = Config.getInstance().getMySQLTablePrefix();
+
+        SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "users (user, lastlogin) VALUES ('" + playerName + "'," + System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR + ")");
+        userId = SQLDatabaseManager.getInt("SELECT id FROM " + tablePrefix + "users WHERE user = '" + playerName + "'");
+
+        SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "huds (user_id, mobhealthbar) VALUES (" + userId + "," + mobHealthbarType.name() + ")");
+        SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "cooldowns (user_id) VALUES (" + userId + ")");
+        SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "skills (user_id) VALUES (" + userId + ")");
+        SQLDatabaseManager.write("INSERT INTO " + tablePrefix + "experience (user_id) VALUES (" + userId + ")");
+    }
+
+
+    private void addFlatfilePlayer() {
+        try {
+            // Open the file to write the player
+            BufferedWriter out = new BufferedWriter(new FileWriter(mcMMO.getUsersFilePath(), true));
+
+            // Add the player to the end
+            out.append(playerName).append(":");
+            out.append("0:"); // Mining
+            out.append(":");
+            out.append(":");
+            out.append("0:"); // Xp
+            out.append("0:"); // Woodcutting
+            out.append("0:"); // WoodCuttingXp
+            out.append("0:"); // Repair
+            out.append("0:"); // Unarmed
+            out.append("0:"); // Herbalism
+            out.append("0:"); // Excavation
+            out.append("0:"); // Archery
+            out.append("0:"); // Swords
+            out.append("0:"); // Axes
+            out.append("0:"); // Acrobatics
+            out.append("0:"); // RepairXp
+            out.append("0:"); // UnarmedXp
+            out.append("0:"); // HerbalismXp
+            out.append("0:"); // ExcavationXp
+            out.append("0:"); // ArcheryXp
+            out.append("0:"); // SwordsXp
+            out.append("0:"); // AxesXp
+            out.append("0:"); // AcrobaticsXp
+            out.append(":");
+            out.append("0:"); // Taming
+            out.append("0:"); // TamingXp
+            out.append("0:"); // DATS
+            out.append("0:"); // DATS
+            out.append("0:"); // DATS
+            out.append("0:"); // DATS
+            out.append("0:"); // DATS
+            out.append("0:"); // DATS
+            out.append("0:"); // DATS
+            out.append(hudType.toString()).append(":"); // HUD
+            out.append("0:"); // Fishing
+            out.append("0:"); // FishingXp
+            out.append("0:"); // Blast Mining
+            out.append(String.valueOf(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)).append(":"); // LastLogin
+            out.append(mobHealthbarType.toString()).append(":"); // Mob Healthbar HUD
+
+            // Add more in the same format as the line above
+
+            out.newLine();
+            out.close();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private boolean loadFlatfile() {
+        try {
+            // Open the user file
+            FileReader file = new FileReader(mcMMO.getUsersFilePath());
+            BufferedReader in = new BufferedReader(file);
+            String line;
+
+            while ((line = in.readLine()) != null) {
+                // Find if the line contains the player we want.
+                String[] character = line.split(":");
+
+                if (!character[0].equalsIgnoreCase(playerName)) {
+                    continue;
+                }
+
+                loadSkillData(SkillType.MINING, character, 1);
+                loadSkillData(SkillType.WOODCUTTING, character, 5);
+                loadSkillData(SkillType.REPAIR, character, 7);
+                loadSkillData(SkillType.UNARMED, character, 8);
+                loadSkillData(SkillType.HERBALISM, character, 9);
+                loadSkillData(SkillType.EXCAVATION, character, 10);
+                loadSkillData(SkillType.ARCHERY, character, 11);
+                loadSkillData(SkillType.SWORDS, character, 12);
+                loadSkillData(SkillType.AXES, character, 13);
+                loadSkillData(SkillType.ACROBATICS, character, 14);
+                loadSkillData(SkillType.TAMING, character, 24);
+                loadSkillData(SkillType.FISHING, character, 34);
+
+                loadSkillXpData(SkillType.MINING, character, 4);
+                loadSkillXpData(SkillType.WOODCUTTING, character, 6);
+                loadSkillXpData(SkillType.REPAIR, character, 15);
+                loadSkillXpData(SkillType.UNARMED, character, 16);
+                loadSkillXpData(SkillType.HERBALISM, character, 17);
+                loadSkillXpData(SkillType.EXCAVATION, character, 18);
+                loadSkillXpData(SkillType.ARCHERY, character, 19);
+                loadSkillXpData(SkillType.SWORDS, character, 20);
+                loadSkillXpData(SkillType.AXES, character, 21);
+                loadSkillXpData(SkillType.ACROBATICS, character, 22);
+                loadSkillXpData(SkillType.TAMING, character, 25);
+                loadSkillXpData(SkillType.FISHING, character, 35);
+
+                loadDATSData(AbilityType.BERSERK, character, 26);
+                loadDATSData(AbilityType.GIGA_DRILL_BREAKER, character, 27);
+                loadDATSData(AbilityType.TREE_FELLER, character, 28);
+                loadDATSData(AbilityType.GREEN_TERRA, character, 29);
+                loadDATSData(AbilityType.SERRATED_STRIKES, character, 30);
+                loadDATSData(AbilityType.SKULL_SPLITTER, character, 31);
+                loadDATSData(AbilityType.SUPER_BREAKER, character, 32);
+                loadDATSData(AbilityType.BLAST_MINING, character, 36);
+
+                hudType = character.length > 33 ? HudType.valueOf(character[33]) : null;
+                mobHealthbarType = character.length > 38 ? MobHealthbarType.valueOf(character[38]) : null;
+
+                loaded = true;
+            }
+
+            in.close();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return loaded;
+    }
+
+    private void saveMySQL() {
+        if (Config.getInstance().getUseMySQL()) {
+            String tablePrefix = Config.getInstance().getMySQLTablePrefix();
+
+            SQLDatabaseManager.write("UPDATE " + tablePrefix + "users SET lastlogin = " + ((int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)) + " WHERE id = " + userId);
+            SQLDatabaseManager.write("UPDATE " + tablePrefix + "huds SET "
+                    + "  hudtype = " + hudType.toString()
+                    + ", mobhealthbar = " + mobHealthbarType.toString()
+                    + "  WHERE user_id = " + userId);
+            SQLDatabaseManager.write("UPDATE " + tablePrefix + "cooldowns SET "
+                    + "  mining = " + skillsDATS.get(AbilityType.SUPER_BREAKER)
+                    + ", woodcutting = " + skillsDATS.get(AbilityType.TREE_FELLER)
+                    + ", unarmed = " + skillsDATS.get(AbilityType.BERSERK)
+                    + ", herbalism = " + skillsDATS.get(AbilityType.GREEN_TERRA)
+                    + ", excavation = " + skillsDATS.get(AbilityType.GIGA_DRILL_BREAKER)
+                    + ", swords = " + skillsDATS.get(AbilityType.SERRATED_STRIKES)
+                    + ", axes = " + skillsDATS.get(AbilityType.SKULL_SPLITTER)
+                    + ", blast_mining = " + skillsDATS.get(AbilityType.BLAST_MINING)
+                    + "  WHERE user_id = " + userId);
+            SQLDatabaseManager.write("UPDATE " + tablePrefix + "skills SET "
+                    + "  taming = " + skills.get(SkillType.TAMING)
+                    + ", mining = " + skills.get(SkillType.MINING)
+                    + ", repair = " + skills.get(SkillType.REPAIR)
+                    + ", woodcutting = " + skills.get(SkillType.WOODCUTTING)
+                    + ", unarmed = " + skills.get(SkillType.UNARMED)
+                    + ", herbalism = " + skills.get(SkillType.HERBALISM)
+                    + ", excavation = " + skills.get(SkillType.EXCAVATION)
+                    + ", archery = " + skills.get(SkillType.ARCHERY)
+                    + ", swords = " + skills.get(SkillType.SWORDS)
+                    + ", axes = " + skills.get(SkillType.AXES)
+                    + ", acrobatics = " + skills.get(SkillType.ACROBATICS)
+                    + ", fishing = " + skills.get(SkillType.FISHING)
+                    + "  WHERE user_id = " + userId);
+            SQLDatabaseManager.write("UPDATE " + tablePrefix + "experience SET "
+                    + "  taming = " + skillsXp.get(SkillType.TAMING)
+                    + ", mining = " + skillsXp.get(SkillType.MINING)
+                    + ", repair = " + skillsXp.get(SkillType.REPAIR)
+                    + ", woodcutting = " + skillsXp.get(SkillType.WOODCUTTING)
+                    + ", unarmed = " + skillsXp.get(SkillType.UNARMED)
+                    + ", herbalism = " + skillsXp.get(SkillType.HERBALISM)
+                    + ", excavation = " + skillsXp.get(SkillType.EXCAVATION)
+                    + ", archery = " + skillsXp.get(SkillType.ARCHERY)
+                    + ", swords = " + skillsXp.get(SkillType.SWORDS)
+                    + ", axes = " + skillsXp.get(SkillType.AXES)
+                    + ", acrobatics = " + skillsXp.get(SkillType.ACROBATICS)
+                    + ", fishing = " + skillsXp.get(SkillType.FISHING)
+                    + "  WHERE user_id = " + userId);
+        }
+    }
+
+    private void saveFlatfile() {
+        try {
+            // Open the file
+            BufferedReader in = new BufferedReader(new FileReader(mcMMO.getUsersFilePath()));
+            StringBuilder writer = new StringBuilder();
+            String line;
+
+            // While not at the end of the file
+            while ((line = in.readLine()) != null) {
+                // Read the line in and copy it to the output it's not the player we want to edit
+                if (!line.split(":")[0].equalsIgnoreCase(playerName)) {
+                    writer.append(line).append("\r\n");
+                }
+                else {
+                    // Otherwise write the new player information
+                    writer.append(playerName).append(":");
+                    writer.append(skills.get(SkillType.MINING)).append(":");
+                    writer.append(":");
+                    writer.append(":");
+                    writer.append(skillsXp.get(SkillType.MINING)).append(":");
+                    writer.append(skills.get(SkillType.WOODCUTTING)).append(":");
+                    writer.append(skillsXp.get(SkillType.WOODCUTTING)).append(":");
+                    writer.append(skills.get(SkillType.REPAIR)).append(":");
+                    writer.append(skills.get(SkillType.UNARMED)).append(":");
+                    writer.append(skills.get(SkillType.HERBALISM)).append(":");
+                    writer.append(skills.get(SkillType.EXCAVATION)).append(":");
+                    writer.append(skills.get(SkillType.ARCHERY)).append(":");
+                    writer.append(skills.get(SkillType.SWORDS)).append(":");
+                    writer.append(skills.get(SkillType.AXES)).append(":");
+                    writer.append(skills.get(SkillType.ACROBATICS)).append(":");
+                    writer.append(skillsXp.get(SkillType.REPAIR)).append(":");
+                    writer.append(skillsXp.get(SkillType.UNARMED)).append(":");
+                    writer.append(skillsXp.get(SkillType.HERBALISM)).append(":");
+                    writer.append(skillsXp.get(SkillType.EXCAVATION)).append(":");
+                    writer.append(skillsXp.get(SkillType.ARCHERY)).append(":");
+                    writer.append(skillsXp.get(SkillType.SWORDS)).append(":");
+                    writer.append(skillsXp.get(SkillType.AXES)).append(":");
+                    writer.append(skillsXp.get(SkillType.ACROBATICS)).append(":");
+                    writer.append(":");
+                    writer.append(skills.get(SkillType.TAMING)).append(":");
+                    writer.append(skillsXp.get(SkillType.TAMING)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.BERSERK)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.GIGA_DRILL_BREAKER)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.TREE_FELLER)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.GREEN_TERRA)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.SERRATED_STRIKES)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.SKULL_SPLITTER)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.SUPER_BREAKER)).append(":");
+                    writer.append(hudType.toString()).append(":");
+                    writer.append(skills.get(SkillType.FISHING)).append(":");
+                    writer.append(skillsXp.get(SkillType.FISHING)).append(":");
+                    writer.append(skillsDATS.get(AbilityType.BLAST_MINING)).append(":");
+                    writer.append(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR).append(":");
+                    writer.append(mobHealthbarType.toString()).append(":");
+                    writer.append("\r\n");
+                }
+            }
+
+            in.close();
+
+            // Write the new file
+            FileWriter out = new FileWriter(mcMMO.getUsersFilePath());
+            out.write(writer.toString());
+            out.flush();
+            out.close();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void loadSkillXpData(SkillType skill, String[] data, int dataIndex) {
+        if (data.length > dataIndex) {
+            skillsXp.put(skill, Integer.valueOf(data[dataIndex]));
+        }
+    }
+
+    private void loadSkillData(SkillType skill, String[] data, int dataIndex) {
+        if (data.length > dataIndex) {
+            skills.put(skill, Integer.valueOf(data[dataIndex]));
+        }
+    }
+
+    private void loadDATSData(AbilityType ability, String[] data, int dataIndex) {
+        if (data.length > dataIndex) {
+            skillsDATS.put(ability, Integer.valueOf(data[dataIndex]));
+        }
+    }
 }

+ 3 - 15
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -23,7 +23,6 @@ import com.gmail.nossr50.config.mods.CustomToolConfig;
 import com.gmail.nossr50.config.spout.SpoutConfig;
 import com.gmail.nossr50.config.treasure.TreasureConfig;
 import com.gmail.nossr50.database.DatabaseManager;
-import com.gmail.nossr50.database.LeaderboardManager;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.listeners.BlockListener;
@@ -65,8 +64,9 @@ public class mcMMO extends JavaPlugin {
 
     public static mcMMO p;
 
-    public static ChunkManager  placeStore;
+    public static ChunkManager placeStore;
     public static RepairableManager repairableManager;
+    public static DatabaseManager databaseManager;
 
     // Jar Stuff
     public static File mcmmo;
@@ -111,25 +111,13 @@ public class mcMMO extends JavaPlugin {
             setupSpout();
             loadConfigFiles();
 
-            if (!Config.getInstance().getUseMySQL()) {
-                UserManager.loadUsers();
-            }
+            databaseManager = new DatabaseManager(this, Config.getInstance().getUseMySQL());
 
             registerEvents();
             registerCustomRecipes();
 
             PartyManager.loadParties();
 
-            // Setup the leader boards
-            if (Config.getInstance().getUseMySQL()) {
-                // TODO: Why do we have to check for a connection that hasn't be made yet?
-                DatabaseManager.checkConnected();
-                DatabaseManager.createStructure();
-            }
-            else {
-                LeaderboardManager.updateLeaderboards();
-            }
-
             for (Player player : getServer().getOnlinePlayers()) {
                 UserManager.addUser(player); // In case of reload add all users back into UserManager
             }

+ 4 - 2
src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandAsyncTask.java

@@ -6,7 +6,9 @@ import org.bukkit.command.CommandSender;
 import org.bukkit.scheduler.BukkitRunnable;
 
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.database.SQLDatabaseManager;
+import com.gmail.nossr50.database.FlatfileDatabaseManager;
 
 public class McrankCommandAsyncTask extends BukkitRunnable {
     private final String playerName;
@@ -19,7 +21,7 @@ public class McrankCommandAsyncTask extends BukkitRunnable {
 
     @Override
     public void run() {
-        Map<String, Integer> skills = DatabaseManager.readSQLRank(playerName);
+        Map<String, Integer> skills = Config.getInstance().getUseMySQL() ? SQLDatabaseManager.readSQLRank(playerName) : FlatfileDatabaseManager.getPlayerRanks(playerName);
 
         new McrankCommandDisplayTask(skills, sender, playerName).runTaskLater(mcMMO.p, 1);
     }

+ 10 - 14
src/main/java/com/gmail/nossr50/runnables/commands/McrankCommandDisplayTask.java

@@ -6,6 +6,7 @@ import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 import org.bukkit.scheduler.BukkitRunnable;
 
+import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.Permissions;
@@ -24,27 +25,22 @@ public class McrankCommandDisplayTask extends BukkitRunnable {
 
     @Override
     public void run() {
+        Player player = mcMMO.p.getServer().getPlayer(playerName);
+        Integer rank;
+
         sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Heading"));
         sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Player", playerName));
 
-        for (SkillType skillType : SkillType.values()) {
-            if ((sender instanceof Player && !Permissions.skillEnabled(sender, skillType)) || skillType.isChildSkill()) {
+        for (SkillType skill : SkillType.values()) {
+            if (skill.isChildSkill() || !Permissions.skillEnabled(player, skill)) {
                 continue;
             }
 
-            if (skills.get(skillType.name()) == null) {
-                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillUtils.getSkillName(skillType), LocaleLoader.getString("Commands.mcrank.Unranked")));
-            }
-            else {
-                sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillUtils.getSkillName(skillType), skills.get(skillType.name())));
-            }
+            rank = skills.get(skill.name());
+            sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Skill", SkillUtils.getSkillName(skill), (rank == null ? LocaleLoader.getString("Commands.mcrank.Unranked") : rank)));
         }
 
-        if (skills.get("ALL") == null) {
-            sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Overall", LocaleLoader.getString("Commands.mcrank.Unranked")));
-        }
-        else {
-            sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Overall", skills.get("ALL")));
-        }
+        rank = skills.get("ALL");
+        sender.sendMessage(LocaleLoader.getString("Commands.mcrank.Overall", (rank == null ? LocaleLoader.getString("Commands.mcrank.Unranked") : rank)));
     }
 }

+ 3 - 3
src/main/java/com/gmail/nossr50/runnables/commands/MctopCommandAsyncTask.java

@@ -8,7 +8,7 @@ import org.bukkit.scheduler.BukkitRunnable;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.database.SQLDatabaseManager;
 
 public class MctopCommandAsyncTask extends BukkitRunnable {
 
@@ -18,14 +18,14 @@ public class MctopCommandAsyncTask extends BukkitRunnable {
 
     public MctopCommandAsyncTask(int page, String query, CommandSender sender) {
         this.page = page;
-        this.query = query;
+        this.query = query.equalsIgnoreCase("ALL") ? "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing" : query;
         this.sender = sender;
     }
 
     @Override
     public void run() {
         String tablePrefix = Config.getInstance().getMySQLTablePrefix();
-        final HashMap<Integer, ArrayList<String>> userslist = DatabaseManager.read("SELECT " + query + ", user, NOW() FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON (user_id = id) WHERE " + query + " > 0 ORDER BY " + query + " DESC, user LIMIT " + ((page * 10) - 10) + ",10");
+        final HashMap<Integer, ArrayList<String>> userslist = SQLDatabaseManager.read("SELECT " + query + ", user, NOW() FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON (user_id = id) WHERE " + query + " > 0 ORDER BY " + query + " DESC, user LIMIT " + ((page * 10) - 10) + ",10");
 
         new MctopCommandDisplayTask(userslist, page, tablePrefix, sender).runTaskLater(mcMMO.p, 1);
     }

+ 12 - 12
src/main/java/com/gmail/nossr50/runnables/database/SQLConversionTask.java

@@ -7,7 +7,7 @@ import org.bukkit.scheduler.BukkitRunnable;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.database.SQLDatabaseManager;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.StringUtils;
 
@@ -158,7 +158,7 @@ public class SQLConversionTask extends BukkitRunnable {
                 }
 
                 // Check to see if the user is in the DB
-                id = DatabaseManager.getInt("SELECT id FROM "
+                id = SQLDatabaseManager.getInt("SELECT id FROM "
                         + tablePrefix
                         + "users WHERE user = '" + playerName + "'");
 
@@ -166,11 +166,11 @@ public class SQLConversionTask extends BukkitRunnable {
                     theCount++;
 
                     // Update the skill values
-                    DatabaseManager.write("UPDATE "
+                    SQLDatabaseManager.write("UPDATE "
                             + tablePrefix
                             + "users SET lastlogin = " + 0
                             + " WHERE id = " + id);
-                    DatabaseManager.write("UPDATE "
+                    SQLDatabaseManager.write("UPDATE "
                             + tablePrefix
                             + "skills SET "
                             + "  taming = taming+" + StringUtils.getInt(taming)
@@ -186,7 +186,7 @@ public class SQLConversionTask extends BukkitRunnable {
                             + ", acrobatics = acrobatics+" + StringUtils.getInt(acrobatics)
                             + ", fishing = fishing+" + StringUtils.getInt(fishing)
                             + " WHERE user_id = " + id);
-                    DatabaseManager.write("UPDATE "
+                    SQLDatabaseManager.write("UPDATE "
                             + tablePrefix
                             + "experience SET "
                             + "  taming = " + StringUtils.getInt(tamingXP)
@@ -207,24 +207,24 @@ public class SQLConversionTask extends BukkitRunnable {
                     theCount++;
 
                     // Create the user in the DB
-                    DatabaseManager.write("INSERT INTO "
+                    SQLDatabaseManager.write("INSERT INTO "
                             + tablePrefix
                             + "users (user, lastlogin) VALUES ('"
                             + playerName + "',"
                             + System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR + ")");
-                    id = DatabaseManager.getInt("SELECT id FROM "
+                    id = SQLDatabaseManager.getInt("SELECT id FROM "
                             + tablePrefix
                             + "users WHERE user = '"
                             + playerName + "'");
-                    DatabaseManager.write("INSERT INTO "
+                    SQLDatabaseManager.write("INSERT INTO "
                             + tablePrefix
                             + "skills (user_id) VALUES (" + id + ")");
-                    DatabaseManager.write("INSERT INTO "
+                    SQLDatabaseManager.write("INSERT INTO "
                             + tablePrefix
                             + "experience (user_id) VALUES (" + id
                             + ")");
                     // Update the skill values
-                    DatabaseManager.write("UPDATE "
+                    SQLDatabaseManager.write("UPDATE "
                             + tablePrefix
                             + "users SET lastlogin = " + 0
                             + " WHERE id = " + id);
@@ -234,7 +234,7 @@ public class SQLConversionTask extends BukkitRunnable {
                             + "users SET party = '" + party
                             + "' WHERE id = " + id);
                     */
-                    DatabaseManager.write("UPDATE "
+                    SQLDatabaseManager.write("UPDATE "
                             + tablePrefix
                             + "skills SET "
                             + "  taming = taming+" + StringUtils.getInt(taming)
@@ -250,7 +250,7 @@ public class SQLConversionTask extends BukkitRunnable {
                             + ", acrobatics = acrobatics+" + StringUtils.getInt(acrobatics)
                             + ", fishing = fishing+" + StringUtils.getInt(fishing)
                             + " WHERE user_id = " + id);
-                    DatabaseManager.write("UPDATE "
+                    SQLDatabaseManager.write("UPDATE "
                             + tablePrefix
                             + "experience SET "
                             + "  taming = " + StringUtils.getInt(tamingXP)

+ 2 - 2
src/main/java/com/gmail/nossr50/runnables/database/SQLReconnectTask.java

@@ -4,13 +4,13 @@ import org.bukkit.entity.Player;
 import org.bukkit.scheduler.BukkitRunnable;
 
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.database.SQLDatabaseManager;
 import com.gmail.nossr50.util.player.UserManager;
 
 public class SQLReconnectTask extends BukkitRunnable {
     @Override
     public void run() {
-        if (DatabaseManager.checkConnected()) {
+        if (SQLDatabaseManager.checkConnected()) {
             UserManager.saveAll();  // Save all profiles
             UserManager.clearAll(); // Clear the profiles
 

+ 4 - 14
src/main/java/com/gmail/nossr50/runnables/database/UserPurgeTask.java

@@ -2,26 +2,16 @@ package com.gmail.nossr50.runnables.database;
 
 import org.bukkit.scheduler.BukkitRunnable;
 
+import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.database.DatabaseManager;
-import com.gmail.nossr50.database.LeaderboardManager;
 
 public class UserPurgeTask extends BukkitRunnable {
     @Override
     public void run() {
-        if (Config.getInstance().getUseMySQL()) {
-            DatabaseManager.purgePowerlessSQL();
+        mcMMO.databaseManager.purgePowerlessUsers();
 
-            if (Config.getInstance().getOldUsersCutoff() != -1) {
-                DatabaseManager.purgeOldSQL();
-            }
-        }
-        else {
-            LeaderboardManager.purgePowerlessFlatfile();
-
-            if (Config.getInstance().getOldUsersCutoff() != -1) {
-                LeaderboardManager.purgeOldFlatfile();
-            }
+        if (Config.getInstance().getOldUsersCutoff() != -1) {
+            mcMMO.databaseManager.purgeOldUsers();
         }
     }
 }

+ 5 - 2
src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileSaveTask.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.runnables.player;
 
+import org.bukkit.entity.Player;
 import org.bukkit.scheduler.BukkitRunnable;
 
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
@@ -19,8 +20,10 @@ public class PlayerProfileSaveTask extends BukkitRunnable {
     public void run() {
         playerProfile.save();
 
-        if (!mcMMOPlayer.getPlayer().isOnline()) {
-            UserManager.remove(playerProfile.getPlayerName());
+        Player player = mcMMOPlayer.getPlayer();
+
+        if (!player.isOnline()) {
+            UserManager.remove(player.getName());
         }
     }
 }

+ 0 - 16
src/main/java/com/gmail/nossr50/util/player/UserManager.java

@@ -1,7 +1,5 @@
 package com.gmail.nossr50.util.player;
 
-import java.io.File;
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -17,20 +15,6 @@ public final class UserManager {
 
     private UserManager() {};
 
-    /**
-     * Load users.
-     */
-    public static void loadUsers() {
-        new File(mcMMO.getFlatFileDirectory()).mkdir();
-
-        try {
-            new File(mcMMO.getUsersFilePath()).createNewFile();
-        }
-        catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
     /**
      * Add a new user.
      *

+ 1 - 3
src/main/java/net/shatteredlands/shatt/backup/ZipLibrary.java

@@ -48,10 +48,8 @@ public class ZipLibrary {
 
         // Create the Source List, and add directories/etc to the file.
         List<File> sources = new ArrayList<File>();
-        if (!Config.getInstance().getUseMySQL()) {
-            sources.add(FlatFileDirectory);
-        }
 
+        sources.add(FlatFileDirectory);
         sources.add(ConfigFile);
         sources.add(TreasuresFile);
         sources.add(AdvancedConfigFile);