2012-07-13 02:44:22 +00:00
/ *
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.io.* ;
2015-01-06 02:41:11 +00:00
import java.nio.charset.Charset ;
2012-07-13 02:44:22 +00:00
import java.text.DateFormat ;
import java.text.ParseException ;
import java.text.SimpleDateFormat ;
import java.util.* ;
2015-09-12 21:17:00 +00:00
import java.util.concurrent.ConcurrentHashMap ;
2014-09-12 01:53:39 +00:00
import java.util.regex.Matcher ;
2015-09-12 21:17:00 +00:00
2012-07-13 02:44:22 +00:00
import org.bukkit.* ;
2015-09-12 21:17:00 +00:00
import org.bukkit.configuration.InvalidConfigurationException ;
import org.bukkit.configuration.file.YamlConfiguration ;
2012-07-13 02:44:22 +00:00
2015-01-06 02:41:11 +00:00
import com.google.common.io.Files ;
2012-07-13 02:44:22 +00:00
//manages data stored in the file system
public class FlatFileDataStore extends DataStore
{
private final static String claimDataFolderPath = dataLayerFolderPath + File . separator + " ClaimData " ;
private final static String nextClaimIdFilePath = claimDataFolderPath + File . separator + " _nextClaimID " ;
2014-09-22 20:46:13 +00:00
private final static String schemaVersionFilePath = dataLayerFolderPath + File . separator + " _schemaVersion " ;
2014-09-12 01:53:39 +00:00
2012-07-13 02:44:22 +00:00
static boolean hasData ( )
{
File claimsDataFolder = new File ( claimDataFolderPath ) ;
2015-05-16 20:00:55 +00:00
return claimsDataFolder . exists ( ) ;
2012-07-13 02:44:22 +00:00
}
//initialization!
2012-07-17 03:17:12 +00:00
FlatFileDataStore ( ) throws Exception
2012-07-13 02:44:22 +00:00
{
2012-07-17 03:17:12 +00:00
this . initialize ( ) ;
2012-07-13 02:44:22 +00:00
}
@Override
2012-07-17 03:17:12 +00:00
void initialize ( ) throws Exception
2012-07-13 02:44:22 +00:00
{
//ensure data folders exist
2014-09-22 20:46:13 +00:00
boolean newDataStore = false ;
File playerDataFolder = new File ( playerDataFolderPath ) ;
File claimDataFolder = new File ( claimDataFolderPath ) ;
if ( ! playerDataFolder . exists ( ) | | ! claimDataFolder . exists ( ) )
{
newDataStore = true ;
playerDataFolder . mkdirs ( ) ;
claimDataFolder . mkdirs ( ) ;
}
//if there's no data yet, then anything written will use the schema implemented by this code
if ( newDataStore )
{
this . setSchemaVersion ( DataStore . latestSchemaVersion ) ;
}
2012-07-13 02:44:22 +00:00
//load group data into memory
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 the rest, which are player data files.
if ( ! file . getName ( ) . startsWith ( " $ " ) ) continue ;
String groupName = file . getName ( ) . substring ( 1 ) ;
if ( groupName = = null | | groupName . isEmpty ( ) ) continue ; //defensive coding, avoid unlikely cases
BufferedReader inStream = null ;
try
{
inStream = new BufferedReader ( new FileReader ( file . getAbsolutePath ( ) ) ) ;
String line = inStream . readLine ( ) ;
int groupBonusBlocks = Integer . parseInt ( line ) ;
this . permissionToBonusBlocksMap . put ( groupName , groupBonusBlocks ) ;
}
catch ( Exception e )
{
2015-05-17 02:17:11 +00:00
StringWriter errors = new StringWriter ( ) ;
e . printStackTrace ( new PrintWriter ( errors ) ) ;
GriefPrevention . AddLogEntry ( errors . toString ( ) , CustomLogEntryTypes . Exception ) ;
2012-07-13 02:44:22 +00:00
}
try
{
if ( inStream ! = null ) inStream . close ( ) ;
}
catch ( IOException exception ) { }
}
//load next claim number from file
File nextClaimIdFile = new File ( nextClaimIdFilePath ) ;
if ( nextClaimIdFile . exists ( ) )
{
BufferedReader inStream = null ;
try
{
inStream = new BufferedReader ( new FileReader ( nextClaimIdFile . getAbsolutePath ( ) ) ) ;
//read the id
String line = inStream . readLine ( ) ;
//try to parse into a long value
this . nextClaimID = Long . parseLong ( line ) ;
}
catch ( Exception e ) { }
try
{
if ( inStream ! = null ) inStream . close ( ) ;
}
catch ( IOException exception ) { }
}
2014-10-09 02:32:43 +00:00
//if converting up from schema version 0, rename player data files using UUIDs instead of player names
//get a list of all the files in the claims data folder
if ( this . getSchemaVersion ( ) = = 0 )
{
files = playerDataFolder . listFiles ( ) ;
ArrayList < String > namesToConvert = new ArrayList < String > ( ) ;
for ( File playerFile : files )
{
2014-10-10 23:06:53 +00:00
namesToConvert . add ( playerFile . getName ( ) ) ;
2014-10-09 02:32:43 +00:00
}
//resolve and cache as many as possible through various means
try
{
UUIDFetcher fetcher = new UUIDFetcher ( namesToConvert ) ;
fetcher . call ( ) ;
}
catch ( Exception e )
{
GriefPrevention . AddLogEntry ( " Failed to resolve a batch of names to UUIDs. Details: " + e . getMessage ( ) ) ;
e . printStackTrace ( ) ;
}
//rename files
for ( File playerFile : files )
{
String currentFilename = playerFile . getName ( ) ;
2015-01-20 01:57:52 +00:00
//if corrected casing and a record already exists using the correct casing, skip this one
String correctedCasing = UUIDFetcher . correctedNames . get ( currentFilename ) ;
2015-01-31 21:05:51 +00:00
if ( correctedCasing ! = null & & ! currentFilename . equals ( correctedCasing ) )
2015-01-20 01:57:52 +00:00
{
File correctedCasingFile = new File ( playerDataFolder . getPath ( ) + File . separator + correctedCasing ) ;
if ( correctedCasingFile . exists ( ) )
{
continue ;
}
}
2014-10-09 02:32:43 +00:00
//try to convert player name to UUID
UUID playerID = null ;
try
{
playerID = UUIDFetcher . getUUIDOf ( currentFilename ) ;
//if successful, rename the file using the UUID
if ( playerID ! = null )
{
playerFile . renameTo ( new File ( playerDataFolder , playerID . toString ( ) ) ) ;
}
}
2014-10-09 22:05:50 +00:00
catch ( Exception ex ) { }
2014-10-09 02:32:43 +00:00
}
}
2012-07-13 02:44:22 +00:00
//load claims data into memory
//get a list of all the files in the claims data folder
files = claimDataFolder . listFiles ( ) ;
2015-09-12 21:17:00 +00:00
if ( this . getSchemaVersion ( ) < = 1 )
{
this . loadClaimData_Legacy ( files ) ;
}
else
{
this . loadClaimData ( files ) ;
2012-07-13 02:44:22 +00:00
}
2012-07-17 03:17:12 +00:00
super . initialize ( ) ;
2012-07-13 02:44:22 +00:00
}
2015-09-12 21:17:00 +00:00
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 ;
2015-12-10 22:41:55 +00:00
String lesserCornerString = " " ;
2015-09-12 21:17:00 +00:00
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
2015-12-10 22:41:55 +00:00
lesserCornerString = line ;
Location lesserBoundaryCorner = this . locationFromString ( lesserCornerString , validWorlds ) ;
2015-09-12 21:17:00 +00:00
//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 )
{
2015-12-23 15:56:14 +00:00
if ( e . getMessage ( ) ! = null & & e . getMessage ( ) . contains ( " World not found " ) )
2015-09-12 21:17:00 +00:00
{
2015-12-15 21:43:38 +00:00
GriefPrevention . AddLogEntry ( " Failed to load a claim " + files [ i ] . getName ( ) + " because its world isn't loaded (yet?). Please delete the claim file or contact the GriefPrevention developer with information about which plugin(s) you're using to load or create worlds. " + lesserCornerString ) ;
2015-09-12 21:17:00 +00:00
inStream . close ( ) ;
2015-12-10 22:41:55 +00:00
2015-09-12 21:17:00 +00:00
}
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 ( ) ;
}
2012-07-13 02:44:22 +00:00
@Override
2012-08-08 04:46:31 +00:00
synchronized void writeClaimToStorage ( Claim claim )
2012-07-13 02:44:22 +00:00
{
String claimID = String . valueOf ( claim . id ) ;
2015-09-12 21:17:00 +00:00
String yaml = this . getYamlForClaim ( claim ) ;
2012-07-13 02:44:22 +00:00
try
{
//open the claim's file
2015-09-12 21:17:00 +00:00
File claimFile = new File ( claimDataFolderPath + File . separator + claimID + " .yml " ) ;
2012-07-13 02:44:22 +00:00
claimFile . createNewFile ( ) ;
2015-09-12 21:17:00 +00:00
Files . write ( yaml . getBytes ( " UTF-8 " ) , claimFile ) ;
2012-07-13 02:44:22 +00:00
}
//if any problem, log it
catch ( Exception e )
{
2015-05-17 02:17:11 +00:00
StringWriter errors = new StringWriter ( ) ;
e . printStackTrace ( new PrintWriter ( errors ) ) ;
GriefPrevention . AddLogEntry ( claimID + " " + errors . toString ( ) , CustomLogEntryTypes . Exception ) ;
2012-07-13 02:44:22 +00:00
}
}
2015-09-12 21:17:00 +00:00
//deletes a claim from the file system
2012-07-13 02:44:22 +00:00
@Override
2012-08-08 04:46:31 +00:00
synchronized void deleteClaimFromSecondaryStorage ( Claim claim )
2012-07-13 02:44:22 +00:00
{
String claimID = String . valueOf ( claim . id ) ;
//remove from disk
2015-09-12 21:17:00 +00:00
File claimFile = new File ( claimDataFolderPath + File . separator + claimID + " .yml " ) ;
2012-07-13 02:44:22 +00:00
if ( claimFile . exists ( ) & & ! claimFile . delete ( ) )
{
GriefPrevention . AddLogEntry ( " Error: Unable to delete claim file \" " + claimFile . getAbsolutePath ( ) + " \" . " ) ;
}
}
@Override
2014-09-22 20:46:13 +00:00
synchronized PlayerData getPlayerDataFromStorage ( UUID playerID )
2012-07-13 02:44:22 +00:00
{
2014-09-22 20:46:13 +00:00
File playerFile = new File ( playerDataFolderPath + File . separator + playerID . toString ( ) ) ;
2012-07-13 02:44:22 +00:00
PlayerData playerData = new PlayerData ( ) ;
2014-09-22 20:46:13 +00:00
playerData . playerID = playerID ;
2012-07-13 02:44:22 +00:00
2014-10-11 04:27:12 +00:00
//if it exists as a file, read the file
if ( playerFile . exists ( ) )
2012-07-13 02:44:22 +00:00
{
2015-01-06 02:41:11 +00:00
boolean needRetry = false ;
int retriesRemaining = 5 ;
Exception latestException = null ;
do
{
try
{
needRetry = false ;
//read the file content and immediately close it
List < String > lines = Files . readLines ( playerFile , Charset . forName ( " UTF-8 " ) ) ;
Iterator < String > iterator = lines . iterator ( ) ;
//first line is last login timestamp
String lastLoginTimestampString = iterator . next ( ) ;
//convert that to a date and store it
DateFormat dateFormat = new SimpleDateFormat ( " yyyy.MM.dd.HH.mm.ss " ) ;
try
{
playerData . setLastLogin ( dateFormat . parse ( lastLoginTimestampString ) ) ;
}
catch ( ParseException parseException )
{
GriefPrevention . AddLogEntry ( " Unable to load last login for \" " + playerFile . getName ( ) + " \" . " ) ;
playerData . setLastLogin ( null ) ;
}
//second line is accrued claim blocks
String accruedBlocksString = iterator . next ( ) ;
//convert that to a number and store it
playerData . setAccruedClaimBlocks ( Integer . parseInt ( accruedBlocksString ) ) ;
//third line is any bonus claim blocks granted by administrators
String bonusBlocksString = iterator . next ( ) ;
//convert that to a number and store it
playerData . setBonusClaimBlocks ( Integer . parseInt ( bonusBlocksString ) ) ;
//fourth line is a double-semicolon-delimited list of claims, which is currently ignored
//String claimsString = inStream.readLine();
//iterator.next();
}
//if there's any problem with the file's content, retry up to 5 times with 5 milliseconds between
catch ( Exception e )
{
latestException = e ;
needRetry = true ;
retriesRemaining - - ;
}
try
{
if ( needRetry ) Thread . sleep ( 5 ) ;
}
catch ( InterruptedException exception ) { }
} while ( needRetry & & retriesRemaining > = 0 ) ;
2012-07-13 02:44:22 +00:00
2015-01-06 02:41:11 +00:00
//if last attempt failed, log information about the problem
if ( needRetry )
2012-07-13 02:44:22 +00:00
{
2015-05-17 02:17:11 +00:00
StringWriter errors = new StringWriter ( ) ;
latestException . printStackTrace ( new PrintWriter ( errors ) ) ;
GriefPrevention . AddLogEntry ( playerID + " " + errors . toString ( ) , CustomLogEntryTypes . Exception ) ;
2012-07-13 02:44:22 +00:00
}
}
return playerData ;
}
//saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them
@Override
2015-05-16 20:00:55 +00:00
public void overrideSavePlayerData ( UUID playerID , PlayerData playerData )
2012-07-13 02:44:22 +00:00
{
2014-09-22 20:46:13 +00:00
//never save data for the "administrative" account. null for claim owner ID indicates administrative account
if ( playerID = = null ) return ;
2012-07-13 02:44:22 +00:00
2015-01-06 02:41:11 +00:00
StringBuilder fileContent = new StringBuilder ( ) ;
2012-07-13 02:44:22 +00:00
try
{
//first line is last login timestamp
2014-10-31 02:24:07 +00:00
if ( playerData . getLastLogin ( ) = = null ) playerData . setLastLogin ( new Date ( ) ) ;
2012-07-13 02:44:22 +00:00
DateFormat dateFormat = new SimpleDateFormat ( " yyyy.MM.dd.HH.mm.ss " ) ;
2015-01-06 02:41:11 +00:00
fileContent . append ( dateFormat . format ( playerData . getLastLogin ( ) ) ) ;
fileContent . append ( " \ n " ) ;
2012-07-13 02:44:22 +00:00
//second line is accrued claim blocks
2015-01-06 02:41:11 +00:00
fileContent . append ( String . valueOf ( playerData . getAccruedClaimBlocks ( ) ) ) ;
fileContent . append ( " \ n " ) ;
2012-07-13 02:44:22 +00:00
//third line is bonus claim blocks
2015-01-06 02:41:11 +00:00
fileContent . append ( String . valueOf ( playerData . getBonusClaimBlocks ( ) ) ) ;
fileContent . append ( " \ n " ) ;
2012-07-13 02:44:22 +00:00
2015-08-21 19:30:47 +00:00
//fourth line is blank
2015-01-06 02:41:11 +00:00
fileContent . append ( " \ n " ) ;
//write data to file
File playerDataFile = new File ( playerDataFolderPath + File . separator + playerID . toString ( ) ) ;
Files . write ( fileContent . toString ( ) . getBytes ( " UTF-8 " ) , playerDataFile ) ;
2012-07-13 02:44:22 +00:00
}
//if any problem, log it
catch ( Exception e )
{
2014-09-22 20:46:13 +00:00
GriefPrevention . AddLogEntry ( " GriefPrevention: Unexpected exception saving data for player \" " + playerID . toString ( ) + " \" : " + e . getMessage ( ) ) ;
2015-05-16 20:00:55 +00:00
e . printStackTrace ( ) ;
2012-07-13 02:44:22 +00:00
}
}
@Override
2012-08-08 04:46:31 +00:00
synchronized void incrementNextClaimID ( )
2012-07-13 02:44:22 +00:00
{
//increment in memory
this . nextClaimID + + ;
BufferedWriter outStream = null ;
try
{
//open the file and write the new value
File nextClaimIdFile = new File ( nextClaimIdFilePath ) ;
nextClaimIdFile . createNewFile ( ) ;
outStream = new BufferedWriter ( new FileWriter ( nextClaimIdFile ) ) ;
outStream . write ( String . valueOf ( this . nextClaimID ) ) ;
}
//if any problem, log it
catch ( Exception e )
{
GriefPrevention . AddLogEntry ( " Unexpected exception saving next claim ID: " + e . getMessage ( ) ) ;
2014-09-24 02:48:26 +00:00
e . printStackTrace ( ) ;
2012-07-13 02:44:22 +00:00
}
//close the file
try
{
if ( outStream ! = null ) outStream . close ( ) ;
}
catch ( IOException exception ) { }
}
//grants a group (players with a specific permission) bonus claim blocks as long as they're still members of the group
@Override
2012-08-08 04:46:31 +00:00
synchronized void saveGroupBonusBlocks ( String groupName , int currentValue )
2012-07-13 02:44:22 +00:00
{
//write changes to file to ensure they don't get lost
BufferedWriter outStream = null ;
try
{
//open the group's file
File groupDataFile = new File ( playerDataFolderPath + File . separator + " $ " + groupName ) ;
groupDataFile . createNewFile ( ) ;
outStream = new BufferedWriter ( new FileWriter ( groupDataFile ) ) ;
//first line is number of bonus blocks
outStream . write ( String . valueOf ( currentValue ) ) ;
outStream . newLine ( ) ;
}
//if any problem, log it
catch ( Exception e )
{
GriefPrevention . AddLogEntry ( " Unexpected exception saving data for group \" " + groupName + " \" : " + e . getMessage ( ) ) ;
}
try
{
//close the file
if ( outStream ! = null )
{
outStream . close ( ) ;
}
}
catch ( IOException exception ) { }
}
2012-07-17 03:17:12 +00:00
2012-08-08 04:46:31 +00:00
synchronized void migrateData ( DatabaseDataStore databaseStore )
2012-07-17 03:17:12 +00:00
{
//migrate claims
for ( int i = 0 ; i < this . claims . size ( ) ; i + + )
{
Claim claim = this . claims . get ( i ) ;
2014-10-05 02:48:03 +00:00
databaseStore . addClaim ( claim , true ) ;
2015-09-12 21:17:00 +00:00
for ( Claim child : claim . children )
{
databaseStore . addClaim ( child , true ) ;
}
2012-07-17 03:17:12 +00:00
}
//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 ;
2015-05-22 02:24:55 +00:00
//ignore special files
2014-09-22 20:46:13 +00:00
if ( file . getName ( ) . startsWith ( " _ " ) ) continue ;
2015-05-22 02:24:55 +00:00
if ( file . getName ( ) . endsWith ( " .ignore " ) ) continue ;
2014-09-22 20:46:13 +00:00
UUID playerID = UUID . fromString ( file . getName ( ) ) ;
databaseStore . savePlayerData ( playerID , this . getPlayerData ( playerID ) ) ;
this . clearCachedPlayerData ( playerID ) ;
2012-07-17 03:17:12 +00:00
}
//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
2012-08-08 04:46:31 +00:00
synchronized void close ( ) { }
2014-09-22 20:46:13 +00:00
@Override
int getSchemaVersionFromStorage ( )
{
File schemaVersionFile = new File ( schemaVersionFilePath ) ;
if ( schemaVersionFile . exists ( ) )
{
BufferedReader inStream = null ;
int schemaVersion = 0 ;
try
{
inStream = new BufferedReader ( new FileReader ( schemaVersionFile . getAbsolutePath ( ) ) ) ;
//read the version number
String line = inStream . readLine ( ) ;
//try to parse into an int value
schemaVersion = Integer . parseInt ( line ) ;
}
catch ( Exception e ) { }
try
{
if ( inStream ! = null ) inStream . close ( ) ;
}
catch ( IOException exception ) { }
return schemaVersion ;
}
else
{
this . updateSchemaVersionInStorage ( 0 ) ;
return 0 ;
}
}
@Override
void updateSchemaVersionInStorage ( int versionToSet )
{
BufferedWriter outStream = null ;
try
{
//open the file and write the new value
File schemaVersionFile = new File ( schemaVersionFilePath ) ;
schemaVersionFile . createNewFile ( ) ;
outStream = new BufferedWriter ( new FileWriter ( schemaVersionFile ) ) ;
outStream . write ( String . valueOf ( versionToSet ) ) ;
}
//if any problem, log it
catch ( Exception e )
{
GriefPrevention . AddLogEntry ( " Unexpected exception saving schema version: " + e . getMessage ( ) ) ;
}
//close the file
try
{
if ( outStream ! = null ) outStream . close ( ) ;
}
catch ( IOException exception ) { }
}
2012-07-13 02:44:22 +00:00
}