def copyToFolderIter(destFolder, sourceNode, presaveNode=None): # Progress counts: # 0-10: deleted chunks # 10-80: new/modified chunks # 80-90: deleted files # 90-100: new/modified files if presaveNode: presaveFolder = presaveNode.worldFolder else: presaveFolder = None sourceFolder = sourceNode.worldFolder maxprogress = 100 # Remove deleted chunks for deadProgress, (cx, cz, dimName) in enumProgress(sourceNode.deadChunks, 0, 10): yield deadProgress, maxprogress, "Removing deleted chunks" if destFolder.containsChunk(cx, cz, dimName): if presaveFolder and not presaveFolder.containsChunk(cx, cz, dimName): presaveFolder.writeChunkBytes(cx, cz, dimName, destFolder.readChunkBytes(cx, cz, dimName)) destFolder.deleteChunk(cx, cz, dimName) # Write new and modified chunks dims = list(sourceFolder.listDimensions()) dimProgress = 70 / len(dims) for i, dimName in enumerate(dims): progress = 10 + i * dimProgress cPos = list(sourceFolder.chunkPositions(dimName)) for chunkProgress, (cx, cz) in enumProgress(cPos, progress, progress + dimProgress): yield chunkProgress, maxprogress, "Writing new and modified chunks" if presaveFolder and not presaveFolder.containsChunk(cx, cz, dimName): if destFolder.containsChunk(cx, cz, dimName): presaveFolder.writeChunkBytes(cx, cz, dimName, destFolder.readChunkBytes(cx, cz, dimName)) else: # new chunk presaveNode.deleteChunk(cx, cz, dimName) destFolder.writeChunkBytes(cx, cz, dimName, sourceFolder.readChunkBytes(cx, cz, dimName)) # Remove deleted files for delProgress, path in enumProgress(sourceNode.deadFiles, 80, 10): yield delProgress, maxprogress, "Removing deleted files" if destFolder.containsFile(path): if presaveFolder and not presaveFolder.containsFile(path): presaveFolder.writeFile(path, destFolder.readFile(path)) destFolder.deleteFile(path) # Write new and modified files files = list(sourceFolder.listAllFiles()) for delProgress, path in enumProgress(files, 90, 10): yield delProgress, maxprogress, "Writing new and modified files" if presaveFolder and not presaveFolder.containsFile(path): if destFolder.containsFile(path): presaveFolder.writeFile(path, destFolder.readFile(path)) else: # new file presaveNode.deleteFile(path) destFolder.writeFile(path, sourceFolder.readFile(path)) yield maxprogress, maxprogress, "Done"
def writeAllChangesIter(self, requestedRevision=None): """ Write all changes to the root world folder, preserving undo history. The world folder becomes the new head node. The previous head node is no longer valid after calling writeAllChanges. Specify a revision to only save changes up to and including that revision. :return: :rtype: """ # XXXXX wait for async writes to complete here # Progress counts: # 0-20: Orphaned chains # 20-100: History nodes maxprogress = 100 if isinstance(requestedRevision, RevisionHistoryNode): requestedIndex = self.nodes.index(requestedRevision) elif requestedRevision is None: requestedIndex = len(self.nodes) - 1 else: requestedIndex = requestedRevision orphanChainProgress = 20 if self.orphanChainIndex is not None: # Root node is orphaned - collapse orphan chain into it in reverse order orphanNodes = [] orphanChainNode = self.nodes[self.orphanChainIndex] while orphanChainNode is not self.rootNode: orphanNodes.append(orphanChainNode) orphanChainNode = orphanChainNode.parentNode for progress, orphanChainNode in enumProgress(reversed(orphanNodes), 0, 20): yield (progress, maxprogress, "Collapsing orphaned chain") copyTask = copyToFolderIter(self.rootFolder, orphanChainNode) copyTask = rescaleProgress(copyTask, progress, 20/len(orphanNodes)) for current, _, status in copyTask: yield current, maxprogress, status self.nodes[self.orphanChainIndex] = self.rootNode self.orphanChainIndex = None if requestedIndex == self.rootNodeIndex: return # nothing to do elif requestedIndex < self.rootNodeIndex: direction = -1 indexes = xrange(self.rootNodeIndex-1, requestedIndex-1, -1) else: direction = 1 indexes = xrange(self.rootNodeIndex+1, requestedIndex+1) log.info("writeAllChanges: moving %s", "forwards" if direction == 1 else "backwards") for progress, currentIndex in enumProgress(indexes, 20, 80): # Write all changes from each node into the initial folder. Save the previous # chunk and file data from the initial folder into a reverse revision. currentNode = self.nodes[currentIndex] reverseFolder = self._createRevisionFolder() reverseNode = RevisionHistoryNode(self, reverseFolder, self.nodes[currentIndex - direction]) reverseNode.differences = self.rootNode.differences self.rootNode.differences = currentNode.getChanges() copyTask = copyToFolderIter(self.rootFolder, currentNode, reverseNode) copyTask = rescaleProgress(copyTask, progress, 80 / len(indexes)) for current, _, status in copyTask: yield current, maxprogress, status # xxx look ahead one or more nodes to skip some copies reverseNode.setRevisionInfo(self.rootNode.getRevisionInfo()) reverseNode.readonly = True self.rootNode.setRevisionInfo(currentNode.getRevisionInfo()) # Replace the previousNode with the reverse node, and the currentNode with the rootNode self.nodes[currentIndex - direction] = reverseNode self.nodes[currentIndex] = self.rootNode self.rootNodeIndex = currentIndex log.info("Root node now at index %d", currentIndex) assert currentNode is not self.rootNode, "Root node appears twice in nodes!" currentNode.worldFolder.close() currentNode.invalid = True shutil.rmtree(currentNode.worldFolder.filename, ignore_errors=True)
def copyToFolderIter(destFolder, sourceNode, reversionNode=None): # Progress counts: # 0-10: deleted chunks # 10-80: new/modified chunks # 80-90: deleted files # 90-100: new/modified files if reversionNode: reversionFolder = reversionNode.worldFolder else: reversionFolder = None sourceFolder = sourceNode.worldFolder maxprogress = 100 # Remove deleted chunks for deadProgress, (cx, cz, dimName) in enumProgress(sourceNode.deadChunks, 0, 10): yield deadProgress, maxprogress, "Removing deleted chunks" if destFolder.containsChunk(cx, cz, dimName): if reversionFolder and not reversionFolder.containsChunk( cx, cz, dimName): reversionFolder.writeChunkBytes( cx, cz, dimName, destFolder.readChunkBytes(cx, cz, dimName)) destFolder.deleteChunk(cx, cz, dimName) # Write new and modified chunks dims = list(sourceFolder.listDimensions()) dimProgress = 70. / len(dims) for i, dimName in enumerate(dims): progress = 10 + i * dimProgress cPos = list(sourceFolder.chunkPositions(dimName)) for chunkProgress, (cx, cz) in enumProgress(cPos, progress, progress + dimProgress): yield chunkProgress, maxprogress, "Writing new and modified chunks" if reversionFolder and not reversionFolder.containsChunk( cx, cz, dimName): if destFolder.containsChunk(cx, cz, dimName): reversionFolder.writeChunkBytes( cx, cz, dimName, destFolder.readChunkBytes(cx, cz, dimName)) else: # new chunk reversionNode.deleteChunk(cx, cz, dimName) destFolder.writeChunkBytes( cx, cz, dimName, sourceFolder.readChunkBytes(cx, cz, dimName)) # Remove deleted files for delProgress, path in enumProgress(sourceNode.deadFiles, 80, 10): yield delProgress, maxprogress, "Removing deleted files" if destFolder.containsFile(path): if reversionFolder and not reversionFolder.containsFile(path): reversionFolder.writeFile(path, destFolder.readFile(path)) destFolder.deleteFile(path) # Write new and modified files files = list(sourceFolder.listAllFiles()) for delProgress, path in enumProgress(files, 90, 10): yield delProgress, maxprogress, "Writing new and modified files" if reversionFolder and not reversionFolder.containsFile(path): if destFolder.containsFile(path): reversionFolder.writeFile(path, destFolder.readFile(path)) else: # new file reversionNode.deleteFile(path) destFolder.writeFile(path, sourceFolder.readFile(path)) yield maxprogress, maxprogress, "Done"
def writeAllChangesIter(self, requestedRevision=None): """ Write all changes to the root world folder, preserving undo history. If a revision is requested, the state of the world at that revision will be written, otherwise, the last node in the history is used. All nodes between the root node and the requested node, inclusive, are replaced with new nodes. The old nodes are no longer valid. The root node will be placed at the position in the nodes list previously occupied by the requested node. Parameters ---------- requestedRevision: RevisionHistoryNode | int | None If given, this specifies the revision to write to the world folder, otherwise the most recent revision is written. Returns ------- progress: Iterator[(current, max, status)] Progress information for the write-changes task. """ # XXXXX wait for async writes to complete here # Progress counts: # 0-20: Orphaned chains # 20-100: History nodes maxprogress = 100 if isinstance(requestedRevision, RevisionHistoryNode): requestedIndex = self.nodes.index(requestedRevision) elif requestedRevision is None: requestedIndex = len(self.nodes) - 1 else: requestedIndex = requestedRevision if self.orphanChainIndex is not None: # Root node is orphaned - collapse orphan chain into it in reverse order orphanNodes = [] orphanChainNode = self.nodes[self.orphanChainIndex] while orphanChainNode is not self.rootNode: orphanNodes.append(orphanChainNode) orphanChainNode = orphanChainNode.parentNode # Apply each orphaned node onto the root node. for progress, orphanChainNode in enumProgress( orphanNodes[::-1], 0, 20): yield (progress, maxprogress, "Collapsing orphaned chain") copyTask = copyToFolderIter(self.rootFolder, orphanChainNode) copyTask = rescaleProgress(copyTask, progress, progress + 20. / len(orphanNodes)) for current, _, status in copyTask: yield current, maxprogress, status # Root node now replaces the orphan chain's tail in the history. # (the nodes ahead and behind of the root node should now point to this node) self.nodes[self.orphanChainIndex] = self.rootNode self.orphanChainIndex = None if requestedIndex == self.rootNodeIndex: return # nothing to do elif requestedIndex < self.rootNodeIndex: # Nodes behind the root node in the history will be re-reverted and replaced # with plain nodes direction = -1 indexes = xrange(self.rootNodeIndex - 1, requestedIndex - 1, -1) else: # Nodes ahead of the root node will be reverted and replaced with # "reversion" nodes. direction = 1 indexes = xrange(self.rootNodeIndex + 1, requestedIndex + 1) log.info("writeAllChanges: moving %s", "forwards" if direction == 1 else "backwards") for progress, currentIndex in enumProgress(indexes, 20, 100): # Write all changes from each node into the initial folder. Save the previous # chunk and file data from the initial folder into a reverse revision. currentNode = self.nodes[currentIndex] reverseFolder = self._createRevisionFolder() reverseNode = RevisionHistoryNode( self, reverseFolder, self.nodes[currentIndex - direction]) reverseNode.differences = self.rootNode.differences self.rootNode.differences = currentNode.getChanges() copyTask = copyToFolderIter(self.rootFolder, currentNode, reverseNode) copyTask = rescaleProgress(copyTask, progress, progress + 80. / len(indexes)) for current, _, status in copyTask: yield current, maxprogress, status # xxx look ahead one or more nodes to skip some copies reverseNode.setRevisionInfo(self.rootNode.getRevisionInfo()) reverseNode.readonly = True self.rootNode.setRevisionInfo(currentNode.getRevisionInfo()) # Replace the previousNode with the reverse node, and the currentNode with the rootNode self.nodes[currentIndex - direction] = reverseNode self.nodes[currentIndex] = self.rootNode self.rootNodeIndex = currentIndex log.info("Root node now at index %d", currentIndex) assert currentNode is not self.rootNode, "Root node appears twice in nodes!" currentNode.worldFolder.close() currentNode.invalid = True shutil.rmtree(currentNode.worldFolder.filename, ignore_errors=True)
def writeAllChangesIter(self, requestedRevision=None): """ Write all changes to the root world folder, preserving undo history. If a revision is requested, the state of the world at that revision will be written, otherwise, the last node in the history is used. All nodes between the root node and the requested node, inclusive, are replaced with new nodes. The old nodes are no longer valid. The root node will be placed at the position in the nodes list previously occupied by the requested node. Parameters ---------- requestedRevision: RevisionHistoryNode | int | None If given, this specifies the revision to write to the world folder, otherwise the most recent revision is written. Returns ------- progress: Iterator[(current, max, status)] Progress information for the write-changes task. """ # XXXXX wait for async writes to complete here # Progress counts: # 0-20: Orphaned chains # 20-100: History nodes maxprogress = 100 if isinstance(requestedRevision, RevisionHistoryNode): requestedIndex = self.nodes.index(requestedRevision) elif requestedRevision is None: requestedIndex = len(self.nodes) - 1 else: requestedIndex = requestedRevision if self.orphanChainIndex is not None: # Root node is orphaned - collapse orphan chain into it in reverse order orphanNodes = [] orphanChainNode = self.nodes[self.orphanChainIndex] while orphanChainNode is not self.rootNode: orphanNodes.append(orphanChainNode) orphanChainNode = orphanChainNode.parentNode # Apply each orphaned node onto the root node. for progress, orphanChainNode in enumProgress(orphanNodes[::-1], 0, 20): yield (progress, maxprogress, "Collapsing orphaned chain") copyTask = copyToFolderIter(self.rootFolder, orphanChainNode) copyTask = rescaleProgress(copyTask, progress, progress + 20./len(orphanNodes)) for current, _, status in copyTask: yield current, maxprogress, status # Root node now replaces the orphan chain's tail in the history. # (the nodes ahead and behind of the root node should now point to this node) self.nodes[self.orphanChainIndex] = self.rootNode self.orphanChainIndex = None if requestedIndex == self.rootNodeIndex: return # nothing to do elif requestedIndex < self.rootNodeIndex: # Nodes behind the root node in the history will be re-reverted and replaced # with plain nodes direction = -1 indexes = xrange(self.rootNodeIndex-1, requestedIndex-1, -1) else: # Nodes ahead of the root node will be reverted and replaced with # "reversion" nodes. direction = 1 indexes = xrange(self.rootNodeIndex+1, requestedIndex+1) log.info("writeAllChanges: moving %s", "forwards" if direction == 1 else "backwards") for progress, currentIndex in enumProgress(indexes, 20, 100): # Write all changes from each node into the initial folder. Save the previous # chunk and file data from the initial folder into a reverse revision. currentNode = self.nodes[currentIndex] reverseFolder = self._createRevisionFolder() reverseNode = RevisionHistoryNode(self, reverseFolder, self.nodes[currentIndex - direction]) reverseNode.differences = self.rootNode.differences self.rootNode.differences = currentNode.getChanges() copyTask = copyToFolderIter(self.rootFolder, currentNode, reverseNode) copyTask = rescaleProgress(copyTask, progress, progress + 80. / len(indexes)) for current, _, status in copyTask: yield current, maxprogress, status # xxx look ahead one or more nodes to skip some copies reverseNode.setRevisionInfo(self.rootNode.getRevisionInfo()) reverseNode.readonly = True self.rootNode.setRevisionInfo(currentNode.getRevisionInfo()) # Replace the previousNode with the reverse node, and the currentNode with the rootNode self.nodes[currentIndex - direction] = reverseNode self.nodes[currentIndex] = self.rootNode self.rootNodeIndex = currentIndex log.info("Root node now at index %d", currentIndex) assert currentNode is not self.rootNode, "Root node appears twice in nodes!" currentNode.worldFolder.close() currentNode.invalid = True shutil.rmtree(currentNode.worldFolder.filename, ignore_errors=True)
def writeAllChangesIter(self, requestedRevision=None): """ Write all changes to the root world folder, preserving undo history. The world folder becomes the new head node. The previous head node is no longer valid after calling writeAllChanges. Specify a revision to only save changes up to and including that revision. :return: :rtype: """ # XXXXX wait for async writes to complete here # Progress counts: # 0-20: Orphaned chains # 20-100: History nodes maxprogress = 100 if isinstance(requestedRevision, RevisionHistoryNode): requestedIndex = self.nodes.index(requestedRevision) elif requestedRevision is None: requestedIndex = len(self.nodes) - 1 else: requestedIndex = requestedRevision orphanChainProgress = 20 if self.orphanChainIndex is not None: # Root node is orphaned - collapse orphan chain into it in reverse order orphanNodes = [] orphanChainNode = self.nodes[self.orphanChainIndex] while orphanChainNode is not self.rootNode: orphanNodes.append(orphanChainNode) orphanChainNode = orphanChainNode.parentNode for progress, orphanChainNode in enumProgress( reversed(orphanNodes), 0, 20): yield (progress, maxprogress, "Collapsing orphaned chain") copyTask = copyToFolderIter(self.rootFolder, orphanChainNode) copyTask = rescaleProgress(copyTask, progress, 20 / len(orphanNodes)) for current, _, status in copyTask: yield current, maxprogress, status self.nodes[self.orphanChainIndex] = self.rootNode self.orphanChainIndex = None if requestedIndex == self.rootNodeIndex: return # nothing to do elif requestedIndex < self.rootNodeIndex: direction = -1 indexes = xrange(self.rootNodeIndex - 1, requestedIndex - 1, -1) else: direction = 1 indexes = xrange(self.rootNodeIndex + 1, requestedIndex + 1) log.info("writeAllChanges: moving %s", "forwards" if direction == 1 else "backwards") for progress, currentIndex in enumProgress(indexes, 20, 80): # Write all changes from each node into the initial folder. Save the previous # chunk and file data from the initial folder into a reverse revision. currentNode = self.nodes[currentIndex] reverseFolder = self._createRevisionFolder() reverseNode = RevisionHistoryNode( self, reverseFolder, self.nodes[currentIndex - direction]) reverseNode.differences = self.rootNode.differences self.rootNode.differences = currentNode.getChanges() copyTask = copyToFolderIter(self.rootFolder, currentNode, reverseNode) copyTask = rescaleProgress(copyTask, progress, 80 / len(indexes)) for current, _, status in copyTask: yield current, maxprogress, status # xxx look ahead one or more nodes to skip some copies reverseNode.setRevisionInfo(self.rootNode.getRevisionInfo()) reverseNode.readonly = True self.rootNode.setRevisionInfo(currentNode.getRevisionInfo()) # Replace the previousNode with the reverse node, and the currentNode with the rootNode self.nodes[currentIndex - direction] = reverseNode self.nodes[currentIndex] = self.rootNode self.rootNodeIndex = currentIndex log.info("Root node now at index %d", currentIndex) assert currentNode is not self.rootNode, "Root node appears twice in nodes!" currentNode.worldFolder.close() currentNode.invalid = True shutil.rmtree(currentNode.worldFolder.filename, ignore_errors=True)