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