This commit is contained in:
Ryan Hamshire 2012-07-16 20:17:12 -07:00
parent 311db20522
commit 341e200c42
5 changed files with 606 additions and 14 deletions

View File

@ -1,7 +1,7 @@
name: GriefPrevention
main: me.ryanhamshire.GriefPrevention.GriefPrevention
softdepend: [Vault, Multiverse-Core]
version: 5.1
version: 5.2
commands:
abandonclaim:
description: Deletes a claim.

View File

@ -51,12 +51,8 @@ public abstract class DataStore
final static String messagesFilePath = dataLayerFolderPath + File.separator + "messages.yml";
//initialization!
abstract void initialize();
DataStore()
void initialize() throws Exception
{
this.initialize();
GriefPrevention.AddLogEntry(this.claims.size() + " total claims loaded.");
//make a list of players who own claims
@ -329,6 +325,16 @@ public abstract class DataStore
playerData = this.getPlayerDataFromStorage(playerName);
playerData.playerName = playerName;
//find all the claims belonging to this player and note them for future reference
for(int i = 0; i < this.claims.size(); i++)
{
Claim claim = this.claims.get(i);
if(claim.ownerName.equals(playerName))
{
playerData.claims.add(claim);
}
}
//shove that new player data into the hash map cache
this.playerNameToPlayerDataMap.put(playerName, playerData);
}
@ -1047,7 +1053,8 @@ public abstract class DataStore
message = message.replace("{" + i + "}", param);
}
return message;
return message;
}
abstract void close();
}

View File

