Class: Archive::Zip::Codec::Deflate::Reader

Inherits:
IO::LikeHelpers::DelegatedIO
  • Object
show all
Defined in:
lib/archive/zip/codec/deflate/reader.rb

Overview

Archive::Zip::Codec::Deflate#decompressor method.

Constant Summary collapse

DEFAULT_DELEGATE_READ_SIZE =

The number of bytes to read from the delegate object each time the internal read buffer is filled.

8192

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(delegate, autoclose: true, delegate_read_size: DEFAULT_DELEGATE_READ_SIZE) ⇒ Reader

Creates a new instance of this class. delegate must respond to the read method as an IO instance would.

In all cases, Zlib::DataError is raised if the wrong stream format is found when reading.

This class has extremely limited seek capabilities. It is possible to seek with an offset of 0 and a whence of IO::SEEK_CUR. As a result, the pos and tell methods also work as expected.

Due to certain optimizations within IO::Like#seek and if there is data in the read buffer, the seek method can be used to seek forward from the current stream position up to the end of the buffer. Unless it is known definitively how much data is in the buffer, it is best to avoid relying on this behavior.

If delegate also responds to rewind, then the rewind method of this class can be used to reset the whole stream back to the beginning. Using seek of this class to seek directly to offset 0 using IO::SEEK_SET for whence will also work in this case.

Any other seeking attempts, will raise Errno::EINVAL exceptions.

NOTE: Due to limitations in Ruby’s finalization capabilities, the #close method is not automatically called when this object is garbage collected. Make sure to call #close when finished with this object.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/archive/zip/codec/deflate/reader.rb', line 48

def initialize(
  delegate,
  autoclose: true,
  delegate_read_size: DEFAULT_DELEGATE_READ_SIZE
)
  super(delegate, autoclose: autoclose)

  @delegate_read_size = delegate_read_size
  @read_buffer = "\0".b * @delegate_read_size
  @inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
  @inflate_buffer = ''
  @inflate_buffer_idx = 0
  @compressed_size = nil
  @uncompressed_size = nil
  @crc32 = 0
end

Instance Attribute Details

#crc32Object (readonly)

The CRC32 checksum of the uncompressed data read using this object.

NOTE: The contents of the internal read buffer are immediately processed any time the internal buffer is filled, so this checksum is only accurate if all data has been read out of this object.



95
96
97
# File 'lib/archive/zip/codec/deflate/reader.rb', line 95

def crc32
  @crc32
end

#delegate_read_sizeObject

The number of bytes to read from the delegate object each time the internal read buffer is filled.



67
68
69
# File 'lib/archive/zip/codec/deflate/reader.rb', line 67

def delegate_read_size
  @delegate_read_size
end

Instance Method Details

#closeObject

Closes the reader.

Raises IOError if called more than once.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/archive/zip/codec/deflate/reader.rb', line 72

def close
  return nil if closed?

  result = super
  return result if Symbol === result

  @compressed_size = @inflater.total_in
  @uncompressed_size = @inflater.total_out
  @inflate_buffer = nil
  @inflate_buffer_idx = 0

  # Avoid warnings by only attempting to close the inflater if it was
  # correctly finished.
  @inflater.close if @inflater.finished?

  nil
end

#compressed_sizeObject

Returns the number of bytes sent to be compressed so far.

NOTE: This value is updated whenever the internal read buffer needs to be filled, not when data is read out of this stream.



101
102
103
# File 'lib/archive/zip/codec/deflate/reader.rb', line 101

def compressed_size
  @inflater.closed? ? @compressed_size : @inflater.total_in
end

#data_descriptorObject

Returns an instance of Archive::Zip::DataDescriptor with information regarding the data which has passed through this object from the delegate object. It is recommended to call the close method before calling this in order to ensure that no further read operations change the state of this object.



118
119
120
# File 'lib/archive/zip/codec/deflate/reader.rb', line 118

def data_descriptor
  DataDescriptor.new(crc32, compressed_size, uncompressed_size)
end

#read(length, buffer: nil, buffer_offset: 0) ⇒ Object

Raises:

  • (ArgumentError)


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/archive/zip/codec/deflate/reader.rb', line 122

def read(length, buffer: nil, buffer_offset: 0)
  length = Integer(length)
  raise ArgumentError, 'length must be at least 0' if length < 0
  if ! buffer.nil?
    if buffer_offset < 0 || buffer_offset >= buffer.bytesize
      raise ArgumentError, 'buffer_offset is not a valid buffer index'
    end
    if buffer.bytesize - buffer_offset < length
      raise ArgumentError, 'length is greater than available buffer space'
    end
  end

  assert_readable

  if @inflate_buffer_idx >= @inflate_buffer.size
    raise EOFError, 'end of file reached' if @inflater.finished?

    @inflate_buffer =
      begin
        result = super(@delegate_read_size, buffer: @read_buffer)
        return result if Symbol === result
        @inflater.inflate(@read_buffer[0, result])
      rescue EOFError
        @inflater.inflate(nil)
      end
    @inflate_buffer_idx = 0
  end

  available = @inflate_buffer.size - @inflate_buffer_idx
  length = available if available < length
  content = @inflate_buffer[@inflate_buffer_idx, length]
  @inflate_buffer_idx += length
  @crc32 = Zlib.crc32(content, @crc32)
  return content if buffer.nil?

  buffer[buffer_offset, length] = content
  return length
end

#seek(amount, whence = IO::SEEK_SET) ⇒ Object

Allows resetting this object and the delegate object back to the beginning of the stream or reporting the current position in the stream.

Raises Errno::EINVAL unless offset is 0 and whence is either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if whence is IO::SEEK_SEK and the delegate object does not respond to the rewind method.

Raises:

  • (Errno::ESPIPE)


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/archive/zip/codec/deflate/reader.rb', line 168

def seek(amount, whence = IO::SEEK_SET)
  assert_open
  raise Errno::ESPIPE if amount != 0 || whence == IO::SEEK_END

  case whence
  when IO::SEEK_SET
    result = super
    return result if Symbol === result
    @inflater.reset
    @inflate_buffer_idx = @inflate_buffer.size
    @crc32 = 0
    result
  when IO::SEEK_CUR
    @inflater.total_out - (@inflate_buffer.size - @inflate_buffer_idx)
  else
    raise Errno::EINVAL
  end
end

#uncompressed_sizeObject

Returns the number of bytes of decompressed data produced so far.

NOTE: This value is updated whenever the internal read buffer needs to be filled, not when data is read out of this stream.



109
110
111
# File 'lib/archive/zip/codec/deflate/reader.rb', line 109

def uncompressed_size
  @inflater.closed? ? @uncompressed_size : @inflater.total_out
end