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}