@ -0,0 +1,467 @@
/*
GriefPrevention Server Plugin for Minecraft
Copyright (C) 2012 Ryan Hamshire
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.ryanhamshire.GriefPrevention;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.*;
import org.bukkit.*;
//manages data stored in the file system
public class DatabaseDataStore extends DataStore
{
private Connection databaseConnection = null;
private String databaseUrl;
private String userName;
private String password;
DatabaseDataStore(String url, String userName, String password) throws Exception
{
this.databaseUrl = url;
this.userName = userName;
this.password = password;
this.initialize();
}
@Override
void initialize() throws Exception
{
try
{
//load the java driver for mySQL
Class.forName("com.mysql.jdbc.Driver");
}
catch(Exception e)
{
GriefPrevention.AddLogEntry("ERROR: Unable to load Java's mySQL database driver. Check to make sure you've installed it properly.");
throw e;
}
try
{
//set username/pass properties
Properties connectionProps = new Properties();
connectionProps.put("user", this.userName);
connectionProps.put("password", this.password);
//establish connection
this.databaseConnection = DriverManager.getConnection(this.databaseUrl, connectionProps);
}
catch(Exception e2)
{
GriefPrevention.AddLogEntry("ERROR: Unable to connect to database. Check your config file settings.");
throw e2;
}
try
{
//ensure the data tables exist
Statement statement = databaseConnection.createStatement();
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_nextclaimid (nextid INT(15));");
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_claimdata (id INT(15), owner VARCHAR(50), lessercorner VARCHAR(100), greatercorner VARCHAR(100), builders VARCHAR(1000), containers VARCHAR(1000), accessors VARCHAR(1000), managers VARCHAR(1000), parentid INT(15));");
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_playerdata (name VARCHAR(50), lastlogin DATETIME, accruedblocks INT(15), bonusblocks INT(15));");
}
catch(Exception e3)
{
GriefPrevention.AddLogEntry("ERROR: Unable to create the necessary database table. Details:");
GriefPrevention.AddLogEntry(e3.getMessage());
throw e3;
}
//load group data into memory
Statement statement = databaseConnection.createStatement();
ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;");
while(results.next())
{
String name = results.getString("name");
//ignore non-groups. all group names start with a dollar sign.
if(!name.startsWith("$")) continue;
String groupName = name.substring(1);
if(groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases
int groupBonusBlocks = results.getInt("bonusblocks");
this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks);
}
//load next claim number into memory
results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;");
//if there's nothing yet, add it
if(!results.next())
{
statement.execute("INSERT INTO griefprevention_nextclaimid VALUES(0);");
this.nextClaimID = (long)0;
}
//otherwise load it
else
{
this.nextClaimID = results.getLong("nextid");
}
//load claims data into memory
results = statement.executeQuery("SELECT * FROM griefprevention_claimdata;");
ArrayList<Claim> claimsToRemove = new ArrayList<Claim>();
while(results.next())
{
try
{
//skip subdivisions
long parentId = results.getLong("parentid");
if(parentId != -1) continue;
long claimID = results.getLong("id");
String lesserCornerString = results.getString("lessercorner");
Location lesserBoundaryCorner = this.locationFromString(lesserCornerString);
String greaterCornerString = results.getString("greatercorner");
Location greaterBoundaryCorner = this.locationFromString(greaterCornerString);
String ownerName = results.getString("owner");
String buildersString = results.getString("builders");
String [] builderNames = buildersString.split(";");
String containersString = results.getString("containers");
String [] containerNames = containersString.split(";");
String accessorsString = results.getString("accessors");
String [] accessorNames = accessorsString.split(";");
String managersString = results.getString("managers");
String [] managerNames = managersString.split(";");
Claim topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerName, builderNames, containerNames, accessorNames, managerNames, claimID);
//search for another claim overlapping this one
Claim conflictClaim = this.getClaimAt(topLevelClaim.lesserBoundaryCorner, true, null);
//if there is such a claim, mark it for later removal
if(conflictClaim != null)
{
claimsToRemove.add(conflictClaim);
continue;
}
//otherwise, add this claim to the claims collection
else
{
int j = 0;
while(j < this.claims.size() && !this.claims.get(j).greaterThan(topLevelClaim)) j++;
if(j < this.claims.size())
this.claims.add(j, topLevelClaim);
else
this.claims.add(this.claims.size(), topLevelClaim);
topLevelClaim.inDataStore = true;
}
//look for any subdivisions for this claim
Statement statement2 = this.databaseConnection.createStatement();
ResultSet childResults = statement2.executeQuery("SELECT * FROM griefprevention_claimdata WHERE parentid=" + topLevelClaim.id + ";");
while(childResults.next())
{
lesserCornerString = childResults.getString("lessercorner");
lesserBoundaryCorner = this.locationFromString(lesserCornerString);
greaterCornerString = childResults.getString("greatercorner");
greaterBoundaryCorner = this.locationFromString(greaterCornerString);
buildersString = childResults.getString("builders");
builderNames = buildersString.split(";");
containersString = childResults.getString("containers");
containerNames = containersString.split(";");
accessorsString = childResults.getString("accessors");
accessorNames = accessorsString.split(";");
managersString = childResults.getString("managers");
managerNames = managersString.split(";");
Claim childClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerName, builderNames, containerNames, accessorNames, managerNames, null);
//add this claim to the list of children of the current top level claim
childClaim.parent = topLevelClaim;
topLevelClaim.children.add(childClaim);
childClaim.inDataStore = true;
}
}
catch(SQLException e)
{
GriefPrevention.AddLogEntry("Unable to load a claim. Details: " + e.getMessage() + " ... " + results.toString());
e.printStackTrace();
}
}
for(int i = 0; i < claimsToRemove.size(); i++)
{
this.deleteClaimFromSecondaryStorage(claimsToRemove.get(i));
}
super.initialize();
}
@Override
void writeClaimToStorage(Claim claim) //see datastore.cs. this will ALWAYS be a top level claim
{
try
{
//wipe out any existing data about this claim
this.deleteClaimFromSecondaryStorage(claim);
//write top level 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)
{
GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:");
GriefPrevention.AddLogEntry(e.getMessage());
}
}
//actually writes claim data to the database
private void writeClaimData(Claim claim) throws SQLException
{
String lesserCornerString = this.locationToString(claim.getLesserBoundaryCorner());
String greaterCornerString = this.locationToString(claim.getGreaterBoundaryCorner());
String owner = claim.ownerName;
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);
String buildersString = "";
for(int i = 0; i < builders.size(); i++)
{
buildersString += builders.get(i) + ";";
}
String containersString = "";
for(int i = 0; i < containers.size(); i++)
{
containersString += containers.get(i) + ";";
}
String accessorsString = "";
for(int i = 0; i < accessors.size(); i++)
{
accessorsString += accessors.get(i) + ";";
}
String managersString = "";
for(int i = 0; i < managers.size(); i++)
{
managersString += managers.get(i) + ";";
}
long parentId;
if(claim.parent == null)
{
parentId = -1;
}
else
{
parentId = claim.parent.id;
}
long id;
if(claim.id == null)
{
id = -1;
}
else
{
id = claim.id;
}
try
{
Statement statement = databaseConnection.createStatement();
statement.execute("INSERT INTO griefprevention_claimdata VALUES(" +
id + ", '" +
owner + "', '" +
lesserCornerString + "', '" +
greaterCornerString + "', '" +
buildersString + "', '" +
containersString + "', '" +
accessorsString + "', '" +
managersString + "', " +
parentId +
");");
}
catch(SQLException e)
{
GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:");
GriefPrevention.AddLogEntry(e.getMessage());
}
}
//deletes a top level claim from the database
@Override
void deleteClaimFromSecondaryStorage(Claim claim)
{
try
{
Statement statement = this.databaseConnection.createStatement();
statement.execute("DELETE FROM griefprevention_claimdata WHERE id=" + claim.id + ";");
statement.execute("DELETE FROM griefprevention_claimdata WHERE parentid=" + claim.id + ";");
}
catch(SQLException e)
{
GriefPrevention.AddLogEntry("Unable to delete data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:");
GriefPrevention.AddLogEntry(e.getMessage());
}
}
@Override
PlayerData getPlayerDataFromStorage(String playerName)
{
PlayerData playerData = new PlayerData();
playerData.playerName = playerName;
try
{
Statement statement = this.databaseConnection.createStatement();
ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata WHERE name='" + playerName + "';");
//if there's no data for this player, create it with defaults
if(!results.next())
{
this.savePlayerData(playerName, playerData);
}
//otherwise, just read from the database
else
{
playerData.lastLogin = results.getTimestamp("lastlogin");
playerData.accruedClaimBlocks = results.getInt("accruedblocks");
playerData.bonusClaimBlocks = results.getInt("bonusblocks");
}
}
catch(SQLException e)
{
GriefPrevention.AddLogEntry("Unable to retrieve data for player " + playerName + ". Details:");
GriefPrevention.AddLogEntry(e.getMessage());
}
return playerData;
}
//saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them
@Override
public void savePlayerData(String playerName, PlayerData playerData)
{
//never save data for the "administrative" account. an empty string for player name indicates administrative account
if(playerName.length() == 0) return;
try
{
SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = sqlFormat.format(playerData.lastLogin);
Statement statement = databaseConnection.createStatement();
statement.execute("DELETE FROM griefprevention_playerdata WHERE name='" + playerName + "';");
statement.execute("INSERT INTO griefprevention_playerdata VALUES ('" + playerName + "', '" + dateString + "', " + playerData.accruedClaimBlocks + ", " + playerData.bonusClaimBlocks + ");");
}
catch(SQLException e)
{
GriefPrevention.AddLogEntry("Unable to save data for player " + playerName + ". Details:");
GriefPrevention.AddLogEntry(e.getMessage());
}
}
@Override
void incrementNextClaimID()
{
this.setNextClaimID(this.nextClaimID + 1);
}
//sets the next claim ID. used by incrementNextClaimID() above, and also while migrating data from a flat file data store
void setNextClaimID(long nextID)
{
this.nextClaimID = nextID;
try
{
Statement statement = databaseConnection.createStatement();
statement.execute("DELETE FROM griefprevention_nextclaimid;");
statement.execute("INSERT INTO griefprevention_nextclaimid VALUES (" + nextID + ");");
}
catch(SQLException e)
{
GriefPrevention.AddLogEntry("Unable to set next claim ID to " + nextID + ". Details:");
GriefPrevention.AddLogEntry(e.getMessage());
}
}
//updates the database with a group's bonus blocks
@Override
void saveGroupBonusBlocks(String groupName, int currentValue)
{
//group bonus blocks are stored in the player data table, with player name = $groupName
String playerName = "$" + groupName;
PlayerData playerData = new PlayerData();
playerData.bonusClaimBlocks = currentValue;
this.savePlayerData(playerName, playerData);
}
@Override
void close()
{
if(this.databaseConnection != null)
{
try
{
this.databaseConnection.close();
}
catch(SQLException e){};
this.databaseConnection = null;
}
}
}

View File

@ -42,13 +42,13 @@ public class FlatFileDataStore extends DataStore
}
//initialization!
FlatFileDataStore()
FlatFileDataStore() throws Exception
{
super();
this.initialize();
}
@Override
void initialize()
void initialize() throws Exception
{
//ensure data folders exist
new File(playerDataFolderPath).mkdirs();
@ -226,8 +226,6 @@ public class FlatFileDataStore extends DataStore
{
Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, "--subdivision--", builderNames, containerNames, accessorNames, managerNames, null);
//make sure there are no other subdivisions overlapping this one
subdivision.modifiedDate = new Date(files[i].lastModified());
subdivision.parent = topLevelClaim;
topLevelClaim.children.add(subdivision);
@ -254,6 +252,8 @@ public class FlatFileDataStore extends DataStore
catch(IOException exception) {}
}
}
super.initialize();
}
@Override
@ -571,4 +571,73 @@ public class FlatFileDataStore extends DataStore
}
catch(IOException exception){}
}
void migrateData(DatabaseDataStore databaseStore)
{
//migrate claims
for(int i = 0; i < this.claims.size(); i++)
{
Claim claim = this.claims.get(i);
databaseStore.addClaim(claim);
}
//migrate groups
Iterator<String> groupNamesEnumerator = this.permissionToBonusBlocksMap.keySet().iterator();
while(groupNamesEnumerator.hasNext())
{
String groupName = groupNamesEnumerator.next();
databaseStore.saveGroupBonusBlocks(groupName, this.permissionToBonusBlocksMap.get(groupName));
}
//migrate players
File playerDataFolder = new File(playerDataFolderPath);
File [] files = playerDataFolder.listFiles();
for(int i = 0; i < files.length; i++)
{
File file = files[i];
if(!file.isFile()) continue; //avoids folders
//all group data files start with a dollar sign. ignoring those, already handled above
if(file.getName().startsWith("$")) continue;
String playerName = file.getName();
databaseStore.savePlayerData(playerName, this.getPlayerData(playerName));
this.clearCachedPlayerData(playerName);
}
//migrate next claim ID
if(this.nextClaimID > databaseStore.nextClaimID)
{
databaseStore.setNextClaimID(this.nextClaimID);
}
//rename player and claim data folders so the migration won't run again
int i = 0;
File claimsBackupFolder;
File playersBackupFolder;
do
{
String claimsFolderBackupPath = claimDataFolderPath;
if(i > 0) claimsFolderBackupPath += String.valueOf(i);
claimsBackupFolder = new File(claimsFolderBackupPath);
String playersFolderBackupPath = playerDataFolderPath;
if(i > 0) playersFolderBackupPath += String.valueOf(i);
playersBackupFolder = new File(playersFolderBackupPath);
i++;
} while(claimsBackupFolder.exists() || playersBackupFolder.exists());
File claimsFolder = new File(claimDataFolderPath);
File playersFolder = new File(playerDataFolderPath);
claimsFolder.renameTo(claimsBackupFolder);
playersFolder.renameTo(playersBackupFolder);
GriefPrevention.AddLogEntry("Backed your file system data up to " + claimsBackupFolder.getName() + " and " + playersBackupFolder.getName() + ".");
GriefPrevention.AddLogEntry("If your migration encountered any problems, you can restore those data with a quick copy/paste.");
GriefPrevention.AddLogEntry("When you're satisfied that all your data have been safely migrated, consider deleting those folders.");
}
@Override
void close() { }
}

View File

@ -366,6 +366,11 @@ public class GriefPrevention extends JavaPlugin
}
}
//optional database settings
String databaseUrl = config.getString("GriefPrevention.Database.URL", "");
String databaseUserName = config.getString("GriefPrevention.Database.UserName", "");
String databasePassword = config.getString("GriefPrevention.Database.Password", "");
config.set("GriefPrevention.Claims.Worlds", claimsEnabledWorldNames);
config.set("GriefPrevention.Claims.CreativeRulesWorlds", creativeClaimsEnabledWorldNames);
config.set("GriefPrevention.Claims.PreventTheft", this.config_claims_preventTheft);
@ -423,6 +428,10 @@ public class GriefPrevention extends JavaPlugin
config.set("GriefPrevention.EndermenMoveBlocks", this.config_endermenMoveBlocks);
config.set("GriefPrevention.CreaturesTrampleCrops", this.config_creaturesTrampleCrops);
config.set("GriefPrevention.Database.URL", databaseUrl);
config.set("GriefPrevention.Database.UserName", databaseUserName);
config.set("GriefPrevention.Database.Password", databasePassword);
try
{
config.save(DataStore.configFilePath);
@ -441,7 +450,45 @@ public class GriefPrevention extends JavaPlugin
}
//when datastore initializes, it loads player and claim data, and posts some stats to the log
this.dataStore = new FlatFileDataStore();
if(databaseUrl.length() > 0)
{
try
{
DatabaseDataStore databaseStore = new DatabaseDataStore(databaseUrl, databaseUserName, databasePassword);
if(FlatFileDataStore.hasData())
{
GriefPrevention.AddLogEntry("There appears to be some data on the hard drive. Migrating those data to the database...");
FlatFileDataStore flatFileStore = new FlatFileDataStore();
flatFileStore.migrateData(databaseStore);
GriefPrevention.AddLogEntry("Data migration process complete. Reloading data from the database...");
databaseStore.close();
databaseStore = new DatabaseDataStore(databaseUrl, databaseUserName, databasePassword);
}
this.dataStore = databaseStore;
}
catch(Exception e)
{
GriefPrevention.AddLogEntry("Because there was a problem with the database, GriefPrevention will not function properly. Either update the database config settings resolve the issue, or delete those lines from your config.yml so that GriefPrevention can use the file system to store data.");
return;
}
}
//if not using the database because it's not configured or because there was a problem, use the file system to store data
//this is the preferred method, as it's simpler than the database scenario
if(this.dataStore == null)
{
try
{
this.dataStore = new FlatFileDataStore();
}
catch(Exception e)
{
GriefPrevention.AddLogEntry("Unable to initialize the file system data store. Details:");
GriefPrevention.AddLogEntry(e.getMessage());
}
}
//unless claim block accrual is disabled, start the recurring per 5 minute event to give claim blocks to online players
//20L ~ 1 second
@ -1715,6 +1762,8 @@ public class GriefPrevention extends JavaPlugin
this.dataStore.savePlayerData(playerName, playerData);
}
this.dataStore.close();
AddLogEntry("GriefPrevention disabled.");
}