class Zip::File

def add(entry, src_path, &continue_on_exists_proc)

Convenience method for adding the contents of a file to the archive
def add(entry, src_path, &continue_on_exists_proc)
  continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
  check_entry_exists(entry, continue_on_exists_proc, 'add')
  new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s)
  new_entry.gather_fileinfo_from_srcpath(src_path)
  new_entry.dirty = true
  @entry_set << new_entry
end

def add_buffer

Same as #open. But outputs data to a buffer instead of a file
def add_buffer
  io = ::StringIO.new('')
  zf = ::Zip::File.new(io, true, true)
  yield zf
  zf.write_buffer(io)
end

def add_stored(entry, src_path, &continue_on_exists_proc)

in Stored format (uncompressed)
Convenience method for adding the contents of a file to the archive
def add_stored(entry, src_path, &continue_on_exists_proc)
  entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED)
  add(entry, src_path, &continue_on_exists_proc)
end

def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)

def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
  continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
  return unless @entry_set.include?(entry_name)
  if continue_on_exists_proc.call
    remove get_entry(entry_name)
  else
    raise ::Zip::EntryExistsError,
          proc_name + " failed. Entry #{entry_name} already exists"
  end
end

def check_file(path)

def check_file(path)
  raise Errno::ENOENT, path unless ::File.readable?(path)
end

def close

Closes the zip file committing any changes that has been made.
def close
  commit
end

def commit

the zip archive.
Commits changes that has been made since the previous commit to
def commit
  return if name.kind_of?(StringIO) || !commit_required?
  on_success_replace do |tmp_file|
    ::Zip::OutputStream.open(tmp_file) do |zos|
      @entry_set.each do |e|
        e.write_to_zip_output_stream(zos)
        e.dirty = false
        e.clean_up
      end
      zos.comment = comment
    end
    true
  end
  initialize(name)
end

def commit_required?

the previous commit
Returns true if any changes has been made to this archive since
def commit_required?
  @entry_set.each do |e|
    return true if e.dirty
  end
  @comment != @stored_comment || @entry_set != @stored_entries || @create
end

def directory?(new_entry, src_path)

def directory?(new_entry, src_path)
  path_is_directory = ::File.directory?(src_path)
  if new_entry.directory? && !path_is_directory
    raise ArgumentError,
          "entry name '#{new_entry}' indicates directory entry, but " \
              "'#{src_path}' is not a directory"
  elsif !new_entry.directory? && path_is_directory
    new_entry.name += '/'
  end
  new_entry.directory? && path_is_directory
end

def extract(entry, dest_path, &block)

Extracts entry to file dest_path.
def extract(entry, dest_path, &block)
  block ||= proc { ::Zip.on_exists_proc }
  found_entry = get_entry(entry)
  found_entry.extract(dest_path, &block)
end

def find_entry(entry_name)

no entry is found. See also get_entry
Searches for entry with the specified name. Returns nil if
def find_entry(entry_name)
  selected_entry = @entry_set.find_entry(entry_name)
  return if selected_entry.nil?
  selected_entry.restore_ownership   = @restore_ownership
  selected_entry.restore_permissions = @restore_permissions
  selected_entry.restore_times       = @restore_times
  selected_entry
end

def foreach(zip_file_name, &block)

