    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 world.
    081     */
    082    public File getWorldDirectory()
    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 getWorldDirectoryName()
    367    {
    368        return this.saveDirectoryName;
    369    }
    370}