From 857e12b96edf68bc2e2fdbdf72659ce82612bda1 Mon Sep 17 00:00:00 2001 From: t00thpick1 Date: Fri, 1 Aug 2014 13:35:36 -0400 Subject: [PATCH] SQLDatabaseManager optimizations, async profile loading -t00thpick1, zreed This commit changes our shared connection into a connection pool utility to prevent thread locks from multiple actions attempting to access the database at the same time. In additon, profile loading has been moved off the main thread at login time, to allieviate the performance issues caused by it. Fixes #2138, Fixes #2119, Fixes #1982, Fixes #1953 --- Changelog.txt | 1 + pom.xml | 15 + .../com/gmail/nossr50/api/ExperienceAPI.java | 4 +- .../database/ConvertDatabaseCommand.java | 5 +- .../experience/ConvertExperienceCommand.java | 3 +- .../java/com/gmail/nossr50/config/Config.java | 3 + .../nossr50/database/DatabaseManager.java | 12 +- .../database/FlatfileDatabaseManager.java | 195 +- .../nossr50/database/SQLDatabaseManager.java | 1814 ++++++++--------- .../nossr50/datatypes/player/McMMOPlayer.java | 70 +- .../nossr50/listeners/PlayerListener.java | 11 +- src/main/java/com/gmail/nossr50/mcMMO.java | 5 +- .../database/SQLDatabaseKeepaliveTask.java | 39 - .../runnables/database/SQLReconnectTask.java | 22 - .../database/UUIDFetcherRunnable.java | 51 - .../player/PlayerProfileLoadingTask.java | 108 + .../java/com/gmail/nossr50/util/Misc.java | 3 +- .../nossr50/util/player/UserManager.java | 12 +- src/main/resources/config.yml | 8 + .../resources/locale/locale_en_US.properties | 7 +- 20 files changed, 1227 insertions(+), 1161 deletions(-) delete mode 100644 src/main/java/com/gmail/nossr50/runnables/database/SQLDatabaseKeepaliveTask.java delete mode 100644 src/main/java/com/gmail/nossr50/runnables/database/SQLReconnectTask.java delete mode 100644 src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java create mode 100644 src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java 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.