central directory).
local entry headers (which contain the same information as the
whereas ZipInputStream jumps through the entire archive accessing the
through the entries in the central directory structure in the archive
than using a ZipInputStream since this methods simply iterates
Iterates over the contents of the ZipFile. This is more efficient
def foreach(zip_file_name, &block)
  ::Zip::File.open(zip_file_name) do |zip_file|
    zip_file.each(&block)
  end
end

def get_entry(entry)

if no entry is found.
Searches for an entry just as find_entry, but throws Errno::ENOENT
def get_entry(entry)
  selected_entry = find_entry(entry)
  raise Errno::ENOENT, entry if selected_entry.nil?
  selected_entry
end

def get_input_stream(entry, &a_proc)

closed afterwards just as with ruby's builtin File.open method.
the stream object is passed to the block and the stream is automatically
Returns an input stream to the specified entry. If a block is passed
def get_input_stream(entry, &a_proc)
  get_entry(entry).get_input_stream(&a_proc)
end

def get_output_stream(entry, permission_int = nil, comment = nil,

File.open method.
the stream is automatically closed afterwards just as with ruby's builtin
specified. If a block is passed the stream object is passed to the block and
of Zip::Entry, a new Zip::Entry will be initialized using the arguments
Returns an output stream to the specified entry. If entry is not an instance
def get_output_stream(entry, permission_int = nil, comment = nil,
                      extra = nil, compressed_size = nil, crc = nil,
                      compression_method = nil, size = nil, time = nil,
                      &a_proc)
  new_entry =
    if entry.kind_of?(Entry)
      entry
    else
      Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, size, time)
    end
  if new_entry.directory?
    raise ArgumentError,
          "cannot open stream to directory entry - '#{new_entry}'"
  end
  new_entry.unix_perms = permission_int
  zip_streamable_entry = StreamableStream.new(new_entry)
  @entry_set << zip_streamable_entry
  zip_streamable_entry.get_output_stream(&a_proc)
end

def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)

def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
  unless partial_zip_file_name.nil?
    partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
                                              partial_zip_file_name + ::File.extname(zip_file_name))
  end
  partial_zip_file_name ||= zip_file_name
  partial_zip_file_name
end

def get_segment_count_for_split(zip_file_size, segment_size)

def get_segment_count_for_split(zip_file_size, segment_size)
  (zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1)
end

def get_segment_size_for_split(segment_size)

def get_segment_size_for_split(segment_size)
  if MIN_SEGMENT_SIZE > segment_size
    MIN_SEGMENT_SIZE
  elsif MAX_SEGMENT_SIZE < segment_size
    MAX_SEGMENT_SIZE
  else
    segment_size
  end
end

def glob(*args, &block)

Searches for entries given a glob
def glob(*args, &block)
  @entry_set.glob(*args, &block)
end

def initialize(path_or_io, create = false, buffer = false, options = {})

a new archive if it doesn't exist already.
Opens a zip archive. Pass true as the second parameter to create
def initialize(path_or_io, create = false, buffer = false, options = {})
  super()
  options  = DEFAULT_OPTIONS.merge(options)
  @name    = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
  @comment = ''
  @create  = create ? true : false # allow any truthy value to mean true
  if ::File.size?(@name.to_s)
    # There is a file, which exists, that is associated with this zip.
    @create = false
    @file_permissions = ::File.stat(@name).mode
    if buffer
      read_from_stream(path_or_io)
    else
      ::File.open(@name, 'rb') do |f|
        read_from_stream(f)
      end
    end
  elsif buffer && path_or_io.size > 0
    # This zip is probably a non-empty StringIO.
    read_from_stream(path_or_io)
  elsif @create
    # This zip is completely new/empty and is to be created.
    @entry_set = EntrySet.new
  elsif ::File.zero?(@name)
    # A file exists, but it is empty.
    raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
  else
    # Everything is wrong.
    raise Error, "File #{@name} not found"
  end
  @stored_entries      = @entry_set.dup
  @stored_comment      = @comment
  @restore_ownership   = options[:restore_ownership]
  @restore_permissions = options[:restore_permissions]
  @restore_times       = options[:restore_times]
end

def mkdir(entry_name, permission = 0o755)

Creates a directory
def mkdir(entry_name, permission = 0o755)
  raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
  entry_name = entry_name.dup.to_s
  entry_name << '/' unless entry_name.end_with?('/')
  @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
end

def on_success_replace

def on_success_replace
  dirname, basename = ::File.split(name)
  ::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
    begin
      if yield tmp_filename
        ::File.rename(tmp_filename, name)
        ::File.chmod(@file_permissions, name) unless @create
      end
    ensure
      ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
    end
  end
