Unique IDs for claim subdivisions.

Also performance updates and switch to YAML file format for flat file
data stores.
This commit is contained in:
ryanhamshire 2015-09-12 14:17:00 -07:00
parent 4b3802d189
commit 9c00a47a29
9 changed files with 462 additions and 367 deletions

View File

@ -173,14 +173,14 @@ commands:
permission: griefprevention.ignore permission: griefprevention.ignore
separate: separate:
description: Forces two players to ignore each other in chat. description: Forces two players to ignore each other in chat.
usage: /Separate usage: /Separate <player1> <player2>
permission: griefprevention.separate permission: griefprevention.separate
unseparate: unseparate:
description: Reverses /separate. description: Reverses /separate.
usage: /UnSeparate usage: /UnSeparate <player1> <player2>
permission: griefprevention.separate permission: griefprevention.separate
claimbook: claimbook:
description: Gives a player a replacement land claiming book. description: Gives a player a manual about claiming land.
usage: /ClaimBook <player> usage: /ClaimBook <player>
permission: griefprevention.claimbook permission: griefprevention.claimbook
permissions: permissions:

View File

@ -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 //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<String> builderIDs, List<String> containerIDs, List<String> accessorIDs, List<String> managerIDs, Long id)
{ {
//modification date //modification date
this.modifiedDate = Calendar.getInstance().getTime(); this.modifiedDate = Calendar.getInstance().getTime();
@ -202,36 +202,32 @@ public class Claim
this.ownerID = ownerID; this.ownerID = ownerID;
//other permissions //other permissions
for(int i = 0; i < builderIDs.length; i++) for(String builderID : builderIDs)
{ {
String builderID = builderIDs[i];
if(builderID != null && !builderID.isEmpty()) if(builderID != null && !builderID.isEmpty())
{ {
this.playerIDToClaimPermissionMap.put(builderID, ClaimPermission.Build); 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()) if(containerID != null && !containerID.isEmpty())
{ {
this.playerIDToClaimPermissionMap.put(containerID, ClaimPermission.Inventory); 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()) if(accessorID != null && !accessorID.isEmpty())
{ {
this.playerIDToClaimPermissionMap.put(accessorID, ClaimPermission.Access); 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()) if(managerID != null && !managerID.isEmpty())
{ {
this.managers.add(managerID); this.managers.add(managerID);
@ -264,7 +260,7 @@ public class Claim
Claim claim = new Claim Claim claim = new Claim
(new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), (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), 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<String>(), new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), null);
return claim.contains(location, false, true); return claim.contains(location, false, true);
} }

View File

@ -19,9 +19,6 @@
package me.ryanhamshire.GriefPrevention; package me.ryanhamshire.GriefPrevention;
import java.io.*; import java.io.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -66,7 +63,7 @@ public abstract class DataStore
final static String softMuteFilePath = dataLayerFolderPath + File.separator + "softMute.txt"; final static String softMuteFilePath = dataLayerFolderPath + File.separator + "softMute.txt";
//the latest version of the data schema implemented here //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 //reading and writing the schema version to the data store
abstract int getSchemaVersionFromStorage(); abstract int getSchemaVersionFromStorage();
@ -129,6 +126,11 @@ public abstract class DataStore
for(Claim claim : this.claims) for(Claim claim : this.claims)
{ {
this.saveClaim(claim); this.saveClaim(claim);
for(Claim subClaim : claim.children)
{
this.saveClaim(subClaim);
}
} }
//clean up any UUID conversion work //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 //adds a claim to the datastore, making it an effective claim
synchronized void addClaim(Claim newClaim, boolean writeToStorage) 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 != null)
{ {
if(!newClaim.parent.children.contains(newClaim)) if(!newClaim.parent.children.contains(newClaim))
@ -425,7 +427,7 @@ public abstract class DataStore
} }
//turns a location string back into a location //turns a location string back into a location
Location locationFromString(String string) throws Exception Location locationFromString(String string, List<World> validWorlds) throws Exception
{ {
//split the input string on the space //split the input string on the space
String [] elements = string.split(locationStringDelimiter); String [] elements = string.split(locationStringDelimiter);
@ -442,7 +444,16 @@ public abstract class DataStore
String zString = elements[3]; String zString = elements[3];
//identify world the claim is in //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) if(world == null)
{ {
throw new Exception("World not found: \"" + worldName + "\""); throw new Exception("World not found: \"" + worldName + "\"");
@ -459,15 +470,7 @@ public abstract class DataStore
//saves any changes to a claim to secondary storage //saves any changes to a claim to secondary storage
synchronized public void saveClaim(Claim claim) synchronized public void saveClaim(Claim claim)
{ {
//subdivisions don't save to their own files, but instead live in their parent claim's file //ensure a unique identifier for the claim which will be used to name the file on disk
//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
if(claim.id == null) if(claim.id == null)
{ {
claim.id = this.nextClaimID; claim.id = this.nextClaimID;
@ -512,23 +515,20 @@ public abstract class DataStore
synchronized void deleteClaim(Claim claim, boolean fireEvent) 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) if(claim.parent != null)
{ {
Claim parentClaim = claim.parent; Claim parentClaim = claim.parent;
parentClaim.children.remove(claim); parentClaim.children.remove(claim);
claim.inDataStore = false;
this.saveClaim(parentClaim);
return;
} }
//delete any children //mark as deleted so any references elsewhere can be ignored
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
claim.inDataStore = false; claim.inDataStore = false;
//remove from memory //remove from memory
@ -558,8 +558,8 @@ public abstract class DataStore
//remove from secondary storage //remove from secondary storage
this.deleteClaimFromSecondaryStorage(claim); this.deleteClaimFromSecondaryStorage(claim);
//update player data, except for administrative claims, which have no owner //update player data
if(!claim.isAdminClaim()) if(claim.ownerID != null)
{ {
PlayerData ownerData = this.getPlayerData(claim.ownerID); PlayerData ownerData = this.getPlayerData(claim.ownerID);
for(int i = 0; i < ownerData.getClaims().size(); i++) for(int i = 0; i < ownerData.getClaims().size(); i++)
@ -597,14 +597,14 @@ public abstract class DataStore
for(Claim claim : claimsInChunk) 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, //when we find a top level claim, if the location is in one of its subdivisions,
//return the SUBDIVISION, not the top level claim //return the SUBDIVISION, not the top level claim
for(int j = 0; j < claim.children.size(); j++) for(int j = 0; j < claim.children.size(); j++)
{ {
Claim subdivision = claim.children.get(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; return claim;
@ -620,7 +620,7 @@ public abstract class DataStore
{ {
for(Claim claim : this.claims) for(Claim claim : this.claims)
{ {
if(claim.getID() == id) return claim; if(claim.inDataStore && claim.getID() == id) return claim;
} }
return null; return null;
@ -702,10 +702,10 @@ public abstract class DataStore
new Location(world, smallx, smally, smallz), new Location(world, smallx, smally, smallz),
new Location(world, bigx, bigy, bigz), new Location(world, bigx, bigy, bigz),
ownerID, ownerID,
new String [] {}, new ArrayList<String>(),
new String [] {}, new ArrayList<String>(),
new String [] {}, new ArrayList<String>(),
new String [] {}, new ArrayList<String>(),
id); id);
newClaim.parent = parent; newClaim.parent = parent;
@ -714,7 +714,7 @@ public abstract class DataStore
ArrayList<Claim> claimsToCheck; ArrayList<Claim> claimsToCheck;
if(newClaim.parent != null) if(newClaim.parent != null)
{ {
claimsToCheck = newClaim.parent.children; claimsToCheck = newClaim.parent.children;
} }
else else
{ {
@ -726,7 +726,7 @@ public abstract class DataStore
Claim otherClaim = claimsToCheck.get(i); Claim otherClaim = claimsToCheck.get(i);
//if we find an existing claim which will be overlapped //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 = fail, return conflicting claim
result.succeeded = false; result.succeeded = false;
@ -821,27 +821,18 @@ public abstract class DataStore
if(claim.parent != null) claim = claim.parent; if(claim.parent != null) claim = claim.parent;
//note any subdivisions //adjust to new depth
ArrayList<Claim> subdivisions = new ArrayList<Claim>(claim.children);
//delete the claim
this.deleteClaim(claim, false);
//re-create it at the new depth
claim.lesserBoundaryCorner.setY(newDepth); claim.lesserBoundaryCorner.setY(newDepth);
claim.greaterBoundaryCorner.setY(newDepth); claim.greaterBoundaryCorner.setY(newDepth);
for(Claim subdivision : claim.children)
//re-add the subdivisions (deleteClaim() removed them) with the new depth
for(Claim subdivision : subdivisions)
{ {
subdivision.lesserBoundaryCorner.setY(newDepth); subdivision.lesserBoundaryCorner.setY(newDepth);
subdivision.greaterBoundaryCorner.setY(newDepth); subdivision.greaterBoundaryCorner.setY(newDepth);
subdivision.parent = claim; this.saveClaim(subdivision);
this.addClaim(subdivision, false);
} }
//save changes //save changes
this.addClaim(claim, true); this.saveClaim(claim);
} }
//starts a siege on a claim //starts a siege on a claim
@ -1084,12 +1075,6 @@ public abstract class DataStore
//see CreateClaim() for details on return value //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) 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<Claim> subdivisions = new ArrayList<Claim>(claim.children);
//remove old claim
this.deleteClaim(claim, false);
//try to create this new claim, ignoring the original when checking for overlap //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); 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 //restore subdivisions
for(Claim subdivision : subdivisions) for(Claim subdivision : claim.children)
{ {
subdivision.parent = result.claim; subdivision.parent = result.claim;
this.addClaim(subdivision, false); result.claim.children.add(subdivision);
} }
//save those changes //save those changes
this.saveClaim(result.claim); this.saveClaim(result.claim);
}
//make original claim ineffective (it's still in the hash map, so let's make it ignored)
else claim.inDataStore = false;
{
//put original claim back
this.addClaim(claim, true);
} }
return result; return result;
@ -1421,7 +1403,7 @@ public abstract class DataStore
//used in updating the data schema from 0 to 1. //used in updating the data schema from 0 to 1.
//converts player names in a list to uuids //converts player names in a list to uuids
protected String[] convertNameListToUUIDList(String[] names) protected List<String> convertNameListToUUIDList(List<String> names)
{ {
//doesn't apply after schema has been updated to version 1 //doesn't apply after schema has been updated to version 1
if(this.getSchemaVersion() >= 1) return names; if(this.getSchemaVersion() >= 1) return names;
@ -1453,14 +1435,7 @@ public abstract class DataStore
} }
} }
//return final result of conversion return resultNames;
String [] resultArray = new String [resultNames.size()];
for(int i = 0; i < resultNames.size(); i++)
{
resultArray[i] = resultNames.get(i);
}
return resultArray;
} }
abstract void close(); abstract void close();

View File

@ -225,6 +225,7 @@ public class DatabaseDataStore extends DataStore
ArrayList<Claim> claimsToRemove = new ArrayList<Claim>(); ArrayList<Claim> claimsToRemove = new ArrayList<Claim>();
ArrayList<Claim> subdivisionsToLoad = new ArrayList<Claim>(); ArrayList<Claim> subdivisionsToLoad = new ArrayList<Claim>();
List<World> validWorlds = Bukkit.getServer().getWorlds();
while(results.next()) while(results.next())
{ {
@ -242,10 +243,10 @@ public class DatabaseDataStore extends DataStore
try try
{ {
lesserCornerString = results.getString("lessercorner"); lesserCornerString = results.getString("lessercorner");
lesserBoundaryCorner = this.locationFromString(lesserCornerString); lesserBoundaryCorner = this.locationFromString(lesserCornerString, validWorlds);
String greaterCornerString = results.getString("greatercorner"); String greaterCornerString = results.getString("greatercorner");
greaterBoundaryCorner = this.locationFromString(greaterCornerString); greaterBoundaryCorner = this.locationFromString(greaterCornerString, validWorlds);
} }
catch(Exception e) catch(Exception e)
{ {
@ -293,19 +294,19 @@ public class DatabaseDataStore extends DataStore
} }
String buildersString = results.getString("builders"); String buildersString = results.getString("builders");
String [] builderNames = buildersString.split(";"); List<String> builderNames = Arrays.asList(buildersString.split(";"));
builderNames = this.convertNameListToUUIDList(builderNames); builderNames = this.convertNameListToUUIDList(builderNames);
String containersString = results.getString("containers"); String containersString = results.getString("containers");
String [] containerNames = containersString.split(";"); List<String> containerNames = Arrays.asList(containersString.split(";"));
containerNames = this.convertNameListToUUIDList(containerNames); containerNames = this.convertNameListToUUIDList(containerNames);
String accessorsString = results.getString("accessors"); String accessorsString = results.getString("accessors");
String [] accessorNames = accessorsString.split(";"); List<String> accessorNames = Arrays.asList(accessorsString.split(";"));
accessorNames = this.convertNameListToUUIDList(accessorNames); accessorNames = this.convertNameListToUUIDList(accessorNames);
String managersString = results.getString("managers"); String managersString = results.getString("managers");
String [] managerNames = managersString.split(";"); List<String> managerNames = Arrays.asList(managersString.split(";"));
managerNames = this.convertNameListToUUIDList(managerNames); managerNames = this.convertNameListToUUIDList(managerNames);
Claim claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID); 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)); 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(); super.initialize();
} }
@ -369,15 +377,8 @@ public class DatabaseDataStore extends DataStore
//wipe out any existing data about this claim //wipe out any existing data about this claim
this.deleteClaimFromSecondaryStorage(claim); this.deleteClaimFromSecondaryStorage(claim);
//write top level claim data to the database //write claim data to the database
this.writeClaimData(claim); 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) catch(SQLException e)
{ {
@ -435,23 +436,13 @@ public class DatabaseDataStore extends DataStore
parentId = claim.parent.id; parentId = claim.parent.id;
} }
long id;
if(claim.id == null)
{
id = -1;
}
else
{
id = claim.id;
}
try try
{ {
this.refreshDataConnection(); this.refreshDataConnection();
Statement statement = databaseConnection.createStatement(); Statement statement = databaseConnection.createStatement();
statement.execute("INSERT INTO griefprevention_claimdata (id, owner, lessercorner, greatercorner, builders, containers, accessors, managers, parentid) VALUES(" + statement.execute("INSERT INTO griefprevention_claimdata (id, owner, lessercorner, greatercorner, builders, containers, accessors, managers, parentid) VALUES(" +
id + ", '" + claim.id + ", '" +
owner + "', '" + owner + "', '" +
lesserCornerString + "', '" + lesserCornerString + "', '" +
greaterCornerString + "', '" + greaterCornerString + "', '" +
@ -479,11 +470,7 @@ public class DatabaseDataStore extends DataStore
Statement statement = this.databaseConnection.createStatement(); Statement statement = this.databaseConnection.createStatement();
statement.execute("DELETE FROM griefprevention_claimdata WHERE lessercorner='" + this.locationToString(claim.lesserBoundaryCorner) + "' AND greatercorner = '" + this.locationToString(claim.greaterBoundaryCorner) + "';"); statement.execute("DELETE FROM griefprevention_claimdata WHERE id='" + claim.id + "';");
if(claim.id != -1)
{
statement.execute("DELETE FROM griefprevention_claimdata WHERE parentid=" + claim.id + ";");
}
} }
catch(SQLException e) catch(SQLException e)
{ {

View File

@ -24,8 +24,13 @@ import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import org.bukkit.*; 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; 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 //get a list of all the files in the claims data folder
files = claimDataFolder.listFiles(); files = claimDataFolder.listFiles();
for(int i = 0; i < files.length; i++) if(this.getSchemaVersion() <= 1)
{ {
if(files[i].isFile()) //avoids folders this.loadClaimData_Legacy(files);
{ }
//skip any file starting with an underscore, to avoid special files not representing land claims else
if(files[i].getName().startsWith("_")) continue; {
this.loadClaimData(files);
//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) {}
}
} }
super.initialize(); super.initialize();
} }
void loadClaimData_Legacy(File [] files) throws Exception
{
List<World> 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<String> builderNames = Arrays.asList(line.split(";"));
builderNames = this.convertNameListToUUIDList(builderNames);
//fifth line is list of players who can access containers
line = inStream.readLine();
List<String> 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<String> 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<String> 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<Claim, Long> orphans = new ConcurrentHashMap<Claim, Long>();
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<Long> out_parentID = new ArrayList<Long>(); //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<Long> out_parentID, long claimID) throws IOException, InvalidConfigurationException, Exception
{
List<String> 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<Long> out_parentID, long lastModifiedDate, long claimID, List<World> 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<String> builders = yaml.getStringList("Builders");
List<String> containers = yaml.getStringList("Containers");
List<String> accessors = yaml.getStringList("Accessors");
List<String> 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<String> builders = new ArrayList<String>();
ArrayList<String> containers = new ArrayList<String>();
ArrayList<String> accessors = new ArrayList<String>();
ArrayList<String> managers = new ArrayList<String>();
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 @Override
synchronized void writeClaimToStorage(Claim claim) synchronized void writeClaimToStorage(Claim claim)
{ {
String claimID = String.valueOf(claim.id); String claimID = String.valueOf(claim.id);
BufferedWriter outStream = null; String yaml = this.getYamlForClaim(claim);
try try
{ {
//open the claim's file //open the claim's file
File claimFile = new File(claimDataFolderPath + File.separator + claimID); File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml");
claimFile.createNewFile(); claimFile.createNewFile();
outStream = new BufferedWriter(new FileWriter(claimFile)); Files.write(yaml.getBytes("UTF-8"), 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);
}
} }
//if any problem, log it //if any problem, log it
@ -392,80 +571,16 @@ public class FlatFileDataStore extends DataStore
e.printStackTrace(new PrintWriter(errors)); e.printStackTrace(new PrintWriter(errors));
GriefPrevention.AddLogEntry(claimID + " " + errors.toString(), CustomLogEntryTypes.Exception); 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 //deletes a claim from the file system
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<String> builders = new ArrayList<String>();
ArrayList<String> containers = new ArrayList<String>();
ArrayList<String> accessors = new ArrayList<String>();
ArrayList<String> managers = new ArrayList<String>();
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
@Override @Override
synchronized void deleteClaimFromSecondaryStorage(Claim claim) synchronized void deleteClaimFromSecondaryStorage(Claim claim)
{ {
String claimID = String.valueOf(claim.id); String claimID = String.valueOf(claim.id);
//remove from disk //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()) if(claimFile.exists() && !claimFile.delete())
{ {
GriefPrevention.AddLogEntry("Error: Unable to delete claim file \"" + claimFile.getAbsolutePath() + "\"."); 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); Claim claim = this.claims.get(i);
databaseStore.addClaim(claim, true); databaseStore.addClaim(claim, true);
for(Claim child : claim.children)
{
databaseStore.addClaim(child, true);
}
} }
//migrate groups //migrate groups

View File

@ -298,6 +298,11 @@ public class PlayerData
for(int i = 0; i < dataStore.claims.size(); i++) for(int i = 0; i < dataStore.claims.size(); i++)
{ {
Claim claim = dataStore.claims.get(i); Claim claim = dataStore.claims.get(i);
if(!claim.inDataStore)
{
dataStore.claims.remove(i--);
continue;
}
if(playerID.equals(claim.ownerID)) if(playerID.equals(claim.ownerID))
{ {
this.claims.add(claim); this.claims.add(claim);
@ -317,6 +322,7 @@ public class PlayerData
GriefPrevention.AddLogEntry("Total blocks: " + totalBlocks + " Total claimed area: " + totalClaimsArea, CustomLogEntryTypes.Debug, true); GriefPrevention.AddLogEntry("Total blocks: " + totalBlocks + " Total claimed area: " + totalClaimsArea, CustomLogEntryTypes.Debug, true);
for(Claim claim : this.claims) for(Claim claim : this.claims)
{ {
if(!claim.inDataStore) continue;
GriefPrevention.AddLogEntry( GriefPrevention.AddLogEntry(
GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " // " GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " // "
+ GriefPrevention.getfriendlyLocationString(claim.getGreaterBoundaryCorner()) + " = " + 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; return claims;
} }

View File

@ -2130,7 +2130,7 @@ class PlayerEventHandler implements Listener
Claim newClaim = new Claim( Claim newClaim = new Claim(
new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1), new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1),
new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx2, newy2, newz2), new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx2, newy2, newz2),
null, new String[]{}, new String[]{}, new String[]{}, new String[]{}, null); null, new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), null);
//if the new claim is smaller //if the new claim is smaller
if(!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false)) 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); GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimStart);
//show him where he's working //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<String>(), new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), null), clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation());
Visualization.Apply(player, visualization); Visualization.Apply(player, visualization);
} }

View File

@ -18,6 +18,8 @@
package me.ryanhamshire.GriefPrevention; package me.ryanhamshire.GriefPrevention;
import java.util.ArrayList;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
@ -112,7 +114,7 @@ class RestoreNatureExecutionTask implements Runnable
//show visualization to player who started the restoration //show visualization to player who started the restoration
if(player != null) 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<String>(), new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), null);
Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation());
Visualization.Apply(player, visualization); Visualization.Apply(player, visualization);
} }

View File

@ -86,7 +86,7 @@ public class Visualization
player.sendBlockChange(element.location, element.realMaterial, element.realData); player.sendBlockChange(element.location, element.realMaterial, element.realData);
} }
playerData.currentVisualization = null; playerData.currentVisualization = null;
} }
} }
@ -105,7 +105,9 @@ public class Visualization
//add subdivisions first //add subdivisions first
for(int i = 0; i < claim.children.size(); i++) 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 //special visualization for administrative land claims