Added: Infinite worlds can now be opened read-only. This doesn't acquire the session lock.

This commit is contained in:
David Vierra 2012-11-30 22:41:41 -10:00
parent 4d1d96863b
commit a9fdcee89e
2 changed files with 35 additions and 18 deletions

View File

@ -987,7 +987,7 @@ class AnvilWorldFolder(object):
class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
def __init__(self, filename=None, create=False, random_seed=None, last_played=None):
def __init__(self, filename=None, create=False, random_seed=None, last_played=None, readonly=False):
"""
Load an Alpha level from the given filename. It can point to either
a level.dat or a folder containing one. If create is True, it will
@ -1004,6 +1004,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.playerTagCache = {}
self.players = []
assert not (create and readonly)
if os.path.basename(filename) in ("level.dat", "level.dat_old"):
filename = os.path.dirname(filename)
@ -1020,16 +1021,18 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.worldFolder = AnvilWorldFolder(filename)
self.filename = self.worldFolder.getFilePath("level.dat")
self.acquireSessionLock()
self.readonly = readonly
if not readonly:
self.acquireSessionLock()
workFolderPath = self.worldFolder.getFolderPath("##MCEDIT.TEMP##")
if os.path.exists(workFolderPath):
# xxxxxxx Opening a world a second time deletes the first world's work folder and crashes when the first
# world tries to read a modified chunk from the work folder. This mainly happens when importing a world
# into itself after modifying it.
shutil.rmtree(workFolderPath, True)
workFolderPath = self.worldFolder.getFolderPath("##MCEDIT.TEMP##")
if os.path.exists(workFolderPath):
# xxxxxxx Opening a world a second time deletes the first world's work folder and crashes when the first
# world tries to read a modified chunk from the work folder. This mainly happens when importing a world
# into itself after modifying it.
shutil.rmtree(workFolderPath, True)
self.unsavedWorkFolder = AnvilWorldFolder(workFolderPath)
self.unsavedWorkFolder = AnvilWorldFolder(workFolderPath)
# maps (cx, cz) pairs to AnvilChunk
self._loadedChunks = weakref.WeakValueDictionary()
@ -1090,6 +1093,9 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
def checkSessionLock(self):
if self.readonly:
raise SessionLockLost, "World is opened read only."
lockfile = self.worldFolder.getFilePath("session.lock")
try:
(lock, ) = struct.unpack(">q", file(lockfile, "rb").read())
@ -1120,6 +1126,9 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self._create(self.filename, random_seed, last_played)
def saveInPlace(self):
if self.readonly:
raise IOError, "World is opened read only."
self.checkSessionLock()
for level in self.dimensions.itervalues():
@ -1158,7 +1167,8 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
Unload all chunks and close all open filehandles.
"""
self.worldFolder.closeRegions()
self.unsavedWorkFolder.closeRegions()
if not self.readonly:
self.unsavedWorkFolder.closeRegions()
self._allChunks = None
self._loadedChunks.clear()
@ -1299,7 +1309,8 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
def preloadChunkPositions(self):
log.info(u"Scanning for regions...")
self._allChunks = self.worldFolder.listChunks()
self._allChunks.update(self.unsavedWorkFolder.listChunks())
if not self.readonly:
self._allChunks.update(self.unsavedWorkFolder.listChunks())
self._allChunks.update(self._loadedChunkData.iterkeys())
def getRegionForChunk(self, cx, cz):
@ -1363,6 +1374,8 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
Copy a chunk from world into the same chunk position in self.
"""
assert isinstance(world, MCInfdevOldLevel)
if self.readonly:
raise IOError, "World is opened read only."
self.checkSessionLock()
destChunk = self._loadedChunks.get((cx, cz))
@ -1405,9 +1418,9 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.unsavedWorkFolder.copyChunkFrom(sourceFolder, cx, cz)
def _getChunkBytes(self, cx, cz):
try:
if not self.readonly and self.unsavedWorkFolder.containsChunk(cx, cz):
return self.unsavedWorkFolder.readChunk(cx, cz)
except ChunkNotPresent:
else:
return self.worldFolder.readChunk(cx, cz)
def _getChunkData(self, cx, cz):
@ -1423,20 +1436,22 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
except Exception, e:
raise ChunkMalformed, "Chunk {0} had an error: {1!r}".format((cx, cz), e), sys.exc_info()[2]
if self.unsavedWorkFolder.containsChunk(cx, cz):
if not self.readonly and self.unsavedWorkFolder.containsChunk(cx, cz):
chunkData.dirty = True
self._storeLoadedChunkData(chunkData)
return chunkData
def _storeLoadedChunkData(self, chunkData):
self.checkSessionLock()
if len(self._loadedChunkData) > self.loadedChunkLimit:
# Try to find a chunk to unload. The chunk must not be in _loadedChunks, which contains only chunks that
# are in use by another object. If the chunk is dirty, save it to the temporary folder.
if not self.readonly:
self.checkSessionLock()
for (ocx, ocz), oldChunkData in self._loadedChunkData.items():
if (ocx, ocz) not in self._loadedChunks:
if oldChunkData.dirty:
if oldChunkData.dirty and not self.readonly:
data = oldChunkData.savedTagData()
self.unsavedWorkFolder.saveChunk(ocx, ocz, data)
@ -1755,6 +1770,8 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
playerTag['Rotation'] = nbt.TAG_List([nbt.TAG_Float(0), nbt.TAG_Float(0)])
if playerName != "Player":
if self.readonly:
raise IOError, "World is opened read only."
self.checkSessionLock()
playerTag.save(self.getPlayerPath(playerName))

View File

@ -189,7 +189,7 @@ class LoadingError(RuntimeError):
pass
def fromFile(filename, loadInfinite=True):
def fromFile(filename, loadInfinite=True, readonly=False):
''' The preferred method for loading Minecraft levels of any type.
pass False to loadInfinite if you'd rather not load infdev levels.
'''
@ -212,7 +212,7 @@ def fromFile(filename, loadInfinite=True):
if MCInfdevOldLevel._isLevel(filename):
log.info(u"Detected Infdev level.dat")
if loadInfinite:
return MCInfdevOldLevel(filename=filename)
return MCInfdevOldLevel(filename=filename, readonly=readonly)
else:
raise ValueError("Asked to load {0} which is an infinite level, loadInfinite was False".format(os.path.basename(filename)))