lib/fs/MiqFsUtil.rb
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