From 9c00a47a29b7746dbcd93fb3ddd622d671ba6da8 Mon Sep 17 00:00:00 2001 From: ryanhamshire Date: Sat, 12 Sep 2015 14:17:00 -0700 Subject: [PATCH] Unique IDs for claim subdivisions. Also performance updates and switch to YAML file format for flat file data stores. --- plugin.yml | 6 +- .../ryanhamshire/GriefPrevention/Claim.java | 16 +- .../GriefPrevention/DataStore.java | 123 ++-- .../GriefPrevention/DatabaseDataStore.java | 47 +- .../GriefPrevention/FlatFileDataStore.java | 609 +++++++++++------- .../GriefPrevention/PlayerData.java | 14 + .../GriefPrevention/PlayerEventHandler.java | 4 +- .../RestoreNatureExecutionTask.java | 4 +- .../GriefPrevention/Visualization.java | 6 +- 9 files changed, 462 insertions(+), 367 deletions(-) diff --git a/plugin.yml b/plugin.yml index 30890a3..d6f3a41 100644 --- a/plugin.yml +++ b/plugin.yml @@ -173,14 +173,14 @@ commands: permission: griefprevention.ignore separate: description: Forces two players to ignore each other in chat. - usage: /Separate + usage: /Separate permission: griefprevention.separate unseparate: description: Reverses /separate. - usage: /UnSeparate + usage: /UnSeparate permission: griefprevention.separate claimbook: - description: Gives a player a replacement land claiming book. + description: Gives a player a manual about claiming land. usage: /ClaimBook permission: griefprevention.claimbook permissions: diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index 9db2666..aa0d8c7 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -186,7 +186,7 @@ public class Claim } //main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective - Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, String [] builderIDs, String [] containerIds, String [] accessorIDs, String [] managerIDs, Long id) + Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, List builderIDs, List containerIDs, List accessorIDs, List managerIDs, Long id) { //modification date this.modifiedDate = Calendar.getInstance().getTime(); @@ -202,36 +202,32 @@ public class Claim this.ownerID = ownerID; //other permissions - for(int i = 0; i < builderIDs.length; i++) + for(String builderID : builderIDs) { - String builderID = builderIDs[i]; if(builderID != null && !builderID.isEmpty()) { this.playerIDToClaimPermissionMap.put(builderID, ClaimPermission.Build); } } - for(int i = 0; i < containerIds.length; i++) + for(String containerID : containerIDs) { - String containerID = containerIds[i]; if(containerID != null && !containerID.isEmpty()) { this.playerIDToClaimPermissionMap.put(containerID, ClaimPermission.Inventory); } } - for(int i = 0; i < accessorIDs.length; i++) + for(String accessorID : accessorIDs) { - String accessorID = accessorIDs[i]; if(accessorID != null && !accessorID.isEmpty()) { this.playerIDToClaimPermissionMap.put(accessorID, ClaimPermission.Access); } } - for(int i = 0; i < managerIDs.length; i++) + for(String managerID : managerIDs) { - String managerID = managerIDs[i]; if(managerID != null && !managerID.isEmpty()) { this.managers.add(managerID); @@ -264,7 +260,7 @@ public class Claim Claim claim = new Claim (new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear), - null, new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); + null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); return claim.contains(location, false, true); } diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 7179706..4dd87ce 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -19,9 +19,6 @@ package me.ryanhamshire.GriefPrevention; import java.io.*; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; @@ -66,7 +63,7 @@ public abstract class DataStore final static String softMuteFilePath = dataLayerFolderPath + File.separator + "softMute.txt"; //the latest version of the data schema implemented here - protected static final int latestSchemaVersion = 1; + protected static final int latestSchemaVersion = 2; //reading and writing the schema version to the data store abstract int getSchemaVersionFromStorage(); @@ -129,6 +126,11 @@ public abstract class DataStore for(Claim claim : this.claims) { this.saveClaim(claim); + + for(Claim subClaim : claim.children) + { + this.saveClaim(subClaim); + } } //clean up any UUID conversion work @@ -363,7 +365,7 @@ public abstract class DataStore //adds a claim to the datastore, making it an effective claim synchronized void addClaim(Claim newClaim, boolean writeToStorage) { - //subdivisions are easy + //subdivisions are added under their parent, not directly to the hash map for direct search if(newClaim.parent != null) { if(!newClaim.parent.children.contains(newClaim)) @@ -425,7 +427,7 @@ public abstract class DataStore } //turns a location string back into a location - Location locationFromString(String string) throws Exception + Location locationFromString(String string, List validWorlds) throws Exception { //split the input string on the space String [] elements = string.split(locationStringDelimiter); @@ -442,7 +444,16 @@ public abstract class DataStore String zString = elements[3]; //identify world the claim is in - World world = GriefPrevention.instance.getServer().getWorld(worldName); + World world = null; + for(World w : validWorlds) + { + if(w.getName().equalsIgnoreCase(worldName)) + { + world = w; + break; + } + } + if(world == null) { throw new Exception("World not found: \"" + worldName + "\""); @@ -459,15 +470,7 @@ public abstract class DataStore //saves any changes to a claim to secondary storage synchronized public void saveClaim(Claim claim) { - //subdivisions don't save to their own files, but instead live in their parent claim's file - //so any attempt to save a subdivision will save its parent (and thus the subdivision) - if(claim.parent != null) - { - this.saveClaim(claim.parent); - return; - } - - //otherwise get a unique identifier for the claim which will be used to name the file on disk + //ensure a unique identifier for the claim which will be used to name the file on disk if(claim.id == null) { claim.id = this.nextClaimID; @@ -512,23 +515,20 @@ public abstract class DataStore synchronized void deleteClaim(Claim claim, boolean fireEvent) { - //subdivisions are simple - just remove them from their parent claim and save that claim + //delete any children + for(int j = 0; j < claim.children.size(); j++) + { + this.deleteClaim(claim.children.get(j--), true); + } + + //subdivisions must also be removed from the parent claim child list if(claim.parent != null) { Claim parentClaim = claim.parent; parentClaim.children.remove(claim); - claim.inDataStore = false; - this.saveClaim(parentClaim); - return; } - //delete any children - for(int j = 0; j < claim.children.size(); j++) - { - this.deleteClaim(claim.children.get(j), false); - } - - //mark as deleted so any references elsewhere can be ignored + //mark as deleted so any references elsewhere can be ignored claim.inDataStore = false; //remove from memory @@ -558,8 +558,8 @@ public abstract class DataStore //remove from secondary storage this.deleteClaimFromSecondaryStorage(claim); - //update player data, except for administrative claims, which have no owner - if(!claim.isAdminClaim()) + //update player data + if(claim.ownerID != null) { PlayerData ownerData = this.getPlayerData(claim.ownerID); for(int i = 0; i < ownerData.getClaims().size(); i++) @@ -597,14 +597,14 @@ public abstract class DataStore for(Claim claim : claimsInChunk) { - if(claim.contains(location, ignoreHeight, false)) + if(claim.inDataStore && claim.contains(location, ignoreHeight, false)) { //when we find a top level claim, if the location is in one of its subdivisions, //return the SUBDIVISION, not the top level claim for(int j = 0; j < claim.children.size(); j++) { Claim subdivision = claim.children.get(j); - if(subdivision.contains(location, ignoreHeight, false)) return subdivision; + if(subdivision.inDataStore && subdivision.contains(location, ignoreHeight, false)) return subdivision; } return claim; @@ -620,7 +620,7 @@ public abstract class DataStore { for(Claim claim : this.claims) { - if(claim.getID() == id) return claim; + if(claim.inDataStore && claim.getID() == id) return claim; } return null; @@ -702,10 +702,10 @@ public abstract class DataStore new Location(world, smallx, smally, smallz), new Location(world, bigx, bigy, bigz), ownerID, - new String [] {}, - new String [] {}, - new String [] {}, - new String [] {}, + new ArrayList(), + new ArrayList(), + new ArrayList(), + new ArrayList(), id); newClaim.parent = parent; @@ -714,7 +714,7 @@ public abstract class DataStore ArrayList claimsToCheck; if(newClaim.parent != null) { - claimsToCheck = newClaim.parent.children; + claimsToCheck = newClaim.parent.children; } else { @@ -726,7 +726,7 @@ public abstract class DataStore Claim otherClaim = claimsToCheck.get(i); //if we find an existing claim which will be overlapped - if(otherClaim.overlaps(newClaim)) + if(otherClaim.id != newClaim.id && otherClaim.inDataStore && otherClaim.overlaps(newClaim)) { //result = fail, return conflicting claim result.succeeded = false; @@ -821,27 +821,18 @@ public abstract class DataStore if(claim.parent != null) claim = claim.parent; - //note any subdivisions - ArrayList subdivisions = new ArrayList(claim.children); - - //delete the claim - this.deleteClaim(claim, false); - - //re-create it at the new depth + //adjust to new depth claim.lesserBoundaryCorner.setY(newDepth); claim.greaterBoundaryCorner.setY(newDepth); - - //re-add the subdivisions (deleteClaim() removed them) with the new depth - for(Claim subdivision : subdivisions) + for(Claim subdivision : claim.children) { subdivision.lesserBoundaryCorner.setY(newDepth); subdivision.greaterBoundaryCorner.setY(newDepth); - subdivision.parent = claim; - this.addClaim(subdivision, false); + this.saveClaim(subdivision); } //save changes - this.addClaim(claim, true); + this.saveClaim(claim); } //starts a siege on a claim @@ -1084,12 +1075,6 @@ public abstract class DataStore //see CreateClaim() for details on return value synchronized public CreateClaimResult resizeClaim(Claim claim, int newx1, int newx2, int newy1, int newy2, int newz1, int newz2, Player resizingPlayer) { - //note any subdivisions before deleting the claim - ArrayList subdivisions = new ArrayList(claim.children); - - //remove old claim - this.deleteClaim(claim, false); - //try to create this new claim, ignoring the original when checking for overlap CreateClaimResult result = this.createClaim(claim.getLesserBoundaryCorner().getWorld(), newx1, newx2, newy1, newy2, newz1, newz2, claim.ownerID, claim.parent, claim.id, resizingPlayer); @@ -1118,20 +1103,17 @@ public abstract class DataStore } //restore subdivisions - for(Claim subdivision : subdivisions) + for(Claim subdivision : claim.children) { subdivision.parent = result.claim; - this.addClaim(subdivision, false); + result.claim.children.add(subdivision); } //save those changes this.saveClaim(result.claim); - } - - else - { - //put original claim back - this.addClaim(claim, true); + + //make original claim ineffective (it's still in the hash map, so let's make it ignored) + claim.inDataStore = false; } return result; @@ -1421,7 +1403,7 @@ public abstract class DataStore //used in updating the data schema from 0 to 1. //converts player names in a list to uuids - protected String[] convertNameListToUUIDList(String[] names) + protected List convertNameListToUUIDList(List names) { //doesn't apply after schema has been updated to version 1 if(this.getSchemaVersion() >= 1) return names; @@ -1453,14 +1435,7 @@ public abstract class DataStore } } - //return final result of conversion - String [] resultArray = new String [resultNames.size()]; - for(int i = 0; i < resultNames.size(); i++) - { - resultArray[i] = resultNames.get(i); - } - - return resultArray; + return resultNames; } abstract void close(); diff --git a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java index b7fd596..d31abac 100644 --- a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java @@ -225,6 +225,7 @@ public class DatabaseDataStore extends DataStore ArrayList claimsToRemove = new ArrayList(); ArrayList subdivisionsToLoad = new ArrayList(); + List validWorlds = Bukkit.getServer().getWorlds(); while(results.next()) { @@ -242,10 +243,10 @@ public class DatabaseDataStore extends DataStore try { lesserCornerString = results.getString("lessercorner"); - lesserBoundaryCorner = this.locationFromString(lesserCornerString); + lesserBoundaryCorner = this.locationFromString(lesserCornerString, validWorlds); String greaterCornerString = results.getString("greatercorner"); - greaterBoundaryCorner = this.locationFromString(greaterCornerString); + greaterBoundaryCorner = this.locationFromString(greaterCornerString, validWorlds); } catch(Exception e) { @@ -293,19 +294,19 @@ public class DatabaseDataStore extends DataStore } String buildersString = results.getString("builders"); - String [] builderNames = buildersString.split(";"); + List builderNames = Arrays.asList(buildersString.split(";")); builderNames = this.convertNameListToUUIDList(builderNames); String containersString = results.getString("containers"); - String [] containerNames = containersString.split(";"); + List containerNames = Arrays.asList(containersString.split(";")); containerNames = this.convertNameListToUUIDList(containerNames); String accessorsString = results.getString("accessors"); - String [] accessorNames = accessorsString.split(";"); + List accessorNames = Arrays.asList(accessorsString.split(";")); accessorNames = this.convertNameListToUUIDList(accessorNames); String managersString = results.getString("managers"); - String [] managerNames = managersString.split(";"); + List managerNames = Arrays.asList(managersString.split(";")); managerNames = this.convertNameListToUUIDList(managerNames); Claim claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID); @@ -356,6 +357,13 @@ public class DatabaseDataStore extends DataStore this.deleteClaimFromSecondaryStorage(claimsToRemove.get(i)); } + if(this.getSchemaVersion() <= 2) + { + this.refreshDataConnection(); + statement = this.databaseConnection.createStatement(); + statement.execute("DELETE FROM griefprevention_claimdata WHERE id='-1';"); + } + super.initialize(); } @@ -369,15 +377,8 @@ public class DatabaseDataStore extends DataStore //wipe out any existing data about this claim this.deleteClaimFromSecondaryStorage(claim); - //write top level claim data to the database + //write claim data to the database this.writeClaimData(claim); - - //for each subdivision - for(int i = 0; i < claim.children.size(); i++) - { - //write the subdivision's data to the database - this.writeClaimData(claim.children.get(i)); - } } catch(SQLException e) { @@ -435,23 +436,13 @@ public class DatabaseDataStore extends DataStore parentId = claim.parent.id; } - long id; - if(claim.id == null) - { - id = -1; - } - else - { - id = claim.id; - } - try { this.refreshDataConnection(); Statement statement = databaseConnection.createStatement(); statement.execute("INSERT INTO griefprevention_claimdata (id, owner, lessercorner, greatercorner, builders, containers, accessors, managers, parentid) VALUES(" + - id + ", '" + + claim.id + ", '" + owner + "', '" + lesserCornerString + "', '" + greaterCornerString + "', '" + @@ -479,11 +470,7 @@ public class DatabaseDataStore extends DataStore Statement statement = this.databaseConnection.createStatement(); - statement.execute("DELETE FROM griefprevention_claimdata WHERE lessercorner='" + this.locationToString(claim.lesserBoundaryCorner) + "' AND greatercorner = '" + this.locationToString(claim.greaterBoundaryCorner) + "';"); - if(claim.id != -1) - { - statement.execute("DELETE FROM griefprevention_claimdata WHERE parentid=" + claim.id + ";"); - } + statement.execute("DELETE FROM griefprevention_claimdata WHERE id='" + claim.id + "';"); } catch(SQLException e) { diff --git a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java index 4fb3de7..192c409 100644 --- a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -24,8 +24,13 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; + import org.bukkit.*; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; import com.google.common.io.Files; @@ -189,200 +194,374 @@ public class FlatFileDataStore extends DataStore //get a list of all the files in the claims data folder files = claimDataFolder.listFiles(); - for(int i = 0; i < files.length; i++) - { - if(files[i].isFile()) //avoids folders - { - //skip any file starting with an underscore, to avoid special files not representing land claims - if(files[i].getName().startsWith("_")) continue; - - //the filename is the claim ID. try to parse it - long claimID; - - try - { - claimID = Long.parseLong(files[i].getName()); - } - - //because some older versions used a different file name pattern before claim IDs were introduced, - //those files need to be "converted" by renaming them to a unique ID - catch(Exception e) - { - claimID = this.nextClaimID; - this.incrementNextClaimID(); - File newFile = new File(claimDataFolderPath + File.separator + String.valueOf(this.nextClaimID)); - files[i].renameTo(newFile); - files[i] = newFile; - } - - BufferedReader inStream = null; - try - { - Claim topLevelClaim = null; - - inStream = new BufferedReader(new FileReader(files[i].getAbsolutePath())); - String line = inStream.readLine(); - - while(line != null) - { - //skip any SUB:### lines from previous versions - if(line.toLowerCase().startsWith("sub:")) - { - line = inStream.readLine(); - } - - //skip any UUID lines from previous versions - Matcher match = uuidpattern.matcher(line.trim()); - if(match.find()) - { - line = inStream.readLine(); - } - - //first line is lesser boundary corner location - Location lesserBoundaryCorner = this.locationFromString(line); - - //second line is greater boundary corner location - line = inStream.readLine(); - Location greaterBoundaryCorner = this.locationFromString(line); - - //third line is owner name - line = inStream.readLine(); - String ownerName = line; - UUID ownerID = null; - if(ownerName.isEmpty() || ownerName.startsWith("--")) - { - ownerID = null; //administrative land claim or subdivision - } - else if(this.getSchemaVersion() == 0) - { - try - { - ownerID = UUIDFetcher.getUUIDOf(ownerName); - } - catch(Exception ex) - { - GriefPrevention.AddLogEntry("Couldn't resolve this name to a UUID: " + ownerName + "."); - GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); - } - } - else - { - try - { - ownerID = UUID.fromString(ownerName); - } - catch(Exception ex) - { - GriefPrevention.AddLogEntry("Error - this is not a valid UUID: " + ownerName + "."); - GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); - } - } - - //fourth line is list of builders - line = inStream.readLine(); - String [] builderNames = line.split(";"); - builderNames = this.convertNameListToUUIDList(builderNames); - - //fifth line is list of players who can access containers - line = inStream.readLine(); - String [] containerNames = line.split(";"); - containerNames = this.convertNameListToUUIDList(containerNames); - - //sixth line is list of players who can use buttons and switches - line = inStream.readLine(); - String [] accessorNames = line.split(";"); - accessorNames = this.convertNameListToUUIDList(accessorNames); - - //seventh line is list of players who can grant permissions - line = inStream.readLine(); - if(line == null) line = ""; - String [] managerNames = line.split(";"); - managerNames = this.convertNameListToUUIDList(managerNames); - - //skip any remaining extra lines, until the "===" string, indicating the end of this claim or subdivision - line = inStream.readLine(); - while(line != null && !line.contains("===")) - line = inStream.readLine(); - - //build a claim instance from those data - //if this is the first claim loaded from this file, it's the top level claim - if(topLevelClaim == null) - { - //instantiate - topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID); - - topLevelClaim.modifiedDate = new Date(files[i].lastModified()); - this.addClaim(topLevelClaim, false); - } - - //otherwise there's already a top level claim, so this must be a subdivision of that top level claim - else - { - Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, null, builderNames, containerNames, accessorNames, managerNames, null); - - subdivision.modifiedDate = new Date(files[i].lastModified()); - subdivision.parent = topLevelClaim; - topLevelClaim.children.add(subdivision); - subdivision.inDataStore = true; - } - - //move up to the first line in the next subdivision - line = inStream.readLine(); - } - - inStream.close(); - } - - //if there's any problem with the file's content, log an error message and skip it - catch(Exception e) - { - if(e.getMessage().contains("World not found")) - { - inStream.close(); - files[i].delete(); - } - else - { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - GriefPrevention.AddLogEntry(files[i].getName() + " " + errors.toString(), CustomLogEntryTypes.Exception); - } - } - - try - { - if(inStream != null) inStream.close(); - } - catch(IOException exception) {} - } + if(this.getSchemaVersion() <= 1) + { + this.loadClaimData_Legacy(files); + } + else + { + this.loadClaimData(files); } super.initialize(); } + void loadClaimData_Legacy(File [] files) throws Exception + { + List validWorlds = Bukkit.getServer().getWorlds(); + + for(int i = 0; i < files.length; i++) + { + if(files[i].isFile()) //avoids folders + { + //skip any file starting with an underscore, to avoid special files not representing land claims + if(files[i].getName().startsWith("_")) continue; + + //the filename is the claim ID. try to parse it + long claimID; + + try + { + claimID = Long.parseLong(files[i].getName()); + } + + //because some older versions used a different file name pattern before claim IDs were introduced, + //those files need to be "converted" by renaming them to a unique ID + catch(Exception e) + { + claimID = this.nextClaimID; + this.incrementNextClaimID(); + File newFile = new File(claimDataFolderPath + File.separator + String.valueOf(this.nextClaimID)); + files[i].renameTo(newFile); + files[i] = newFile; + } + + BufferedReader inStream = null; + try + { + Claim topLevelClaim = null; + + inStream = new BufferedReader(new FileReader(files[i].getAbsolutePath())); + String line = inStream.readLine(); + + while(line != null) + { + //skip any SUB:### lines from previous versions + if(line.toLowerCase().startsWith("sub:")) + { + line = inStream.readLine(); + } + + //skip any UUID lines from previous versions + Matcher match = uuidpattern.matcher(line.trim()); + if(match.find()) + { + line = inStream.readLine(); + } + + //first line is lesser boundary corner location + Location lesserBoundaryCorner = this.locationFromString(line, validWorlds); + + //second line is greater boundary corner location + line = inStream.readLine(); + Location greaterBoundaryCorner = this.locationFromString(line, validWorlds); + + //third line is owner name + line = inStream.readLine(); + String ownerName = line; + UUID ownerID = null; + if(ownerName.isEmpty() || ownerName.startsWith("--")) + { + ownerID = null; //administrative land claim or subdivision + } + else if(this.getSchemaVersion() == 0) + { + try + { + ownerID = UUIDFetcher.getUUIDOf(ownerName); + } + catch(Exception ex) + { + GriefPrevention.AddLogEntry("Couldn't resolve this name to a UUID: " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } + } + else + { + try + { + ownerID = UUID.fromString(ownerName); + } + catch(Exception ex) + { + GriefPrevention.AddLogEntry("Error - this is not a valid UUID: " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } + } + + //fourth line is list of builders + line = inStream.readLine(); + List builderNames = Arrays.asList(line.split(";")); + builderNames = this.convertNameListToUUIDList(builderNames); + + //fifth line is list of players who can access containers + line = inStream.readLine(); + List containerNames = Arrays.asList(line.split(";")); + containerNames = this.convertNameListToUUIDList(containerNames); + + //sixth line is list of players who can use buttons and switches + line = inStream.readLine(); + List accessorNames = Arrays.asList(line.split(";")); + accessorNames = this.convertNameListToUUIDList(accessorNames); + + //seventh line is list of players who can grant permissions + line = inStream.readLine(); + if(line == null) line = ""; + List managerNames = Arrays.asList(line.split(";")); + managerNames = this.convertNameListToUUIDList(managerNames); + + //skip any remaining extra lines, until the "===" string, indicating the end of this claim or subdivision + line = inStream.readLine(); + while(line != null && !line.contains("===")) + line = inStream.readLine(); + + //build a claim instance from those data + //if this is the first claim loaded from this file, it's the top level claim + if(topLevelClaim == null) + { + //instantiate + topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID); + + topLevelClaim.modifiedDate = new Date(files[i].lastModified()); + this.addClaim(topLevelClaim, false); + } + + //otherwise there's already a top level claim, so this must be a subdivision of that top level claim + else + { + Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, null, builderNames, containerNames, accessorNames, managerNames, null); + + subdivision.modifiedDate = new Date(files[i].lastModified()); + subdivision.parent = topLevelClaim; + topLevelClaim.children.add(subdivision); + subdivision.inDataStore = true; + } + + //move up to the first line in the next subdivision + line = inStream.readLine(); + } + + inStream.close(); + } + + //if there's any problem with the file's content, log an error message and skip it + catch(Exception e) + { + if(e.getMessage().contains("World not found")) + { + inStream.close(); + files[i].delete(); + } + else + { + StringWriter errors = new StringWriter(); + e.printStackTrace(new PrintWriter(errors)); + GriefPrevention.AddLogEntry(files[i].getName() + " " + errors.toString(), CustomLogEntryTypes.Exception); + } + } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + } + } + } + + void loadClaimData(File [] files) throws Exception + { + ConcurrentHashMap orphans = new ConcurrentHashMap(); + for(int i = 0; i < files.length; i++) + { + if(files[i].isFile()) //avoids folders + { + //skip any file starting with an underscore, to avoid special files not representing land claims + if(files[i].getName().startsWith("_")) continue; + + //delete any which don't end in .yml + if(!files[i].getName().endsWith(".yml")) + { + files[i].delete(); + continue; + } + + //the filename is the claim ID. try to parse it + long claimID; + + try + { + claimID = Long.parseLong(files[i].getName().split("\\.")[0]); + } + + //because some older versions used a different file name pattern before claim IDs were introduced, + //those files need to be "converted" by renaming them to a unique ID + catch(Exception e) + { + claimID = this.nextClaimID; + this.incrementNextClaimID(); + File newFile = new File(claimDataFolderPath + File.separator + String.valueOf(this.nextClaimID) + ".yml"); + files[i].renameTo(newFile); + files[i] = newFile; + } + + try + { + ArrayList out_parentID = new ArrayList(); //hacky output parameter + Claim claim = this.loadClaim(files[i], out_parentID, claimID); + if(out_parentID.size() == 0 || out_parentID.get(0) == -1) + { + this.addClaim(claim, false); + } + else + { + orphans.put(claim, out_parentID.get(0)); + } + } + + //if there's any problem with the file's content, log an error message and skip it + catch(Exception e) + { + if(e.getMessage() != null && e.getMessage().contains("World not found")) + { + files[i].delete(); + } + else + { + StringWriter errors = new StringWriter(); + e.printStackTrace(new PrintWriter(errors)); + GriefPrevention.AddLogEntry(files[i].getName() + " " + errors.toString(), CustomLogEntryTypes.Exception); + } + } + } + } + + //link children to parents + for(Claim child : orphans.keySet()) + { + Claim parent = this.getClaim(orphans.get(child)); + if(parent != null) + { + child.parent = parent; + this.addClaim(child, false); + } + } + } + + Claim loadClaim(File file, ArrayList out_parentID, long claimID) throws IOException, InvalidConfigurationException, Exception + { + List lines = Files.readLines(file, Charset.forName("UTF-8")); + StringBuilder builder = new StringBuilder(); + for(String line : lines) + { + builder.append(line).append('\n'); + } + + return this.loadClaim(builder.toString(), out_parentID, file.lastModified(), claimID, Bukkit.getServer().getWorlds()); + } + + Claim loadClaim(String input, ArrayList out_parentID, long lastModifiedDate, long claimID, List validWorlds) throws InvalidConfigurationException, Exception + { + Claim claim = null; + YamlConfiguration yaml = new YamlConfiguration(); + yaml.loadFromString(input); + + //boundaries + Location lesserBoundaryCorner = this.locationFromString(yaml.getString("Lesser Boundary Corner"), validWorlds); + Location greaterBoundaryCorner = this.locationFromString(yaml.getString("Greater Boundary Corner"), validWorlds); + + //owner + String ownerIdentifier = yaml.getString("Owner"); + UUID ownerID = null; + if(!ownerIdentifier.isEmpty()) + { + try + { + ownerID = UUID.fromString(ownerIdentifier); + } + catch(Exception ex) + { + GriefPrevention.AddLogEntry("Error - this is not a valid UUID: " + ownerIdentifier + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } + } + + List builders = yaml.getStringList("Builders"); + + List containers = yaml.getStringList("Containers"); + + List accessors = yaml.getStringList("Accessors"); + + List managers = yaml.getStringList("Managers"); + + out_parentID.add(yaml.getLong("Parent Claim ID", -1L)); + + //instantiate + claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builders, containers, accessors, managers, claimID); + claim.modifiedDate = new Date(lastModifiedDate); + claim.id = claimID; + + return claim; + } + + String getYamlForClaim(Claim claim) + { + YamlConfiguration yaml = new YamlConfiguration(); + + //boundaries + yaml.set("Lesser Boundary Corner", this.locationToString(claim.lesserBoundaryCorner)); + yaml.set("Greater Boundary Corner", this.locationToString(claim.greaterBoundaryCorner)); + + //owner + String ownerID = ""; + if(claim.ownerID != null) ownerID = claim.ownerID.toString(); + yaml.set("Owner", ownerID); + + ArrayList builders = new ArrayList(); + ArrayList containers = new ArrayList(); + ArrayList accessors = new ArrayList(); + ArrayList managers = new ArrayList(); + claim.getPermissions(builders, containers, accessors, managers); + + yaml.set("Builders", builders); + yaml.set("Containers", containers); + yaml.set("Accessors", accessors); + yaml.set("Managers", managers); + + Long parentID = -1L; + if(claim.parent != null) + { + parentID = claim.parent.id; + } + + yaml.set("Parent Claim ID", parentID); + + return yaml.saveToString(); + } + @Override synchronized void writeClaimToStorage(Claim claim) { String claimID = String.valueOf(claim.id); - BufferedWriter outStream = null; + String yaml = this.getYamlForClaim(claim); try { //open the claim's file - File claimFile = new File(claimDataFolderPath + File.separator + claimID); + File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml"); claimFile.createNewFile(); - outStream = new BufferedWriter(new FileWriter(claimFile)); - - //write top level claim data to the file - this.writeClaimData(claim, outStream); - - //for each subdivision - for(int i = 0; i < claim.children.size(); i++) - { - //write the subdivision's data to the file - this.writeClaimData(claim.children.get(i), outStream); - } + Files.write(yaml.getBytes("UTF-8"), claimFile); } //if any problem, log it @@ -392,80 +571,16 @@ public class FlatFileDataStore extends DataStore e.printStackTrace(new PrintWriter(errors)); GriefPrevention.AddLogEntry(claimID + " " + errors.toString(), CustomLogEntryTypes.Exception); } - - //close the file - try - { - if(outStream != null) outStream.close(); - } - catch(IOException exception) {} } - //actually writes claim data to an output stream - synchronized private void writeClaimData(Claim claim, BufferedWriter outStream) throws IOException - { - //first line is lesser boundary corner location - outStream.write(this.locationToString(claim.getLesserBoundaryCorner())); - outStream.newLine(); - - //second line is greater boundary corner location - outStream.write(this.locationToString(claim.getGreaterBoundaryCorner())); - outStream.newLine(); - - //third line is owner name - String lineToWrite = ""; - if(claim.ownerID != null) lineToWrite = claim.ownerID.toString(); - outStream.write(lineToWrite); - outStream.newLine(); - - ArrayList builders = new ArrayList(); - ArrayList containers = new ArrayList(); - ArrayList accessors = new ArrayList(); - ArrayList managers = new ArrayList(); - - claim.getPermissions(builders, containers, accessors, managers); - - //fourth line is list of players with build permission - for(int i = 0; i < builders.size(); i++) - { - outStream.write(builders.get(i) + ";"); - } - outStream.newLine(); - - //fifth line is list of players with container permission - for(int i = 0; i < containers.size(); i++) - { - outStream.write(containers.get(i) + ";"); - } - outStream.newLine(); - - //sixth line is list of players with access permission - for(int i = 0; i < accessors.size(); i++) - { - outStream.write(accessors.get(i) + ";"); - } - outStream.newLine(); - - //seventh line is list of players who may grant permissions for others - for(int i = 0; i < managers.size(); i++) - { - outStream.write(managers.get(i) + ";"); - } - outStream.newLine(); - - //cap each claim with "==========" - outStream.write("=========="); - outStream.newLine(); - } - - //deletes a top level claim from the file system + //deletes a claim from the file system @Override synchronized void deleteClaimFromSecondaryStorage(Claim claim) { String claimID = String.valueOf(claim.id); //remove from disk - File claimFile = new File(claimDataFolderPath + File.separator + claimID); + File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml"); if(claimFile.exists() && !claimFile.delete()) { GriefPrevention.AddLogEntry("Error: Unable to delete claim file \"" + claimFile.getAbsolutePath() + "\"."); @@ -671,6 +786,10 @@ public class FlatFileDataStore extends DataStore { Claim claim = this.claims.get(i); databaseStore.addClaim(claim, true); + for(Claim child : claim.children) + { + databaseStore.addClaim(child, true); + } } //migrate groups diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index a7e58a0..d2cfa80 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -298,6 +298,11 @@ public class PlayerData for(int i = 0; i < dataStore.claims.size(); i++) { Claim claim = dataStore.claims.get(i); + if(!claim.inDataStore) + { + dataStore.claims.remove(i--); + continue; + } if(playerID.equals(claim.ownerID)) { this.claims.add(claim); @@ -317,6 +322,7 @@ public class PlayerData GriefPrevention.AddLogEntry("Total blocks: " + totalBlocks + " Total claimed area: " + totalClaimsArea, CustomLogEntryTypes.Debug, true); for(Claim claim : this.claims) { + if(!claim.inDataStore) continue; GriefPrevention.AddLogEntry( GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " // " + GriefPrevention.getfriendlyLocationString(claim.getGreaterBoundaryCorner()) + " = " @@ -341,6 +347,14 @@ public class PlayerData } } + for(int i = 0; i < this.claims.size(); i++) + { + if(!claims.get(i).inDataStore) + { + claims.remove(i--); + } + } + return claims; } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 7fa11ec..1ae18f8 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -2130,7 +2130,7 @@ class PlayerEventHandler implements Listener Claim newClaim = new Claim( new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1), new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx2, newy2, newz2), - null, new String[]{}, new String[]{}, new String[]{}, new String[]{}, null); + null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); //if the new claim is smaller if(!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false)) @@ -2353,7 +2353,7 @@ class PlayerEventHandler implements Listener GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimStart); //show him where he's working - Visualization visualization = Visualization.FromClaim(new Claim(clickedBlock.getLocation(), clickedBlock.getLocation(), null, new String[]{}, new String[]{}, new String[]{}, new String[]{}, null), clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation()); + Visualization visualization = Visualization.FromClaim(new Claim(clickedBlock.getLocation(), clickedBlock.getLocation(), null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null), clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation()); Visualization.Apply(player, visualization); } diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java index 84a1598..ff1f8e8 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java @@ -18,6 +18,8 @@ package me.ryanhamshire.GriefPrevention; +import java.util.ArrayList; + import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; @@ -112,7 +114,7 @@ class RestoreNatureExecutionTask implements Runnable //show visualization to player who started the restoration if(player != null) { - Claim claim = new Claim(lesserCorner, greaterCorner, null, new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); + Claim claim = new Claim(lesserCorner, greaterCorner, null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); Visualization.Apply(player, visualization); } diff --git a/src/me/ryanhamshire/GriefPrevention/Visualization.java b/src/me/ryanhamshire/GriefPrevention/Visualization.java index 9e001df..6e840c5 100644 --- a/src/me/ryanhamshire/GriefPrevention/Visualization.java +++ b/src/me/ryanhamshire/GriefPrevention/Visualization.java @@ -86,7 +86,7 @@ public class Visualization player.sendBlockChange(element.location, element.realMaterial, element.realData); } - playerData.currentVisualization = null; + playerData.currentVisualization = null; } } @@ -105,7 +105,9 @@ public class Visualization //add subdivisions first for(int i = 0; i < claim.children.size(); i++) { - visualization.addClaimElements(claim.children.get(i), height, VisualizationType.Subdivision, locality); + Claim child = claim.children.get(i); + if(!child.inDataStore) continue; + visualization.addClaimElements(child, height, VisualizationType.Subdivision, locality); } //special visualization for administrative land claims