Rewrote and retested parts of the plugin to use UUIDs instead of player names to uniquely identify players. Added data migration code to convert old data to the new (UUID) format.
641 lines
21 KiB
Java
641 lines
21 KiB
Java
/*
|
|
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
|
|
{
|
|
this.refreshDataConnection();
|
|
}
|
|
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));");
|
|
|
|
statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_schemaversion (version INT(15));");
|
|
|
|
//if the next claim id table is empty, this is a brand new database which will write using the latest schema
|
|
//otherwise, schema version is determined by schemaversion table (or =0 if table is empty, see getSchemaVersion())
|
|
ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;");
|
|
if(!results.next())
|
|
{
|
|
this.setSchemaVersion(latestSchemaVersion);
|
|
}
|
|
}
|
|
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");
|
|
UUID ownerID = null;
|
|
if(ownerName.isEmpty())
|
|
{
|
|
ownerID = null; //administrative land claim
|
|
}
|
|
else if(this.getSchemaVersion() < 0)
|
|
{
|
|
try
|
|
{
|
|
ownerID = UUIDFetcher.getUUIDOf(ownerName);
|
|
}
|
|
catch(Exception ex){ } //if UUID not found, use NULL
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
ownerID = UUID.fromString(ownerName);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
GriefPrevention.AddLogEntry("Failed to look up UUID for player " + ownerName + ".");
|
|
GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString());
|
|
}
|
|
}
|
|
|
|
String buildersString = results.getString("builders");
|
|
String [] builderNames = buildersString.split(";");
|
|
builderNames = this.convertNameListToUUIDList(builderNames);
|
|
|
|
String containersString = results.getString("containers");
|
|
String [] containerNames = containersString.split(";");
|
|
containerNames = this.convertNameListToUUIDList(containerNames);
|
|
|
|
String accessorsString = results.getString("accessors");
|
|
String [] accessorNames = accessorsString.split(";");
|
|
accessorNames = this.convertNameListToUUIDList(accessorNames);
|
|
|
|
String managersString = results.getString("managers");
|
|
String [] managerNames = managersString.split(";");
|
|
managerNames = this.convertNameListToUUIDList(managerNames);
|
|
|
|
Claim topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, 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(";");
|
|
builderNames = this.convertNameListToUUIDList(builderNames);
|
|
|
|
containersString = childResults.getString("containers");
|
|
containerNames = containersString.split(";");
|
|
containerNames = this.convertNameListToUUIDList(containerNames);
|
|
|
|
accessorsString = childResults.getString("accessors");
|
|
accessorNames = accessorsString.split(";");
|
|
accessorNames = this.convertNameListToUUIDList(accessorNames);
|
|
|
|
managersString = childResults.getString("managers");
|
|
managerNames = managersString.split(";");
|
|
managerNames = this.convertNameListToUUIDList(managerNames);
|
|
|
|
Claim childClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, null, 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));
|
|
}
|
|
|
|
if(this.getSchemaVersion() == 0)
|
|
{
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
//pull ALL player data from the database
|
|
statement = this.databaseConnection.createStatement();
|
|
results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;");
|
|
|
|
//make a list of changes to be made
|
|
HashMap<String, UUID> changes = new HashMap<String, UUID>();
|
|
|
|
//for each result
|
|
while(results.next())
|
|
{
|
|
//get the id
|
|
String playerName = results.getString("name");
|
|
|
|
//ignore groups
|
|
if(playerName.startsWith("$")) continue;
|
|
|
|
//try to convert player name to UUID
|
|
try
|
|
{
|
|
UUID playerID = UUIDFetcher.getUUIDOf(playerName);
|
|
|
|
//if successful, update the playerdata row by replacing the player's name with the player's UUID
|
|
if(playerID != null)
|
|
{
|
|
changes.put(playerName, playerID);
|
|
}
|
|
}
|
|
//otherwise leave it as-is. no harm done - it won't be requested by name, and this update only happens once.
|
|
catch(Exception ex){ }
|
|
}
|
|
|
|
for(String name : changes.keySet())
|
|
{
|
|
statement = this.databaseConnection.createStatement();
|
|
statement.execute("UPDATE griefprevention_playerdata SET name = '" + changes.get(name).toString() + "' WHERE name = '" + name + "';");
|
|
}
|
|
}
|
|
catch(SQLException e)
|
|
{
|
|
GriefPrevention.AddLogEntry("Unable to convert player data. Details:");
|
|
GriefPrevention.AddLogEntry(e.getMessage());
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
super.initialize();
|
|
}
|
|
|
|
@Override
|
|
synchronized void writeClaimToStorage(Claim claim) //see datastore.cs. this will ALWAYS be a top level claim
|
|
{
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
//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
|
|
synchronized private void writeClaimData(Claim claim) throws SQLException
|
|
{
|
|
String lesserCornerString = this.locationToString(claim.getLesserBoundaryCorner());
|
|
String greaterCornerString = this.locationToString(claim.getGreaterBoundaryCorner());
|
|
String owner = "";
|
|
if(claim.ownerID != null) owner = claim.ownerID.toString();
|
|
|
|
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
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
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
|
|
synchronized void deleteClaimFromSecondaryStorage(Claim claim)
|
|
{
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
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
|
|
synchronized PlayerData getPlayerDataFromStorage(UUID playerID)
|
|
{
|
|
PlayerData playerData = new PlayerData();
|
|
playerData.playerID = playerID;
|
|
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
Statement statement = this.databaseConnection.createStatement();
|
|
ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata WHERE name='" + playerID.toString() + "';");
|
|
|
|
//if there's no data for this player, create it with defaults
|
|
if(!results.next())
|
|
{
|
|
this.savePlayerData(playerID, 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 " + playerID.toString() + ". 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
|
|
synchronized public void savePlayerData(UUID playerID, PlayerData playerData)
|
|
{
|
|
//never save data for the "administrative" account. an empty string for player name indicates administrative account
|
|
if(playerID == null) return;
|
|
|
|
this.savePlayerData(playerID.toString(), playerData);
|
|
}
|
|
|
|
private void savePlayerData(String playerID, PlayerData playerData)
|
|
{
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
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='" + playerID.toString() + "';");
|
|
statement.execute("INSERT INTO griefprevention_playerdata VALUES ('" + playerID.toString() + "', '" + dateString + "', " + playerData.accruedClaimBlocks + ", " + playerData.bonusClaimBlocks + ");");
|
|
}
|
|
catch(SQLException e)
|
|
{
|
|
GriefPrevention.AddLogEntry("Unable to save data for player " + playerID.toString() + ". Details:");
|
|
GriefPrevention.AddLogEntry(e.getMessage());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
synchronized 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
|
|
synchronized void setNextClaimID(long nextID)
|
|
{
|
|
this.nextClaimID = nextID;
|
|
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
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
|
|
synchronized 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
|
|
synchronized void close()
|
|
{
|
|
if(this.databaseConnection != null)
|
|
{
|
|
try
|
|
{
|
|
if(!this.databaseConnection.isClosed())
|
|
{
|
|
this.databaseConnection.close();
|
|
}
|
|
}
|
|
catch(SQLException e){};
|
|
}
|
|
|
|
this.databaseConnection = null;
|
|
}
|
|
|
|
private void refreshDataConnection() throws SQLException
|
|
{
|
|
if(this.databaseConnection == null || this.databaseConnection.isClosed())
|
|
{
|
|
//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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected int getSchemaVersionFromStorage()
|
|
{
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
Statement statement = this.databaseConnection.createStatement();
|
|
ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_schemaversion;");
|
|
|
|
//if there's nothing yet, assume 0 and add it
|
|
if(!results.next())
|
|
{
|
|
this.setSchemaVersion(0);
|
|
return 0;
|
|
}
|
|
|
|
//otherwise return the value that's in the table
|
|
else
|
|
{
|
|
return results.getInt(0);
|
|
}
|
|
|
|
}
|
|
catch(SQLException e)
|
|
{
|
|
GriefPrevention.AddLogEntry("Unable to retrieve schema version from database. Details:");
|
|
GriefPrevention.AddLogEntry(e.getMessage());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void updateSchemaVersionInStorage(int versionToSet)
|
|
{
|
|
try
|
|
{
|
|
this.refreshDataConnection();
|
|
|
|
Statement statement = databaseConnection.createStatement();
|
|
statement.execute("DELETE FROM griefprevention_schemaversion;");
|
|
statement.execute("INSERT INTO griefprevention_schemaversion VALUES (" + versionToSet + ");");
|
|
}
|
|
catch(SQLException e)
|
|
{
|
|
GriefPrevention.AddLogEntry("Unable to set next schema version to " + versionToSet + ". Details:");
|
|
GriefPrevention.AddLogEntry(e.getMessage());
|
|
}
|
|
}
|
|
}
|