146 lines
4.2 KiB
Ruby
146 lines
4.2 KiB
Ruby
|
module Zip
|
||
|
# ZipInputStream is the basic class for reading zip entries in a
|
||
|
# zip file. It is possible to create a ZipInputStream object directly,
|
||
|
# passing the zip file name to the constructor, but more often than not
|
||
|
# the ZipInputStream will be obtained from a ZipFile (perhaps using the
|
||
|
# ZipFileSystem interface) object for a particular entry in the zip
|
||
|
# archive.
|
||
|
#
|
||
|
# A ZipInputStream inherits IOExtras::AbstractInputStream in order
|
||
|
# to provide an IO-like interface for reading from a single zip
|
||
|
# entry. Beyond methods for mimicking an IO-object it contains
|
||
|
# the method get_next_entry for iterating through the entries of
|
||
|
# an archive. get_next_entry returns a ZipEntry object that describes
|
||
|
# the zip entry the ZipInputStream is currently reading from.
|
||
|
#
|
||
|
# Example that creates a zip archive with ZipOutputStream and reads it
|
||
|
# back again with a ZipInputStream.
|
||
|
#
|
||
|
# require 'zip/zip'
|
||
|
#
|
||
|
# Zip::ZipOutputStream::open("my.zip") {
|
||
|
# |io|
|
||
|
#
|
||
|
# io.put_next_entry("first_entry.txt")
|
||
|
# io.write "Hello world!"
|
||
|
#
|
||
|
# io.put_next_entry("adir/first_entry.txt")
|
||
|
# io.write "Hello again!"
|
||
|
# }
|
||
|
#
|
||
|
#
|
||
|
# Zip::ZipInputStream::open("my.zip") {
|
||
|
# |io|
|
||
|
#
|
||
|
# while (entry = io.get_next_entry)
|
||
|
# puts "Contents of #{entry.name}: '#{io.read}'"
|
||
|
# end
|
||
|
# }
|
||
|
#
|
||
|
# java.util.zip.ZipInputStream is the original inspiration for this
|
||
|
# class.
|
||
|
|
||
|
class InputStream
|
||
|
include ::Zip::IOExtras::AbstractInputStream
|
||
|
|
||
|
# Opens the indicated zip file. An exception is thrown
|
||
|
# if the specified offset in the specified filename is
|
||
|
# not a local zip entry header.
|
||
|
def initialize(filename, offset = 0, io = nil)
|
||
|
super()
|
||
|
if (io.nil?)
|
||
|
@archiveIO = ::File.open(filename, "rb")
|
||
|
@archiveIO.seek(offset, IO::SEEK_SET)
|
||
|
else
|
||
|
@archiveIO = io
|
||
|
end
|
||
|
@decompressor = NullDecompressor.instance
|
||
|
@currentEntry = nil
|
||
|
end
|
||
|
|
||
|
def close
|
||
|
@archiveIO.close
|
||
|
end
|
||
|
|
||
|
# Same as #initialize but if a block is passed the opened
|
||
|
# stream is passed to the block and closed when the block
|
||
|
# returns.
|
||
|
def InputStream.open(filename)
|
||
|
return new(filename) unless block_given?
|
||
|
|
||
|
zio = new(filename)
|
||
|
yield zio
|
||
|
ensure
|
||
|
zio.close if zio
|
||
|
end
|
||
|
|
||
|
def InputStream.open_buffer(io)
|
||
|
return new('',0,io) unless block_given?
|
||
|
zio = new('',0,io)
|
||
|
yield zio
|
||
|
ensure
|
||
|
zio.close if zio
|
||
|
end
|
||
|
|
||
|
# Returns a ZipEntry object. It is necessary to call this
|
||
|
# method on a newly created ZipInputStream before reading from
|
||
|
# the first entry in the archive. Returns nil when there are
|
||
|
# no more entries.
|
||
|
|
||
|
def get_next_entry
|
||
|
@archiveIO.seek(@currentEntry.next_header_offset, IO::SEEK_SET) if @currentEntry
|
||
|
open_entry
|
||
|
end
|
||
|
|
||
|
# Rewinds the stream to the beginning of the current entry
|
||
|
def rewind
|
||
|
return if @currentEntry.nil?
|
||
|
@lineno = 0
|
||
|
@pos = 0
|
||
|
@archiveIO.seek(@currentEntry.local_header_offset,
|
||
|
IO::SEEK_SET)
|
||
|
open_entry
|
||
|
end
|
||
|
|
||
|
# Modeled after IO.sysread
|
||
|
def sysread(numberOfBytes = nil, buf = nil)
|
||
|
@decompressor.sysread(numberOfBytes, buf)
|
||
|
end
|
||
|
|
||
|
def eof
|
||
|
@output_buffer.empty? && @decompressor.eof
|
||
|
end
|
||
|
alias :eof? :eof
|
||
|
|
||
|
protected
|
||
|
|
||
|
def open_entry
|
||
|
@currentEntry = Entry.read_local_entry(@archiveIO)
|
||
|
if @currentEntry.nil?
|
||
|
@decompressor = NullDecompressor.instance
|
||
|
elsif @currentEntry.compression_method == Entry::STORED
|
||
|
@decompressor = PassThruDecompressor.new(@archiveIO, @currentEntry.size)
|
||
|
elsif @currentEntry.compression_method == Entry::DEFLATED
|
||
|
@decompressor = Inflater.new(@archiveIO)
|
||
|
else
|
||
|
raise ZipCompressionMethodError,
|
||
|
"Unsupported compression method #{@currentEntry.compression_method}"
|
||
|
end
|
||
|
flush
|
||
|
return @currentEntry
|
||
|
end
|
||
|
|
||
|
def produce_input
|
||
|
@decompressor.produce_input
|
||
|
end
|
||
|
|
||
|
def input_finished?
|
||
|
@decompressor.input_finished?
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||
|
# rubyzip is free software; you can redistribute it and/or
|
||
|
# modify it under the terms of the ruby license.
|