Explorar el Código

Add an UpgradeManager

zreed hace 11 años
padre
commit
48c7aa3e84

+ 9 - 0
src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java

@@ -22,6 +22,7 @@ import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.MobHealthbarType;
 import com.gmail.nossr50.datatypes.database.DatabaseType;
 import com.gmail.nossr50.datatypes.database.PlayerStat;
+import com.gmail.nossr50.datatypes.database.UpgradeType;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
@@ -762,6 +763,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                     tryClose(out);
                 }
             }
+
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_FISHING);
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_BLAST_MINING_COOLDOWN);
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_SQL_INDEXES);
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_MOB_HEALTHBARS);
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.DROP_SQL_PARTY_NAMES);
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.DROP_SPOUT);
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_ALCHEMY);
             return;
         }
 

+ 198 - 162
src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java

@@ -4,6 +4,7 @@ import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
@@ -17,8 +18,8 @@ import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.MobHealthbarType;
 import com.gmail.nossr50.datatypes.database.DatabaseType;
-import com.gmail.nossr50.datatypes.database.DatabaseUpdateType;
 import com.gmail.nossr50.datatypes.database.PlayerStat;
+import com.gmail.nossr50.datatypes.database.UpgradeType;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
@@ -135,9 +136,9 @@ public final class SQLDatabaseManager implements DatabaseManager {
         success &= saveHuds(userId, (mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()));
         success &= saveLongs(
                 "UPDATE " + tablePrefix + "cooldowns SET "
-                    + "  mining = ?, woodcutting = ?, unarmed = ?"
-                    + ", herbalism = ?, excavation = ?, swords = ?"
-                    + ", axes = ?, blast_mining = ? WHERE user_id = ?",
+                        + "  mining = ?, woodcutting = ?, unarmed = ?"
+                        + ", herbalism = ?, excavation = ?, swords = ?"
+                        + ", axes = ?, blast_mining = ? WHERE user_id = ?",
                 userId,
                 profile.getAbilityDATS(AbilityType.SUPER_BREAKER),
                 profile.getAbilityDATS(AbilityType.TREE_FELLER),
@@ -149,10 +150,10 @@ public final class SQLDatabaseManager implements DatabaseManager {
                 profile.getAbilityDATS(AbilityType.BLAST_MINING));
         success &= saveIntegers(
                 "UPDATE " + tablePrefix + "skills SET "
-                    + " taming = ?, mining = ?, repair = ?, woodcutting = ?"
-                    + ", unarmed = ?, herbalism = ?, excavation = ?"
-                    + ", archery = ?, swords = ?, axes = ?, acrobatics = ?"
-                    + ", fishing = ?, alchemy = ? WHERE user_id = ?",
+                        + " taming = ?, mining = ?, repair = ?, woodcutting = ?"
+                        + ", unarmed = ?, herbalism = ?, excavation = ?"
+                        + ", archery = ?, swords = ?, axes = ?, acrobatics = ?"
+                        + ", fishing = ?, alchemy = ? WHERE user_id = ?",
                 profile.getSkillLevel(SkillType.TAMING),
                 profile.getSkillLevel(SkillType.MINING),
                 profile.getSkillLevel(SkillType.REPAIR),
@@ -169,10 +170,10 @@ public final class SQLDatabaseManager implements DatabaseManager {
                 userId);
         success &= saveIntegers(
                 "UPDATE " + tablePrefix + "experience SET "
-                    + " taming = ?, mining = ?, repair = ?, woodcutting = ?"
-                    + ", unarmed = ?, herbalism = ?, excavation = ?"
-                    + ", archery = ?, swords = ?, axes = ?, acrobatics = ?"
-                    + ", fishing = ?, alchemy = ? WHERE user_id = ?",
+                        + " taming = ?, mining = ?, repair = ?, woodcutting = ?"
+                        + ", unarmed = ?, herbalism = ?, excavation = ?"
+                        + ", archery = ?, swords = ?, axes = ?, acrobatics = ?"
+                        + ", fishing = ?, alchemy = ? WHERE user_id = ?",
                 profile.getSkillXpLevel(SkillType.TAMING),
                 profile.getSkillXpLevel(SkillType.MINING),
                 profile.getSkillXpLevel(SkillType.REPAIR),
@@ -242,8 +243,8 @@ public final class SQLDatabaseManager implements DatabaseManager {
                 for (SkillType skillType : SkillType.NON_CHILD_SKILLS) {
                     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 = ?)";
+                            "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);
@@ -254,8 +255,8 @@ public final class SQLDatabaseManager implements DatabaseManager {
                     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";
+                            "AND " + skillName + " = (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
+                            "WHERE user = '" + playerName + "') ORDER BY user";
 
                     statement.close();
 
@@ -361,16 +362,16 @@ public final class SQLDatabaseManager implements DatabaseManager {
         try {
             statement = connection.prepareStatement(
                     "SELECT "
-                    + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
-                    + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
-                    + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
-                    + "h.mobhealthbar "
-                    + "FROM " + tablePrefix + "users u "
-                    + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
-                    + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
-                    + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
-                    + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
-                    + "WHERE u.user = ?");
+                            + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
+                            + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
+                            + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
+                            + "h.mobhealthbar "
+                            + "FROM " + tablePrefix + "users u "
+                            + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
+                            + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
+                            + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
+                            + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
+                            + "WHERE u.user = ?");
             statement.setString(1, playerName);
 
             ResultSet result = statement.executeQuery();
@@ -437,16 +438,16 @@ public final class SQLDatabaseManager implements DatabaseManager {
         try {
             statement = connection.prepareStatement(
                     "SELECT "
-                    + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
-                    + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
-                    + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
-                    + "h.mobhealthbar "
-                    + "FROM " + tablePrefix + "users u "
-                    + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
-                    + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
-                    + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
-                    + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
-                    + "WHERE u.user = ?");
+                            + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
+                            + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
+                            + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
+                            + "h.mobhealthbar "
+                            + "FROM " + tablePrefix + "users u "
+                            + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
+                            + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
+                            + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
+                            + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
+                            + "WHERE u.user = ?");
             List<String> usernames = getStoredUsers();
             ResultSet resultSet;
             int convertedUsers = 0;
@@ -483,20 +484,20 @@ public final class SQLDatabaseManager implements DatabaseManager {
     }
 
     /**
-    * 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 attempting a database connection each
-    * time McMMO needs the database.
-    *
-    * @return the boolean value for whether or not we are connected
-    */
+     * Check connection status and re-establish if dead or stale.
+     * <p/>
+     * If the very first immediate attempt fails, further attempts
+     * will be made in progressively larger intervals up to MAX_WAIT
+     * intervals.
+     * <p/>
+     * 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 attempting a database connection each
+     * time McMMO needs the database.
+     *
+     * @return the boolean value for whether or not we are connected
+     */
     public boolean checkConnected() {
         boolean isClosed = true;
         boolean isValid = false;
@@ -709,144 +710,174 @@ public final class SQLDatabaseManager implements DatabaseManager {
                 + "PRIMARY KEY (`user_id`)) "
                 + "DEFAULT CHARSET=latin1;");
 
-        for (DatabaseUpdateType updateType : DatabaseUpdateType.values()) {
+        for (UpgradeType updateType : UpgradeType.values()) {
             checkDatabaseStructure(updateType);
         }
+
+        mcMMO.p.getLogger().info("Killing orphans");
+        write("DELETE FROM `" + tablePrefix + "experience` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "experience`.`user_id` = `u`.`id`)");
+        write("DELETE FROM `" + tablePrefix + "huds` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "huds`.`user_id` = `u`.`id`)");
+        write("DELETE FROM `" + tablePrefix + "cooldowns` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "cooldowns`.`user_id` = `u`.`id`)");
+        write("DELETE FROM `" + tablePrefix + "skills` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "skills`.`user_id` = `u`.`id`)");
     }
 
     /**
-     * Check database structure for missing values.
+     * Check database structure for necessary upgrades.
      *
-     * @param update Type of data to check updates for
+     * @param upgrade Upgrade to attempt to apply
      */
-    private void checkDatabaseStructure(DatabaseUpdateType update) {
-        String sql = "";
-
-        switch (update) {
-            case BLAST_MINING:
-                sql = "SELECT * FROM `" + tablePrefix + "cooldowns` ORDER BY `" + tablePrefix + "cooldowns`.`blast_mining` ASC LIMIT 0 , 30";
-                break;
-
-            case FISHING:
-                sql = "SELECT * FROM `" + tablePrefix + "experience` ORDER BY `" + tablePrefix + "experience`.`fishing` ASC LIMIT 0 , 30";
-                break;
-
-            case ALCHEMY:
-                sql = "SELECT * FROM `" + tablePrefix + "experience` ORDER BY `" + tablePrefix + "experience`.`alchemy` 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;");
-                }
-                return;
-
-            case MOB_HEALTHBARS:
-                sql = "SELECT * FROM `" + tablePrefix + "huds` ORDER BY `" + tablePrefix + "huds`.`mobhealthbar` ASC LIMIT 0 , 30";
-                break;
-
-            case PARTY_NAMES:
-                write("ALTER TABLE `" + tablePrefix + "users` DROP COLUMN `party` ;");
-                return;
-
-            case DROPPED_SPOUT:
-                write("ALTER TABLE `" + tablePrefix + "huds` DROP COLUMN `hudtype` ;");
-                return;
-
-            case KILL_ORPHANS:
-                mcMMO.p.getLogger().info("Killing orphans");
-                write(
-                        "DELETE FROM " + tablePrefix + "experience " +
-                         "WHERE NOT EXISTS (SELECT * FROM " +
-                         tablePrefix + "users u WHERE " +
-                         tablePrefix + "experience.user_id = u.id);"
-                         );
-                write(
-                        "DELETE FROM " + tablePrefix + "huds " +
-                         "WHERE NOT EXISTS (SELECT * FROM " +
-                         tablePrefix + "users u WHERE " +
-                         tablePrefix + "huds.user_id = u.id);"
-                         );
-                write(
-                        "DELETE FROM " + tablePrefix + "cooldowns " +
-                         "WHERE NOT EXISTS (SELECT * FROM " +
-                         tablePrefix + "users u WHERE " +
-                         tablePrefix + "cooldowns.user_id = u.id);"
-                         );
-                write(
-                        "DELETE FROM " + tablePrefix + "skills " +
-                         "WHERE NOT EXISTS (SELECT * FROM " +
-                         tablePrefix + "users u WHERE " +
-                         tablePrefix + "skills.user_id = u.id);"
-                         );
-                return;
-
-            default:
-                break;
-        }
-
-        ResultSet resultSet;
-        PreparedStatement statement = null;
+    private void checkDatabaseStructure(UpgradeType upgrade) {
+        if (!checkConnected()) {
+            return;
+        }
+
+        if (!mcMMO.getUpgradeManager().shouldUpgrade(upgrade)) {
+            mcMMO.p.debug("Skipping " + upgrade.name() + " upgrade (unneeded)");
+            return;
+        }
+
+        Statement statement = null;
+        ResultSet resultSet = null;
 
         try {
-            if (!checkConnected()) {
-                return;
-            }
+            statement = connection.createStatement();
 
-            statement = connection.prepareStatement(sql);
-            resultSet = statement.executeQuery();
+            switch (upgrade) {
+                case ADD_FISHING:
+                    try {
+                        statement.executeQuery("SELECT `fishing` FROM `" + tablePrefix + "skills` LIMIT 1");
+                    }
+                    catch (SQLException ex) {
+                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
+                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
+                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
+                    }
+                    break;
 
-            while (resultSet.next()) {
-                // No reason to do anything here... we're just trying to catch exceptions
-            }
-        }
-        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' ;");
+                case ADD_BLAST_MINING_COOLDOWN:
+                    try {
+                        statement.executeQuery("SELECT `blast_mining` FROM `" + tablePrefix + "cooldowns` LIMIT 1");
+                    }
+                    catch (SQLException ex) {
+                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
+                        statement.executeQuery("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' ;");
+                case ADD_SQL_INDEXES:
+                    resultSet = statement.executeQuery("SHOW INDEX FROM `" + tablePrefix + "skills` WHERE `Key_name` LIKE 'idx\\_%'");
+                    resultSet.last();
+
+                    if (resultSet.getRow() != SkillType.NON_CHILD_SKILLS.size()) {
+                        mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
+
+                        for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
+                            String skill_name = skill.name().toLowerCase();
+
+                            try {
+                                statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_" + skill_name + "` (`" + skill_name + "`) USING BTREE");
+                            }
+                            catch (SQLException ex) {
+                                // Ignore
+                            }
+                        }
+                    }
+                    break;
+
+                case ADD_MOB_HEALTHBARS:
+                    try {
+                        statement.executeQuery("SELECT `mobhealthbar` FROM `" + tablePrefix + "huds` LIMIT 1");
+                    }
+                    catch (SQLException ex) {
+                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for mob healthbars...");
+                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "huds` ADD `mobhealthbar` varchar(50) NOT NULL DEFAULT '" + Config.getInstance().getMobHealthbarDefault() + "'");
+                    }
+                    break;
+
+                case DROP_SQL_PARTY_NAMES:
+                    try {
+                        resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "users` LIMIT 1");
+
+                        ResultSetMetaData rsmeta = resultSet.getMetaData();
+                        boolean column_exists = false;
+
+                        for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
+                            if (rsmeta.getColumnName(i).equalsIgnoreCase("party")) {
+                                column_exists = true;
+                                break;
+                            }
+                        }
+
+                        if (column_exists) {
+                            mcMMO.p.getLogger().info("Removing party name from users table...");
+                            statement.executeQuery("ALTER TABLE `" + tablePrefix + "users` DROP COLUMN `party`");
+                        }
+                    }
+                    catch (SQLException ex) {
+                        // Ignore
+                    }
                     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 '" + Config.getInstance().getMobHealthbarDefault() + "' ;");
+                case DROP_SPOUT:
+                    try {
+                        resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "huds` LIMIT 1");
+
+                        ResultSetMetaData rsmeta = resultSet.getMetaData();
+                        boolean column_exists = false;
+
+                        for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
+                            if (rsmeta.getColumnName(i).equalsIgnoreCase("hudtype")) {
+                                column_exists = true;
+                                break;
+                            }
+                        }
+
+                        if (column_exists) {
+                            mcMMO.p.getLogger().info("Removing Spout HUD type from huds table...");
+                            statement.executeQuery("ALTER TABLE `" + tablePrefix + "huds` DROP COLUMN `hudtype`");
+                        }
+                    }
+                    catch (SQLException ex) {
+                        // Ignore
+                    }
                     break;
 
-                case ALCHEMY:
-                    mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Alchemy...");
-                    write("ALTER TABLE `"+tablePrefix + "skills` ADD `alchemy` int(10) NOT NULL DEFAULT '0' ;");
-                    write("ALTER TABLE `"+tablePrefix + "experience` ADD `alchemy` int(10) NOT NULL DEFAULT '0' ;");
+                case ADD_ALCHEMY:
+                    try {
+                        statement.executeQuery("SELECT `alchemy` FROM `" + tablePrefix + "skills` LIMIT 1");
+                    }
+                    catch (SQLException ex) {
+                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Alchemy...");
+                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "skills` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
+                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "experience` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
+                    }
                     break;
-                    
+
                 default:
                     break;
+
             }
+
+            mcMMO.getUpgradeManager().setUpgradeCompleted(upgrade);
+        }
+        catch (SQLException ex) {
+
         }
         finally {
+            if (resultSet != null) {
+                try {
+                    resultSet.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
             if (statement != null) {
                 try {
                     statement.close();
                 }
                 catch (SQLException e) {
-                    // Ignore the error, we're leaving
+                    // Ignore
                 }
             }
         }
@@ -856,6 +887,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
      * Attempt to write the SQL query.
      *
      * @param sql Query to write.
+     *
      * @return true if the query was successfully written, false otherwise.
      */
     private boolean write(String sql) {
@@ -891,6 +923,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
      * 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
      */
     private int update(String sql) {
@@ -925,6 +958,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
      * Read SQL query.
      *
      * @param sql SQL query to read
+     *
      * @return the rows in this SQL query
      */
     private HashMap<Integer, ArrayList<String>> read(String sql) {
@@ -970,6 +1004,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
      * Get the Integer. Only return first row / first field.
      *
      * @param statement SQL query to execute
+     *
      * @return the value in the first row / first field
      */
     private int readInt(PreparedStatement statement) {
@@ -1113,6 +1148,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
      * Retrieve the database id for a player
      *
      * @param playerName The name of the user to retrieve the id for
+     *
      * @return the requested id or -1 if not found
      */
     private int readId(String playerName) {
@@ -1183,8 +1219,8 @@ public final class SQLDatabaseManager implements DatabaseManager {
     }
 
     private PlayerProfile loadFromResult(String playerName, ResultSet result) throws SQLException {
-        Map<SkillType, Integer>   skills     = new HashMap<SkillType, Integer>();   // Skill & Level
-        Map<SkillType, Float>     skillsXp   = new HashMap<SkillType, Float>();     // Skill & XP
+        Map<SkillType, Integer> skills = new HashMap<SkillType, Integer>();   // Skill & Level
+        Map<SkillType, Float> skillsXp = new HashMap<SkillType, Float>();     // Skill & XP
         Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
         MobHealthbarType mobHealthbarType;
 

+ 0 - 13
src/main/java/com/gmail/nossr50/datatypes/database/DatabaseUpdateType.java

@@ -1,13 +0,0 @@
-package com.gmail.nossr50.datatypes.database;
-
-public enum DatabaseUpdateType {
-    FISHING,
-    BLAST_MINING,
-    INDEX,
-    MOB_HEALTHBARS,
-    PARTY_NAMES,
-    KILL_ORPHANS,
-    DROPPED_SPOUT,
-    ALCHEMY
-    ;
-}

+ 11 - 0
src/main/java/com/gmail/nossr50/datatypes/database/UpgradeType.java

@@ -0,0 +1,11 @@
+package com.gmail.nossr50.datatypes.database;
+
+public enum UpgradeType {
+    ADD_FISHING,
+    ADD_BLAST_MINING_COOLDOWN,
+    ADD_SQL_INDEXES,
+    ADD_MOB_HEALTHBARS,
+    DROP_SQL_PARTY_NAMES,
+    DROP_SPOUT,
+    ADD_ALCHEMY;
+}

+ 8 - 0
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -60,6 +60,7 @@ import com.gmail.nossr50.util.commands.CommandRegistrationManager;
 import com.gmail.nossr50.util.experience.FormulaManager;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
+import com.gmail.nossr50.util.upgrade.UpgradeManager;
 
 import net.shatteredlands.shatt.backup.ZipLibrary;
 
@@ -72,6 +73,7 @@ public class mcMMO extends JavaPlugin {
     private static DatabaseManager    databaseManager;
     private static FormulaManager     formulaManager;
     private static HolidayManager     holidayManager;
+    private static UpgradeManager     upgradeManager;
 
     /* File Paths */
     private static String mainDirectory;
@@ -129,6 +131,8 @@ public class mcMMO extends JavaPlugin {
             PluginManager pluginManager = getServer().getPluginManager();
             healthBarPluginEnabled = pluginManager.getPlugin("HealthBar") != null;
 
+            upgradeManager = new UpgradeManager();
+
             setupFilePaths();
 
             modManager = new ModManager();
@@ -309,6 +313,10 @@ public class mcMMO extends JavaPlugin {
         return modManager;
     }
 
+    public static UpgradeManager getUpgradeManager() {
+        return upgradeManager;
+    }
+
     @Deprecated
     public static void setDatabaseManager(DatabaseManager databaseManager) {
         mcMMO.databaseManager = databaseManager;

+ 65 - 0
src/main/java/com/gmail/nossr50/util/upgrade/UpgradeManager.java

@@ -0,0 +1,65 @@
+package com.gmail.nossr50.util.upgrade;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
+
+import com.gmail.nossr50.config.ConfigLoader;
+import com.gmail.nossr50.datatypes.database.UpgradeType;
+
+public class UpgradeManager extends ConfigLoader {
+    private final Set<UpgradeType> setNeededUpgrades;
+
+    public UpgradeManager() {
+        super("upgrades.yml");
+
+        setNeededUpgrades = EnumSet.allOf(UpgradeType.class);
+
+        loadKeys();
+    }
+
+    /**
+     * Check if the given {@link UpgradeType} is necessary.
+     *
+     * @param type Upgrade type to check
+     *
+     * @return true if plugin data needs to have the given upgrade
+     */
+    public boolean shouldUpgrade(final UpgradeType type) {
+        return setNeededUpgrades.contains(type);
+    }
+
+    /**
+     * Set the given {@link UpgradeType} as completed. Does nothing if
+     * the upgrade was applied previously.
+     *
+     * @param type Upgrade type to set as complete
+     */
+    public void setUpgradeCompleted(final UpgradeType type) {
+        if (!setNeededUpgrades.remove(type)) {
+            return;
+        }
+
+        plugin.debug("Saving upgrade status for type " + type.toString() + "...");
+
+        config.set("Upgrades_Finished." + type.toString(), true);
+
+        try {
+            config.save(getFile());
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void loadKeys() {
+        for (UpgradeType type : UpgradeType.values()) {
+            if (config.getBoolean("Upgrades_Finished." + type.toString())) {
+                setNeededUpgrades.remove(type);
+            }
+        }
+
+        plugin.debug("Needed upgrades: " + Arrays.toString(setNeededUpgrades.toArray(new UpgradeType[setNeededUpgrades.size()])));
+    }
+}

+ 9 - 0
src/main/resources/upgrades.yml

@@ -0,0 +1,9 @@
+# WARNING: DO NOT MODIFY THIS CONFIG
+Upgrades_Finished:
+    ADD_FISHING: false
+    ADD_BLAST_MINING_COOLDOWN: false
+    ADD_SQL_INDEXES: false
+    ADD_MOB_HEALTHBARS: false
+    DROP_SQL_PARTY_NAMES: false
+    DROP_SPOUT: false
+    ADD_ALCHEMY: false