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}