SQLDatabaseManager.java 57 KB

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