ManageIQ/manageiq-smartstate

View on GitHub
lib/fs/MiqFsUtil.rb

Summary

Maintainability
F
3 days
Test Coverage
require 'find'
require 'fileutils'
require 'yaml'
require 'ostruct'
require 'zlib'

class MiqFsUtil
  attr_accessor :verbose, :fromFs, :toFs

  def initialize(fromFs, toFs, us = nil)
    setUpdateSpec(us)
    @fromFs = fromFs
    @toFs = toFs

    @verbose = false
  end

  def updateSpec=(us)
    setUpdateSpec(us)
  end

  def setUpdateSpec(us)
    if us
      @csa = us
      @csa = YAML.load_file(us) if @csa.kind_of? String
      @csa = [@csa] if @csa.kind_of? Hash
      raise "Invalid collection spec" unless @csa.kind_of? Array
    else
      @csa = nil
    end
  end

  #
  # Load the update spec from a file in the fromFs.
  #
  def loadUpdateSpec(path)
    @fromFs.fileOpen(path) { |fo| @csa = YAML.load(fo.read) }
  end

  def update
    raise "MiqFsUpdate.update: no current update spec" unless @csa
    @csa.each { |cs| doUpdate(OpenStruct.new(cs)) }
  end

  def dumpSpec(specFile)
    raise "MiqFsUpdate.dumpSpec: no current update spec" unless @csa
    YAML.dump(@csa, File.open(specFile, "w"))
  end

  #
  # Copy files and directories from fromFs to the toFs.
  #
  # FILE -> FILE
  # FILE -> DIR
  # DIR  -> DIR (recursive = true)
  #
  def copy(from, to, recursive = false)
    allTargets = []
    from = [from] unless from.kind_of?(Array)
    from.each { |t| allTargets.concat(dirGlob(t)) }

    raise "copy: no source files matched" if allTargets.length == 0
    if allTargets.length > 1 || recursive
      raise "copy: destination directory does not exist" unless @toFs.fileExists?(to)
      raise "copy: destination must be a directory for multi-file copy" unless @toFs.fileDirectory?(to)
    end

    allTargets.each do |f|
      owd = @fromFs.pwd
      @fromFs.chdir(File.dirname(f))
      f = File.basename(f)

      #
      # Copy plain files.
      #
      if @fromFs.fileFile?(f)
        if @toFs.fileDirectory?(to)
          tf = File.join(to, File.basename(f))
        else
          tf = to
        end
        copySingle(f, tf)
        next
      end

      #
      # If the recursive flag is not set, skip directories.
      #
      next unless recursive

      #
      # Recursively copy directory sub-tree.
      #
      @fromFs.chdir(f)
      td = File.join(to, f)
      @toFs.dirMkdir(td) unless @toFs.fileExists?(td)
      @fromFs.findEach('.') do |ff|
        tf = File.join(td, ff)
        if @fromFs.fileDirectory?(ff)
          @toFs.dirMkdir(tf)
        elsif @fromFs.fileFile?(ff)
          copySingle(ff, tf)
        end
      end # findEach
      @fromFs.chdir(owd)
    end # allTargets.each
  end

  private

  def log_puts(str = "")
    if $log
      $log.info str
    else
      puts str
    end
  end

  GLOB_CHARS = '*?[{'
  def isGlob?(str)
    str.count(GLOB_CHARS) != 0
  end

  def dirGlob(glb, *flags, &block)
    return([glb]) unless isGlob?(glb)

    if glb[0, 1] == '/'
      dir = '/'
      glb = glb[1..-1]
    else
      dir = @fromFs.pwd
    end

    matches = doGlob(glb.split('/'), dir, flags)
    return(matches) unless block_given?

    matches.each do |e|
      block.call(e)
    end
    (false)
  end

  def doGlob(glbArr, dir, flags)
    return [] if !glbArr || glbArr.length == 0

    retArr = []
    glb = glbArr[0]

    dirForeach(dir) do |e|
      if flags.length == 0
        match = File.fnmatch(glb, e)
      else
        match = File.fnmatch(glb, e, flags)
      end
      if match
        if glbArr.length == 1
          retArr << File.join(dir, e)
        else
          next unless @fromFs.fileDirectory?(nf = File.join(dir, e))
          retArr.concat(doGlob(glbArr[1..-1], nf, flags))
        end
      end
    end
    (retArr)
  end

  def copySingle(ff, tf)
    if @verbose
      log_puts "Copying: #{ff}"
      log_puts "     to: #{tf}"
    end
    @fromFs.fileOpen(ff) do |ffo|
      tfo = @toFs.fileOpen(tf, "wb")
      while (buf = ffo.read(1024))
        tfo.write(buf)
      end
      tfo.close
    end
  end

  def doUpdate(cs)
    #
    # The directory where the collection will be created.
    #
    destDir = cs.todir

    #
    # Save the current directory.
    #
    fowd = @fromFs.pwd
    towd = @toFs.pwd

    begin
      #
      # The directory relative to which the files will be collected.
      #
      @fromFs.chdir(cs.basedir)

      #
      # Loop through the files and directories that are to be included in the collection.
      #
      cs.include.each do |i|
        raise "File: #{i} does not exist" unless @fromFs.fileExists? i
        #
        # If this is a plain file, then include it in the collection.
        #
        unless @fromFs.fileDirectory? i
          toFile = File.join(destDir, i)
          makePath(File.dirname(toFile))
          #
          # If the file path matches an encrypt RE and doesn't
          # match a noencrypt RE, then encrypt the contents of
          # the file before copying it to the collection.
          #
          if cs.encrypt && cs.encrypt.detect { |e| i =~ e }
            if !cs.noencrypt || !cs.noencrypt.detect { |ne| i =~ ne }
              compressFile(i, toFile)
              next
            end
          end
          #
          # If the file path matches an compress RE and doesn't
          # match a nocompress RE, then compress the contents of
          # the file before copying it to the collection.
          #
          if cs.compress && cs.compress.detect { |e| i =~ e }
            if !cs.nocompress || !cs.nocompress.detect { |ne| i =~ ne }
              compressFile(i, toFile)
              next
            end
          end
          copyFile(i, toFile)
          next
        end

        #
        # If this is a directory, then recursively copy its contents
        # to the collection directory.
        #
        @fromFs.findEach(i) do |path|
          #
          # Prune directories that match an exclude RE.
          #
          if @fromFs.fileDirectory? path
            @fromFs.findEachPrune if cs.exclude && cs.exclude.detect { |e| path =~ e }
            next
          end
          #
          # Skip files that match an exclude RE.
          #
          next if cs.exclude && cs.exclude.detect { |e| path =~ e }
          toFile = File.join(destDir, path)
          makePath(File.dirname(toFile))
          #
          # If the file path matches an encrypt RE and doesn't
          # match a noencrypt RE, then encrypt the contents of
          # the file before copying it to the collection.
          #
          if cs.encrypt && cs.encrypt.detect { |e| path =~ e }
            if !cs.noencrypt || !cs.noencrypt.detect { |ne| path =~ ne }
              compressFile(path, toFile)
              next
            end
          end
          #
          # If the file path matches an compress RE and doesn't
          # match a nocompress RE, then compress the contents of
          # the file before copying it to the collection.
          #
          if cs.compress && cs.compress.detect { |e| path =~ e }
            if !cs.nocompress || !cs.nocompress.detect { |ne| path =~ ne }
              compressFile(path, toFile)
              next
            end
          end
          copyFile(path, toFile)
        end
      end if cs.include

      #
      # Remove files from the destination fs.
      #
      @toFs.chdir(destDir)
      cs.remove.each do |r|
        if r.kind_of? Regexp
          #
          # If the entry is a RE, then remove all files and directories in
          # the destination directory that match the RE>
          #
          @toFs.findEach(".") do |p|
            next unless r.match(p)

            log_puts "\tRemoving: #{p}" if @verbose
            if !@toFs.fileDirectory? p
              @toFs.fileDelete(p)
            else
              @toFs.rmBranch(p)
              @toFs.findEachPrune
            end
          end
        else
          #
          # If the entry is a string, then it should be the path to the file
          # or directory to be removed.
          #
          unless @toFs.exists? r
            log_puts "Remove file: #{i} does not exist" if @verbose
            next
          end

          log_puts "\tRemoving: #{r}" if @verbose
          if !@toFs.fileDirectory? r
            @toFs.fileDelete(r)
          else
            @toFs.rmBranch(r)
          end
        end
      end if cs.remove
    ensure
      @fromFs.chdir(fowd)
      @toFs.chdir(towd)
    end
  end

  def makePath(path)
    return if @toFs.fileExists? path
    parentDir = @toFs.fileDirname(path)
    makePath(parentDir) unless @toFs.fileExists? parentDir
    @toFs.dirMkdir(path)
  end

  def copyFile(src, dest)
    if @fromFs.respond_to?(:hasTag?) && @fromFs.hasTag?(src, "compressed")
      decompressFile(src, dest)
      return
    end

    if @verbose
      log_puts "\t    COPY: #{src}"
      log_puts "\t      TO: #{dest}"
    end
    @fromFs.fileOpen(src) do |ffo|
      tfo = @toFs.fileOpen(dest, "wb")
      while (buf = ffo.read(4096))
        tfo.write(buf, buf.length)
      end
      tfo.close
    end
  end

  def compressFile(src, dest)
    if @verbose
      log_puts "\tCOMPRESS: #{src}"
      log_puts "\t      TO: #{dest}"
    end
    @fromFs.fileOpen(src) do |ffo|
      tfo = @toFs.fileOpen(dest, "wb")
      zipper = Zlib::Deflate.new
      while (buf = ffo.read(4096))
        zipper << buf
      end
      tfo.write(zipper.deflate(nil, Zlib::FINISH))
      tfo.addTag("compressed") if tfo.respond_to?(:addTag)
      tfo.close
    end
  end

  def decompressFile(src, dest)
    if @verbose
      log_puts "\tDECOMPRESS: #{src}"
      log_puts "\t      TO: #{dest}"
    end
    @fromFs.fileOpen(src) do |ffo|
      tfo = @toFs.fileOpen(dest, "wb")
      unzipper = Zlib::Inflate.new
      while (buf = ffo.read(4096))
        unzipper << buf
      end
      tfo.write(unzipper.inflate(nil))
      tfo.close
    end
  end
end # class MiqFsUpdate