001package net.minecraft.world.storage; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.File; 006import java.io.FileInputStream; 007import java.io.FileOutputStream; 008import java.io.IOException; 009 010import cpw.mods.fml.common.FMLCommonHandler; 011import net.minecraft.entity.player.EntityPlayer; 012import net.minecraft.nbt.CompressedStreamTools; 013import net.minecraft.nbt.NBTTagCompound; 014import net.minecraft.server.MinecraftServer; 015import net.minecraft.world.MinecraftException; 016import net.minecraft.world.WorldProvider; 017import net.minecraft.world.chunk.storage.IChunkLoader; 018 019public class SaveHandler implements ISaveHandler, IPlayerFileData 020{ 021 /** The directory in which to save world data. */ 022 private final File worldDirectory; 023 024 /** The directory in which to save player data. */ 025 private final File playersDirectory; 026 private final File mapDataDir; 027 028 /** 029 * The time in milliseconds when this field was initialized. Stored in the session lock file. 030 */ 031 private final long initializationTime = System.currentTimeMillis(); 032 033 /** The directory name of the world */ 034 private final String saveDirectoryName; 035 036 public SaveHandler(File par1File, String par2Str, boolean par3) 037 { 038 this.worldDirectory = new File(par1File, par2Str); 039 this.worldDirectory.mkdirs(); 040 this.playersDirectory = new File(this.worldDirectory, "players"); 041 this.mapDataDir = new File(this.worldDirectory, "data"); 042 this.mapDataDir.mkdirs(); 043 this.saveDirectoryName = par2Str; 044 045 if (par3) 046 { 047 this.playersDirectory.mkdirs(); 048 } 049 050 this.setSessionLock(); 051 } 052 053 /** 054 * Creates a session lock file for this process 055 */ 056 private void setSessionLock() 057 { 058 try 059 { 060 File file1 = new File(this.worldDirectory, "session.lock"); 061 DataOutputStream dataoutputstream = new DataOutputStream(new FileOutputStream(file1)); 062 063 try 064 { 065 dataoutputstream.writeLong(this.initializationTime); 066 } 067 finally 068 { 069 dataoutputstream.close(); 070 } 071 } 072 catch (IOException ioexception) 073 { 074 ioexception.printStackTrace(); 075 throw new RuntimeException("Failed to check session lock, aborting"); 076 } 077 } 078 079 /** 080 * gets the File object corresponding to the base directory of this save (saves/404 for a save called 404 etc) 081 */ 082 public File getSaveDirectory() 083 { 084 return this.worldDirectory; 085 } 086 087 /** 088 * Checks the session lock to prevent save collisions 089 */ 090 public void checkSessionLock() throws MinecraftException 091 { 092 try 093 { 094 File file1 = new File(this.worldDirectory, "session.lock"); 095 DataInputStream datainputstream = new DataInputStream(new FileInputStream(file1)); 096 097 try 098 { 099 if (datainputstream.readLong() != this.initializationTime) 100 { 101 throw new MinecraftException("The save is being accessed from another location, aborting"); 102 } 103 } 104 finally 105 { 106 datainputstream.close(); 107 } 108 } 109 catch (IOException ioexception) 110 { 111 throw new MinecraftException("Failed to check session lock, aborting"); 112 } 113 } 114 115 /** 116 * Returns the chunk loader with the provided world provider 117 */ 118 public IChunkLoader getChunkLoader(WorldProvider par1WorldProvider) 119 { 120 throw new RuntimeException("Old Chunk Storage is no longer supported."); 121 } 122 123 /** 124 * Loads and returns the world info 125 */ 126 public WorldInfo loadWorldInfo() 127 { 128 File file1 = new File(this.worldDirectory, "level.dat"); 129 NBTTagCompound nbttagcompound; 130 NBTTagCompound nbttagcompound1; 131 132 WorldInfo worldInfo = null; 133 134 if (file1.exists()) 135 { 136 try 137 { 138 nbttagcompound = CompressedStreamTools.readCompressed(new FileInputStream(file1)); 139 nbttagcompound1 = nbttagcompound.getCompoundTag("Data"); 140 worldInfo = new WorldInfo(nbttagcompound1); 141 FMLCommonHandler.instance().handleWorldDataLoad(this, worldInfo, nbttagcompound); 142 return worldInfo; 143 } 144 catch (Exception exception) 145 { 146 if (FMLCommonHandler.instance().shouldServerBeKilledQuietly()) 147 { 148 throw (RuntimeException)exception; 149 } 150 exception.printStackTrace(); 151 } 152 } 153 154 file1 = new File(this.worldDirectory, "level.dat_old"); 155 156 if (file1.exists()) 157 { 158 try 159 { 160 nbttagcompound = CompressedStreamTools.readCompressed(new FileInputStream(file1)); 161 nbttagcompound1 = nbttagcompound.getCompoundTag("Data"); 162 worldInfo = new WorldInfo(nbttagcompound1); 163 FMLCommonHandler.instance().handleWorldDataLoad(this, worldInfo, nbttagcompound); 164 return worldInfo; 165 } 166 catch (Exception exception1) 167 { 168 exception1.printStackTrace(); 169 } 170 } 171 172 return null; 173 } 174 175 /** 176 * Saves the given World Info with the given NBTTagCompound as the Player. 177 */ 178 public void saveWorldInfoWithPlayer(WorldInfo par1WorldInfo, NBTTagCompound par2NBTTagCompound) 179 { 180 NBTTagCompound nbttagcompound1 = par1WorldInfo.cloneNBTCompound(par2NBTTagCompound); 181 NBTTagCompound nbttagcompound2 = new NBTTagCompound(); 182 nbttagcompound2.setTag("Data", nbttagcompound1); 183 184 FMLCommonHandler.instance().handleWorldDataSave(this, par1WorldInfo, nbttagcompound2); 185 186 try 187 { 188 File file1 = new File(this.worldDirectory, "level.dat_new"); 189 File file2 = new File(this.worldDirectory, "level.dat_old"); 190 File file3 = new File(this.worldDirectory, "level.dat"); 191 CompressedStreamTools.writeCompressed(nbttagcompound2, new FileOutputStream(file1)); 192 193 if (file2.exists()) 194 { 195 file2.delete(); 196 } 197 198 file3.renameTo(file2); 199 200 if (file3.exists()) 201 { 202 file3.delete(); 203 } 204 205 file1.renameTo(file3); 206 207 if (file1.exists()) 208 { 209 file1.delete(); 210 } 211 } 212 catch (Exception exception) 213 { 214 exception.printStackTrace(); 215 } 216 } 217 218 /** 219 * Saves the passed in world info. 220 */ 221 public void saveWorldInfo(WorldInfo par1WorldInfo) 222 { 223 NBTTagCompound nbttagcompound = par1WorldInfo.getNBTTagCompound(); 224 NBTTagCompound nbttagcompound1 = new NBTTagCompound(); 225 nbttagcompound1.setTag("Data", nbttagcompound); 226 227 FMLCommonHandler.instance().handleWorldDataSave(this, par1WorldInfo, nbttagcompound1); 228 229 try 230 { 231 File file1 = new File(this.worldDirectory, "level.dat_new"); 232 File file2 = new File(this.worldDirectory, "level.dat_old"); 233 File file3 = new File(this.worldDirectory, "level.dat"); 234 CompressedStreamTools.writeCompressed(nbttagcompound1, new FileOutputStream(file1)); 235 236 if (file2.exists()) 237 { 238 file2.delete(); 239 } 240 241 file3.renameTo(file2); 242 243 if (file3.exists()) 244 { 245 file3.delete(); 246 } 247 248 file1.renameTo(file3); 249 250 if (file1.exists()) 251 { 252 file1.delete(); 253 } 254 } 255 catch (Exception exception) 256 { 257 exception.printStackTrace(); 258 } 259 } 260 261 /** 262 * Writes the player data to disk from the specified PlayerEntityMP. 263 */ 264 public void writePlayerData(EntityPlayer par1EntityPlayer) 265 { 266 try 267 { 268 NBTTagCompound nbttagcompound = new NBTTagCompound(); 269 par1EntityPlayer.writeToNBT(nbttagcompound); 270 File file1 = new File(this.playersDirectory, par1EntityPlayer.username + ".dat.tmp"); 271 File file2 = new File(this.playersDirectory, par1EntityPlayer.username + ".dat"); 272 CompressedStreamTools.writeCompressed(nbttagcompound, new FileOutputStream(file1)); 273 274 if (file2.exists()) 275 { 276 file2.delete(); 277 } 278 279 file1.renameTo(file2); 280 } 281 catch (Exception exception) 282 { 283 MinecraftServer.getServer().getLogAgent().logWarning("Failed to save player data for " + par1EntityPlayer.username); 284 } 285 } 286 287 /** 288 * Reads the player data from disk into the specified PlayerEntityMP. 289 */ 290 public NBTTagCompound readPlayerData(EntityPlayer par1EntityPlayer) 291 { 292 NBTTagCompound nbttagcompound = this.getPlayerData(par1EntityPlayer.username); 293 294 if (nbttagcompound != null) 295 { 296 par1EntityPlayer.readFromNBT(nbttagcompound); 297 } 298 299 return nbttagcompound; 300 } 301 302 /** 303 * Gets the player data for the given playername as a NBTTagCompound. 304 */ 305 public NBTTagCompound getPlayerData(String par1Str) 306 { 307 try 308 { 309 File file1 = new File(this.playersDirectory, par1Str + ".dat"); 310 311 if (file1.exists()) 312 { 313 return CompressedStreamTools.readCompressed(new FileInputStream(file1)); 314 } 315 } 316 catch (Exception exception) 317 { 318 MinecraftServer.getServer().getLogAgent().logWarning("Failed to load player data for " + par1Str); 319 } 320 321 return null; 322 } 323 324 /** 325 * returns null if no saveHandler is relevent (eg. SMP) 326 */ 327 public IPlayerFileData getSaveHandler() 328 { 329 return this; 330 } 331 332 /** 333 * Returns an array of usernames for which player.dat exists for. 334 */ 335 public String[] getAvailablePlayerDat() 336 { 337 String[] astring = this.playersDirectory.list(); 338 339 for (int i = 0; i < astring.length; ++i) 340 { 341 if (astring[i].endsWith(".dat")) 342 { 343 astring[i] = astring[i].substring(0, astring[i].length() - 4); 344 } 345 } 346 347 return astring; 348 } 349 350 /** 351 * Called to flush all changes to disk, waiting for them to complete. 352 */ 353 public void flush() {} 354 355 /** 356 * Gets the file location of the given map 357 */ 358 public File getMapFileFromName(String par1Str) 359 { 360 return new File(this.mapDataDir, par1Str + ".dat"); 361 } 362 363 /** 364 * Returns the name of the directory where world information is saved. 365 */ 366 public String getSaveDirectoryName() 367 { 368 return this.saveDirectoryName; 369 } 370}