class Dockly::TarDiff

Constants

HEADER_UNPACK_FORMAT

Tar header format for a ustar tar

PAX_FILE_FORMAT_REGEX

Attributes

base[R]
base_enum[R]
output[R]
target[R]
target_enum[R]

Public Class Methods

new(base, target, output) click to toggle source
# File lib/dockly/tar_diff.rb, line 12
def initialize(base, target, output)
  @base, @target, @output = base, target, output

  @base_enum = to_enum(:read_header, base)
  @target_enum = to_enum(:read_header, target)
end

Public Instance Methods

process() click to toggle source
# File lib/dockly/tar_diff.rb, line 54
def process
  debug "Started processing tar diff"
  target_data = nil
  base_data = nil
  loop do
    begin

      target_header, target_name,  \
      target_prefix, target_mtime,  \
      target_typeflag,               \
      target_size, target_remainder,  \
      target_empty                     = target_enum.peek
    rescue StopIteration
      debug "Finished target file"
      break
    end

    if target_empty
      debug "End of target file/Empty"
      break
    end

    begin
      _, base_name, base_prefix, base_mtime, base_typeflag, base_size, _, base_empty = base_enum.peek
    rescue StopIteration
      target_data ||= target.read(target_size)
      write_tar_section(target_header, target_data, target_remainder)
      target_data = nil
      target_enum.next
      next
    end

    if base_empty
      target_data ||= target.read(target_size)
      write_tar_section(target_header, target_data, target_remainder)
      target_data = nil
      target_enum.next
      next
    end

    target_full_name = File.join(target_prefix, target_name)
    base_full_name = File.join(base_prefix, base_name)

    target_full_name = target_full_name[1..-1] if target_full_name[0] == '/'
    base_full_name = base_full_name[1..-1] if base_full_name[0] == '/'

    if target_typeflag == 'x'
      target_file = File.basename(target_full_name)
      target_dir  = File.dirname(File.dirname(target_full_name))
      target_full_name = File.join(target_dir, target_file)
    end

    if base_typeflag == 'x'
      base_file = File.basename(base_full_name)
      base_dir  = File.dirname(File.dirname(base_full_name))
      base_full_name = File.join(base_dir, base_file)
    end

    # Remove the PaxHeader.PID from the file
    # Format: /base/directory/PaxHeader.1234/file.ext
    # After:  /base/directory/file.ext
    if (target_typeflag == 'x' && base_typeflag == 'x')
      target_data = target.read(target_size)
      base_data = base.read(base_size)

      if target_match = target_data.match(PAX_FILE_FORMAT_REGEX) && \
          base_match = base_data.match(PAX_FILE_FORMAT_REGEX)
        target_full_name = target_match[1]
        base_full_name   = base_match[1]
      end
    end

    if (target_full_name < base_full_name)
      target_data ||= target.read(target_size)
      write_tar_section(target_header, target_data, target_remainder)
      target_data = nil
      target_enum.next
    elsif (base_full_name < target_full_name)
      base.read(base_size) unless base_data
      base_data = nil
      base_enum.next
    elsif (target_mtime != base_mtime) || (target_size != base_size)
      target_data ||= target.read(target_size)
      write_tar_section(target_header, target_data, target_remainder)
      target_data = nil
      target_enum.next
    else
      target.read(target_size) unless target_data
      target_data = nil
      target_enum.next
      base.read(base_size) unless base_data
      base_data = nil
      base_enum.next
    end
  end
end
quick_write(size) click to toggle source
# File lib/dockly/tar_diff.rb, line 25
def quick_write(size)
  while size > 0
    bread = target.read([size, 4096].min)
    output.write(bread)
    size -= bread.to_s.size
  end
end
read_header(io) { |data, name, prefix, mtime, typeflag, size, remainder, empty| ... } click to toggle source
# File lib/dockly/tar_diff.rb, line 33
def read_header(io)
  loop do
    return if io.eof?
    # Tar header is 512 bytes large
    data = io.read(512)
    fields = data.unpack(HEADER_UNPACK_FORMAT)
    name = fields[0]
    size = fields[4].oct
    mtime = fields[5].oct
    typeflag = fields[7]
    prefix = fields[15]

    empty = (data == "\0" * 512)
    remainder = (512 - (size % 512)) % 512

    yield data, name, prefix, mtime, typeflag, size, remainder, empty

    io.read(remainder)
  end
end
write_tar_section(header, data, remainder) click to toggle source
# File lib/dockly/tar_diff.rb, line 19
def write_tar_section(header, data, remainder)
  output.write(header)
  output.write(data)
  output.write("\0" * remainder)
end