end

def open(file_name, create = false, options = {})

ruby's builtin File::open method.
to the block and is automatically closed afterwards, just as with
Similar to ::new. If a block is passed the Zip::File object is passed
def open(file_name, create = false, options = {})
  zf = ::Zip::File.new(file_name, create, false, options)
  return zf unless block_given?
  begin
    yield zf
  ensure
    zf.close
  end
end

def open_buffer(io, options = {})

downloaded zip archive without first saving it to disk.)
(This can be used to extract data from a
stream, and outputs data to a buffer.
Like #open, but reads zip archive contents from a String or open IO
def open_buffer(io, options = {})
  unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
    raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
  end
  io = ::StringIO.new(io) if io.kind_of?(::String)
  # https://github.com/rubyzip/rubyzip/issues/119
  io.binmode if io.respond_to?(:binmode)
  zf = ::Zip::File.new(io, true, true, options)
  return zf unless block_given?
  yield zf
  begin
    zf.write_buffer(io)
  rescue IOError => e
    raise unless e.message == 'not opened for writing'
  end
end

def put_split_signature(szip_file, segment_size)

def put_split_signature(szip_file, segment_size)
  signature_packed = [SPLIT_SIGNATURE].pack('V')
  szip_file << signature_packed
  segment_size - signature_packed.size
end

def read(entry)

Returns a string containing the contents of the specified entry
def read(entry)
  get_input_stream(entry, &:read)
end

def remove(entry)

Removes the specified entry.
def remove(entry)
  @entry_set.delete(get_entry(entry))
end

def rename(entry, new_name, &continue_on_exists_proc)

Renames the specified entry.
def rename(entry, new_name, &continue_on_exists_proc)
  found_entry = get_entry(entry)
  check_entry_exists(new_name, continue_on_exists_proc, 'rename')
  @entry_set.delete(found_entry)
  found_entry.name = new_name
  @entry_set << found_entry
end

def replace(entry, src_path)

the file system).
Replaces the specified entry with the contents of src_path (from
def replace(entry, src_path)
  check_file(src_path)
  remove(entry)
  add(entry, src_path)
end

def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)


TODO: Make the code more understandable
def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
  ssegment_size  = zip_file_size - zip_file.pos
  ssegment_size  = segment_size if ssegment_size > segment_size
  szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
  ::File.open(szip_file_name, 'wb') do |szip_file|
    if szip_file_index == 1
      ssegment_size = put_split_signature(szip_file, segment_size)
    end
    chunk_bytes = 0
    until ssegment_size == chunk_bytes || zip_file.eof?
      segment_bytes_left = ssegment_size - chunk_bytes
      buffer_size        = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
      chunk              = zip_file.read(buffer_size)
      chunk_bytes += buffer_size
      szip_file << chunk
      # Info for track splitting
      yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
    end
  end
end

def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)

Splits an archive into parts with segment size
def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
  raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
  raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
  zip_file_size = ::File.size(zip_file_name)
  segment_size  = get_segment_size_for_split(segment_size)
  return if zip_file_size <= segment_size
  segment_count = get_segment_count_for_split(zip_file_size, segment_size)
  # Checking for correct zip structure
  ::Zip::File.open(zip_file_name) {}
  partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
  szip_file_index       = 0
  ::File.open(zip_file_name, 'rb') do |zip_file|
    until zip_file.eof?
      szip_file_index += 1
      save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
    end
  end
  ::File.delete(zip_file_name) if delete_zip_file
  szip_file_index
end

def to_s

Returns the name of the zip archive
def to_s
  @name
end

def write_buffer(io = ::StringIO.new(''))

Write buffer write changes to buffer and return
def write_buffer(io = ::StringIO.new(''))
  ::Zip::OutputStream.write_buffer(io) do |zos|
    @entry_set.each { |e| e.write_to_zip_output_stream(zos) }
    zos.comment = comment
  end
end