diff --git a/infiniteworld.py b/infiniteworld.py index 7f666cf..ad7576d 100644 --- a/infiniteworld.py +++ b/infiniteworld.py @@ -4,6 +4,7 @@ Created on Jul 22, 2011 @author: Rio ''' +import copy from datetime import datetime import itertools from logging import getLogger @@ -971,13 +972,17 @@ class AnvilWorldFolder(object): def readChunk(self, cx, cz): if not self.containsChunk(cx, cz): raise ChunkNotPresent((cx, cz)) - + return self.getRegionForChunk(cx, cz).readChunk(cx, cz) def saveChunk(self, cx, cz, data): regionFile = self.getRegionForChunk(cx, cz) regionFile.saveChunk(cx, cz, data) + def copyChunkFrom(self, worldFolder, cx, cz): + fromRF = worldFolder.getRegionForChunk(cx, cz) + rf = self.getRegionForChunk(cx, cz) + rf.copyChunkFrom(fromRF, cx, cz) class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel): @@ -1329,6 +1334,45 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel): self.preloadChunkPositions() return self._allChunks.__iter__() + def copyChunkFrom(self, world, cx, cz): + """ + Copy a chunk from world into the same chunk position in self. + """ + assert isinstance(world, MCInfdevOldLevel) + + destChunk = self._loadedChunks.get((cx, cz)) + sourceChunk = world._loadedChunks.get((cx, cz)) + + if sourceChunk: + if destChunk: + # Both chunks loaded. Use block copy. + destChunk.copyBlocksFrom(sourceChunk, destChunk.bounds, destChunk.bounds.origin) + return + else: + # Only source chunk loaded. Discard destination chunk and save source chunk in its place. + self._loadedChunkData.pop((cx, cz), None) + self.unsavedWorkFolder.saveChunk(cx, cz, sourceChunk.savedTagData()) + return + else: + if destChunk: + # Only destination chunk loaded. Use block copy. + destChunk.copyBlocksFrom(world.getChunk(cx, cz), destChunk.bounds, destChunk.bounds.origin) + else: + # Neither chunk loaded. Copy via world folders. + self._loadedChunkData.pop((cx, cz), None) + + # If the source chunk is dirty, write it to the work folder. + chunkData = world._loadedChunkData.pop((cx, cz), None) + if chunkData and chunkData.dirty: + data = chunkData.savedTagData() + world.unsavedWorkFolder.saveChunk(cx, cz, data) + + if world.unsavedWorkFolder.containsChunk(cx, cz): + sourceFolder = world.unsavedWorkFolder + else: + sourceFolder = world.worldFolder + + self.unsavedWorkFolder.copyChunkFrom(sourceFolder, cx, cz) def _getChunkBytes(self, cx, cz): try: diff --git a/regionfile.py b/regionfile.py index 1451b41..dce3635 100644 --- a/regionfile.py +++ b/regionfile.py @@ -167,7 +167,7 @@ class MCRegionFile(object): log.info("Repair complete. Removed {0} chunks, recovered {1} chunks, net {2}".format(deleted, recovered, recovered - deleted)) - def readChunk(self, cx, cz): + def _readChunk(self, cx, cz): cx &= 0x1f cz &= 0x1f offset = self.getOffset(cx, cz) @@ -191,7 +191,10 @@ class MCRegionFile(object): length = struct.unpack_from(">I", data)[0] format = struct.unpack_from("B", data, 4)[0] data = data[5:length + 5] + return data, format + def readChunk(self, cx, cz): + data, format = self._readChunk(cx, cz) if format == self.VERSION_GZIP: return nbt.gunzip(data) if format == self.VERSION_DEFLATE: @@ -199,14 +202,28 @@ class MCRegionFile(object): raise IOError("Unknown compress format: {0}".format(format)) + def copyChunkFrom(self, regionFile, cx, cz): + """ + Silently fails if regionFile does not contain the requested chunk. + """ + try: + data, format = regionFile._readChunk(cx, cz) + self._saveChunk(cx, cz, data, format) + except ChunkNotPresent: + pass + def saveChunk(self, cx, cz, uncompressedData): + data = deflate(uncompressedData) + self._saveChunk(cx, cz, data, self.VERSION_DEFLATE) + + def _saveChunk(self, cx, cz, data, format): cx &= 0x1f cz &= 0x1f offset = self.getOffset(cx, cz) sectorNumber = offset >> 8 sectorsAllocated = offset & 0xff - format = self.VERSION_DEFLATE - data = deflate(uncompressedData) + + sectorsNeeded = (len(data) + self.CHUNK_HEADER_SIZE) / self.SECTOR_BYTES + 1 if sectorsNeeded >= 256: diff --git a/test/anvil_test.py b/test/anvil_test.py index 1404365..f7b48f7 100644 --- a/test/anvil_test.py +++ b/test/anvil_test.py @@ -45,6 +45,16 @@ class TestAnvilLevel(unittest.TestCase): level.deleteChunk(*ch) level.createChunksInBox(BoundingBox((0, 0, 0), (32, 0, 32))) + def testCopyChunks(self): + level = self.anvilLevel.level + temppath = mktemp("AnvilCreate") + newLevel = MCInfdevOldLevel(filename=temppath, create=True) + for cx, cz in level.allChunks: + newLevel.copyChunkFrom(level, cx, cz) + + newLevel.close() + shutil.rmtree(temppath) + def testCopyConvertBlocks(self): indevlevel = self.indevLevel.level level = self.anvilLevel.level