SQLDatabaseManager.java 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650
  1. package com.gmail.nossr50.database;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.ResultSetMetaData;
  7. import java.sql.SQLException;
  8. import java.sql.Statement;
  9. import java.util.ArrayList;
  10. import java.util.Collection;
  11. import java.util.HashMap;
  12. import java.util.List;
  13. import java.util.Map;
  14. import java.util.Properties;
  15. import java.util.UUID;
  16. import com.gmail.nossr50.mcMMO;
  17. import com.gmail.nossr50.config.Config;
  18. import com.gmail.nossr50.datatypes.MobHealthbarType;
  19. import com.gmail.nossr50.datatypes.database.DatabaseType;
  20. import com.gmail.nossr50.datatypes.database.PlayerStat;
  21. import com.gmail.nossr50.datatypes.database.UpgradeType;
  22. import com.gmail.nossr50.datatypes.player.PlayerProfile;
  23. import com.gmail.nossr50.datatypes.skills.AbilityType;
  24. import com.gmail.nossr50.datatypes.skills.SkillType;
  25. import com.gmail.nossr50.runnables.database.SQLDatabaseKeepaliveTask;
  26. import com.gmail.nossr50.runnables.database.SQLReconnectTask;
  27. import com.gmail.nossr50.runnables.database.UUIDUpdateAsyncTask;
  28. import com.gmail.nossr50.util.Misc;
  29. public final class SQLDatabaseManager implements DatabaseManager {
  30. private String connectionString;
  31. private String tablePrefix = Config.getInstance().getMySQLTablePrefix();
  32. private Connection connection = null;
  33. // Scale waiting time by this much per failed attempt
  34. private final double SCALING_FACTOR = 40.0;
  35. // Minimum wait in nanoseconds (default 500ms)
  36. private final long MIN_WAIT = 500L * 1000000L;
  37. // Maximum time to wait between reconnects (default 5 minutes)
  38. private final long MAX_WAIT = 5L * 60L * 1000L * 1000000L;
  39. // How long to wait when checking if connection is valid (default 3 seconds)
  40. private final int VALID_TIMEOUT = 3;
  41. // When next to try connecting to Database in nanoseconds
  42. private long nextReconnectTimestamp = 0L;
  43. // How many connection attempts have failed
  44. private int reconnectAttempt = 0;
  45. protected SQLDatabaseManager() {
  46. checkStructure();
  47. new SQLDatabaseKeepaliveTask(this).runTaskTimerAsynchronously(mcMMO.p, 10, 60L * 60 * Misc.TICK_CONVERSION_FACTOR);
  48. }
  49. public void purgePowerlessUsers() {
  50. if (!checkConnected()) {
  51. return;
  52. }
  53. mcMMO.p.getLogger().info("Purging powerless users...");
  54. Collection<ArrayList<String>> usernames = read("SELECT u.user FROM " + tablePrefix + "skills AS s, " + tablePrefix + "users AS u WHERE s.user_id = u.id AND (s.taming+s.mining+s.woodcutting+s.repair+s.unarmed+s.herbalism+s.excavation+s.archery+s.swords+s.axes+s.acrobatics+s.fishing) = 0").values();
  55. write("DELETE FROM u, e, h, s, c USING " + tablePrefix + "users u " +
  56. "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " +
  57. "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " +
  58. "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " +
  59. "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " +
  60. "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");
  61. processPurge(usernames);
  62. mcMMO.p.getLogger().info("Purged " + usernames.size() + " users from the database.");
  63. }
  64. public void purgeOldUsers() {
  65. if (!checkConnected()) {
  66. return;
  67. }
  68. long currentTime = System.currentTimeMillis();
  69. mcMMO.p.getLogger().info("Purging old users...");
  70. Collection<ArrayList<String>> usernames = read("SELECT user FROM " + tablePrefix + "users WHERE ((" + currentTime + " - lastlogin * " + Misc.TIME_CONVERSION_FACTOR + ") > " + PURGE_TIME + ")").values();
  71. write("DELETE FROM u, e, h, s, c USING " + tablePrefix + "users u " +
  72. "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " +
  73. "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " +
  74. "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " +
  75. "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " +
  76. "WHERE ((" + currentTime + " - lastlogin * " + Misc.TIME_CONVERSION_FACTOR + ") > " + PURGE_TIME + ")");
  77. processPurge(usernames);
  78. mcMMO.p.getLogger().info("Purged " + usernames.size() + " users from the database.");
  79. }
  80. public boolean removeUser(String playerName) {
  81. if (!checkConnected()) {
  82. return false;
  83. }
  84. boolean success = update("DELETE FROM u, e, h, s, c " +
  85. "USING " + tablePrefix + "users u " +
  86. "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " +
  87. "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " +
  88. "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " +
  89. "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " +
  90. "WHERE u.user = '" + playerName + "'") != 0;
  91. Misc.profileCleanup(playerName);
  92. return success;
  93. }
  94. public boolean saveUser(PlayerProfile profile) {
  95. if (!checkConnected()) {
  96. return false;
  97. }
  98. int userId = readId(profile.getPlayerName());
  99. if (userId == -1) {
  100. newUser(profile.getPlayerName(), profile.getUniqueId().toString());
  101. userId = readId(profile.getPlayerName());
  102. if (userId == -1) {
  103. return false;
  104. }
  105. }
  106. boolean success = true;
  107. MobHealthbarType mobHealthbarType = profile.getMobHealthbarType();
  108. success &= saveUniqueId(userId, profile.getUniqueId().toString());
  109. success &= saveLogin(userId, ((int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)));
  110. success &= saveHuds(userId, (mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()));
  111. success &= saveLongs(
  112. "UPDATE " + tablePrefix + "cooldowns SET "
  113. + " mining = ?, woodcutting = ?, unarmed = ?"
  114. + ", herbalism = ?, excavation = ?, swords = ?"
  115. + ", axes = ?, blast_mining = ? WHERE user_id = ?",
  116. userId,
  117. profile.getAbilityDATS(AbilityType.SUPER_BREAKER),
  118. profile.getAbilityDATS(AbilityType.TREE_FELLER),
  119. profile.getAbilityDATS(AbilityType.BERSERK),
  120. profile.getAbilityDATS(AbilityType.GREEN_TERRA),
  121. profile.getAbilityDATS(AbilityType.GIGA_DRILL_BREAKER),
  122. profile.getAbilityDATS(AbilityType.SERRATED_STRIKES),
  123. profile.getAbilityDATS(AbilityType.SKULL_SPLITTER),
  124. profile.getAbilityDATS(AbilityType.BLAST_MINING));
  125. success &= saveIntegers(
  126. "UPDATE " + tablePrefix + "skills SET "
  127. + " taming = ?, mining = ?, repair = ?, woodcutting = ?"
  128. + ", unarmed = ?, herbalism = ?, excavation = ?"
  129. + ", archery = ?, swords = ?, axes = ?, acrobatics = ?"
  130. + ", fishing = ?, alchemy = ? WHERE user_id = ?",
  131. profile.getSkillLevel(SkillType.TAMING),
  132. profile.getSkillLevel(SkillType.MINING),
  133. profile.getSkillLevel(SkillType.REPAIR),
  134. profile.getSkillLevel(SkillType.WOODCUTTING),
  135. profile.getSkillLevel(SkillType.UNARMED),
  136. profile.getSkillLevel(SkillType.HERBALISM),
  137. profile.getSkillLevel(SkillType.EXCAVATION),
  138. profile.getSkillLevel(SkillType.ARCHERY),
  139. profile.getSkillLevel(SkillType.SWORDS),
  140. profile.getSkillLevel(SkillType.AXES),
  141. profile.getSkillLevel(SkillType.ACROBATICS),
  142. profile.getSkillLevel(SkillType.FISHING),
  143. profile.getSkillLevel(SkillType.ALCHEMY),
  144. userId);
  145. success &= saveIntegers(
  146. "UPDATE " + tablePrefix + "experience SET "
  147. + " taming = ?, mining = ?, repair = ?, woodcutting = ?"
  148. + ", unarmed = ?, herbalism = ?, excavation = ?"
  149. + ", archery = ?, swords = ?, axes = ?, acrobatics = ?"
  150. + ", fishing = ?, alchemy = ? WHERE user_id = ?",
  151. profile.getSkillXpLevel(SkillType.TAMING),
  152. profile.getSkillXpLevel(SkillType.MINING),
  153. profile.getSkillXpLevel(SkillType.REPAIR),
  154. profile.getSkillXpLevel(SkillType.WOODCUTTING),
  155. profile.getSkillXpLevel(SkillType.UNARMED),
  156. profile.getSkillXpLevel(SkillType.HERBALISM),
  157. profile.getSkillXpLevel(SkillType.EXCAVATION),
  158. profile.getSkillXpLevel(SkillType.ARCHERY),
  159. profile.getSkillXpLevel(SkillType.SWORDS),
  160. profile.getSkillXpLevel(SkillType.AXES),
  161. profile.getSkillXpLevel(SkillType.ACROBATICS),
  162. profile.getSkillXpLevel(SkillType.FISHING),
  163. profile.getSkillXpLevel(SkillType.ALCHEMY),
  164. userId);
  165. return success;
  166. }
  167. public List<PlayerStat> readLeaderboard(SkillType skill, int pageNumber, int statsPerPage) {
  168. List<PlayerStat> stats = new ArrayList<PlayerStat>();
  169. if (checkConnected()) {
  170. String query = skill == null ? "taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy" : skill.name().toLowerCase();
  171. ResultSet resultSet;
  172. PreparedStatement statement = null;
  173. try {
  174. 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 ?, ?");
  175. statement.setInt(1, (pageNumber * statsPerPage) - statsPerPage);
  176. statement.setInt(2, statsPerPage);
  177. resultSet = statement.executeQuery();
  178. while (resultSet.next()) {
  179. ArrayList<String> column = new ArrayList<String>();
  180. for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
  181. column.add(resultSet.getString(i));
  182. }
  183. stats.add(new PlayerStat(column.get(1), Integer.valueOf(column.get(0))));
  184. }
  185. }
  186. catch (SQLException ex) {
  187. printErrors(ex);
  188. }
  189. finally {
  190. if (statement != null) {
  191. try {
  192. statement.close();
  193. }
  194. catch (SQLException e) {
  195. // Ignore
  196. }
  197. }
  198. }
  199. }
  200. return stats;
  201. }
  202. public Map<SkillType, Integer> readRank(String playerName) {
  203. Map<SkillType, Integer> skills = new HashMap<SkillType, Integer>();
  204. if (checkConnected()) {
  205. ResultSet resultSet;
  206. try {
  207. for (SkillType skillType : SkillType.NON_CHILD_SKILLS) {
  208. String skillName = skillType.name().toLowerCase();
  209. String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " +
  210. "AND " + skillName + " > (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
  211. "WHERE user = ?)";
  212. PreparedStatement statement = connection.prepareStatement(sql);
  213. statement.setString(1, playerName);
  214. resultSet = statement.executeQuery();
  215. resultSet.next();
  216. int rank = resultSet.getInt("rank");
  217. sql = "SELECT user, " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " +
  218. "AND " + skillName + " = (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
  219. "WHERE user = '" + playerName + "') ORDER BY user";
  220. statement.close();
  221. statement = connection.prepareStatement(sql);
  222. resultSet = statement.executeQuery();
  223. while (resultSet.next()) {
  224. if (resultSet.getString("user").equalsIgnoreCase(playerName)) {
  225. skills.put(skillType, rank + resultSet.getRow());
  226. break;
  227. }
  228. }
  229. statement.close();
  230. }
  231. String sql = "SELECT COUNT(*) AS rank FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
  232. "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy > 0 " +
  233. "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy > " +
  234. "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy " +
  235. "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?)";
  236. PreparedStatement statement = connection.prepareStatement(sql);
  237. statement.setString(1, playerName);
  238. resultSet = statement.executeQuery();
  239. resultSet.next();
  240. int rank = resultSet.getInt("rank");
  241. statement.close();
  242. sql = "SELECT user, taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy " +
  243. "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " +
  244. "WHERE taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy > 0 " +
  245. "AND taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy = " +
  246. "(SELECT taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy " +
  247. "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?) ORDER BY user";
  248. statement = connection.prepareStatement(sql);
  249. statement.setString(1, playerName);
  250. resultSet = statement.executeQuery();
  251. while (resultSet.next()) {
  252. if (resultSet.getString("user").equalsIgnoreCase(playerName)) {
  253. skills.put(null, rank + resultSet.getRow());
  254. break;
  255. }
  256. }
  257. statement.close();
  258. }
  259. catch (SQLException ex) {
  260. printErrors(ex);
  261. }
  262. }
  263. return skills;
  264. }
  265. public void newUser(String playerName, String uuid) {
  266. if (!checkConnected()) {
  267. return;
  268. }
  269. PreparedStatement statement = null;
  270. try {
  271. statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, uuid, lastlogin) VALUES (?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
  272. statement.setString(1, playerName);
  273. statement.setString(2, uuid);
  274. statement.setLong(3, System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR);
  275. statement.execute();
  276. int id = readId(playerName);
  277. writeMissingRows(id);
  278. }
  279. catch (SQLException ex) {
  280. printErrors(ex);
  281. }
  282. finally {
  283. if (statement != null) {
  284. try {
  285. statement.close();
  286. }
  287. catch (SQLException e) {
  288. // Ignore
  289. }
  290. }
  291. }
  292. }
  293. /**
  294. * This is a fallback method to provide the old way of getting a PlayerProfile
  295. * in case there is no UUID match found
  296. */
  297. private PlayerProfile loadPlayerNameProfile(String playerName, String uuid, boolean create, boolean retry) {
  298. if (!checkConnected()) {
  299. // return fake profile if not connected
  300. if (uuid.isEmpty()) {
  301. return new PlayerProfile(playerName, false);
  302. }
  303. return new PlayerProfile(playerName, UUID.fromString(uuid), false);
  304. }
  305. PreparedStatement statement = null;
  306. try {
  307. statement = connection.prepareStatement(
  308. "SELECT "
  309. + "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, "
  310. + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
  311. + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
  312. + "h.mobhealthbar, u.uuid "
  313. + "FROM " + tablePrefix + "users u "
  314. + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
  315. + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
  316. + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
  317. + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
  318. + "WHERE u.user = ?");
  319. statement.setString(1, playerName);
  320. ResultSet result = statement.executeQuery();
  321. if (result.next()) {
  322. try {
  323. PlayerProfile ret = loadFromResult(playerName, result);
  324. result.close();
  325. return ret;
  326. }
  327. catch (SQLException e) {
  328. }
  329. }
  330. result.close();
  331. }
  332. catch (SQLException ex) {
  333. printErrors(ex);
  334. }
  335. finally {
  336. if (statement != null) {
  337. try {
  338. statement.close();
  339. }
  340. catch (SQLException e) {
  341. // Ignore
  342. }
  343. }
  344. }
  345. // Problem, nothing was returned
  346. // Quit if this is second time around
  347. if (!retry) {
  348. return new PlayerProfile(playerName, false);
  349. }
  350. // First, read User Id - this is to check for orphans
  351. int id = readId(playerName);
  352. if (id == -1) {
  353. // There is no such user
  354. if (create) {
  355. newUser(playerName, uuid);
  356. return loadPlayerNameProfile(playerName, uuid, false, false);
  357. }
  358. // Return unloaded profile if can't create
  359. return new PlayerProfile(playerName, false);
  360. }
  361. // There is such a user
  362. writeMissingRows(id);
  363. // Retry, and abort on re-failure
  364. return loadPlayerNameProfile(playerName, uuid, create, false);
  365. }
  366. @Deprecated
  367. public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
  368. return loadPlayerProfile(playerName, "", create, true);
  369. }
  370. public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) {
  371. return loadPlayerProfile("", uuid.toString(), create, true);
  372. }
  373. public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) {
  374. return loadPlayerProfile(playerName, uuid.toString(), create, true);
  375. }
  376. private PlayerProfile loadPlayerProfile(String playerName, String uuid, boolean create, boolean retry) {
  377. if (!checkConnected()) {
  378. // return fake profile if not connected
  379. if (uuid.isEmpty()) {
  380. return new PlayerProfile(playerName, false);
  381. }
  382. return new PlayerProfile(playerName, UUID.fromString(uuid), false);
  383. }
  384. PreparedStatement statement = null;
  385. try {
  386. statement = connection.prepareStatement(
  387. "SELECT "
  388. + "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, "
  389. + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
  390. + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
  391. + "h.mobhealthbar, u.uuid "
  392. + "FROM " + tablePrefix + "users u "
  393. + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
  394. + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
  395. + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
  396. + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
  397. + "WHERE u.UUID = ?");
  398. statement.setString(1, uuid);
  399. ResultSet result = statement.executeQuery();
  400. if (result.next()) {
  401. try {
  402. PlayerProfile profile = loadFromResult(playerName, result);
  403. result.close();
  404. if (!playerName.isEmpty() && !profile.getPlayerName().isEmpty()) {
  405. statement = connection.prepareStatement(
  406. "UPDATE `" + tablePrefix + "users` "
  407. + "SET user = ? "
  408. + "WHERE UUID = ?");
  409. statement.setString(1, playerName);
  410. statement.setString(2, uuid);
  411. result = statement.executeQuery();
  412. result.close();
  413. }
  414. return profile;
  415. }
  416. catch (SQLException e) {
  417. }
  418. }
  419. result.close();
  420. }
  421. catch (SQLException ex) {
  422. printErrors(ex);
  423. }
  424. finally {
  425. if (statement != null) {
  426. try {
  427. statement.close();
  428. }
  429. catch (SQLException e) {
  430. // Ignore
  431. }
  432. }
  433. }
  434. // Problem, nothing was returned
  435. // Retry the old fashioned way if this is second time around
  436. if (!retry) {
  437. return loadPlayerNameProfile(playerName, uuid, create, true);
  438. }
  439. // First, read User Id - this is to check for orphans
  440. int id = readId(playerName);
  441. if (id == -1) {
  442. // There is no such user
  443. if (create) {
  444. newUser(playerName, uuid);
  445. return loadPlayerProfile(playerName, uuid, false, false);
  446. }
  447. // Return unloaded profile if can't create
  448. return new PlayerProfile(playerName, false);
  449. }
  450. // There is such a user
  451. writeMissingRows(id);
  452. // Retry, and abort on re-failure
  453. return loadPlayerProfile(playerName, uuid, create, false);
  454. }
  455. public void convertUsers(DatabaseManager destination) {
  456. if (!checkConnected()) {
  457. return;
  458. }
  459. PreparedStatement statement = null;
  460. try {
  461. statement = connection.prepareStatement(
  462. "SELECT "
  463. + "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, "
  464. + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
  465. + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
  466. + "h.mobhealthbar, u.uuid "
  467. + "FROM " + tablePrefix + "users u "
  468. + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
  469. + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
  470. + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
  471. + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
  472. + "WHERE u.user = ?");
  473. List<String> usernames = getStoredUsers();
  474. ResultSet resultSet;
  475. int convertedUsers = 0;
  476. long startMillis = System.currentTimeMillis();
  477. for (String playerName : usernames) {
  478. statement.setString(1, playerName);
  479. try {
  480. resultSet = statement.executeQuery();
  481. resultSet.next();
  482. destination.saveUser(loadFromResult(playerName, resultSet));
  483. resultSet.close();
  484. }
  485. catch (SQLException e) {
  486. // Ignore
  487. }
  488. convertedUsers++;
  489. Misc.printProgress(convertedUsers, progressInterval, startMillis);
  490. }
  491. }
  492. catch (SQLException e) {
  493. printErrors(e);
  494. }
  495. finally {
  496. if (statement != null) {
  497. try {
  498. statement.close();
  499. }
  500. catch (SQLException e) {
  501. // Ignore
  502. }
  503. }
  504. }
  505. }
  506. public boolean saveUserUUID(String userName, UUID uuid) {
  507. if (!checkConnected()) {
  508. // return false
  509. return false;
  510. }
  511. PreparedStatement statement = null;
  512. try {
  513. statement = connection.prepareStatement(
  514. "UPDATE `" + tablePrefix + "users` SET "
  515. + " uuid = ? WHERE user = ?");
  516. statement.setString(1, uuid.toString());
  517. statement.setString(2, userName);
  518. statement.execute();
  519. return true;
  520. }
  521. catch (SQLException ex) {
  522. printErrors(ex);
  523. return false;
  524. }
  525. finally {
  526. if (statement != null) {
  527. try {
  528. statement.close();
  529. }
  530. catch (SQLException e) {
  531. // Ignore
  532. }
  533. }
  534. }
  535. // Problem, nothing was returned
  536. }
  537. public boolean saveUserUUIDs(Map<String, UUID> fetchedUUIDs) {
  538. if (!checkConnected()) {
  539. return false;
  540. }
  541. PreparedStatement statement = null;
  542. int count = 0;
  543. try {
  544. statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE user = ?");
  545. for (Map.Entry<String, UUID> entry : fetchedUUIDs.entrySet()) {
  546. statement.setString(1, entry.getValue().toString());
  547. statement.setString(2, entry.getKey());
  548. statement.addBatch();
  549. count++;
  550. if ((count % 500) == 0) {
  551. statement.executeBatch();
  552. count = 0;
  553. }
  554. }
  555. if (count != 0) {
  556. statement.executeBatch();
  557. }
  558. return true;
  559. }
  560. catch (SQLException ex) {
  561. printErrors(ex);
  562. return false;
  563. }
  564. finally {
  565. if (statement != null) {
  566. try {
  567. statement.close();
  568. }
  569. catch (SQLException e) {
  570. // Ignore
  571. }
  572. }
  573. }
  574. }
  575. /**
  576. * Check connection status and re-establish if dead or stale.
  577. * <p/>
  578. * If the very first immediate attempt fails, further attempts
  579. * will be made in progressively larger intervals up to MAX_WAIT
  580. * intervals.
  581. * <p/>
  582. * This allows for MySQL to time out idle connections as needed by
  583. * server operator, without affecting McMMO, while still providing
  584. * protection against a database outage taking down Bukkit's tick
  585. * processing loop due to attempting a database connection each
  586. * time McMMO needs the database.
  587. *
  588. * @return the boolean value for whether or not we are connected
  589. */
  590. public boolean checkConnected() {
  591. boolean isClosed = true;
  592. boolean isValid = false;
  593. boolean exists = (connection != null);
  594. // If we're waiting for server to recover then leave early
  595. if (nextReconnectTimestamp > 0 && nextReconnectTimestamp > System.nanoTime()) {
  596. return false;
  597. }
  598. if (exists) {
  599. try {
  600. isClosed = connection.isClosed();
  601. }
  602. catch (SQLException e) {
  603. isClosed = true;
  604. e.printStackTrace();
  605. printErrors(e);
  606. }
  607. if (!isClosed) {
  608. try {
  609. isValid = connection.isValid(VALID_TIMEOUT);
  610. }
  611. catch (SQLException e) {
  612. // Don't print stack trace because it's valid to lose idle connections to the server and have to restart them.
  613. isValid = false;
  614. }
  615. }
  616. }
  617. // Leave if all ok
  618. if (exists && !isClosed && isValid) {
  619. // Housekeeping
  620. nextReconnectTimestamp = 0;
  621. reconnectAttempt = 0;
  622. return true;
  623. }
  624. // Cleanup after ourselves for GC and MySQL's sake
  625. if (exists && !isClosed) {
  626. try {
  627. connection.close();
  628. }
  629. catch (SQLException ex) {
  630. // This is a housekeeping exercise, ignore errors
  631. }
  632. }
  633. // Try to connect again
  634. connect();
  635. // Leave if connection is good
  636. try {
  637. if (connection != null && !connection.isClosed()) {
  638. // Schedule a database save if we really had an outage
  639. if (reconnectAttempt > 1) {
  640. new SQLReconnectTask().runTaskLater(mcMMO.p, 5);
  641. }
  642. nextReconnectTimestamp = 0;
  643. reconnectAttempt = 0;
  644. return true;
  645. }
  646. }
  647. catch (SQLException e) {
  648. // Failed to check isClosed, so presume connection is bad and attempt later
  649. e.printStackTrace();
  650. printErrors(e);
  651. }
  652. reconnectAttempt++;
  653. nextReconnectTimestamp = (long) (System.nanoTime() + Math.min(MAX_WAIT, (reconnectAttempt * SCALING_FACTOR * MIN_WAIT)));
  654. return false;
  655. }
  656. public List<String> getStoredUsers() {
  657. ArrayList<String> users = new ArrayList<String>();
  658. if (checkConnected()) {
  659. Statement stmt = null;
  660. try {
  661. stmt = connection.createStatement();
  662. ResultSet result = stmt.executeQuery("SELECT user FROM " + tablePrefix + "users");
  663. while (result.next()) {
  664. users.add(result.getString("user"));
  665. }
  666. result.close();
  667. }
  668. catch (SQLException e) {
  669. printErrors(e);
  670. }
  671. finally {
  672. if (stmt != null) {
  673. try {
  674. stmt.close();
  675. }
  676. catch (SQLException e) {
  677. // Ignore
  678. }
  679. }
  680. }
  681. }
  682. return users;
  683. }
  684. /**
  685. * Attempt to connect to the mySQL database.
  686. */
  687. private void connect() {
  688. connectionString = "jdbc:mysql://" + Config.getInstance().getMySQLServerName() + ":" + Config.getInstance().getMySQLServerPort() + "/" + Config.getInstance().getMySQLDatabaseName();
  689. try {
  690. mcMMO.p.getLogger().info("Attempting connection to MySQL...");
  691. // Force driver to load if not yet loaded
  692. Class.forName("com.mysql.jdbc.Driver");
  693. Properties connectionProperties = new Properties();
  694. connectionProperties.put("user", Config.getInstance().getMySQLUserName());
  695. connectionProperties.put("password", Config.getInstance().getMySQLUserPassword());
  696. connectionProperties.put("autoReconnect", "false");
  697. connection = DriverManager.getConnection(connectionString, connectionProperties);
  698. mcMMO.p.getLogger().info("Connection to MySQL was a success!");
  699. }
  700. catch (SQLException ex) {
  701. connection = null;
  702. if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
  703. mcMMO.p.getLogger().severe("Connection to MySQL failed!");
  704. printErrors(ex);
  705. }
  706. }
  707. catch (ClassNotFoundException ex) {
  708. connection = null;
  709. if (reconnectAttempt == 0 || reconnectAttempt >= 11) {
  710. mcMMO.p.getLogger().severe("MySQL database driver not found!");
  711. }
  712. }
  713. }
  714. /**
  715. * Checks that the database structure is present and correct
  716. */
  717. private void checkStructure() {
  718. if (!checkConnected()) {
  719. return;
  720. }
  721. write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` ("
  722. + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
  723. + "`user` varchar(40) NOT NULL,"
  724. + "`uuid` varchar(36) NOT NULL DEFAULT '',"
  725. + "`lastlogin` int(32) unsigned NOT NULL,"
  726. + "PRIMARY KEY (`id`),"
  727. + "UNIQUE KEY `user` (`user`)) DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;");
  728. write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "huds` ("
  729. + "`user_id` int(10) unsigned NOT NULL,"
  730. + "`mobhealthbar` varchar(50) NOT NULL DEFAULT '" + Config.getInstance().getMobHealthbarDefault() + "',"
  731. + "PRIMARY KEY (`user_id`)) "
  732. + "DEFAULT CHARSET=latin1;");
  733. write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "cooldowns` ("
  734. + "`user_id` int(10) unsigned NOT NULL,"
  735. + "`taming` int(32) unsigned NOT NULL DEFAULT '0',"
  736. + "`mining` int(32) unsigned NOT NULL DEFAULT '0',"
  737. + "`woodcutting` int(32) unsigned NOT NULL DEFAULT '0',"
  738. + "`repair` int(32) unsigned NOT NULL DEFAULT '0',"
  739. + "`unarmed` int(32) unsigned NOT NULL DEFAULT '0',"
  740. + "`herbalism` int(32) unsigned NOT NULL DEFAULT '0',"
  741. + "`excavation` int(32) unsigned NOT NULL DEFAULT '0',"
  742. + "`archery` int(32) unsigned NOT NULL DEFAULT '0',"
  743. + "`swords` int(32) unsigned NOT NULL DEFAULT '0',"
  744. + "`axes` int(32) unsigned NOT NULL DEFAULT '0',"
  745. + "`acrobatics` int(32) unsigned NOT NULL DEFAULT '0',"
  746. + "`blast_mining` int(32) unsigned NOT NULL DEFAULT '0',"
  747. + "PRIMARY KEY (`user_id`)) "
  748. + "DEFAULT CHARSET=latin1;");
  749. write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "skills` ("
  750. + "`user_id` int(10) unsigned NOT NULL,"
  751. + "`taming` int(10) unsigned NOT NULL DEFAULT '0',"
  752. + "`mining` int(10) unsigned NOT NULL DEFAULT '0',"
  753. + "`woodcutting` int(10) unsigned NOT NULL DEFAULT '0',"
  754. + "`repair` int(10) unsigned NOT NULL DEFAULT '0',"
  755. + "`unarmed` int(10) unsigned NOT NULL DEFAULT '0',"
  756. + "`herbalism` int(10) unsigned NOT NULL DEFAULT '0',"
  757. + "`excavation` int(10) unsigned NOT NULL DEFAULT '0',"
  758. + "`archery` int(10) unsigned NOT NULL DEFAULT '0',"
  759. + "`swords` int(10) unsigned NOT NULL DEFAULT '0',"
  760. + "`axes` int(10) unsigned NOT NULL DEFAULT '0',"
  761. + "`acrobatics` int(10) unsigned NOT NULL DEFAULT '0',"
  762. + "`fishing` int(10) unsigned NOT NULL DEFAULT '0',"
  763. + "`alchemy` int(10) unsigned NOT NULL DEFAULT '0',"
  764. + "PRIMARY KEY (`user_id`)) "
  765. + "DEFAULT CHARSET=latin1;");
  766. write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "experience` ("
  767. + "`user_id` int(10) unsigned NOT NULL,"
  768. + "`taming` int(10) unsigned NOT NULL DEFAULT '0',"
  769. + "`mining` int(10) unsigned NOT NULL DEFAULT '0',"
  770. + "`woodcutting` int(10) unsigned NOT NULL DEFAULT '0',"
  771. + "`repair` int(10) unsigned NOT NULL DEFAULT '0',"
  772. + "`unarmed` int(10) unsigned NOT NULL DEFAULT '0',"
  773. + "`herbalism` int(10) unsigned NOT NULL DEFAULT '0',"
  774. + "`excavation` int(10) unsigned NOT NULL DEFAULT '0',"
  775. + "`archery` int(10) unsigned NOT NULL DEFAULT '0',"
  776. + "`swords` int(10) unsigned NOT NULL DEFAULT '0',"
  777. + "`axes` int(10) unsigned NOT NULL DEFAULT '0',"
  778. + "`acrobatics` int(10) unsigned NOT NULL DEFAULT '0',"
  779. + "`fishing` int(10) unsigned NOT NULL DEFAULT '0',"
  780. + "`alchemy` int(10) unsigned NOT NULL DEFAULT '0',"
  781. + "PRIMARY KEY (`user_id`)) "
  782. + "DEFAULT CHARSET=latin1;");
  783. for (UpgradeType updateType : UpgradeType.values()) {
  784. checkDatabaseStructure(updateType);
  785. }
  786. mcMMO.p.getLogger().info("Killing orphans");
  787. write("DELETE FROM `" + tablePrefix + "experience` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "experience`.`user_id` = `u`.`id`)");
  788. write("DELETE FROM `" + tablePrefix + "huds` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "huds`.`user_id` = `u`.`id`)");
  789. write("DELETE FROM `" + tablePrefix + "cooldowns` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "cooldowns`.`user_id` = `u`.`id`)");
  790. write("DELETE FROM `" + tablePrefix + "skills` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "skills`.`user_id` = `u`.`id`)");
  791. }
  792. /**
  793. * Check database structure for necessary upgrades.
  794. *
  795. * @param upgrade Upgrade to attempt to apply
  796. */
  797. private void checkDatabaseStructure(UpgradeType upgrade) {
  798. if (!checkConnected()) {
  799. return;
  800. }
  801. if (!mcMMO.getUpgradeManager().shouldUpgrade(upgrade)) {
  802. mcMMO.p.debug("Skipping " + upgrade.name() + " upgrade (unneeded)");
  803. return;
  804. }
  805. Statement statement = null;
  806. try {
  807. statement = connection.createStatement();
  808. switch (upgrade) {
  809. case ADD_FISHING:
  810. checkUpgradeAddFishing(statement);
  811. break;
  812. case ADD_BLAST_MINING_COOLDOWN:
  813. checkUpgradeAddBlastMiningCooldown(statement);
  814. break;
  815. case ADD_SQL_INDEXES:
  816. checkUpgradeAddSQLIndexes(statement);
  817. break;
  818. case ADD_MOB_HEALTHBARS:
  819. checkUpgradeAddMobHealthbars(statement);
  820. break;
  821. case DROP_SQL_PARTY_NAMES:
  822. checkUpgradeDropPartyNames(statement);
  823. break;
  824. case DROP_SPOUT:
  825. checkUpgradeDropSpout(statement);
  826. break;
  827. case ADD_ALCHEMY:
  828. checkUpgradeAddAlchemy(statement);
  829. break;
  830. case ADD_UUIDS:
  831. checkUpgradeAddUUIDs(statement);
  832. return;
  833. default:
  834. break;
  835. }
  836. mcMMO.getUpgradeManager().setUpgradeCompleted(upgrade);
  837. }
  838. catch (SQLException ex) {
  839. printErrors(ex);
  840. }
  841. finally {
  842. if (statement != null) {
  843. try {
  844. statement.close();
  845. }
  846. catch (SQLException e) {
  847. // Ignore
  848. }
  849. }
  850. }
  851. }
  852. /**
  853. * Attempt to write the SQL query.
  854. *
  855. * @param sql Query to write.
  856. *
  857. * @return true if the query was successfully written, false otherwise.
  858. */
  859. private boolean write(String sql) {
  860. if (!checkConnected()) {
  861. return false;
  862. }
  863. PreparedStatement statement = null;
  864. try {
  865. statement = connection.prepareStatement(sql);
  866. statement.executeUpdate();
  867. return true;
  868. }
  869. catch (SQLException ex) {
  870. if (!sql.contains("DROP COLUMN")) {
  871. printErrors(ex);
  872. }
  873. return false;
  874. }
  875. finally {
  876. if (statement != null) {
  877. try {
  878. statement.close();
  879. }
  880. catch (SQLException e) {
  881. // Ignore
  882. }
  883. }
  884. }
  885. }
  886. /**
  887. * Returns the number of rows affected by either a DELETE or UPDATE query
  888. *
  889. * @param sql SQL query to execute
  890. *
  891. * @return the number of rows affected
  892. */
  893. private int update(String sql) {
  894. int rows = 0;
  895. if (checkConnected()) {
  896. PreparedStatement statement = null;
  897. try {
  898. statement = connection.prepareStatement(sql);
  899. rows = statement.executeUpdate();
  900. }
  901. catch (SQLException ex) {
  902. printErrors(ex);
  903. }
  904. finally {
  905. if (statement != null) {
  906. try {
  907. statement.close();
  908. }
  909. catch (SQLException e) {
  910. // Ignore
  911. }
  912. }
  913. }
  914. }
  915. return rows;
  916. }
  917. /**
  918. * Read SQL query.
  919. *
  920. * @param sql SQL query to read
  921. *
  922. * @return the rows in this SQL query
  923. */
  924. private HashMap<Integer, ArrayList<String>> read(String sql) {
  925. HashMap<Integer, ArrayList<String>> rows = new HashMap<Integer, ArrayList<String>>();
  926. if (checkConnected()) {
  927. PreparedStatement statement = null;
  928. ResultSet resultSet;
  929. try {
  930. statement = connection.prepareStatement(sql);
  931. resultSet = statement.executeQuery();
  932. while (resultSet.next()) {
  933. ArrayList<String> column = new ArrayList<String>();
  934. for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
  935. column.add(resultSet.getString(i));
  936. }
  937. rows.put(resultSet.getRow(), column);
  938. }
  939. }
  940. catch (SQLException ex) {
  941. printErrors(ex);
  942. }
  943. finally {
  944. if (statement != null) {
  945. try {
  946. statement.close();
  947. }
  948. catch (SQLException e) {
  949. // Ignore
  950. }
  951. }
  952. }
  953. }
  954. return rows;
  955. }
  956. /**
  957. * Get the Integer. Only return first row / first field.
  958. *
  959. * @param statement SQL query to execute
  960. *
  961. * @return the value in the first row / first field
  962. */
  963. private int readInt(PreparedStatement statement) {
  964. int result = -1;
  965. if (checkConnected()) {
  966. ResultSet resultSet;
  967. try {
  968. resultSet = statement.executeQuery();
  969. if (resultSet.next()) {
  970. result = resultSet.getInt(1);
  971. }
  972. }
  973. catch (SQLException ex) {
  974. printErrors(ex);
  975. }
  976. finally {
  977. if (statement != null) {
  978. try {
  979. statement.close();
  980. }
  981. catch (SQLException e) {
  982. // Ignore
  983. }
  984. }
  985. }
  986. }
  987. return result;
  988. }
  989. private void writeMissingRows(int id) {
  990. PreparedStatement statement = null;
  991. try {
  992. statement = connection.prepareStatement("INSERT IGNORE INTO " + tablePrefix + "experience (user_id) VALUES (?)");
  993. statement.setInt(1, id);
  994. statement.execute();
  995. statement.close();
  996. statement = connection.prepareStatement("INSERT IGNORE INTO " + tablePrefix + "skills (user_id) VALUES (?)");
  997. statement.setInt(1, id);
  998. statement.execute();
  999. statement.close();
  1000. statement = connection.prepareStatement("INSERT IGNORE INTO " + tablePrefix + "cooldowns (user_id) VALUES (?)");
  1001. statement.setInt(1, id);
  1002. statement.execute();
  1003. statement.close();
  1004. statement = connection.prepareStatement("INSERT IGNORE INTO " + tablePrefix + "huds (user_id, mobhealthbar) VALUES (? ,'" + Config.getInstance().getMobHealthbarDefault().name() + "')");
  1005. statement.setInt(1, id);
  1006. statement.execute();
  1007. statement.close();
  1008. }
  1009. catch (SQLException ex) {
  1010. printErrors(ex);
  1011. }
  1012. finally {
  1013. if (statement != null) {
  1014. try {
  1015. statement.close();
  1016. }
  1017. catch (SQLException e) {
  1018. // Ignore
  1019. }
  1020. }
  1021. }
  1022. }
  1023. private void processPurge(Collection<ArrayList<String>> usernames) {
  1024. for (ArrayList<String> user : usernames) {
  1025. Misc.profileCleanup(user.get(0));
  1026. }
  1027. }
  1028. private boolean saveIntegers(String sql, int... args) {
  1029. PreparedStatement statement = null;
  1030. try {
  1031. statement = connection.prepareStatement(sql);
  1032. int i = 1;
  1033. for (int arg : args) {
  1034. statement.setInt(i++, arg);
  1035. }
  1036. statement.execute();
  1037. return true;
  1038. }
  1039. catch (SQLException ex) {
  1040. printErrors(ex);
  1041. return false;
  1042. }
  1043. finally {
  1044. if (statement != null) {
  1045. try {
  1046. statement.close();
  1047. }
  1048. catch (SQLException e) {
  1049. // Ignore
  1050. }
  1051. }
  1052. }
  1053. }
  1054. private boolean saveLongs(String sql, int id, long... args) {
  1055. PreparedStatement statement = null;
  1056. try {
  1057. statement = connection.prepareStatement(sql);
  1058. int i = 1;
  1059. for (long arg : args) {
  1060. statement.setLong(i++, arg);
  1061. }
  1062. statement.setInt(i++, id);
  1063. statement.execute();
  1064. return true;
  1065. }
  1066. catch (SQLException ex) {
  1067. printErrors(ex);
  1068. return false;
  1069. }
  1070. finally {
  1071. if (statement != null) {
  1072. try {
  1073. statement.close();
  1074. }
  1075. catch (SQLException e) {
  1076. // Ignore
  1077. }
  1078. }
  1079. }
  1080. }
  1081. /**
  1082. * Retrieve the database id for a player
  1083. *
  1084. * @param playerName The name of the user to retrieve the id for
  1085. *
  1086. * @return the requested id or -1 if not found
  1087. */
  1088. private int readId(String playerName) {
  1089. int id = -1;
  1090. try {
  1091. PreparedStatement statement = connection.prepareStatement("SELECT id FROM " + tablePrefix + "users WHERE user = ?");
  1092. statement.setString(1, playerName);
  1093. id = readInt(statement);
  1094. }
  1095. catch (SQLException ex) {
  1096. printErrors(ex);
  1097. }
  1098. return id;
  1099. }
  1100. private boolean saveUniqueId(int id, String uuid) {
  1101. PreparedStatement statement = null;
  1102. try {
  1103. statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE id = ?");
  1104. statement.setString(1, uuid);
  1105. statement.setInt(2, id);
  1106. statement.execute();
  1107. return true;
  1108. }
  1109. catch (SQLException ex) {
  1110. printErrors(ex);
  1111. return false;
  1112. }
  1113. finally {
  1114. if (statement != null) {
  1115. try {
  1116. statement.close();
  1117. }
  1118. catch (SQLException e) {
  1119. // Ignore
  1120. }
  1121. }
  1122. }
  1123. }
  1124. private boolean saveLogin(int id, long login) {
  1125. PreparedStatement statement = null;
  1126. try {
  1127. statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET lastlogin = ? WHERE id = ?");
  1128. statement.setLong(1, login);
  1129. statement.setInt(2, id);
  1130. statement.execute();
  1131. return true;
  1132. }
  1133. catch (SQLException ex) {
  1134. printErrors(ex);
  1135. return false;
  1136. }
  1137. finally {
  1138. if (statement != null) {
  1139. try {
  1140. statement.close();
  1141. }
  1142. catch (SQLException e) {
  1143. // Ignore
  1144. }
  1145. }
  1146. }
  1147. }
  1148. private boolean saveHuds(int userId, String mobHealthBar) {
  1149. PreparedStatement statement = null;
  1150. try {
  1151. statement = connection.prepareStatement("UPDATE " + tablePrefix + "huds SET mobhealthbar = ? WHERE user_id = ?");
  1152. statement.setString(1, mobHealthBar);
  1153. statement.setInt(2, userId);
  1154. statement.execute();
  1155. return true;
  1156. }
  1157. catch (SQLException ex) {
  1158. printErrors(ex);
  1159. return false;
  1160. }
  1161. finally {
  1162. if (statement != null) {
  1163. try {
  1164. statement.close();
  1165. }
  1166. catch (SQLException e) {
  1167. // Ignore
  1168. }
  1169. }
  1170. }
  1171. }
  1172. private PlayerProfile loadFromResult(String playerName, ResultSet result) throws SQLException {
  1173. Map<SkillType, Integer> skills = new HashMap<SkillType, Integer>(); // Skill & Level
  1174. Map<SkillType, Float> skillsXp = new HashMap<SkillType, Float>(); // Skill & XP
  1175. Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
  1176. MobHealthbarType mobHealthbarType;
  1177. UUID uuid;
  1178. final int OFFSET_SKILLS = 0; // TODO update these numbers when the query changes (a new skill is added)
  1179. final int OFFSET_XP = 13;
  1180. final int OFFSET_DATS = 26;
  1181. final int OFFSET_OTHER = 38;
  1182. skills.put(SkillType.TAMING, result.getInt(OFFSET_SKILLS + 1));
  1183. skills.put(SkillType.MINING, result.getInt(OFFSET_SKILLS + 2));
  1184. skills.put(SkillType.REPAIR, result.getInt(OFFSET_SKILLS + 3));
  1185. skills.put(SkillType.WOODCUTTING, result.getInt(OFFSET_SKILLS + 4));
  1186. skills.put(SkillType.UNARMED, result.getInt(OFFSET_SKILLS + 5));
  1187. skills.put(SkillType.HERBALISM, result.getInt(OFFSET_SKILLS + 6));
  1188. skills.put(SkillType.EXCAVATION, result.getInt(OFFSET_SKILLS + 7));
  1189. skills.put(SkillType.ARCHERY, result.getInt(OFFSET_SKILLS + 8));
  1190. skills.put(SkillType.SWORDS, result.getInt(OFFSET_SKILLS + 9));
  1191. skills.put(SkillType.AXES, result.getInt(OFFSET_SKILLS + 10));
  1192. skills.put(SkillType.ACROBATICS, result.getInt(OFFSET_SKILLS + 11));
  1193. skills.put(SkillType.FISHING, result.getInt(OFFSET_SKILLS + 12));
  1194. skills.put(SkillType.ALCHEMY, result.getInt(OFFSET_SKILLS + 13));
  1195. skillsXp.put(SkillType.TAMING, result.getFloat(OFFSET_XP + 1));
  1196. skillsXp.put(SkillType.MINING, result.getFloat(OFFSET_XP + 2));
  1197. skillsXp.put(SkillType.REPAIR, result.getFloat(OFFSET_XP + 3));
  1198. skillsXp.put(SkillType.WOODCUTTING, result.getFloat(OFFSET_XP + 4));
  1199. skillsXp.put(SkillType.UNARMED, result.getFloat(OFFSET_XP + 5));
  1200. skillsXp.put(SkillType.HERBALISM, result.getFloat(OFFSET_XP + 6));
  1201. skillsXp.put(SkillType.EXCAVATION, result.getFloat(OFFSET_XP + 7));
  1202. skillsXp.put(SkillType.ARCHERY, result.getFloat(OFFSET_XP + 8));
  1203. skillsXp.put(SkillType.SWORDS, result.getFloat(OFFSET_XP + 9));
  1204. skillsXp.put(SkillType.AXES, result.getFloat(OFFSET_XP + 10));
  1205. skillsXp.put(SkillType.ACROBATICS, result.getFloat(OFFSET_XP + 11));
  1206. skillsXp.put(SkillType.FISHING, result.getFloat(OFFSET_XP + 12));
  1207. skillsXp.put(SkillType.ALCHEMY, result.getFloat(OFFSET_XP + 13));
  1208. // Taming - Unused - result.getInt(OFFSET_DATS + 1)
  1209. skillsDATS.put(AbilityType.SUPER_BREAKER, result.getInt(OFFSET_DATS + 2));
  1210. // Repair - Unused - result.getInt(OFFSET_DATS + 3)
  1211. skillsDATS.put(AbilityType.TREE_FELLER, result.getInt(OFFSET_DATS + 4));
  1212. skillsDATS.put(AbilityType.BERSERK, result.getInt(OFFSET_DATS + 5));
  1213. skillsDATS.put(AbilityType.GREEN_TERRA, result.getInt(OFFSET_DATS + 6));
  1214. skillsDATS.put(AbilityType.GIGA_DRILL_BREAKER, result.getInt(OFFSET_DATS + 7));
  1215. // Archery - Unused - result.getInt(OFFSET_DATS + 8)
  1216. skillsDATS.put(AbilityType.SERRATED_STRIKES, result.getInt(OFFSET_DATS + 9));
  1217. skillsDATS.put(AbilityType.SKULL_SPLITTER, result.getInt(OFFSET_DATS + 10));
  1218. // Acrobatics - Unused - result.getInt(OFFSET_DATS + 11)
  1219. skillsDATS.put(AbilityType.BLAST_MINING, result.getInt(OFFSET_DATS + 12));
  1220. try {
  1221. mobHealthbarType = MobHealthbarType.valueOf(result.getString(OFFSET_OTHER + 2));
  1222. }
  1223. catch (Exception e) {
  1224. mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
  1225. }
  1226. try {
  1227. uuid = UUID.fromString(result.getString(OFFSET_OTHER + 3));
  1228. }
  1229. catch (Exception e) {
  1230. uuid = null;
  1231. }
  1232. return new PlayerProfile(playerName, uuid, skills, skillsXp, skillsDATS, mobHealthbarType);
  1233. }
  1234. private void printErrors(SQLException ex) {
  1235. mcMMO.p.getLogger().severe("SQLException: " + ex.getMessage());
  1236. mcMMO.p.getLogger().severe("SQLState: " + ex.getSQLState());
  1237. mcMMO.p.getLogger().severe("VendorError: " + ex.getErrorCode());
  1238. }
  1239. public DatabaseType getDatabaseType() {
  1240. return DatabaseType.SQL;
  1241. }
  1242. private void checkUpgradeAddAlchemy(final Statement statement) throws SQLException {
  1243. try {
  1244. statement.executeQuery("SELECT `alchemy` FROM `" + tablePrefix + "skills` LIMIT 1");
  1245. }
  1246. catch (SQLException ex) {
  1247. mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Alchemy...");
  1248. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
  1249. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "experience` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
  1250. }
  1251. }
  1252. private void checkUpgradeAddBlastMiningCooldown(final Statement statement) throws SQLException {
  1253. try {
  1254. statement.executeQuery("SELECT `blast_mining` FROM `" + tablePrefix + "cooldowns` LIMIT 1");
  1255. }
  1256. catch (SQLException ex) {
  1257. mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
  1258. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0'");
  1259. }
  1260. }
  1261. private void checkUpgradeAddFishing(final Statement statement) throws SQLException {
  1262. try {
  1263. statement.executeQuery("SELECT `fishing` FROM `" + tablePrefix + "skills` LIMIT 1");
  1264. }
  1265. catch (SQLException ex) {
  1266. mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
  1267. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
  1268. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
  1269. }
  1270. }
  1271. private void checkUpgradeAddMobHealthbars(final Statement statement) throws SQLException {
  1272. try {
  1273. statement.executeQuery("SELECT `mobhealthbar` FROM `" + tablePrefix + "huds` LIMIT 1");
  1274. }
  1275. catch (SQLException ex) {
  1276. mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for mob healthbars...");
  1277. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "huds` ADD `mobhealthbar` varchar(50) NOT NULL DEFAULT '" + Config.getInstance().getMobHealthbarDefault() + "'");
  1278. }
  1279. }
  1280. private void checkUpgradeAddSQLIndexes(final Statement statement) throws SQLException {
  1281. ResultSet resultSet = null;
  1282. try {
  1283. resultSet = statement.executeQuery("SHOW INDEX FROM `" + tablePrefix + "skills` WHERE `Key_name` LIKE 'idx\\_%'");
  1284. resultSet.last();
  1285. if (resultSet.getRow() != SkillType.NON_CHILD_SKILLS.size()) {
  1286. mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
  1287. for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
  1288. String skill_name = skill.name().toLowerCase();
  1289. try {
  1290. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_" + skill_name + "` (`" + skill_name + "`) USING BTREE");
  1291. }
  1292. catch (SQLException ex) {
  1293. // Ignore
  1294. }
  1295. }
  1296. }
  1297. }
  1298. catch (SQLException ex) {
  1299. printErrors(ex);
  1300. }
  1301. finally {
  1302. if (resultSet != null) {
  1303. try {
  1304. resultSet.close();
  1305. }
  1306. catch (SQLException e) {
  1307. // Ignore
  1308. }
  1309. }
  1310. }
  1311. }
  1312. private void checkUpgradeAddUUIDs(final Statement statement) {
  1313. List<String> names = new ArrayList<String>();
  1314. ResultSet resultSet = null;
  1315. try {
  1316. resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "users` LIMIT 1");
  1317. ResultSetMetaData rsmeta = resultSet.getMetaData();
  1318. boolean column_exists = false;
  1319. for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
  1320. if (rsmeta.getColumnName(i).equalsIgnoreCase("uuid")) {
  1321. column_exists = true;
  1322. break;
  1323. }
  1324. }
  1325. if (!column_exists) {
  1326. mcMMO.p.getLogger().info("Adding UUIDs to mcMMO MySQL user table...");
  1327. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` ADD `uuid` varchar(36) NOT NULL DEFAULT ''");
  1328. }
  1329. }
  1330. catch (SQLException ex) {
  1331. printErrors(ex);
  1332. }
  1333. finally {
  1334. if (resultSet != null) {
  1335. try {
  1336. resultSet.close();
  1337. }
  1338. catch (SQLException e) {
  1339. // Ignore
  1340. }
  1341. }
  1342. }
  1343. try {
  1344. resultSet = statement.executeQuery("SELECT `user` FROM `" + tablePrefix + "users` WHERE `uuid` = ''");
  1345. while (resultSet.next()) {
  1346. names.add(resultSet.getString("user"));
  1347. }
  1348. }
  1349. catch (SQLException ex) {
  1350. printErrors(ex);
  1351. }
  1352. finally {
  1353. if (resultSet != null) {
  1354. try {
  1355. resultSet.close();
  1356. }
  1357. catch (SQLException e) {
  1358. // Ignore
  1359. }
  1360. }
  1361. }
  1362. if (!names.isEmpty()) {
  1363. new UUIDUpdateAsyncTask(mcMMO.p, names).runTaskAsynchronously(mcMMO.p);
  1364. }
  1365. }
  1366. private void checkUpgradeDropPartyNames(final Statement statement) {
  1367. ResultSet resultSet = null;
  1368. try {
  1369. resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "users` LIMIT 1");
  1370. ResultSetMetaData rsmeta = resultSet.getMetaData();
  1371. boolean column_exists = false;
  1372. for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
  1373. if (rsmeta.getColumnName(i).equalsIgnoreCase("party")) {
  1374. column_exists = true;
  1375. break;
  1376. }
  1377. }
  1378. if (column_exists) {
  1379. mcMMO.p.getLogger().info("Removing party name from users table...");
  1380. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` DROP COLUMN `party`");
  1381. }
  1382. }
  1383. catch (SQLException ex) {
  1384. printErrors(ex);
  1385. }
  1386. finally {
  1387. if (resultSet != null) {
  1388. try {
  1389. resultSet.close();
  1390. }
  1391. catch (SQLException e) {
  1392. // Ignore
  1393. }
  1394. }
  1395. }
  1396. }
  1397. private void checkUpgradeDropSpout(final Statement statement) {
  1398. ResultSet resultSet = null;
  1399. try {
  1400. resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "huds` LIMIT 1");
  1401. ResultSetMetaData rsmeta = resultSet.getMetaData();
  1402. boolean column_exists = false;
  1403. for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
  1404. if (rsmeta.getColumnName(i).equalsIgnoreCase("hudtype")) {
  1405. column_exists = true;
  1406. break;
  1407. }
  1408. }
  1409. if (column_exists) {
  1410. mcMMO.p.getLogger().info("Removing Spout HUD type from huds table...");
  1411. statement.executeUpdate("ALTER TABLE `" + tablePrefix + "huds` DROP COLUMN `hudtype`");
  1412. }
  1413. }
  1414. catch (SQLException ex) {
  1415. printErrors(ex);
  1416. }
  1417. finally {
  1418. if (resultSet != null) {
  1419. try {
  1420. resultSet.close();
  1421. }
  1422. catch (SQLException e) {
  1423. // Ignore
  1424. }
  1425. }
  1426. }
  1427. }
  1428. }