001    package net.minecraft.world.storage;
002    
003    import java.io.DataInputStream;
004    import java.io.DataOutputStream;
005    import java.io.File;
006    import java.io.FileInputStream;
007    import java.io.FileOutputStream;
008    import java.io.IOException;
009    import java.util.logging.Logger;
010    
011    import cpw.mods.fml.common.FMLCommonHandler;
012    import net.minecraft.entity.player.EntityPlayer;
013    import net.minecraft.nbt.CompressedStreamTools;
014    import net.minecraft.nbt.NBTTagCompound;
015    import net.minecraft.world.MinecraftException;
016    import net.minecraft.world.WorldProvider;
017    import net.minecraft.world.chunk.storage.IChunkLoader;
018    
019    public 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    }