class Synqa::ContentTree
A “content tree” consisting of a description of the contents of files and sub-directories within a base directory. The file contents are described via cryptographic hash values. Each sub-directory within a content tree is also represented as a ContentTree
.
Attributes
where this directory should be copied to
immediate sub-directories of this directory, indexed by name
immediate sub-directories of this directory
the files within this sub-directory, indexed by file name
files within this sub-directory (as FileContent's)
name of the sub-directory within the containing directory (or nil if this is the base directory)
path elements from base directory leading to this one
the UTC time (on the local system, even if this content tree represents a remote directory) that this content tree was constructed. Only set for the base directory.
whether this directory should be deleted
Public Class Methods
# File lib/synqa.rb, line 496 def initialize(name = nil, parentPathElements = nil) @name = name @pathElements = name == nil ? [] : parentPathElements + [name] @files = [] @dirs = [] @fileByName = {} @dirByName = {} @copyDestination = nil @toBeDeleted = false @time = nil end
read a content tree from a file (in format written by writeToFile)
# File lib/synqa.rb, line 640 def self.readFromFile(fileName) contentTree = ContentTree.new() puts "Reading content tree from #{fileName} ..." IO.foreach(fileName) do |line| dirLineMatch = @@dirLineRegex.match(line) if dirLineMatch dirName = dirLineMatch[1] contentTree.addDir(dirName) else fileLineMatch = @@fileLineRegex.match(line) if fileLineMatch hash = fileLineMatch[1] fileName = fileLineMatch[2] contentTree.addFile(fileName, hash) else timeLineMatch = @@timeRegex.match(line) if timeLineMatch timeString = timeLineMatch[1] contentTree.time = Time.strptime(timeString, @@dateTimeFormat) else raise "Invalid line in content tree file: #{line.inspect}" end end end end return contentTree end
read a content tree as a map of hashes, i.e. from relative file path to hash value for the file Actually returns an array of the time entry (if any) and the map of hashes
# File lib/synqa.rb, line 670 def self.readMapOfHashesFromFile(fileName) mapOfHashes = {} time = nil File.open(fileName).each_line do |line| fileLineMatch = @@fileLineRegex.match(line) if fileLineMatch hash = fileLineMatch[1] fileName = fileLineMatch[2] mapOfHashes[fileName] = hash end timeLineMatch = @@timeRegex.match(line) if timeLineMatch timeString = timeLineMatch[1] time = Time.strptime(timeString, @@dateTimeFormat) end end return [time, mapOfHashes] end
Public Instance Methods
add a sub-directory to this content tree
# File lib/synqa.rb, line 540 def addDir(dirPath) pathElements = getPathElements(dirPath) if pathElements.length > 0 pathStart = pathElements[0] restOfPath = pathElements[1..-1] getContentTreeForSubDir(pathStart).addDir(restOfPath) end end
given a relative path, add a file and hash value to this content tree
# File lib/synqa.rb, line 559 def addFile(filePath, hash) pathElements = getPathElements(filePath) if pathElements.length == 0 raise "Invalid file path: #{filePath.inspect}" end if pathElements.length == 1 fileName = pathElements[0] fileContent = FileContent.new(fileName, hash, @pathElements) files << fileContent fileByName[fileName] = fileContent else pathStart = pathElements[0] restOfPath = pathElements[1..-1] getContentTreeForSubDir(pathStart).addFile(restOfPath, hash) end end
get the content tree for a sub-directory (creating it if it doesn't yet exist)
# File lib/synqa.rb, line 529 def getContentTreeForSubDir(subDir) dirContentTree = dirByName.fetch(subDir, nil) if dirContentTree == nil dirContentTree = ContentTree.new(subDir, @pathElements) dirs << dirContentTree dirByName[subDir] = dirContentTree end return dirContentTree end
Get the named sub-directory content tree, if it exists
# File lib/synqa.rb, line 697 def getDir(dir) return dirByName.fetch(dir, nil) end
Get the named file & hash value, if it exists
# File lib/synqa.rb, line 702 def getFile(file) return fileByName.fetch(file, nil) end
convert a path string to an array of path elements (or return it as is if it's already an array)
# File lib/synqa.rb, line 524 def getPathElements(path) return path.is_a?(String) ? (path == "" ? [] : path.split("/")) : path end
Mark copy operations, given that the corresponding destination directory already exists. For files and directories that don't exist in the destination, mark them to be copied. For sub-directories that do exist, recursively mark the corresponding sub-directory copy operations.
# File lib/synqa.rb, line 709 def markCopyOperations(destinationDir) for dir in dirs destinationSubDir = destinationDir.getDir(dir.name) if destinationSubDir != nil dir.markCopyOperations(destinationSubDir) else dir.markToCopy(destinationDir) end end for file in files destinationFile = destinationDir.getFile(file.name) if destinationFile == nil or destinationFile.hash != file.hash file.markToCopy(destinationDir) end end end
Mark delete operations, given that the corresponding source directory exists. For files and directories that don't exist in the source, mark them to be deleted. For sub-directories that do exist, recursively mark the corresponding sub-directory delete operations.
# File lib/synqa.rb, line 729 def markDeleteOptions(sourceDir) for dir in dirs sourceSubDir = sourceDir.getDir(dir.name) if sourceSubDir == nil dir.markToDelete() else dir.markDeleteOptions(sourceSubDir) end end for file in files sourceFile = sourceDir.getFile(file.name) if sourceFile == nil file.markToDelete() end end end
Mark operations for this (source) content tree and the destination content tree in order to synch the destination content tree with this one
# File lib/synqa.rb, line 691 def markSyncOperationsForDestination(destination) markCopyOperations(destination) destination.markDeleteOptions(self) end
mark this directory to be copied to a destination directory
# File lib/synqa.rb, line 509 def markToCopy(destinationDirectory) @copyDestination = destinationDirectory end
mark this directory (on a remote system) to be deleted
# File lib/synqa.rb, line 514 def markToDelete @toBeDeleted = true end
the path of the directory that this content tree represents, relative to the base directory
# File lib/synqa.rb, line 519 def relativePath return @pathElements.join("/") end
pretty-print this content tree
# File lib/synqa.rb, line 580 def showIndented(name = "", indent = " ", currentIndent = "") if time != nil puts "#{currentIndent}[TIME: #{time.strftime(@@dateTimeFormat)}]" end if name != "" puts "#{currentIndent}#{name}" end if copyDestination != nil puts "#{currentIndent} [COPY to #{copyDestination.relativePath}]" end if toBeDeleted puts "#{currentIndent} [DELETE]" end nextIndent = currentIndent + indent for dir in dirs dir.showIndented("#{dir.name}/", indent = indent, currentIndent = nextIndent) end for file in files puts "#{nextIndent}#{file.name} - #{file.hash}" if file.copyDestination != nil puts "#{nextIndent} [COPY to #{file.copyDestination.relativePath}]" end if file.toBeDeleted puts "#{nextIndent} [DELETE]" end end end
recursively sort the files and sub-directories of this content tree alphabetically
# File lib/synqa.rb, line 550 def sort! dirs.sort_by! {|dir| dir.name} files.sort_by! {|file| file.name} for dir in dirs dir.sort! end end
write this content tree to an open file, indented
# File lib/synqa.rb, line 609 def writeLinesToFile(outFile, prefix = "") if time != nil outFile.puts("T #{time.strftime(@@dateTimeFormat)}\n") end for dir in dirs outFile.puts("D #{prefix}#{dir.name}\n") dir.writeLinesToFile(outFile, "#{prefix}#{dir.name}/") end for file in files outFile.puts("F #{file.hash} #{prefix}#{file.name}\n") end end
write this content tree to a file (in a format which readFromFile can read back in)
# File lib/synqa.rb, line 623 def writeToFile(fileName) puts "Writing content tree to file #{fileName} ..." File.open(fileName, "w") do |outFile| writeLinesToFile(outFile) end end