From c588fe742022a10ee227f8c637dd335112471053 Mon Sep 17 00:00:00 2001 From: riking Date: Sun, 23 Jun 2013 11:45:30 -0700 Subject: [PATCH] Better flatfile purge performance; sync flatdb access ... ... to avoid data loss --- .../database/FlatfileDatabaseManager.java | 627 ++++++++++-------- 1 file changed, 347 insertions(+), 280 deletions(-) diff --git a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java index 7dcd5e28a..5e44b5d5e 100644 --- a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import com.gmail.nossr50.mcMMO; @@ -32,6 +33,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager { private final long UPDATE_WAIT_TIME = 600000L; // 10 minutes private final File usersFile; + private static final Object fileWritingLock = new Object(); protected FlatfileDatabaseManager() { usersFile = new File(mcMMO.getUsersFilePath()); @@ -48,65 +50,65 @@ public final class FlatfileDatabaseManager implements DatabaseManager { FileWriter out = null; String usersFilePath = mcMMO.getUsersFilePath(); - // Rationale for doing a file read instead of using the cached values: - // If mulitple users need be removed, this only opens the file once as - // opposed to calling removeUser which opens the file once each time. - try { - in = new BufferedReader(new FileReader(usersFilePath)); - StringBuilder writer = new StringBuilder(); - String line = ""; + // This code is O(n) instead of O(n²) + synchronized (fileWritingLock) { + try { + in = new BufferedReader(new FileReader(usersFilePath)); + StringBuilder writer = new StringBuilder(); + String line = ""; - while ((line = in.readLine()) != null) { - String[] character = line.split(":"); - PlayerProfile profile = null; - try { - profile = loadFromLine(character); - } catch (Exception e) { - e.printStackTrace(); - } - if (profile == null) continue; // skip malformed lines + while ((line = in.readLine()) != null) { + String[] character = line.split(":"); + PlayerProfile profile = null; + try { + profile = loadFromLine(character); + } catch (Exception e) { + e.printStackTrace(); + } + if (profile == null) continue; // skip malformed lines - boolean powerless = true; - for (SkillType skill : SkillType.nonChildSkills()) { - if (profile.getSkillLevel(skill) != 0) { - powerless = false; - break; + boolean powerless = true; + for (SkillType skill : SkillType.nonChildSkills()) { + if (profile.getSkillLevel(skill) != 0) { + powerless = false; + break; + } + } + + // If they're still around, rewrite them to the file. + if (!powerless) { + writer.append(line).append("\r\n"); + } + else { + purgedUsers++; + Misc.profileCleanup(character[0]); } } - // If they're still around, rewrite them to the file. - if (!powerless) { - writer.append(line).append("\r\n"); - } - else { - purgedUsers++; - Misc.profileCleanup(character[0]); - } + // Write the new file + out = new FileWriter(usersFilePath); + out.write(writer.toString()); } - - // Write the new file - out = new FileWriter(usersFilePath); - out.write(writer.toString()); - } - catch (IOException e) { - mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); - } - finally { - if (in != null) { - try { - in.close(); - } - catch (IOException ex) { - ex.printStackTrace(); - } + catch (IOException e) { + mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } - - if (out != null) { - try { - out.close(); + finally { + if (in != null) { + try { + in.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } } - catch (IOException ex) { - ex.printStackTrace(); + + if (out != null) { + try { + out.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } } } } @@ -120,9 +122,61 @@ public final class FlatfileDatabaseManager implements DatabaseManager { mcMMO.p.getLogger().info("Purging old users..."); - for (OfflinePlayer player : mcMMO.p.getServer().getOfflinePlayers()) { - if (!player.isOnline() && (currentTime - player.getLastPlayed() > PURGE_TIME && removeUser(player.getName()))) { - removedPlayers++; + + BufferedReader in = null; + FileWriter out = null; + String usersFilePath = mcMMO.getUsersFilePath(); + + // This code is O(n) instead of O(n²) + synchronized (fileWritingLock) { + try { + in = new BufferedReader(new FileReader(usersFilePath)); + StringBuilder writer = new StringBuilder(); + String line = ""; + + while ((line = in.readLine()) != null) { + String[] character = line.split(":"); + String name = character[0]; + OfflinePlayer player = Bukkit.getOfflinePlayer(name); + boolean old = true; + if (player != null) { + old = (currentTime - player.getLastPlayed()) > PURGE_TIME; + } + + if (!old) { + writer.append(line); + } + else { + removedPlayers++; + Misc.profileCleanup(name); + } + } + + // Write the new file + out = new FileWriter(usersFilePath); + out.write(writer.toString()); + } + catch (IOException e) { + mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); + } + finally { + if (in != null) { + try { + in.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } + } + + if (out != null) { + try { + out.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } + } } } @@ -136,44 +190,46 @@ public final class FlatfileDatabaseManager implements DatabaseManager { FileWriter out = null; String usersFilePath = mcMMO.getUsersFilePath(); - try { - in = new BufferedReader(new FileReader(usersFilePath)); - StringBuilder writer = new StringBuilder(); - String line = ""; + synchronized (fileWritingLock) { + try { + in = new BufferedReader(new FileReader(usersFilePath)); + StringBuilder writer = new StringBuilder(); + String line = ""; - while ((line = in.readLine()) != null) { - // Write out the same file but when we get to the player we want to remove, we skip his line. - if (!worked && line.split(":")[0].equalsIgnoreCase(playerName)) { - mcMMO.p.getLogger().info("User found, removing..."); - worked = true; - continue; // Skip the player + while ((line = in.readLine()) != null) { + // Write out the same file but when we get to the player we want to remove, we skip his line. + if (!worked && line.split(":")[0].equalsIgnoreCase(playerName)) { + mcMMO.p.getLogger().info("User found, removing..."); + worked = true; + continue; // Skip the player + } + + writer.append(line).append("\r\n"); } - writer.append(line).append("\r\n"); + out = new FileWriter(usersFilePath); // Write out the new file + out.write(writer.toString()); } - - out = new FileWriter(usersFilePath); // Write out the new file - out.write(writer.toString()); - } - catch (Exception e) { - mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); - } - finally { - if (in != null) { - try { - in.close(); - } - catch (IOException ex) { - ex.printStackTrace(); - } + catch (Exception e) { + mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); } - - if (out != null) { - try { - out.close(); + finally { + if (in != null) { + try { + in.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } } - catch (IOException ex) { - ex.printStackTrace(); + + if (out != null) { + try { + out.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } } } } @@ -190,88 +246,90 @@ public final class FlatfileDatabaseManager implements DatabaseManager { FileWriter out = null; String usersFilePath = mcMMO.getUsersFilePath(); - try { - // Open the file - in = new BufferedReader(new FileReader(usersFilePath)); - StringBuilder writer = new StringBuilder(); - String line; + synchronized (fileWritingLock) { + try { + // Open the file + in = new BufferedReader(new FileReader(usersFilePath)); + StringBuilder writer = new StringBuilder(); + String line; - // While not at the end of the file - while ((line = in.readLine()) != null) { - // Read the line in and copy it to the output it's not the player we want to edit - if (!line.split(":")[0].equalsIgnoreCase(playerName)) { - writer.append(line).append("\r\n"); - } - else { - // Otherwise write the new player information - writer.append(playerName).append(":"); - writer.append(profile.getSkillLevel(SkillType.MINING)).append(":"); - writer.append(":"); - writer.append(":"); - writer.append(profile.getSkillXpLevel(SkillType.MINING)).append(":"); - writer.append(profile.getSkillLevel(SkillType.WOODCUTTING)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.WOODCUTTING)).append(":"); - writer.append(profile.getSkillLevel(SkillType.REPAIR)).append(":"); - writer.append(profile.getSkillLevel(SkillType.UNARMED)).append(":"); - writer.append(profile.getSkillLevel(SkillType.HERBALISM)).append(":"); - writer.append(profile.getSkillLevel(SkillType.EXCAVATION)).append(":"); - writer.append(profile.getSkillLevel(SkillType.ARCHERY)).append(":"); - writer.append(profile.getSkillLevel(SkillType.SWORDS)).append(":"); - writer.append(profile.getSkillLevel(SkillType.AXES)).append(":"); - writer.append(profile.getSkillLevel(SkillType.ACROBATICS)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.REPAIR)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.UNARMED)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.HERBALISM)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.EXCAVATION)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.ARCHERY)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.SWORDS)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.AXES)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.ACROBATICS)).append(":"); - writer.append(":"); - writer.append(profile.getSkillLevel(SkillType.TAMING)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.TAMING)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.BERSERK)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.GIGA_DRILL_BREAKER)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.TREE_FELLER)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.GREEN_TERRA)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.SERRATED_STRIKES)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.SKULL_SPLITTER)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.SUPER_BREAKER)).append(":"); - HudType hudType = profile.getHudType(); - writer.append(hudType == null ? "STANDARD" : hudType.toString()).append(":"); - writer.append(profile.getSkillLevel(SkillType.FISHING)).append(":"); - writer.append(profile.getSkillXpLevel(SkillType.FISHING)).append(":"); - writer.append((int) profile.getSkillDATS(AbilityType.BLAST_MINING)).append(":"); - writer.append(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR).append(":"); - MobHealthbarType mobHealthbarType = profile.getMobHealthbarType(); - writer.append(mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()).append(":"); - writer.append("\r\n"); + // While not at the end of the file + while ((line = in.readLine()) != null) { + // Read the line in and copy it to the output it's not the player we want to edit + if (!line.split(":")[0].equalsIgnoreCase(playerName)) { + writer.append(line).append("\r\n"); + } + else { + // Otherwise write the new player information + writer.append(playerName).append(":"); + writer.append(profile.getSkillLevel(SkillType.MINING)).append(":"); + writer.append(":"); + writer.append(":"); + writer.append(profile.getSkillXpLevel(SkillType.MINING)).append(":"); + writer.append(profile.getSkillLevel(SkillType.WOODCUTTING)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.WOODCUTTING)).append(":"); + writer.append(profile.getSkillLevel(SkillType.REPAIR)).append(":"); + writer.append(profile.getSkillLevel(SkillType.UNARMED)).append(":"); + writer.append(profile.getSkillLevel(SkillType.HERBALISM)).append(":"); + writer.append(profile.getSkillLevel(SkillType.EXCAVATION)).append(":"); + writer.append(profile.getSkillLevel(SkillType.ARCHERY)).append(":"); + writer.append(profile.getSkillLevel(SkillType.SWORDS)).append(":"); + writer.append(profile.getSkillLevel(SkillType.AXES)).append(":"); + writer.append(profile.getSkillLevel(SkillType.ACROBATICS)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.REPAIR)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.UNARMED)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.HERBALISM)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.EXCAVATION)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.ARCHERY)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.SWORDS)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.AXES)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.ACROBATICS)).append(":"); + writer.append(":"); + writer.append(profile.getSkillLevel(SkillType.TAMING)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.TAMING)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.BERSERK)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.GIGA_DRILL_BREAKER)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.TREE_FELLER)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.GREEN_TERRA)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.SERRATED_STRIKES)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.SKULL_SPLITTER)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.SUPER_BREAKER)).append(":"); + HudType hudType = profile.getHudType(); + writer.append(hudType == null ? "STANDARD" : hudType.toString()).append(":"); + writer.append(profile.getSkillLevel(SkillType.FISHING)).append(":"); + writer.append(profile.getSkillXpLevel(SkillType.FISHING)).append(":"); + writer.append((int) profile.getSkillDATS(AbilityType.BLAST_MINING)).append(":"); + writer.append(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR).append(":"); + MobHealthbarType mobHealthbarType = profile.getMobHealthbarType(); + writer.append(mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()).append(":"); + writer.append("\r\n"); + } } + + // Write the new file + out = new FileWriter(usersFilePath); + out.write(writer.toString()); } - - // Write the new file - out = new FileWriter(usersFilePath); - out.write(writer.toString()); - } - catch (Exception e) { - e.printStackTrace(); - } - finally { - if (in != null) { - try { - in.close(); - } - catch (IOException ex) { - ex.printStackTrace(); - } + catch (Exception e) { + e.printStackTrace(); } - - if (out != null) { - try { - out.close(); + finally { + if (in != null) { + try { + in.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } } - catch (IOException ex) { - ex.printStackTrace(); + + if (out != null) { + try { + out.close(); + } + catch (IOException ex) { + ex.printStackTrace(); + } } } } @@ -300,95 +358,99 @@ public final class FlatfileDatabaseManager implements DatabaseManager { } public void newUser(String playerName) { - try { - // Open the file to write the player - BufferedWriter out = new BufferedWriter(new FileWriter(mcMMO.getUsersFilePath(), true)); + synchronized (fileWritingLock) { + try { + // Open the file to write the player + BufferedWriter out = new BufferedWriter(new FileWriter(mcMMO.getUsersFilePath(), true)); - // Add the player to the end - out.append(playerName).append(":"); - out.append("0:"); // Mining - out.append(":"); - out.append(":"); - out.append("0:"); // Xp - out.append("0:"); // Woodcutting - out.append("0:"); // WoodCuttingXp - out.append("0:"); // Repair - out.append("0:"); // Unarmed - out.append("0:"); // Herbalism - out.append("0:"); // Excavation - out.append("0:"); // Archery - out.append("0:"); // Swords - out.append("0:"); // Axes - out.append("0:"); // Acrobatics - out.append("0:"); // RepairXp - out.append("0:"); // UnarmedXp - out.append("0:"); // HerbalismXp - out.append("0:"); // ExcavationXp - out.append("0:"); // ArcheryXp - out.append("0:"); // SwordsXp - out.append("0:"); // AxesXp - out.append("0:"); // AcrobaticsXp - out.append(":"); - out.append("0:"); // Taming - out.append("0:"); // TamingXp - out.append("0:"); // DATS - out.append("0:"); // DATS - out.append("0:"); // DATS - out.append("0:"); // DATS - out.append("0:"); // DATS - out.append("0:"); // DATS - out.append("0:"); // DATS - out.append("STANDARD").append(":"); // HUD - out.append("0:"); // Fishing - out.append("0:"); // FishingXp - out.append("0:"); // Blast Mining - out.append(String.valueOf(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)).append(":"); // LastLogin - out.append(Config.getInstance().getMobHealthbarDefault().toString()).append(":"); // Mob Healthbar HUD + // Add the player to the end + out.append(playerName).append(":"); + out.append("0:"); // Mining + out.append(":"); + out.append(":"); + out.append("0:"); // Xp + out.append("0:"); // Woodcutting + out.append("0:"); // WoodCuttingXp + out.append("0:"); // Repair + out.append("0:"); // Unarmed + out.append("0:"); // Herbalism + out.append("0:"); // Excavation + out.append("0:"); // Archery + out.append("0:"); // Swords + out.append("0:"); // Axes + out.append("0:"); // Acrobatics + out.append("0:"); // RepairXp + out.append("0:"); // UnarmedXp + out.append("0:"); // HerbalismXp + out.append("0:"); // ExcavationXp + out.append("0:"); // ArcheryXp + out.append("0:"); // SwordsXp + out.append("0:"); // AxesXp + out.append("0:"); // AcrobaticsXp + out.append(":"); + out.append("0:"); // Taming + out.append("0:"); // TamingXp + out.append("0:"); // DATS + out.append("0:"); // DATS + out.append("0:"); // DATS + out.append("0:"); // DATS + out.append("0:"); // DATS + out.append("0:"); // DATS + out.append("0:"); // DATS + out.append("STANDARD").append(":"); // HUD + out.append("0:"); // Fishing + out.append("0:"); // FishingXp + out.append("0:"); // Blast Mining + out.append(String.valueOf(System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)).append(":"); // LastLogin + out.append(Config.getInstance().getMobHealthbarDefault().toString()).append(":"); // Mob Healthbar HUD - // Add more in the same format as the line above + // Add more in the same format as the line above - out.newLine(); - out.close(); - } - catch (Exception e) { - e.printStackTrace(); + out.newLine(); + out.close(); + } + catch (Exception e) { + e.printStackTrace(); + } } } public PlayerProfile loadPlayerProfile(String playerName, boolean create) { FileReader file = null; BufferedReader in = null; - try { - // Open the user file - file = new FileReader(mcMMO.getUsersFilePath()); - in = new BufferedReader(file); - String line; - - while ((line = in.readLine()) != null) { - // Find if the line contains the player we want. - String[] character = line.split(":"); - - if (!character[0].equalsIgnoreCase(playerName)) { - continue; - } - - return loadFromLine(character); - } - } - catch (Exception e) { - e.printStackTrace(); - } - finally { + synchronized (fileWritingLock) { try { - if (in != null) { - in.close(); + // Open the user file + file = new FileReader(mcMMO.getUsersFilePath()); + in = new BufferedReader(file); + String line; + + while ((line = in.readLine()) != null) { + // Find if the line contains the player we want. + String[] character = line.split(":"); + + if (!character[0].equalsIgnoreCase(playerName)) { + continue; + } + + return loadFromLine(character); } - if (file != null) { - file.close(); - } - } catch (IOException e) { + } + catch (Exception e) { e.printStackTrace(); } + finally { + try { + if (in != null) { + in.close(); + } + if (file != null) { + file.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } } if (create) { @@ -402,37 +464,39 @@ public final class FlatfileDatabaseManager implements DatabaseManager { FileReader file = null; BufferedReader in = null; - try { - // Open the user file - file = new FileReader(mcMMO.getUsersFilePath()); - in = new BufferedReader(file); - String line; + synchronized (fileWritingLock) { + try { + // Open the user file + file = new FileReader(mcMMO.getUsersFilePath()); + in = new BufferedReader(file); + String line; - while ((line = in.readLine()) != null) { - String[] character = line.split(":"); + while ((line = in.readLine()) != null) { + String[] character = line.split(":"); - try { - destination.saveUser(loadFromLine(character)); - } - catch (Exception e) { - e.printStackTrace(); + try { + destination.saveUser(loadFromLine(character)); + } + catch (Exception e) { + e.printStackTrace(); + } } } - } - catch (Exception e) { - e.printStackTrace(); - } - finally { - try { - if (in != null) { - in.close(); - } - if (file != null) { - file.close(); - } - } catch (IOException e) { + catch (Exception e) { e.printStackTrace(); } + finally { + try { + if (in != null) { + in.close(); + } + if (file != null) { + file.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } } } @@ -444,26 +508,29 @@ public final class FlatfileDatabaseManager implements DatabaseManager { public List getStoredUsers() { ArrayList users = new ArrayList(); BufferedReader in = null; - try { - // Open the user file - FileReader file = new FileReader(mcMMO.getUsersFilePath()); - in = new BufferedReader(file); - String line; - while ((line = in.readLine()) != null) { - String[] character = line.split(":"); - users.add(character[0]); - } - } - catch (Exception e) { - e.printStackTrace(); - } - finally { + synchronized (fileWritingLock) { try { - in.close(); - } catch (IOException e) { + // Open the user file + FileReader file = new FileReader(mcMMO.getUsersFilePath()); + in = new BufferedReader(file); + String line; + + while ((line = in.readLine()) != null) { + String[] character = line.split(":"); + users.add(character[0]); + } + } + catch (Exception e) { e.printStackTrace(); } + finally { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } return users; }