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

copyDestination[R]

where this directory should be copied to

dirByName[R]

immediate sub-directories of this directory, indexed by name

dirs[R]

immediate sub-directories of this directory

fileByName[R]

the files within this sub-directory, indexed by file name

files[R]

files within this sub-directory (as FileContent's)

name[R]

name of the sub-directory within the containing directory (or nil if this is the base directory)

pathElements[R]

path elements from base directory leading to this one

time[RW]

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.

toBeDeleted[R]

whether this directory should be deleted

Public Class Methods

new(name = nil, parentPathElements = nil) click to toggle source
# 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
readFromFile(fileName) click to toggle source

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
readMapOfHashesFromFile(fileName) click to toggle source

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

addDir(dirPath) click to toggle source

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
addFile(filePath, hash) click to toggle source

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
getContentTreeForSubDir(subDir) click to toggle source

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
getDir(dir) click to toggle source

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
getFile(file) click to toggle source

Get the named file & hash value, if it exists

# File lib/synqa.rb, line 702
def getFile(file)
  return fileByName.fetch(file, nil)
end
getPathElements(path) click to toggle source

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
markCopyOperations(destinationDir) click to toggle source

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
markDeleteOptions(sourceDir) click to toggle source

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
markSyncOperationsForDestination(destination) click to toggle source

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
markToCopy(destinationDirectory) click to toggle source

mark this directory to be copied to a destination directory

# File lib/synqa.rb, line 509
def markToCopy(destinationDirectory)
  @copyDestination = destinationDirectory
end
markToDelete() click to toggle source

mark this directory (on a remote system) to be deleted

# File lib/synqa.rb, line 514
def markToDelete
  @toBeDeleted = true
end
relativePath() click to toggle source

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
showIndented(name = "", indent = " ", currentIndent = "") click to toggle source

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
sort!() click to toggle source

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
writeLinesToFile(outFile, prefix = "") click to toggle source

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
writeToFile(fileName) click to toggle source

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