diff --git a/Changelog.txt b/Changelog.txt index fc019e944..c5c2dddf5 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -10,6 +10,7 @@ Key: Version 1.5.01-dev + Added new child skill; Salvage + Added UUID support! + + Added SQL connection pooling and async loading! + Added new feature to Herbalism. Instantly-regrown crops are protected from being broken for 1 second + Added option to config.yml to show the /mcstats scoreboard automatically after logging in + Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles diff --git a/pom.xml b/pom.xml index 13b794059..2f52c8cfc 100755 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,8 @@ com.turt2live.metrics:MetricsExtension + commons-logging:commons-logging + net.snaq:dbpool @@ -83,6 +85,14 @@ com.turt2live.metrics com.gmail.nossr50.metrics.mcstats + + org.apache.commons.logging + com.gmail.nossr50.commons.logging + + + net.snaq + com.gmail.nossr50.dbpool + @@ -136,6 +146,11 @@ MetricsExtension 0.0.5-SNAPSHOT + + net.snaq + dbpool + 5.1 + diff --git a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java index aef29050b..dbfddb519 100644 --- a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java +++ b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java @@ -930,7 +930,7 @@ public final class ExperienceAPI { } private static PlayerProfile getOfflineProfile(UUID uuid) { - PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false); + PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid); if (!profile.isLoaded()) { throw new InvalidPlayerException(); @@ -942,7 +942,7 @@ public final class ExperienceAPI { @Deprecated private static PlayerProfile getOfflineProfile(String playerName) { UUID uuid = mcMMO.p.getServer().getOfflinePlayer(playerName).getUniqueId(); - PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false); + PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid); if (!profile.isLoaded()) { throw new InvalidPlayerException(); diff --git a/src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java b/src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java index 7f85a8977..8a85a67d0 100644 --- a/src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java @@ -12,6 +12,7 @@ import com.gmail.nossr50.datatypes.database.DatabaseType; import com.gmail.nossr50.datatypes.player.PlayerProfile; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.runnables.database.DatabaseConversionTask; +import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask; import com.gmail.nossr50.util.player.UserManager; public class ConvertDatabaseCommand implements CommandExecutor { @@ -55,13 +56,13 @@ public class ConvertDatabaseCommand implements CommandExecutor { UserManager.clearAll(); for (Player player : mcMMO.p.getServer().getOnlinePlayers()) { - PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId(), false); + PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId()); if (profile.isLoaded()) { mcMMO.getDatabaseManager().saveUser(profile); } - UserManager.addUser(player); + new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading } new DatabaseConversionTask(oldDatabase, sender, previousType.toString(), newType.toString()).runTaskAsynchronously(mcMMO.p); diff --git a/src/main/java/com/gmail/nossr50/commands/experience/ConvertExperienceCommand.java b/src/main/java/com/gmail/nossr50/commands/experience/ConvertExperienceCommand.java index 3c0a97f64..5efd18d06 100644 --- a/src/main/java/com/gmail/nossr50/commands/experience/ConvertExperienceCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/experience/ConvertExperienceCommand.java @@ -9,6 +9,7 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.datatypes.experience.FormulaType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.runnables.database.FormulaConversionTask; +import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask; import com.gmail.nossr50.util.player.UserManager; public class ConvertExperienceCommand implements CommandExecutor { @@ -37,7 +38,7 @@ public class ConvertExperienceCommand implements CommandExecutor { new FormulaConversionTask(sender, newType).runTaskLater(mcMMO.p, 1); for (Player player : mcMMO.p.getServer().getOnlinePlayers()) { - UserManager.addUser(player); + new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading } return true; diff --git a/src/main/java/com/gmail/nossr50/config/Config.java b/src/main/java/com/gmail/nossr50/config/Config.java index 4147af89c..f7ce43726 100644 --- a/src/main/java/com/gmail/nossr50/config/Config.java +++ b/src/main/java/com/gmail/nossr50/config/Config.java @@ -232,6 +232,7 @@ public class Config extends AutoUpdateConfigLoader { /* General Settings */ public String getLocale() { return config.getString("General.Locale", "en_us"); } public boolean getMOTDEnabled() { return config.getBoolean("General.MOTD_Enabled", true); } + public boolean getShowProfileLoadedMessage() { return config.getBoolean("General.Show_Profile_Loaded", true); } public boolean getDonateMessageEnabled() { return config.getBoolean("Commands.mcmmo.Donate_Message", true); } public int getSaveInterval() { return config.getInt("General.Save_Interval", 10); } public boolean getStatsTrackingEnabled() { return config.getBoolean("General.Stats_Tracking", true); } @@ -313,6 +314,8 @@ public class Config extends AutoUpdateConfigLoader { public int getMySQLServerPort() { return config.getInt("MySQL.Server.Port", 3306); } public String getMySQLServerName() { return config.getString("MySQL.Server.Address", "localhost"); } public String getMySQLUserPassword() { return getStringIncludingInts("MySQL.Database.User_Password"); } + public int getMySQLMaxConnections() { return config.getInt("MySQL.Database.MaxConnections"); } + public int getMySQLMaxPoolSize() { return config.getInt("MySQL.Database.MaxPoolSize"); } private String getStringIncludingInts(String key) { String str = config.getString(key); diff --git a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java b/src/main/java/com/gmail/nossr50/database/DatabaseManager.java index af0521b58..6b5e4c079 100644 --- a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/DatabaseManager.java @@ -73,7 +73,7 @@ public interface DatabaseManager { /** * Load a player from the database. * - * @deprecated replaced by {@link #loadPlayerProfile(UUID uuid, boolean createNew)} + * @deprecated replaced by {@link #loadPlayerProfile(String playerName, UUID uuid, boolean createNew)} * * @param playerName The name of the player to load from the database * @param createNew Whether to create a new record if the player is not @@ -88,12 +88,9 @@ public interface DatabaseManager { * Load a player from the database. * * @param uuid The uuid of the player to load from the database - * @param createNew Whether to create a new record if the player is not - * found * @return The player's data, or an unloaded PlayerProfile if not found - * and createNew is false */ - public PlayerProfile loadPlayerProfile(UUID uuid, boolean createNew); + public PlayerProfile loadPlayerProfile(UUID uuid); /** * Load a player from the database. Attempt to use uuid, fall back on playername @@ -132,4 +129,9 @@ public interface DatabaseManager { * @return The type of database */ public DatabaseType getDatabaseType(); + + /** + * Called when the plugin disables + */ + public void onDisable(); } diff --git a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java index e5d4588eb..03710d801 100644 --- a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java @@ -2,7 +2,6 @@ package com.gmail.nossr50.database; import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.Closeable; import java.io.File; import java.io.FileReader; import java.io.FileWriter; @@ -10,6 +9,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -98,8 +98,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } finally { - tryClose(in); - tryClose(out); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } @@ -164,8 +178,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } finally { - tryClose(in); - tryClose(out); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } @@ -203,8 +231,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } finally { - tryClose(in); - tryClose(out); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } @@ -294,8 +336,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager { return false; } finally { - tryClose(in); - tryClose(out); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } } @@ -311,7 +367,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager { public Map readRank(String playerName) { updateLeaderboards(); - Map skills = new HashMap(); + Map skills = new EnumMap(SkillType.class); for (SkillType skill : SkillType.NON_CHILD_SKILLS) { skills.put(skill, getPlayerRank(playerName, playerStatHash.get(skill))); @@ -381,18 +437,25 @@ public final class FlatfileDatabaseManager implements DatabaseManager { e.printStackTrace(); } finally { - tryClose(out); + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } } @Deprecated public PlayerProfile loadPlayerProfile(String playerName, boolean create) { - return loadPlayerProfile(playerName, "", create); + return loadPlayerProfile(playerName, "", false); } - public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) { - return loadPlayerProfile("", uuid.toString(), create); + public PlayerProfile loadPlayerProfile(UUID uuid) { + return loadPlayerProfile("", uuid.toString(), false); } public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) { @@ -448,7 +511,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager { in.close(); } catch (IOException e) { - e.printStackTrace(); + // Ignore } } } @@ -491,7 +554,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager { e.printStackTrace(); } finally { - tryClose(in); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } } } } @@ -532,8 +602,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } finally { - tryClose(in); - tryClose(out); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } @@ -573,8 +657,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } finally { - tryClose(in); - tryClose(out); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } @@ -601,7 +699,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager { e.printStackTrace(); } finally { - tryClose(in); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } } } return users; @@ -671,7 +776,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " during user " + playerName + " (Are you sure you formatted it correctly?) " + e.toString()); } finally { - tryClose(in); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } } } @@ -897,8 +1009,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } finally { - tryClose(in); - tryClose(out); + if (in != null) { + try { + in.close(); + } + catch (IOException e) { + // Ignore + } + } + if (out != null) { + try { + out.close(); + } + catch (IOException e) { + // Ignore + } + } } } @@ -923,18 +1049,6 @@ public final class FlatfileDatabaseManager implements DatabaseManager { } } - private void tryClose(Closeable c) { - if (c == null) { - return; - } - try { - c.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - private Integer getPlayerRank(String playerName, List statsList) { if (statsList == null) { return null; @@ -967,8 +1081,8 @@ public final class FlatfileDatabaseManager implements DatabaseManager { private PlayerProfile loadFromLine(String[] character) { Map skills = getSkillMapFromLine(character); // Skill levels - Map skillsXp = new HashMap(); // Skill & XP - Map skillsDATS = new HashMap(); // Ability & Cooldown + Map skillsXp = new EnumMap(SkillType.class); // Skill & XP + Map skillsDATS = new EnumMap(AbilityType.class); // Ability & Cooldown MobHealthbarType mobHealthbarType; // TODO on updates, put new values in a try{} ? @@ -1019,7 +1133,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager { } private Map getSkillMapFromLine(String[] character) { - Map skills = new HashMap(); // Skill & Level + Map skills = new EnumMap(SkillType.class); // Skill & Level skills.put(SkillType.TAMING, Integer.valueOf(character[24])); skills.put(SkillType.MINING, Integer.valueOf(character[1])); @@ -1041,4 +1155,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager { public DatabaseType getDatabaseType() { return DatabaseType.FLATFILE; } + + @Override + public void onDisable() { } } diff --git a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java index 0ee9e1b90..f219039b5 100644 --- a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java @@ -1,7 +1,6 @@ package com.gmail.nossr50.database; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; @@ -9,6 +8,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,318 +24,201 @@ 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; -import com.gmail.nossr50.runnables.database.SQLDatabaseKeepaliveTask; -import com.gmail.nossr50.runnables.database.SQLReconnectTask; import com.gmail.nossr50.runnables.database.UUIDUpdateAsyncTask; import com.gmail.nossr50.util.Misc; +import snaq.db.ConnectionPool; + public final class SQLDatabaseManager implements DatabaseManager { - private String connectionString; + private static final String ALL_QUERY_VERSION = "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy"; private String tablePrefix = Config.getInstance().getMySQLTablePrefix(); - private Connection connection = null; - // Scale waiting time by this much per failed attempt - private final double SCALING_FACTOR = 40.0; + private final int POOL_FETCH_TIMEOUT = 0; // How long a method will wait for a connection. Since none are on main thread, we can safely say wait for as long as you like. - // Minimum wait in nanoseconds (default 500ms) - private final long MIN_WAIT = 500L * 1000000L; + private final Map cachedUserIDs = new HashMap(); + private final Map cachedUserIDsByName = new HashMap(); - // Maximum time to wait between reconnects (default 5 minutes) - private final long MAX_WAIT = 5L * 60L * 1000L * 1000000L; - - // How long to wait when checking if connection is valid (default 3 seconds) - private final int VALID_TIMEOUT = 3; - - // When next to try connecting to Database in nanoseconds - private long nextReconnectTimestamp = 0L; - - // How many connection attempts have failed - private int reconnectAttempt = 0; + private ConnectionPool connectionPool; protected SQLDatabaseManager() { + String connectionString = "jdbc:mysql://" + Config.getInstance().getMySQLServerName() + ":" + Config.getInstance().getMySQLServerPort() + "/" + Config.getInstance().getMySQLDatabaseName(); + + try { + // Force driver to load if not yet loaded + Class.forName("com.mysql.jdbc.Driver"); + } + catch (ClassNotFoundException e) { + e.printStackTrace(); + return; + //throw e; // aborts onEnable() Riking if you want to do this, fully implement it. + } + + Properties connectionProperties = new Properties(); + connectionProperties.put("user", Config.getInstance().getMySQLUserName()); + connectionProperties.put("password", Config.getInstance().getMySQLUserPassword()); + connectionProperties.put("autoReconnect", "false"); + connectionProperties.put("cachePrepStmts", "true"); + connectionProperties.put("prepStmtCacheSize", "64"); + connectionProperties.put("prepStmtCacheSqlLimit", "2048"); + connectionProperties.put("useServerPrepStmts", "true"); + connectionPool = new ConnectionPool("mcMMO-Pool", + 1 /*Minimum of one*/, + Config.getInstance().getMySQLMaxPoolSize() /*max pool size */, + Config.getInstance().getMySQLMaxConnections() /*max num connections*/, + 0 /* idle timeout of connections */, + connectionString, + connectionProperties); + connectionPool.init(); // Init first connection + connectionPool.registerShutdownHook(); // Auto release on jvm exit just in case + checkStructure(); - new SQLDatabaseKeepaliveTask(this).runTaskTimerAsynchronously(mcMMO.p, 10, 60L * 60 * Misc.TICK_CONVERSION_FACTOR); } public void purgePowerlessUsers() { - if (!checkConnected()) { - return; - } - mcMMO.p.getLogger().info("Purging powerless users..."); - Collection> 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").values(); + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + List usernames = new ArrayList(); - write("DELETE FROM u, e, h, s, c USING " + tablePrefix + "users u " + - "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + - "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + - "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + - "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + - "WHERE (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"); + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + statement = connection.createStatement(); + resultSet = statement.executeQuery("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"); + + while (resultSet.next()) { + usernames.add(resultSet.getString("user")); + } + + resultSet.close(); + + statement.executeUpdate("DELETE FROM u, e, h, s, c USING " + tablePrefix + "users u " + + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + + "WHERE (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"); + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + if (!usernames.isEmpty()) { + processPurge(usernames); + } - processPurge(usernames); mcMMO.p.getLogger().info("Purged " + usernames.size() + " users from the database."); } public void purgeOldUsers() { - if (!checkConnected()) { - return; - } - - long currentTime = System.currentTimeMillis(); - mcMMO.p.getLogger().info("Purging old users..."); - Collection> usernames = read("SELECT user FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin * " + Misc.TIME_CONVERSION_FACTOR + ") > " + PURGE_TIME + ")").values(); + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + List usernames = new ArrayList(); - write("DELETE FROM u, e, h, s, c USING " + tablePrefix + "users u " + - "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + - "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + - "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + - "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + - "WHERE ((" + currentTime + " - lastlogin * " + Misc.TIME_CONVERSION_FACTOR + ") > " + PURGE_TIME + ")"); + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + statement = connection.createStatement(); + resultSet = statement.executeQuery("SELECT user FROM " + tablePrefix + "users WHERE ((NOW() - lastlogin * " + Misc.TIME_CONVERSION_FACTOR + ") > " + PURGE_TIME + ")"); + + while (resultSet.next()) { + usernames.add(resultSet.getString("user")); + } + + resultSet.close(); + + statement.executeUpdate("DELETE FROM u, e, h, s, c USING " + tablePrefix + "users u " + + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + + "WHERE ((NOW() - lastlogin * " + Misc.TIME_CONVERSION_FACTOR + ") > " + PURGE_TIME + ")"); + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + if (!usernames.isEmpty()) { + processPurge(usernames); + } - processPurge(usernames); mcMMO.p.getLogger().info("Purged " + usernames.size() + " users from the database."); } public boolean removeUser(String playerName) { - if (!checkConnected()) { - return false; - } - - boolean success = update("DELETE FROM u, e, h, s, c " + - "USING " + tablePrefix + "users u " + - "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + - "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + - "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + - "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + - "WHERE u.user = '" + playerName + "'") != 0; - - Misc.profileCleanup(playerName); - - return success; - } - - public boolean saveUser(PlayerProfile profile) { - if (!checkConnected()) { - return false; - } - - int userId = readId(profile.getPlayerName()); - if (userId == -1) { - newUser(profile.getPlayerName(), profile.getUniqueId().toString()); - userId = readId(profile.getPlayerName()); - if (userId == -1) { - return false; - } - } - boolean success = true; - MobHealthbarType mobHealthbarType = profile.getMobHealthbarType(); - - success &= saveUniqueId(userId, profile.getUniqueId().toString()); - success &= saveLogin(userId, ((int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR))); - 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 = ?", - userId, - profile.getAbilityDATS(AbilityType.SUPER_BREAKER), - profile.getAbilityDATS(AbilityType.TREE_FELLER), - profile.getAbilityDATS(AbilityType.BERSERK), - profile.getAbilityDATS(AbilityType.GREEN_TERRA), - profile.getAbilityDATS(AbilityType.GIGA_DRILL_BREAKER), - profile.getAbilityDATS(AbilityType.SERRATED_STRIKES), - profile.getAbilityDATS(AbilityType.SKULL_SPLITTER), - 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 = ?", - profile.getSkillLevel(SkillType.TAMING), - profile.getSkillLevel(SkillType.MINING), - profile.getSkillLevel(SkillType.REPAIR), - profile.getSkillLevel(SkillType.WOODCUTTING), - profile.getSkillLevel(SkillType.UNARMED), - profile.getSkillLevel(SkillType.HERBALISM), - profile.getSkillLevel(SkillType.EXCAVATION), - profile.getSkillLevel(SkillType.ARCHERY), - profile.getSkillLevel(SkillType.SWORDS), - profile.getSkillLevel(SkillType.AXES), - profile.getSkillLevel(SkillType.ACROBATICS), - profile.getSkillLevel(SkillType.FISHING), - profile.getSkillLevel(SkillType.ALCHEMY), - userId); - success &= saveIntegers( - "UPDATE " + tablePrefix + "experience SET " - + " 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), - profile.getSkillXpLevel(SkillType.WOODCUTTING), - profile.getSkillXpLevel(SkillType.UNARMED), - profile.getSkillXpLevel(SkillType.HERBALISM), - profile.getSkillXpLevel(SkillType.EXCAVATION), - profile.getSkillXpLevel(SkillType.ARCHERY), - profile.getSkillXpLevel(SkillType.SWORDS), - profile.getSkillXpLevel(SkillType.AXES), - profile.getSkillXpLevel(SkillType.ACROBATICS), - profile.getSkillXpLevel(SkillType.FISHING), - profile.getSkillXpLevel(SkillType.ALCHEMY), - userId); - return success; - } - - public List readLeaderboard(SkillType skill, int pageNumber, int statsPerPage) { - List stats = new ArrayList(); - - if (checkConnected()) { - String query = skill == null ? "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy" : skill.name().toLowerCase(); - ResultSet resultSet; - PreparedStatement statement = null; - - try { - statement = connection.prepareStatement("SELECT " + query + ", user, NOW() FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON (user_id = id) WHERE " + query + " > 0 ORDER BY " + query + " DESC, user LIMIT ?, ?"); - statement.setInt(1, (pageNumber * statsPerPage) - statsPerPage); - statement.setInt(2, statsPerPage); - resultSet = statement.executeQuery(); - - while (resultSet.next()) { - ArrayList column = new ArrayList(); - - for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) { - column.add(resultSet.getString(i)); - } - - stats.add(new PlayerStat(column.get(1), Integer.valueOf(column.get(0)))); - } - } - catch (SQLException ex) { - printErrors(ex); - } - finally { - if (statement != null) { - try { - statement.close(); - } - catch (SQLException e) { - // Ignore - } - } - } - } - - return stats; - } - - public Map readRank(String playerName) { - Map skills = new HashMap(); - - if (checkConnected()) { - ResultSet resultSet; - - try { - 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 = ?)"; - - 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.close(); - - statement = connection.prepareStatement(sql); - resultSet = statement.executeQuery(); - - while (resultSet.next()) { - if (resultSet.getString("user").equalsIgnoreCase(playerName)) { - skills.put(skillType, 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+alchemy > 0 " + - "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy > " + - "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy " + - "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"); - - statement.close(); - - sql = "SELECT user, taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy " + - "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " + - "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy > 0 " + - "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy = " + - "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy " + - "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(null, rank + resultSet.getRow()); - break; - } - } - - statement.close(); - } - catch (SQLException ex) { - printErrors(ex); - } - } - - return skills; - } - - public void newUser(String playerName, String uuid) { - if (!checkConnected()) { - return; - } - + boolean success = false; + Connection connection = null; PreparedStatement statement = null; try { - statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, uuid, lastlogin) VALUES (?, ?, ?)", Statement.RETURN_GENERATED_KEYS); - statement.setString(1, playerName); - statement.setString(2, uuid); - statement.setLong(3, System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR); - statement.execute(); + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + statement = connection.prepareStatement("DELETE FROM u, e, h, s, c " + + "USING " + tablePrefix + "users u " + + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + + "WHERE u.user = ?"); - int id = readId(playerName); - writeMissingRows(id); + statement.setString(1, playerName); + + success = statement.executeUpdate() != 0; } catch (SQLException ex) { printErrors(ex); @@ -349,26 +232,399 @@ public final class SQLDatabaseManager implements DatabaseManager { // Ignore } } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + if (success) { + Misc.profileCleanup(playerName); + } + + return success; + } + + public boolean saveUser(PlayerProfile profile) { + boolean success = true; + PreparedStatement statement = null; + Connection connection = null; + + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + + int id = getUserID(connection, profile.getUniqueId()); + + if (id == -1) { + newUser(profile.getPlayerName(), profile.getUniqueId().toString()); + id = getUserID(connection, profile.getUniqueId()); + if (id == -1) { + return false; + } + } + + statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET lastlogin = UNIX_TIMESTAMP() WHERE uuid = ?"); + statement.setString(1, profile.getUniqueId().toString()); + success &= (statement.executeUpdate() != 0); + statement.close(); + + statement = connection.prepareStatement("UPDATE " + tablePrefix + "skills SET " + + " taming = ?, mining = ?, repair = ?, woodcutting = ?" + + ", unarmed = ?, herbalism = ?, excavation = ?" + + ", archery = ?, swords = ?, axes = ?, acrobatics = ?" + + ", fishing = ?, alchemy = ? WHERE user_id = ?"); + statement.setInt(1, profile.getSkillLevel(SkillType.TAMING)); + statement.setInt(2, profile.getSkillLevel(SkillType.MINING)); + statement.setInt(3, profile.getSkillLevel(SkillType.REPAIR)); + statement.setInt(4, profile.getSkillLevel(SkillType.WOODCUTTING)); + statement.setInt(5, profile.getSkillLevel(SkillType.UNARMED)); + statement.setInt(6, profile.getSkillLevel(SkillType.HERBALISM)); + statement.setInt(7, profile.getSkillLevel(SkillType.EXCAVATION)); + statement.setInt(8, profile.getSkillLevel(SkillType.ARCHERY)); + statement.setInt(9, profile.getSkillLevel(SkillType.SWORDS)); + statement.setInt(10, profile.getSkillLevel(SkillType.AXES)); + statement.setInt(11, profile.getSkillLevel(SkillType.ACROBATICS)); + statement.setInt(12, profile.getSkillLevel(SkillType.FISHING)); + statement.setInt(13, profile.getSkillLevel(SkillType.ALCHEMY)); + statement.setInt(14, id); + success &= (statement.executeUpdate() != 0); + statement.close(); + + statement = connection.prepareStatement("UPDATE " + tablePrefix + "experience SET " + + " taming = ?, mining = ?, repair = ?, woodcutting = ?" + + ", unarmed = ?, herbalism = ?, excavation = ?" + + ", archery = ?, swords = ?, axes = ?, acrobatics = ?" + + ", fishing = ?, alchemy = ? WHERE user_id = ?"); + statement.setInt(1, profile.getSkillXpLevel(SkillType.TAMING)); + statement.setInt(2, profile.getSkillXpLevel(SkillType.MINING)); + statement.setInt(3, profile.getSkillXpLevel(SkillType.REPAIR)); + statement.setInt(4, profile.getSkillXpLevel(SkillType.WOODCUTTING)); + statement.setInt(5, profile.getSkillXpLevel(SkillType.UNARMED)); + statement.setInt(6, profile.getSkillXpLevel(SkillType.HERBALISM)); + statement.setInt(7, profile.getSkillXpLevel(SkillType.EXCAVATION)); + statement.setInt(8, profile.getSkillXpLevel(SkillType.ARCHERY)); + statement.setInt(9, profile.getSkillXpLevel(SkillType.SWORDS)); + statement.setInt(10, profile.getSkillXpLevel(SkillType.AXES)); + statement.setInt(11, profile.getSkillXpLevel(SkillType.ACROBATICS)); + statement.setInt(12, profile.getSkillXpLevel(SkillType.FISHING)); + statement.setInt(13, profile.getSkillXpLevel(SkillType.ALCHEMY)); + statement.setInt(14, id); + success &= (statement.executeUpdate() != 0); + statement.close(); + + statement = connection.prepareStatement("UPDATE " + tablePrefix + "cooldowns SET " + + " mining = ?, woodcutting = ?, unarmed = ?" + + ", herbalism = ?, excavation = ?, swords = ?" + + ", axes = ?, blast_mining = ? WHERE user_id = ?"); + statement.setLong(1, profile.getAbilityDATS(AbilityType.SUPER_BREAKER)); + statement.setLong(2, profile.getAbilityDATS(AbilityType.TREE_FELLER)); + statement.setLong(3, profile.getAbilityDATS(AbilityType.BERSERK)); + statement.setLong(4, profile.getAbilityDATS(AbilityType.GREEN_TERRA)); + statement.setLong(5, profile.getAbilityDATS(AbilityType.GIGA_DRILL_BREAKER)); + statement.setLong(6, profile.getAbilityDATS(AbilityType.SERRATED_STRIKES)); + statement.setLong(7, profile.getAbilityDATS(AbilityType.SKULL_SPLITTER)); + statement.setLong(8, profile.getAbilityDATS(AbilityType.BLAST_MINING)); + statement.setInt(9, id); + success = (statement.executeUpdate() != 0); + statement.close(); + + statement = connection.prepareStatement("UPDATE " + tablePrefix + "huds SET mobhealthbar = ? WHERE user_id = ?"); + statement.setString(1, profile.getMobHealthbarType() == null ? Config.getInstance().getMobHealthbarDefault().name() : profile.getMobHealthbarType().name()); + statement.setInt(2, id); + success = (statement.executeUpdate() != 0); + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + return success; + } + + public List readLeaderboard(SkillType skill, int pageNumber, int statsPerPage) { + List stats = new ArrayList(); + + String query = skill == null ? ALL_QUERY_VERSION : skill.name().toLowerCase(); + ResultSet resultSet = null; + PreparedStatement statement = null; + Connection connection = null; + + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + statement = connection.prepareStatement("SELECT " + query + ", user, NOW() FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON (user_id = id) WHERE " + query + " > 0 ORDER BY " + query + " DESC, user LIMIT ?, ?"); + statement.setInt(1, (pageNumber * statsPerPage) - statsPerPage); + statement.setInt(2, statsPerPage); + resultSet = statement.executeQuery(); + + while (resultSet.next()) { + ArrayList column = new ArrayList(); + + for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) { + column.add(resultSet.getString(i)); + } + + stats.add(new PlayerStat(column.get(1), Integer.valueOf(column.get(0)))); + } + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + return stats; + } + + public Map readRank(String playerName) { + Map skills = new EnumMap(SkillType.class); + + ResultSet resultSet = null; + PreparedStatement statement = null; + Connection connection = null; + + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + 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 = ?)"; + + 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"; + + resultSet.close(); + statement.close(); + + statement = connection.prepareStatement(sql); + resultSet = statement.executeQuery(); + + while (resultSet.next()) { + if (resultSet.getString("user").equalsIgnoreCase(playerName)) { + skills.put(skillType, rank + resultSet.getRow()); + break; + } + } + + resultSet.close(); + statement.close(); + } + + String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " + + "WHERE " + ALL_QUERY_VERSION + " > 0 " + + "AND " + ALL_QUERY_VERSION + " > " + + "(SELECT " + ALL_QUERY_VERSION + " " + + "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?)"; + + statement = connection.prepareStatement(sql); + statement.setString(1, playerName); + resultSet = statement.executeQuery(); + + resultSet.next(); + + int rank = resultSet.getInt("rank"); + + resultSet.close(); + statement.close(); + + sql = "SELECT user, " + ALL_QUERY_VERSION + " " + + "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " + + "WHERE " + ALL_QUERY_VERSION + " > 0 " + + "AND " + ALL_QUERY_VERSION + " = " + + "(SELECT " + ALL_QUERY_VERSION + " " + + "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(null, rank + resultSet.getRow()); + break; + } + } + + resultSet.close(); + statement.close(); + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + return skills; + } + + public void newUser(String playerName, String uuid) { + Connection connection = null; + + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + newUser(connection, playerName, uuid); + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + } + + private void newUser(Connection connection, String playerName, String uuid) { + ResultSet resultSet = null; + PreparedStatement statement = null; + + try { + statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, uuid, lastlogin) VALUES (?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + statement.setString(1, playerName); + statement.setString(2, uuid); + statement.setLong(3, System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR); + statement.executeUpdate(); + + resultSet = statement.getGeneratedKeys(); + + if (!resultSet.next()) { + return; + } + + writeMissingRows(connection, resultSet.getInt(1)); + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } } } /** - * This is a fallback method to provide the old way of getting a PlayerProfile - * in case there is no UUID match found + * This is a fallback method to provide the old way of getting a + * PlayerProfile in case there is no UUID match found */ private PlayerProfile loadPlayerNameProfile(String playerName, String uuid, boolean create, boolean retry) { - if (!checkConnected()) { - // return fake profile if not connected - if (uuid.isEmpty()) { - return new PlayerProfile(playerName, false); - } - - return new PlayerProfile(playerName, UUID.fromString(uuid), false); - } - PreparedStatement statement = null; + Connection connection = null; + ResultSet resultSet = null; try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + int id = getUserID(connection, playerName); + + if (id == -1) { + // There is no such user + if (create) { + newUser(playerName, uuid); + return loadPlayerNameProfile(playerName, uuid, false, false); + } + + // Return unloaded profile if can't create + return new PlayerProfile(playerName, false); + } + // There is such a user + writeMissingRows(connection, id); + 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, " @@ -383,23 +639,29 @@ public final class SQLDatabaseManager implements DatabaseManager { + "WHERE u.user = ?"); statement.setString(1, playerName); - ResultSet result = statement.executeQuery(); + resultSet = statement.executeQuery(); - if (result.next()) { + if (resultSet.next()) { try { - PlayerProfile ret = loadFromResult(playerName, result); - result.close(); + PlayerProfile ret = loadFromResult(playerName, resultSet); return ret; } catch (SQLException e) { } } - result.close(); } catch (SQLException ex) { printErrors(ex); } finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } if (statement != null) { try { statement.close(); @@ -408,6 +670,14 @@ public final class SQLDatabaseManager implements DatabaseManager { // Ignore } } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } } // Problem, nothing was returned @@ -417,33 +687,17 @@ public final class SQLDatabaseManager implements DatabaseManager { return new PlayerProfile(playerName, false); } - // First, read User Id - this is to check for orphans - - int id = readId(playerName); - - if (id == -1) { - // There is no such user - if (create) { - newUser(playerName, uuid); - return loadPlayerNameProfile(playerName, uuid, false, false); - } - - // Return unloaded profile if can't create - return new PlayerProfile(playerName, false); - } - // There is such a user - writeMissingRows(id); // Retry, and abort on re-failure return loadPlayerNameProfile(playerName, uuid, create, false); } @Deprecated public PlayerProfile loadPlayerProfile(String playerName, boolean create) { - return loadPlayerProfile(playerName, "", create, true); + return loadPlayerProfile(playerName, "", false, true); } - public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) { - return loadPlayerProfile("", uuid.toString(), create, true); + public PlayerProfile loadPlayerProfile(UUID uuid) { + return loadPlayerProfile("", uuid.toString(), false, true); } public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) { @@ -451,18 +705,27 @@ public final class SQLDatabaseManager implements DatabaseManager { } private PlayerProfile loadPlayerProfile(String playerName, String uuid, boolean create, boolean retry) { - if (!checkConnected()) { - // return fake profile if not connected - if (uuid.isEmpty()) { - return new PlayerProfile(playerName, false); - } - - return new PlayerProfile(playerName, UUID.fromString(uuid), false); - } - PreparedStatement statement = null; + Connection connection = null; + ResultSet resultSet = null; try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + int id = getUserID(connection, playerName); + + if (id == -1) { + // There is no such user + if (create) { + newUser(playerName, uuid); + return loadPlayerNameProfile(playerName, uuid, false, false); + } + + // Return unloaded profile if can't create + return new PlayerProfile(playerName, false); + } + // There is such a user + writeMissingRows(connection, id); + 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, " @@ -474,25 +737,26 @@ public final class SQLDatabaseManager implements DatabaseManager { + "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.UUID = ?"); + + "WHERE u.uuid = ?"); statement.setString(1, uuid); - ResultSet result = statement.executeQuery(); + resultSet = statement.executeQuery(); - if (result.next()) { + if (resultSet.next()) { try { - PlayerProfile profile = loadFromResult(playerName, result); - result.close(); + PlayerProfile profile = loadFromResult(playerName, resultSet); + resultSet.close(); + statement.close(); if (!playerName.isEmpty() && !profile.getPlayerName().isEmpty()) { statement = connection.prepareStatement( "UPDATE `" + tablePrefix + "users` " + "SET user = ? " - + "WHERE UUID = ?"); + + "WHERE uuid = ?"); statement.setString(1, playerName); statement.setString(2, uuid); - result = statement.executeQuery(); - result.close(); + statement.executeUpdate(); + statement.close(); } return profile; @@ -500,12 +764,20 @@ public final class SQLDatabaseManager implements DatabaseManager { catch (SQLException e) { } } - result.close(); + resultSet.close(); } catch (SQLException ex) { printErrors(ex); } finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } if (statement != null) { try { statement.close(); @@ -514,6 +786,14 @@ public final class SQLDatabaseManager implements DatabaseManager { // Ignore } } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } } // Problem, nothing was returned @@ -523,34 +803,17 @@ public final class SQLDatabaseManager implements DatabaseManager { return loadPlayerNameProfile(playerName, uuid, create, true); } - // First, read User Id - this is to check for orphans - - int id = readId(playerName); - - if (id == -1) { - // There is no such user - if (create) { - newUser(playerName, uuid); - return loadPlayerProfile(playerName, uuid, false, false); - } - - // Return unloaded profile if can't create - return new PlayerProfile(playerName, false); - } - // There is such a user - writeMissingRows(id); // Retry, and abort on re-failure return loadPlayerProfile(playerName, uuid, create, false); } public void convertUsers(DatabaseManager destination) { - if (!checkConnected()) { - return; - } - PreparedStatement statement = null; + Connection connection = null; + ResultSet resultSet = null; try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); 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, " @@ -564,7 +827,6 @@ public final class SQLDatabaseManager implements DatabaseManager { + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + "WHERE u.user = ?"); List usernames = getStoredUsers(); - ResultSet resultSet; int convertedUsers = 0; long startMillis = System.currentTimeMillis(); for (String playerName : usernames) { @@ -586,6 +848,14 @@ public final class SQLDatabaseManager implements DatabaseManager { printErrors(e); } finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } if (statement != null) { try { statement.close(); @@ -594,19 +864,24 @@ public final class SQLDatabaseManager implements DatabaseManager { // Ignore } } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } } } public boolean saveUserUUID(String userName, UUID uuid) { - if (!checkConnected()) { - // return false - return false; - } - PreparedStatement statement = null; + Connection connection = null; try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); statement = connection.prepareStatement( "UPDATE `" + tablePrefix + "users` SET " + " uuid = ? WHERE user = ?"); @@ -628,20 +903,25 @@ public final class SQLDatabaseManager implements DatabaseManager { // Ignore } } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } } - - // Problem, nothing was returned } public boolean saveUserUUIDs(Map fetchedUUIDs) { - if (!checkConnected()) { - return false; - } - PreparedStatement statement = null; int count = 0; + Connection connection = null; + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE user = ?"); for (Map.Entry entry : fetchedUUIDs.entrySet()) { @@ -677,123 +957,58 @@ public final class SQLDatabaseManager implements DatabaseManager { // Ignore } } - } - } - - /** - * 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 - */ - public 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) { + if (connection != null) { try { - isValid = connection.isValid(VALID_TIMEOUT); + connection.close(); } 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; + // Ignore } } } - - // 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; } public List getStoredUsers() { ArrayList users = new ArrayList(); - if (checkConnected()) { - Statement stmt = null; - try { - stmt = connection.createStatement(); - ResultSet result = stmt.executeQuery("SELECT user FROM " + tablePrefix + "users"); - while (result.next()) { - users.add(result.getString("user")); + Statement statement = null; + Connection connection = null; + ResultSet resultSet = null; + + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + statement = connection.createStatement(); + resultSet = statement.executeQuery("SELECT user FROM " + tablePrefix + "users"); + while (resultSet.next()) { + users.add(resultSet.getString("user")); + } + } + catch (SQLException e) { + printErrors(e); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore } - result.close(); } - catch (SQLException e) { - printErrors(e); + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } } - finally { - if (stmt != null) { - try { - stmt.close(); - } - catch (SQLException e) { - // Ignore - } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore } } } @@ -801,122 +1016,114 @@ public final class SQLDatabaseManager implements DatabaseManager { return users; } - /** - * Attempt to connect to the mySQL database. - */ - private void connect() { - connectionString = "jdbc:mysql://" + Config.getInstance().getMySQLServerName() + ":" + Config.getInstance().getMySQLServerPort() + "/" + Config.getInstance().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", Config.getInstance().getMySQLUserName()); - connectionProperties.put("password", Config.getInstance().getMySQLUserPassword()); - connectionProperties.put("autoReconnect", "false"); - 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().severe("Connection to MySQL failed!"); - printErrors(ex); - } - } - catch (ClassNotFoundException ex) { - connection = null; - - if (reconnectAttempt == 0 || reconnectAttempt >= 11) { - mcMMO.p.getLogger().severe("MySQL database driver not found!"); - } - } - } - /** * Checks that the database structure is present and correct */ private void checkStructure() { - if (!checkConnected()) { - return; + + Statement statement = null; + Connection connection = null; + + try { + connection = connectionPool.getConnection(POOL_FETCH_TIMEOUT); + statement = connection.createStatement(); + + statement.executeUpdate("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` (" + + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT," + + "`user` varchar(40) NOT NULL," + + "`uuid` varchar(36) NULL DEFAULT NULL," + + "`lastlogin` int(32) unsigned NOT NULL," + + "PRIMARY KEY (`id`)," + + "UNIQUE KEY `user` (`user`)," + + "UNIQUE KEY `uuid` (`uuid`)) DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;"); + statement.executeUpdate("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "huds` (" + + "`user_id` int(10) unsigned NOT NULL," + + "`mobhealthbar` varchar(50) NOT NULL DEFAULT '" + Config.getInstance().getMobHealthbarDefault() + "'," + + "PRIMARY KEY (`user_id`)) " + + "DEFAULT CHARSET=latin1;"); + statement.executeUpdate("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`)) " + + "DEFAULT CHARSET=latin1;"); + statement.executeUpdate("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'," + + "`fishing` int(10) unsigned NOT NULL DEFAULT '0'," + + "`alchemy` int(10) unsigned NOT NULL DEFAULT '0'," + + "PRIMARY KEY (`user_id`)) " + + "DEFAULT CHARSET=latin1;"); + statement.executeUpdate("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'," + + "`fishing` int(10) unsigned NOT NULL DEFAULT '0'," + + "`alchemy` int(10) unsigned NOT NULL DEFAULT '0'," + + "PRIMARY KEY (`user_id`)) " + + "DEFAULT CHARSET=latin1;"); + + for (UpgradeType updateType : UpgradeType.values()) { + checkDatabaseStructure(connection, updateType); + } + + mcMMO.p.getLogger().info("Killing orphans"); + statement.executeUpdate("DELETE FROM `" + tablePrefix + "experience` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "experience`.`user_id` = `u`.`id`)"); + statement.executeUpdate("DELETE FROM `" + tablePrefix + "huds` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "huds`.`user_id` = `u`.`id`)"); + statement.executeUpdate("DELETE FROM `" + tablePrefix + "cooldowns` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "cooldowns`.`user_id` = `u`.`id`)"); + statement.executeUpdate("DELETE FROM `" + tablePrefix + "skills` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "skills`.`user_id` = `u`.`id`)"); + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (connection != null) { + try { + connection.close(); + } + catch (SQLException e) { + // Ignore + } + } } - write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` (" - + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT," - + "`user` varchar(40) NOT NULL," - + "`uuid` varchar(36) NOT NULL DEFAULT ''," - + "`lastlogin` int(32) unsigned NOT NULL," - + "PRIMARY KEY (`id`)," - + "UNIQUE KEY `user` (`user`)) DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;"); - write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "huds` (" - + "`user_id` int(10) unsigned NOT NULL," - + "`mobhealthbar` varchar(50) NOT NULL DEFAULT '" + Config.getInstance().getMobHealthbarDefault() + "'," - + "PRIMARY KEY (`user_id`)) " - + "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`)) " - + "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'," - + "`fishing` int(10) unsigned NOT NULL DEFAULT '0'," - + "`alchemy` int(10) unsigned NOT NULL DEFAULT '0'," - + "PRIMARY KEY (`user_id`)) " - + "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'," - + "`fishing` int(10) unsigned NOT NULL DEFAULT '0'," - + "`alchemy` int(10) unsigned NOT NULL DEFAULT '0'," - + "PRIMARY KEY (`user_id`)) " - + "DEFAULT CHARSET=latin1;"); - - 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`)"); } /** @@ -924,11 +1131,7 @@ public final class SQLDatabaseManager implements DatabaseManager { * * @param upgrade Upgrade to attempt to apply */ - private void checkDatabaseStructure(UpgradeType upgrade) { - if (!checkConnected()) { - return; - } - + private void checkDatabaseStructure(Connection connection, UpgradeType upgrade) { if (!mcMMO.getUpgradeManager().shouldUpgrade(upgrade)) { mcMMO.p.debug("Skipping " + upgrade.name() + " upgrade (unneeded)"); return; @@ -994,162 +1197,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) { - if (!checkConnected()) { - return false; - } - - PreparedStatement statement = null; - try { - statement = connection.prepareStatement(sql); - statement.executeUpdate(); - return true; - } - catch (SQLException ex) { - if (!sql.contains("DROP COLUMN")) { - printErrors(ex); - } - return false; - } - finally { - if (statement != null) { - try { - statement.close(); - } - catch (SQLException e) { - // Ignore - } - } - } - } - - /** - * 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) { - int rows = 0; - - if (checkConnected()) { - 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) { - // Ignore - } - } - } - } - - return rows; - } - - /** - * Read SQL query. - * - * @param sql SQL query to read - * - * @return the rows in this SQL query - */ - private HashMap> read(String sql) { - HashMap> rows = new HashMap>(); - - if (checkConnected()) { - PreparedStatement statement = null; - ResultSet resultSet; - - try { - statement = connection.prepareStatement(sql); - resultSet = statement.executeQuery(); - - while (resultSet.next()) { - ArrayList column = new ArrayList(); - - 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) { - // Ignore - } - } - } - } - - return rows; - } - - /** - * 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) { - int result = -1; - - if (checkConnected()) { - ResultSet resultSet; - - try { - 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) { - // Ignore - } - } - } - } - - return result; - } - - private void writeMissingRows(int id) { + private void writeMissingRows(Connection connection, int id) { PreparedStatement statement = null; try { @@ -1168,8 +1216,9 @@ public final class SQLDatabaseManager implements DatabaseManager { statement.execute(); statement.close(); - statement = connection.prepareStatement("INSERT IGNORE INTO " + tablePrefix + "huds (user_id, mobhealthbar) VALUES (? ,'" + Config.getInstance().getMobHealthbarDefault().name() + "')"); + statement = connection.prepareStatement("INSERT IGNORE INTO " + tablePrefix + "huds (user_id, mobhealthbar) VALUES (?, ?)"); statement.setInt(1, id); + statement.setString(2, Config.getInstance().getMobHealthbarDefault().name()); statement.execute(); statement.close(); } @@ -1188,181 +1237,21 @@ public final class SQLDatabaseManager implements DatabaseManager { } } - private void processPurge(Collection> usernames) { - for (ArrayList user : usernames) { - Misc.profileCleanup(user.get(0)); - } - } - - private boolean saveIntegers(String sql, int... args) { - PreparedStatement statement = null; - - try { - statement = connection.prepareStatement(sql); - int i = 1; - - for (int arg : args) { - statement.setInt(i++, arg); - } - - statement.execute(); - return true; - } - catch (SQLException ex) { - printErrors(ex); - return false; - } - finally { - if (statement != null) { - try { - statement.close(); - } - catch (SQLException e) { - // Ignore - } - } - } - } - - private boolean saveLongs(String sql, int id, long... args) { - PreparedStatement statement = null; - - try { - statement = connection.prepareStatement(sql); - int i = 1; - - for (long arg : args) { - statement.setLong(i++, arg); - } - - statement.setInt(i++, id); - statement.execute(); - return true; - } - catch (SQLException ex) { - printErrors(ex); - return false; - } - finally { - if (statement != null) { - try { - statement.close(); - } - catch (SQLException e) { - // Ignore - } - } - } - } - - /** - * 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) { - int id = -1; - - try { - PreparedStatement statement = connection.prepareStatement("SELECT id FROM " + tablePrefix + "users WHERE user = ?"); - statement.setString(1, playerName); - id = readInt(statement); - } - catch (SQLException ex) { - printErrors(ex); - } - - return id; - } - - private boolean saveUniqueId(int id, String uuid) { - PreparedStatement statement = null; - - try { - statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE id = ?"); - statement.setString(1, uuid); - statement.setInt(2, id); - statement.execute(); - return true; - } - catch (SQLException ex) { - printErrors(ex); - return false; - } - finally { - if (statement != null) { - try { - statement.close(); - } - catch (SQLException e) { - // Ignore - } - } - } - } - - private boolean saveLogin(int id, long login) { - PreparedStatement statement = null; - - try { - statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET lastlogin = ? WHERE id = ?"); - statement.setLong(1, login); - statement.setInt(2, id); - statement.execute(); - return true; - } - catch (SQLException ex) { - printErrors(ex); - return false; - } - finally { - if (statement != null) { - try { - statement.close(); - } - catch (SQLException e) { - // Ignore - } - } - } - } - - private boolean saveHuds(int userId, String mobHealthBar) { - PreparedStatement statement = null; - - try { - statement = connection.prepareStatement("UPDATE " + tablePrefix + "huds SET mobhealthbar = ? WHERE user_id = ?"); - statement.setString(1, mobHealthBar); - statement.setInt(2, userId); - statement.execute(); - return true; - } - catch (SQLException ex) { - printErrors(ex); - return false; - } - finally { - if (statement != null) { - try { - statement.close(); - } - catch (SQLException e) { - // Ignore - } - } + private void processPurge(Collection usernames) { + for (String user : usernames) { + Misc.profileCleanup(user); } } private PlayerProfile loadFromResult(String playerName, ResultSet result) throws SQLException { - Map skills = new HashMap(); // Skill & Level - Map skillsXp = new HashMap(); // Skill & XP - Map skillsDATS = new HashMap(); // Ability & Cooldown + Map skills = new EnumMap(SkillType.class); // Skill & Level + Map skillsXp = new EnumMap(SkillType.class); // Skill & XP + Map skillsDATS = new EnumMap(AbilityType.class); // Ability & Cooldown MobHealthbarType mobHealthbarType; UUID uuid; - final int OFFSET_SKILLS = 0; // TODO update these numbers when the query changes (a new skill is added) + final int OFFSET_SKILLS = 0; // TODO update these numbers when the query + // changes (a new skill is added) final int OFFSET_XP = 13; final int OFFSET_DATS = 26; final int OFFSET_OTHER = 38; @@ -1426,6 +1315,8 @@ public final class SQLDatabaseManager implements DatabaseManager { } private void printErrors(SQLException ex) { + StackTraceElement element = ex.getStackTrace()[0]; + mcMMO.p.getLogger().severe("Location: " + element.getMethodName() + " " + element.getLineNumber()); mcMMO.p.getLogger().severe("SQLException: " + ex.getMessage()); mcMMO.p.getLogger().severe("SQLState: " + ex.getSQLState()); mcMMO.p.getLogger().severe("VendorError: " + ex.getErrorCode()); @@ -1533,7 +1424,8 @@ public final class SQLDatabaseManager implements DatabaseManager { if (!column_exists) { mcMMO.p.getLogger().info("Adding UUIDs to mcMMO MySQL user table..."); - statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` ADD `uuid` varchar(36) NOT NULL DEFAULT ''"); + statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` ADD `uuid` varchar(36) NULL DEFAULT NULL"); + statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` ADD UNIQUE INDEX `uuid` (`uuid`) USING BTREE"); } } catch (SQLException ex) { @@ -1551,7 +1443,7 @@ public final class SQLDatabaseManager implements DatabaseManager { } try { - resultSet = statement.executeQuery("SELECT `user` FROM `" + tablePrefix + "users` WHERE `uuid` = ''"); + resultSet = statement.executeQuery("SELECT `user` FROM `" + tablePrefix + "users` WHERE `uuid` IS NULL"); while (resultSet.next()) { names.add(resultSet.getString("user")); @@ -1647,4 +1539,110 @@ public final class SQLDatabaseManager implements DatabaseManager { } } } + + private int getUserID(final Connection connection, final String playerName) { + Integer id = cachedUserIDsByName.get(playerName.toLowerCase()); + if (id != null) { + return id; + } + + ResultSet resultSet = null; + PreparedStatement statement = null; + + try { + statement = connection.prepareStatement("SELECT id, uuid FROM " + tablePrefix + "users WHERE user = ?"); + statement.setString(1, playerName); + resultSet = statement.executeQuery(); + + if (resultSet.next()) { + id = resultSet.getInt("id"); + + cachedUserIDsByName.put(playerName.toLowerCase(), id); + + try { + cachedUserIDs.put(UUID.fromString(resultSet.getString("uuid")), id); + } + catch (Exception e) { + + } + + return id; + } + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + return -1; + } + + private int getUserID(final Connection connection, final UUID uuid) { + if (cachedUserIDs.containsKey(uuid)) { + return cachedUserIDs.get(uuid); + } + + ResultSet resultSet = null; + PreparedStatement statement = null; + + try { + statement = connection.prepareStatement("SELECT id, user FROM " + tablePrefix + "users WHERE uuid = ?"); + statement.setString(1, uuid.toString()); + resultSet = statement.executeQuery(); + + if (resultSet.next()) { + int id = resultSet.getInt("id"); + + cachedUserIDs.put(uuid, id); + cachedUserIDsByName.put(resultSet.getString("user").toLowerCase(), id); + + return id; + } + } + catch (SQLException ex) { + printErrors(ex); + } + finally { + if (resultSet != null) { + try { + resultSet.close(); + } + catch (SQLException e) { + // Ignore + } + } + if (statement != null) { + try { + statement.close(); + } + catch (SQLException e) { + // Ignore + } + } + } + + return -1; + } + + @Override + public void onDisable() { + connectionPool.release(); + } } diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java index 148c802d7..060ce77ba 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java @@ -7,12 +7,10 @@ import java.util.UUID; import org.bukkit.GameMode; import org.bukkit.Location; -import org.bukkit.Server; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.scheduler.BukkitRunnable; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.config.AdvancedConfig; @@ -93,13 +91,13 @@ public class McMMOPlayer { private boolean isUsingUnarmed; private final FixedMetadataValue playerMetadata; - public McMMOPlayer(Player player) { + public McMMOPlayer(Player player, PlayerProfile profile) { String playerName = player.getName(); UUID uuid = player.getUniqueId(); this.player = player; playerMetadata = new FixedMetadataValue(mcMMO.p, playerName); - profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, uuid, true); + this.profile = profile; party = PartyManager.getPlayerParty(playerName); ptpRecord = new PartyTeleportRecord(); @@ -130,70 +128,6 @@ public class McMMOPlayer { for (ToolType toolType : ToolType.values()) { toolMode.put(toolType, false); } - - if (!profile.isLoaded()) { - mcMMO.p.getLogger().warning("Unable to load the PlayerProfile for " + playerName + ". Will retry over the next several seconds."); - new RetryProfileLoadingTask().runTaskTimerAsynchronously(mcMMO.p, 11L, 31L); - } - } - - private class RetryProfileLoadingTask extends BukkitRunnable { - private static final int MAX_TRIES = 5; - private final String playerName = McMMOPlayer.this.player.getName(); - private final UUID uniqueId = McMMOPlayer.this.player.getUniqueId(); - private int attempt = 0; - - // WARNING: ASYNC TASK - // DO NOT MODIFY THE McMMOPLAYER FROM THIS CODE - @Override - public void run() { - // Quit if they logged out - if (!player.isOnline()) { - mcMMO.p.getLogger().info("Aborting profile loading recovery for " + playerName + " - player logged out"); - this.cancel(); - return; - } - - // Send the message that we're doing the recovery - if (attempt == 0) { - player.sendMessage(LocaleLoader.getString("Recovery.Notice")); - } - - // Increment attempt counter and try - attempt++; - PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uniqueId, true); - // If successful, schedule the apply - if (profile.isLoaded()) { - new ApplySuccessfulProfile(profile).runTask(mcMMO.p); - player.sendMessage(LocaleLoader.getString("Recovery.Success")); - this.cancel(); - return; - } - - // If we've failed five times, give up - if (attempt >= MAX_TRIES) { - mcMMO.p.getLogger().severe("Giving up on attempting to load the PlayerProfile for " + playerName); - mcMMO.p.getServer().broadcast(LocaleLoader.getString("Recovery.AdminFailureNotice", playerName), Server.BROADCAST_CHANNEL_ADMINISTRATIVE); - player.sendMessage(LocaleLoader.getString("Recovery.Failure").split("\n")); - this.cancel(); - return; - } - } - } - - private class ApplySuccessfulProfile extends BukkitRunnable { - private final PlayerProfile profile; - - private ApplySuccessfulProfile(PlayerProfile profile) { - this.profile = profile; - } - - // Synchronized task - // No database access permitted - @Override - public void run() { - McMMOPlayer.this.profile = profile; - } } public AcrobaticsManager getAcrobaticsManager() { diff --git a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java index 1eacb7fcb..faadec0fe 100644 --- a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java @@ -41,7 +41,7 @@ import com.gmail.nossr50.datatypes.skills.AbilityType; import com.gmail.nossr50.datatypes.skills.SkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.party.ShareHandler; -import com.gmail.nossr50.runnables.commands.McScoreboardKeepTask; +import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask; import com.gmail.nossr50.runnables.skills.BleedTimerTask; import com.gmail.nossr50.skills.fishing.FishingManager; import com.gmail.nossr50.skills.herbalism.HerbalismManager; @@ -387,9 +387,7 @@ public class PlayerListener implements Listener { return; } - McMMOPlayer mcMMOPlayer = UserManager.addUser(player); - mcMMOPlayer.actualizeRespawnATS(); - ScoreboardManager.setupPlayer(player); + new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading if (Config.getInstance().getMOTDEnabled() && Permissions.motd(player)) { Motd.displayAll(player); @@ -403,11 +401,6 @@ public class PlayerListener implements Listener { player.sendMessage(LocaleLoader.getString("UpdateChecker.Outdated")); player.sendMessage(LocaleLoader.getString("UpdateChecker.NewAvailable")); } - - if (Config.getInstance().getShowStatsAfterLogin()) { - ScoreboardManager.enablePlayerStatsScoreboard(player); - new McScoreboardKeepTask(player).runTaskLater(mcMMO.p, 1 * Misc.TICK_CONVERSION_FACTOR); - } } /** diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index d4ea83366..350cb8ec7 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -38,6 +38,7 @@ import com.gmail.nossr50.runnables.UpdaterResultAsyncTask; import com.gmail.nossr50.runnables.backups.CleanBackupsTask; import com.gmail.nossr50.runnables.database.UserPurgeTask; import com.gmail.nossr50.runnables.party.PartyAutoKickTask; +import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask; import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask; import com.gmail.nossr50.runnables.skills.BleedTimerTask; import com.gmail.nossr50.skills.alchemy.Alchemy; @@ -167,8 +168,7 @@ public class mcMMO extends JavaPlugin { holidayManager = new HolidayManager(); for (Player player : getServer().getOnlinePlayers()) { - UserManager.addUser(player); // In case of reload add all users back into UserManager - ScoreboardManager.setupPlayer(player); + new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading } debug("Version " + getDescription().getVersion() + " is enabled!"); @@ -242,6 +242,7 @@ public class mcMMO extends JavaPlugin { } } + databaseManager.onDisable(); debug("Was disabled."); // How informative! } diff --git a/src/main/java/com/gmail/nossr50/runnables/database/SQLDatabaseKeepaliveTask.java b/src/main/java/com/gmail/nossr50/runnables/database/SQLDatabaseKeepaliveTask.java deleted file mode 100644 index 96d301026..000000000 --- a/src/main/java/com/gmail/nossr50/runnables/database/SQLDatabaseKeepaliveTask.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.gmail.nossr50.runnables.database; - -import java.lang.ref.WeakReference; - -import org.bukkit.scheduler.BukkitRunnable; - -import com.gmail.nossr50.database.SQLDatabaseManager; - -/** - * This task is in charge of sending a MySQL ping over the MySQL connection - * every hour to prevent the connection from timing out and losing players' - * data when they join. - *

- * A WeakReference is used to keep the database instance, because - * {@link com.gmail.nossr50.commands.database.ConvertDatabaseCommand database - * conversion} may create a SQLDatabaseManager that will be thrown out. If a - * normal reference was used, the conversion would cause a combined data and - * resource leak through this task. - */ -public class SQLDatabaseKeepaliveTask extends BukkitRunnable { - WeakReference databaseInstance; - - public SQLDatabaseKeepaliveTask(SQLDatabaseManager dbman) { - databaseInstance = new WeakReference(dbman); - } - - public void run() { - SQLDatabaseManager dbman = databaseInstance.get(); - if (dbman != null) { - dbman.checkConnected(); - } - else { - // This happens when the database was started for a conversion, - // or discarded by its creator for any other reason. If this code - // was not present, we would leak the connection resources. - this.cancel(); - } - } -} diff --git a/src/main/java/com/gmail/nossr50/runnables/database/SQLReconnectTask.java b/src/main/java/com/gmail/nossr50/runnables/database/SQLReconnectTask.java deleted file mode 100644 index d60591d3b..000000000 --- a/src/main/java/com/gmail/nossr50/runnables/database/SQLReconnectTask.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.gmail.nossr50.runnables.database; - -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.database.SQLDatabaseManager; -import com.gmail.nossr50.util.player.UserManager; - -public class SQLReconnectTask extends BukkitRunnable { - @Override - public void run() { - if (((SQLDatabaseManager) mcMMO.getDatabaseManager()).checkConnected()) { - UserManager.saveAll(); // Save all profiles - UserManager.clearAll(); // Clear the profiles - - for (Player player : mcMMO.p.getServer().getOnlinePlayers()) { - UserManager.addUser(player); // Add in new profiles, forcing them to 'load' again from MySQL - } - } - } -} diff --git a/src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java b/src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java deleted file mode 100644 index 485e0f855..000000000 --- a/src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.gmail.nossr50.runnables.database; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; - -import org.bukkit.scheduler.BukkitRunnable; - -import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.uuid.UUIDFetcher; - -public class UUIDFetcherRunnable extends BukkitRunnable { - private List names; - - public UUIDFetcherRunnable(List names) { - this.names = names; - } - - public UUIDFetcherRunnable(String name) { - this.names = new ArrayList(); - this.names.add(name); - } - - @Override - public void run() { - try { - Map returns = new UUIDFetcher(this.names).call(); - new CacheReturnedNames(returns).runTask(mcMMO.p); - } - catch (Exception e) { - e.printStackTrace(); - } - } - - private class CacheReturnedNames extends BukkitRunnable { - private Map returns; - - public CacheReturnedNames(Map returns) { - this.returns = returns; - } - - @Override - public void run() { - for (Entry entry : this.returns.entrySet()) { - mcMMO.getDatabaseManager().saveUserUUID(entry.getKey(), entry.getValue()); - } - } - } -} diff --git a/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java b/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java new file mode 100644 index 000000000..a8ed02ffa --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java @@ -0,0 +1,108 @@ +package com.gmail.nossr50.runnables.player; + +import java.util.concurrent.locks.ReentrantLock; + +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.locale.LocaleLoader; +import com.gmail.nossr50.runnables.commands.McScoreboardKeepTask; +import com.gmail.nossr50.util.Misc; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.scoreboards.ScoreboardManager; + +public class PlayerProfileLoadingTask extends BukkitRunnable { + private static final int MAX_TRIES = 5; + private final Player player; + private int attempt = 0; + private ReentrantLock lock = new ReentrantLock(); + private boolean cancelled = false; + + public PlayerProfileLoadingTask(Player player) { + this.player = player; + } + + // WARNING: ASYNC TASK + // DO NOT MODIFY THE McMMOPLAYER FROM THIS CODE + @Override + public void run() { + lock.lock(); + + if (this.cancelled) { + return; + } + + // Quit if they logged out + if (!player.isOnline()) { + mcMMO.p.getLogger().info("Aborting profile loading recovery for " + player.getName() + " - player logged out"); + this.cancel(); + cancelled = true; + lock.unlock(); + return; + } + + // Increment attempt counter and try + attempt++; + + PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(player.getName(), player.getUniqueId(), true); + // If successful, schedule the apply + if (profile.isLoaded()) { + new ApplySuccessfulProfile(profile).runTask(mcMMO.p); + this.cancel(); + cancelled = true; + lock.unlock(); + return; + } + + // If we've failed five times, give up + if (attempt >= MAX_TRIES) { + mcMMO.p.getLogger().severe("Giving up on attempting to load the PlayerProfile for " + player.getName()); + mcMMO.p.getServer().broadcast(LocaleLoader.getString("Profile.Loading.AdminFailureNotice", player.getName()), Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + player.sendMessage(LocaleLoader.getString("Profile.Loading.Failure").split("\n")); + this.cancel(); + cancelled = true; + lock.unlock(); + return; + } + lock.unlock(); + } + + private class ApplySuccessfulProfile extends BukkitRunnable { + private final PlayerProfile profile; + + private ApplySuccessfulProfile(PlayerProfile profile) { + this.profile = profile; + } + + // Synchronized task + // No database access permitted + @Override + public void run() { + if (!player.isOnline()) { + mcMMO.p.getLogger().info("Aborting profile loading recovery for " + player.getName() + " - player logged out"); + return; + } + + McMMOPlayer mcMMOPlayer = new McMMOPlayer(player, profile); + UserManager.track(mcMMOPlayer); + mcMMOPlayer.actualizeRespawnATS(); + ScoreboardManager.setupPlayer(player); + + if (Config.getInstance().getShowProfileLoadedMessage()) { + player.sendMessage(LocaleLoader.getString("Profile.Loading.Success")); + } + + if (Config.getInstance().getShowStatsAfterLogin()) { + ScoreboardManager.enablePlayerStatsScoreboard(player); + new McScoreboardKeepTask(player).runTaskLater(mcMMO.p, 1 * Misc.TICK_CONVERSION_FACTOR); + } + } + } +} + + diff --git a/src/main/java/com/gmail/nossr50/util/Misc.java b/src/main/java/com/gmail/nossr50/util/Misc.java index 1196fa63f..7c3e63d02 100644 --- a/src/main/java/com/gmail/nossr50/util/Misc.java +++ b/src/main/java/com/gmail/nossr50/util/Misc.java @@ -13,6 +13,7 @@ import org.bukkit.inventory.ItemStack; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.events.items.McMMOItemSpawnEvent; +import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask; import com.gmail.nossr50.util.player.UserManager; public final class Misc { @@ -112,7 +113,7 @@ public final class Misc { if (player != null) { UserManager.remove(player); - UserManager.addUser(player); + new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading } } diff --git a/src/main/java/com/gmail/nossr50/util/player/UserManager.java b/src/main/java/com/gmail/nossr50/util/player/UserManager.java index 10923a2eb..61e704818 100644 --- a/src/main/java/com/gmail/nossr50/util/player/UserManager.java +++ b/src/main/java/com/gmail/nossr50/util/player/UserManager.java @@ -18,16 +18,12 @@ public final class UserManager { private UserManager() {} /** - * Add a new user. + * Track a new user. * - * @param player The player to create a user record for - * @return the player's {@link McMMOPlayer} object + * @param mcMMOPlayer the player profile to start tracking */ - public static McMMOPlayer addUser(Player player) { - McMMOPlayer mcMMOPlayer = new McMMOPlayer(player); - player.setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer)); - - return mcMMOPlayer; + public static void track(McMMOPlayer mcMMOPlayer) { + mcMMOPlayer.getPlayer().setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer)); } /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4b0826838..aee39c6f2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -10,6 +10,8 @@ General: Locale: en_US MOTD_Enabled: true + # Send a message to the player when his profile was successfully loaded + Show_Profile_Loaded: false # Amount of time (in minutes) to wait between saves of player information Save_Interval: 10 # Allow mcMMO to report on basic anonymous usage @@ -122,6 +124,12 @@ MySQL: User_Password: UserPassword Name: DataBaseName TablePrefix: mcmmo_ + # This setting is the max simultaneous mysql connections allowed at a time, needs to be + # high enough to support multiple player logins in quick succession + MaxConnections: 30 + # This setting is the max size of the pool of cached connections that we hold available + # at any given time + MaxPoolSize: 20 Server: Port: 3306 Address: localhost diff --git a/src/main/resources/locale/locale_en_US.properties b/src/main/resources/locale/locale_en_US.properties index 34f36eece..7b894fa84 100644 --- a/src/main/resources/locale/locale_en_US.properties +++ b/src/main/resources/locale/locale_en_US.properties @@ -960,7 +960,6 @@ Scoreboard.Misc.Cooldown=[[LIGHT_PURPLE]]Cooldown Scoreboard.Misc.Overall=[[GOLD]]Overall #DATABASE RECOVERY -Recovery.Notice=[[RED]]Notice: mcMMO was [[DARK_RED]]unable to load your data.[[RED]] Retrying 5 times... -Recovery.Success=[[GREEN]]Success! Your mcMMO data was loaded. -Recovery.Failure=[[RED]]mcMMO still cannot load your data. You may want to [[AQUA]]contact the server owner.\n[[YELLOW]]You can still play on the server, but you will have [[BOLD]]no mcMMO levels[[YELLOW]] and any XP you get [[BOLD]]will not be saved[[YELLOW]]. -Recovery.AdminFailureNotice=[[DARK_RED]][A][[RED]] mcMMO was unable to load the player data for [[YELLOW]]{0}[[RED]]. [[LIGHT_PURPLE]]Please inspect your database setup. +Profile.Loading.Success=[[GREEN]]Your mcMMO profile has been loaded. +Profile.Loading.Failure=[[RED]]mcMMO still cannot load your data. You may want to [[AQUA]]contact the server owner.\n[[YELLOW]]You can still play on the server, but you will have [[BOLD]]no mcMMO levels[[YELLOW]] and any XP you get [[BOLD]]will not be saved[[YELLOW]]. +Profile.Loading.AdminFailureNotice=[[DARK_RED]][A][[RED]] mcMMO was unable to load the player data for [[YELLOW]]{0}[[RED]]. [[LIGHT_PURPLE]]Please inspect your database setup.