zip_tricks 2.6.0 → 2.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/zip_tricks/block_deflate.rb +4 -4
- data/lib/zip_tricks/block_write.rb +4 -4
- data/lib/zip_tricks/manifest.rb +13 -13
- data/lib/zip_tricks/rack_body.rb +4 -4
- data/lib/zip_tricks/remote_io.rb +13 -12
- data/lib/zip_tricks/remote_uncap.rb +12 -12
- data/lib/zip_tricks/stored_size_estimator.rb +3 -3
- data/lib/zip_tricks/stream_crc32.rb +4 -4
- data/lib/zip_tricks/streamer.rb +18 -18
- data/lib/zip_tricks/write_and_tell.rb +5 -5
- data/lib/zip_tricks.rb +1 -1
- data/spec/zip_tricks/block_deflate_spec.rb +16 -16
- data/spec/zip_tricks/block_write_spec.rb +16 -16
- data/spec/zip_tricks/manifest_spec.rb +11 -11
- data/spec/zip_tricks/rack_body_spec.rb +6 -6
- data/spec/zip_tricks/remote_io_spec.rb +22 -14
- data/spec/zip_tricks/remote_uncap_spec.rb +20 -20
- data/spec/zip_tricks/stored_size_estimator_spec.rb +4 -4
- data/spec/zip_tricks/stream_crc32_spec.rb +7 -7
- data/spec/zip_tricks/streamer_spec.rb +47 -47
- data/spec/zip_tricks/write_and_tell_spec.rb +6 -6
- data/zip_tricks.gemspec +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbb6bf8e96402a401c0bd282c77f48adb8b6e797
|
4
|
+
data.tar.gz: 7fea8830d270a647f66edd6d89c2c04a4674c2f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f79662655eeccdaeede66019b345fa9274c264beedf48dbcb7098dcaceba10f86d881e5d27ce720c6c875adb5eb7c0d8352d79d9beb1dd4d320932896c164b34
|
7
|
+
data.tar.gz: 0070e683f68a0f72c07dee4992d2213e604d73ee01df0ca7dcbbcd55dc9ab6ff66a26f0a8ecf0098495575b2e9fbda8b52044fb51ee436d58f7e53d20159428a
|
@@ -27,7 +27,7 @@ module ZipTricks::BlockDeflate
|
|
27
27
|
output_io << END_MARKER
|
28
28
|
END_MARKER.bytesize
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
# Compress a given binary string and flush the deflate stream at byte boundary.
|
32
32
|
# The returned string can be spliced into another deflate stream.
|
33
33
|
#
|
@@ -40,11 +40,11 @@ module ZipTricks::BlockDeflate
|
|
40
40
|
compressed_blob = z.deflate(bytes, Zlib::SYNC_FLUSH)
|
41
41
|
compressed_blob << z.finish
|
42
42
|
z.close
|
43
|
-
|
43
|
+
|
44
44
|
# Remove the header (2 bytes), the [3,0] end marker and the adler (4 bytes)
|
45
45
|
compressed_blob[2...-6]
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
# Compress the contents of input_io into output_io, in blocks
|
49
49
|
# of block_size. Aligns the parts so that they can be concatenated later.
|
50
50
|
# Writes deflate end marker (\x3\x0) into `output_io` as the final step, so
|
@@ -64,7 +64,7 @@ module ZipTricks::BlockDeflate
|
|
64
64
|
bytes_written = deflate_in_blocks(input_io, output_io, level: level, block_size: block_size)
|
65
65
|
bytes_written + write_terminator(output_io)
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
# Compress the contents of input_io into output_io, in blocks
|
69
69
|
# of block_size. Align the parts so that they can be concatenated later.
|
70
70
|
# Will not write the deflate end marker (\x3\x0) so more parts can be written
|
@@ -17,7 +17,7 @@ class ZipTricks::BlockWrite
|
|
17
17
|
# Every time this object gets written to, call the Rack body each() block with the bytes given instead.
|
18
18
|
def <<(buf)
|
19
19
|
return if buf.nil?
|
20
|
-
|
20
|
+
|
21
21
|
# Ensure we ALWAYS write in binary encoding.
|
22
22
|
encoded = if buf.encoding != Encoding::BINARY
|
23
23
|
# If we got a frozen string we can't force_encoding on it
|
@@ -25,14 +25,14 @@ class ZipTricks::BlockWrite
|
|
25
25
|
else
|
26
26
|
buf
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# buf.dup.force_encoding(Encoding::BINARY)
|
30
30
|
return if encoded.bytesize.zero? # Zero-size output has a special meaning when using chunked encoding
|
31
|
-
|
31
|
+
|
32
32
|
@block.call(encoded)
|
33
33
|
self
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# Does nothing
|
37
37
|
def close
|
38
38
|
nil
|
data/lib/zip_tricks/manifest.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Helps to estimate archive sizes
|
2
2
|
class ZipTricks::Manifest < Struct.new(:zip_streamer, :io, :part_list)
|
3
|
-
|
3
|
+
|
4
4
|
# Describes a span within the ZIP bytestream
|
5
5
|
class ZipSpan < Struct.new(:part_type, :byte_range_in_zip, :filename, :additional_metadata)
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
# Builds an array of spans within the ZIP file and computes the size of the resulting archive in bytes.
|
9
9
|
#
|
10
10
|
# zip_spans, bytesize = Manifest.build do | b |
|
@@ -26,14 +26,14 @@ class ZipTricks::Manifest < Struct.new(:zip_streamer, :io, :part_list)
|
|
26
26
|
yield(manifest)
|
27
27
|
last_range_end = part_list[-1].byte_range_in_zip.end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# Record the position of the central directory
|
31
31
|
directory_location = (last_range_end + 1)..(output_io.tell - 1)
|
32
32
|
part_list << ZipSpan.new(:central_directory, directory_location, :central_directory, nil)
|
33
|
-
|
33
|
+
|
34
34
|
[part_list, output_io.tell]
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
# Add a fake entry to the archive, to see how big it is going to be in the end.
|
38
38
|
#
|
39
39
|
# @param name [String] the name of the file (filenames are variable-width in the ZIP)
|
@@ -45,14 +45,14 @@ class ZipTricks::Manifest < Struct.new(:zip_streamer, :io, :part_list)
|
|
45
45
|
register_part(:entry_header, name, segment_info) do
|
46
46
|
zip_streamer.add_stored_entry(name, size_uncompressed, C_fake_crc)
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
register_part(:entry_body, name, segment_info) do
|
50
50
|
zip_streamer.simulate_write(size_uncompressed)
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
self
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
# Add a fake entry to the archive, to see how big it is going to be in the end.
|
57
57
|
#
|
58
58
|
# @param name [String] the name of the file (filenames are variable-width in the ZIP)
|
@@ -65,19 +65,19 @@ class ZipTricks::Manifest < Struct.new(:zip_streamer, :io, :part_list)
|
|
65
65
|
register_part(:entry_header, name, segment_info) do
|
66
66
|
zip_streamer.add_compressed_entry(name, size_uncompressed, C_fake_crc, size_compressed)
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
register_part(:entry_body, name, segment_info) do
|
70
70
|
zip_streamer.simulate_write(size_compressed)
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
self
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
private
|
77
|
-
|
77
|
+
|
78
78
|
C_fake_crc = Zlib.crc32('Mary had a little lamb')
|
79
79
|
private_constant :C_fake_crc
|
80
|
-
|
80
|
+
|
81
81
|
def register_part(span_type, filename, metadata)
|
82
82
|
before, _, after = io.tell, yield, (io.tell - 1)
|
83
83
|
part_list << ZipSpan.new(span_type, (before..after), filename, metadata)
|
data/lib/zip_tricks/rack_body.rb
CHANGED
@@ -12,19 +12,19 @@ class ZipTricks::RackBody
|
|
12
12
|
# content_length = ZipTricks::StoredSizeEstimator.perform_fake_archiving do | estimator |
|
13
13
|
# estimator.add_stored_entry('large.tif', size=1289894)
|
14
14
|
# end
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# # Prepare the response body. The block will only be called when the response starts to be written.
|
17
17
|
# body = ZipTricks::RackBody.new do | streamer |
|
18
18
|
# streamer.add_stored_entry('large.tif', size=1289894, crc32=198210)
|
19
19
|
# streamer << large_file.read(1024*1024) until large_file.eof?
|
20
20
|
# ...
|
21
21
|
# end
|
22
|
-
#
|
22
|
+
#
|
23
23
|
# return [200, {'Content-Type' => 'binary/octet-stream', 'Content-Length' => content_length.to_s}, body]
|
24
24
|
def initialize(&blk)
|
25
25
|
@archiving_block = blk
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
# Connects a {ZipTricks::BlockWrite} to the Rack webserver output,
|
29
29
|
# and calls the proc given to the constructor with a {ZipTricks::Streamer}
|
30
30
|
# for archive writing.
|
@@ -32,7 +32,7 @@ class ZipTricks::RackBody
|
|
32
32
|
fake_io = ZipTricks::BlockWrite.new(&body_chunk_block)
|
33
33
|
ZipTricks::Streamer.open(fake_io, &@archiving_block)
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# Does nothing because nothing has to be deallocated or canceled
|
37
37
|
# even if the zip output is incomplete. The archive gets closed
|
38
38
|
# automatically as part of {ZipTricks::Streamer.open}
|
data/lib/zip_tricks/remote_io.rb
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
# - or, more precisely, to be useful as a source for the RubyZip
|
3
3
|
# central directory parser
|
4
4
|
class ZipTricks::RemoteIO
|
5
|
-
|
5
|
+
|
6
6
|
# @param fetcher[#request_object_size, #request_range] an object that can fetch
|
7
7
|
def initialize(fetcher = :NOT_SET)
|
8
8
|
@pos = 0
|
9
9
|
@fetcher = fetcher
|
10
10
|
@remote_size = false
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
# Emulates IO#seek
|
14
14
|
def seek(offset, mode = IO::SEEK_SET)
|
15
15
|
case mode
|
@@ -28,16 +28,17 @@ class ZipTricks::RemoteIO
|
|
28
28
|
# Emulates IO#read
|
29
29
|
def read(n_bytes = nil)
|
30
30
|
@remote_size ||= request_object_size
|
31
|
-
|
31
|
+
|
32
32
|
# If the resource is empty there is nothing to read
|
33
33
|
return nil if @remote_size.zero?
|
34
|
-
|
34
|
+
|
35
35
|
maximum_avaialable = @remote_size - @pos
|
36
36
|
n_bytes ||= maximum_avaialable # nil == read to the end of file
|
37
|
+
return '' if n_bytes.zero?
|
37
38
|
raise ArgumentError, "No negative reads(#{n_bytes})" if n_bytes < 0
|
38
|
-
|
39
|
+
|
39
40
|
n_bytes = clamp(0, n_bytes, maximum_avaialable)
|
40
|
-
|
41
|
+
|
41
42
|
read_n_bytes_from_remote(@pos, n_bytes).tap do |data|
|
42
43
|
if data.bytesize != n_bytes
|
43
44
|
raise "Remote read returned #{data.bytesize} bytes instead of #{n_bytes} as requested"
|
@@ -53,30 +54,30 @@ class ZipTricks::RemoteIO
|
|
53
54
|
def pos
|
54
55
|
@pos
|
55
56
|
end
|
56
|
-
|
57
|
+
|
57
58
|
protected
|
58
59
|
|
59
60
|
def request_range(range)
|
60
61
|
@fetcher.request_range(range)
|
61
62
|
end
|
62
|
-
|
63
|
+
|
63
64
|
def request_object_size
|
64
65
|
@fetcher.request_object_size
|
65
66
|
end
|
66
|
-
|
67
|
+
|
67
68
|
# Reads N bytes at offset from remote
|
68
69
|
def read_n_bytes_from_remote(start_at, n_bytes)
|
69
70
|
range = (start_at..(start_at + n_bytes - 1))
|
70
71
|
request_range(range)
|
71
72
|
end
|
72
|
-
|
73
|
+
|
73
74
|
# Reads the Content-Length and caches it
|
74
75
|
def remote_size
|
75
76
|
@remote_size ||= request_object_size
|
76
77
|
end
|
77
|
-
|
78
|
+
|
78
79
|
private
|
79
|
-
|
80
|
+
|
80
81
|
def clamp(a,b,c)
|
81
82
|
return a if b < a
|
82
83
|
return c if b > c
|
@@ -3,59 +3,59 @@
|
|
3
3
|
# offsets at which the actual file contents is located. You can then
|
4
4
|
# use the `Range:` HTTP headers to download those entries separately.
|
5
5
|
class ZipTricks::RemoteUncap
|
6
|
-
|
6
|
+
|
7
7
|
# Represents a file embedded within a remote ZIP archive
|
8
8
|
class RemoteZipEntry
|
9
|
-
|
9
|
+
|
10
10
|
# @return [String] filename of the file in the remote ZIP
|
11
11
|
attr_accessor :name
|
12
|
-
|
12
|
+
|
13
13
|
# @return [Fixnum] size in bytes of the file when uncompressed
|
14
14
|
attr_accessor :size_uncompressed
|
15
15
|
|
16
16
|
# @return [Fixnum] size in bytes of the file when compressed (the segment in the ZIP)
|
17
17
|
attr_accessor :size_compressed
|
18
|
-
|
18
|
+
|
19
19
|
# @return [Fixnum] compression method (0 for stored, 8 for deflate)
|
20
20
|
attr_accessor :compression_method
|
21
|
-
|
21
|
+
|
22
22
|
# @return [Fixnum] where the file data starts within the ZIP
|
23
23
|
attr_accessor :starts_at_offset
|
24
|
-
|
24
|
+
|
25
25
|
# @return [Fixnum] where the file data ends within the zip.
|
26
26
|
# Will be equal to starts_at_offset if the file is empty
|
27
27
|
attr_accessor :ends_at_offset
|
28
|
-
|
28
|
+
|
29
29
|
# Yields the object during initialization
|
30
30
|
def initialize
|
31
31
|
yield self
|
32
32
|
end
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
# @param uri[String] the HTTP(S) URL to read the ZIP footer from
|
36
36
|
# @return [Array<RemoteZipEntry>] metadata about the files within the remote archive
|
37
37
|
def self.files_within_zip_at(uri)
|
38
38
|
fetcher = new(uri)
|
39
39
|
fake_io = ZipTricks::RemoteIO.new(fetcher)
|
40
40
|
dir = Zip::CentralDirectory.read_from_stream(fake_io)
|
41
|
-
|
41
|
+
|
42
42
|
dir.entries.map do | rubyzip_entry |
|
43
43
|
RemoteZipEntry.new do | entry |
|
44
44
|
entry.name = rubyzip_entry.name
|
45
45
|
entry.size_uncompressed = rubyzip_entry.size
|
46
46
|
entry.size_compressed = rubyzip_entry.compressed_size
|
47
47
|
entry.compression_method = rubyzip_entry.compression_method
|
48
|
-
|
48
|
+
|
49
49
|
entry.starts_at_offset = rubyzip_entry.local_header_offset + rubyzip_entry.calculate_local_header_size
|
50
50
|
entry.ends_at_offset = entry.starts_at_offset + rubyzip_entry.compressed_size
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
def initialize(uri)
|
56
56
|
@uri = URI(uri)
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
# @param range[Range] the HTTP range of data to fetch from remote
|
60
60
|
# @return [String] the response body of the ranged request
|
61
61
|
def request_range(range)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Helps to estimate archive sizes
|
2
2
|
class ZipTricks::StoredSizeEstimator < Struct.new(:manifest)
|
3
|
-
|
3
|
+
|
4
4
|
# Performs the estimate using fake archiving. It needs to know the sizes of the
|
5
5
|
# entries upfront. Usage:
|
6
6
|
#
|
@@ -20,7 +20,7 @@ class ZipTricks::StoredSizeEstimator < Struct.new(:manifest)
|
|
20
20
|
end
|
21
21
|
bytes
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
# Add a fake entry to the archive, to see how big it is going to be in the end.
|
25
25
|
#
|
26
26
|
# @param name [String] the name of the file (filenames are variable-width in the ZIP)
|
@@ -30,7 +30,7 @@ class ZipTricks::StoredSizeEstimator < Struct.new(:manifest)
|
|
30
30
|
manifest.add_stored_entry(name: name, size_uncompressed: size_uncompressed)
|
31
31
|
self
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
# Add a fake entry to the archive, to see how big it is going to be in the end.
|
35
35
|
#
|
36
36
|
# @param name [String] the name of the file (filenames are variable-width in the ZIP)
|
@@ -9,12 +9,12 @@ class ZipTricks::StreamCRC32
|
|
9
9
|
crc << io.read(1024 * 512) until io.eof?
|
10
10
|
crc.to_i
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
# Creates a new streaming CRC32 calculator
|
14
14
|
def initialize
|
15
15
|
@crc = Zlib.crc32('')
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# Append data to the CRC32. Updates the contained CRC32 value in place.
|
19
19
|
#
|
20
20
|
# @param blob[String] the string to compute the CRC32 from
|
@@ -23,14 +23,14 @@ class ZipTricks::StreamCRC32
|
|
23
23
|
@crc = Zlib.crc32_combine(@crc, Zlib.crc32(blob), blob.bytesize)
|
24
24
|
self
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
# Returns the CRC32 value computed so far
|
28
28
|
#
|
29
29
|
# @return crc[Fixnum] the updated CRC32 value for all the blobs so far
|
30
30
|
def to_i
|
31
31
|
@crc
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
# Appends a known CRC32 value to the current one, and combines the
|
35
35
|
# contained CRC32 value in-place.
|
36
36
|
#
|
data/lib/zip_tricks/streamer.rb
CHANGED
@@ -13,13 +13,13 @@
|
|
13
13
|
class ZipTricks::Streamer
|
14
14
|
EntryBodySizeMismatch = Class.new(StandardError)
|
15
15
|
InvalidOutput = Class.new(ArgumentError)
|
16
|
-
|
16
|
+
|
17
17
|
# Language encoding flag (EFS) bit (general purpose bit 11)
|
18
18
|
EFS = 0b100000000000
|
19
|
-
|
19
|
+
|
20
20
|
# Default general purpose flags for each entry.
|
21
21
|
DEFAULT_GP_FLAGS = 0b00000000000
|
22
|
-
|
22
|
+
|
23
23
|
# Creates a new Streamer on top of the given IO-ish object and yields it. Once the given block
|
24
24
|
# returns, the Streamer will have it's `close` method called, which will write out the central
|
25
25
|
# directory of the archive to the output.
|
@@ -31,7 +31,7 @@ class ZipTricks::Streamer
|
|
31
31
|
yield(archive)
|
32
32
|
archive.close
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
# Creates a new Streamer on top of the given IO-ish object.
|
36
36
|
#
|
37
37
|
# @param stream [IO] the destination IO for the ZIP (should respond to `tell` and `<<`)
|
@@ -39,7 +39,7 @@ class ZipTricks::Streamer
|
|
39
39
|
raise InvalidOutput, "The stream should respond to #<<" unless stream.respond_to?(:<<)
|
40
40
|
stream = ZipTricks::WriteAndTell.new(stream) unless stream.respond_to?(:tell) && stream.respond_to?(:advance_position_by)
|
41
41
|
@output_stream = stream
|
42
|
-
|
42
|
+
|
43
43
|
@state_monitor = VeryTinyStateMachine.new(:before_entry, callbacks_to=self)
|
44
44
|
@state_monitor.permit_state :in_entry_header, :in_entry_body, :in_central_directory, :closed
|
45
45
|
@state_monitor.permit_transition :before_entry => :in_entry_header
|
@@ -47,7 +47,7 @@ class ZipTricks::Streamer
|
|
47
47
|
@state_monitor.permit_transition :in_entry_body => :in_entry_header
|
48
48
|
@state_monitor.permit_transition :in_entry_body => :in_central_directory
|
49
49
|
@state_monitor.permit_transition :in_central_directory => :closed
|
50
|
-
|
50
|
+
|
51
51
|
@entry_set = ::Zip::EntrySet.new
|
52
52
|
end
|
53
53
|
|
@@ -61,7 +61,7 @@ class ZipTricks::Streamer
|
|
61
61
|
@bytes_written_for_entry += binary_data.bytesize
|
62
62
|
self
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
# Advances the internal IO pointer to keep the offsets of the ZIP file in check. Use this if you are going
|
66
66
|
# to use accelerated writes to the socket (like the `sendfile()` call) after writing the headers, or if you
|
67
67
|
# just need to figure out the size of the archive.
|
@@ -74,7 +74,7 @@ class ZipTricks::Streamer
|
|
74
74
|
@bytes_written_for_entry += num_bytes
|
75
75
|
@output_stream.tell
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
# Writes out the local header for an entry (file in the ZIP) that is using the deflated storage model (is compressed).
|
79
79
|
# Once this method is called, the `<<` method has to be called to write the actual contents of the body.
|
80
80
|
#
|
@@ -88,21 +88,21 @@ class ZipTricks::Streamer
|
|
88
88
|
# @return [Fixnum] the offset the output IO is at after writing the entry header
|
89
89
|
def add_compressed_entry(entry_name, uncompressed_size, crc32, compressed_size)
|
90
90
|
@state_monitor.transition! :in_entry_header
|
91
|
-
|
91
|
+
|
92
92
|
entry = ::Zip::Entry.new(@file_name, entry_name)
|
93
93
|
entry.compression_method = Zip::Entry::DEFLATED
|
94
94
|
entry.crc = crc32
|
95
95
|
entry.size = uncompressed_size
|
96
96
|
entry.compressed_size = compressed_size
|
97
97
|
set_gp_flags_for_filename(entry, entry_name)
|
98
|
-
|
98
|
+
|
99
99
|
@entry_set << entry
|
100
100
|
entry.write_local_entry(@output_stream)
|
101
101
|
@expected_bytes_for_entry = compressed_size
|
102
102
|
@bytes_written_for_entry = 0
|
103
103
|
@output_stream.tell
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
# Writes out the local header for an entry (file in the ZIP) that is using the stored storage model (is stored as-is).
|
107
107
|
# Once this method is called, the `<<` method has to be called one or more times to write the actual contents of the body.
|
108
108
|
#
|
@@ -112,7 +112,7 @@ class ZipTricks::Streamer
|
|
112
112
|
# @return [Fixnum] the offset the output IO is at after writing the entry header
|
113
113
|
def add_stored_entry(entry_name, uncompressed_size, crc32)
|
114
114
|
@state_monitor.transition! :in_entry_header
|
115
|
-
|
115
|
+
|
116
116
|
entry = ::Zip::Entry.new(@file_name, entry_name)
|
117
117
|
entry.compression_method = Zip::Entry::STORED
|
118
118
|
entry.crc = crc32
|
@@ -125,8 +125,8 @@ class ZipTricks::Streamer
|
|
125
125
|
@expected_bytes_for_entry = uncompressed_size
|
126
126
|
@output_stream.tell
|
127
127
|
end
|
128
|
-
|
129
|
-
|
128
|
+
|
129
|
+
|
130
130
|
# Writes out the global footer and the directory entry header and the global directory of the ZIP
|
131
131
|
# archive using the information about the entries added using `add_stored_entry` and `add_compressed_entry`.
|
132
132
|
#
|
@@ -139,7 +139,7 @@ class ZipTricks::Streamer
|
|
139
139
|
cdir.write_to_stream(@output_stream)
|
140
140
|
@output_stream.tell
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
# Closes the archive. Writes the central directory if it has not yet been written.
|
144
144
|
# Switches the Streamer into a state where it can no longer be written to.
|
145
145
|
#
|
@@ -151,9 +151,9 @@ class ZipTricks::Streamer
|
|
151
151
|
@state_monitor.transition! :closed
|
152
152
|
@output_stream.tell
|
153
153
|
end
|
154
|
-
|
154
|
+
|
155
155
|
private
|
156
|
-
|
156
|
+
|
157
157
|
# Set the general purpose flags for the entry. The only flag we care about is the EFS
|
158
158
|
# bit (bit 11) which should be set if the filename is UTF8. If it is, we need to set the
|
159
159
|
# bit so that the unarchiving application knows that the filename in the archive is UTF-8
|
@@ -164,7 +164,7 @@ class ZipTricks::Streamer
|
|
164
164
|
rescue Encoding::UndefinedConversionError #=> UTF8 filename
|
165
165
|
entry.gp_flags = DEFAULT_GP_FLAGS | EFS
|
166
166
|
end
|
167
|
-
|
167
|
+
|
168
168
|
# Checks whether the number of bytes written conforms to the declared entry size
|
169
169
|
def leaving_in_entry_body_state
|
170
170
|
if @bytes_written_for_entry != @expected_bytes_for_entry
|
@@ -5,7 +5,7 @@ class ZipTricks::WriteAndTell
|
|
5
5
|
@io = io
|
6
6
|
@pos = 0
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def <<(bytes)
|
10
10
|
return self if bytes.nil?
|
11
11
|
binary_bytes = binary(bytes)
|
@@ -13,17 +13,17 @@ class ZipTricks::WriteAndTell
|
|
13
13
|
@pos += binary_bytes.bytesize
|
14
14
|
self
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def advance_position_by(num_bytes)
|
18
18
|
@pos += num_bytes
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def tell
|
22
22
|
@pos
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
private
|
26
|
-
|
26
|
+
|
27
27
|
def binary(str)
|
28
28
|
return str if str.encoding == Encoding::BINARY
|
29
29
|
str.force_encoding(Encoding::BINARY)
|
data/lib/zip_tricks.rb
CHANGED
@@ -4,7 +4,7 @@ describe ZipTricks::BlockDeflate do
|
|
4
4
|
def tag_deflated(deflated_string, raw_string)
|
5
5
|
[120, 156].pack("C*") + deflated_string + [3,0].pack("C*") + [Zlib.adler32(raw_string)].pack("N")
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
describe '.deflate_chunk' do
|
9
9
|
it 'compresses a blob that can be inflated later, when the header, footer and adler32 are added' do
|
10
10
|
blob = 'compressible' * 1024 * 4
|
@@ -13,26 +13,26 @@ describe ZipTricks::BlockDeflate do
|
|
13
13
|
complete_deflated_segment = tag_deflated(compressed, blob)
|
14
14
|
expect(Zlib.inflate(complete_deflated_segment)).to eq(blob)
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
it 'removes the header' do
|
18
18
|
blob = 'compressible' * 1024 * 4
|
19
19
|
compressed = described_class.deflate_chunk(blob)
|
20
20
|
expect(compressed[0..1]).not_to eq([120, 156].pack("C*"))
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
it 'removes the adler32' do
|
24
24
|
blob = 'compressible' * 1024 * 4
|
25
25
|
compressed = described_class.deflate_chunk(blob)
|
26
26
|
adler = [Zlib.adler32(blob)].pack("N")
|
27
27
|
expect(compressed).not_to end_with(adler)
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
it 'removes the end marker' do
|
31
31
|
blob = 'compressible' * 1024 * 4
|
32
32
|
compressed = described_class.deflate_chunk(blob)
|
33
33
|
expect(compressed[-7..-5]).not_to eq([3,0].pack("C*"))
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
it 'honors the compression level' do
|
37
37
|
deflater = Zlib::Deflate.new
|
38
38
|
expect(Zlib::Deflate).to receive(:new).with(2) { deflater }
|
@@ -40,7 +40,7 @@ describe ZipTricks::BlockDeflate do
|
|
40
40
|
compressed = described_class.deflate_chunk(blob, level: 2)
|
41
41
|
end
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
describe 'deflate_in_blocks_and_terminate' do
|
45
45
|
it 'uses deflate_in_blocks' do
|
46
46
|
data = 'compressible' * 1024 * 1024 * 10
|
@@ -50,7 +50,7 @@ describe ZipTricks::BlockDeflate do
|
|
50
50
|
expect(described_class).to receive(:deflate_in_blocks).with(input, output, level: -1, block_size: block_size).and_call_original
|
51
51
|
described_class.deflate_in_blocks_and_terminate(input, output, block_size: block_size)
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
it 'passes a custom compression level' do
|
55
55
|
data = 'compressible' * 1024 * 1024 * 10
|
56
56
|
input = StringIO.new(data)
|
@@ -58,7 +58,7 @@ describe ZipTricks::BlockDeflate do
|
|
58
58
|
expect(described_class).to receive(:deflate_in_blocks).with(input, output, level: 9, block_size: 5242880).and_call_original
|
59
59
|
described_class.deflate_in_blocks_and_terminate(input, output, level: Zlib::BEST_COMPRESSION)
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
it 'writes the end marker' do
|
63
63
|
data = 'compressible' * 1024 * 1024 * 10
|
64
64
|
input = StringIO.new(data)
|
@@ -67,7 +67,7 @@ describe ZipTricks::BlockDeflate do
|
|
67
67
|
expect(output.string[-2..-1]).to eq([3,0].pack("C*"))
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
describe '.write_terminator' do
|
72
72
|
it 'writes the terminator and returns 2 for number of bytes written' do
|
73
73
|
buf = double('IO')
|
@@ -75,35 +75,35 @@ describe ZipTricks::BlockDeflate do
|
|
75
75
|
expect(described_class.write_terminator(buf)).to eq(2)
|
76
76
|
end
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
describe '.deflate_in_blocks' do
|
80
80
|
it 'honors the block size' do
|
81
81
|
data = 'compressible' * 1024 * 1024 * 10
|
82
82
|
input = StringIO.new(data)
|
83
83
|
output = StringIO.new
|
84
84
|
block_size = 1024 * 64
|
85
|
-
|
85
|
+
|
86
86
|
num_chunks = (data.bytesize / block_size.to_f).ceil
|
87
87
|
expect(described_class).to receive(:deflate_chunk).exactly(num_chunks).times.and_call_original
|
88
88
|
expect(input).to receive(:read).with(block_size).exactly(num_chunks + 1).times.and_call_original
|
89
89
|
expect(output).to receive(:<<).exactly(num_chunks).times.and_call_original
|
90
|
-
|
90
|
+
|
91
91
|
described_class.deflate_in_blocks(input, output, block_size: block_size)
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
it 'does not write the end marker' do
|
95
95
|
input_string = 'compressible' * 1024 * 1024 * 10
|
96
96
|
output_string = ''
|
97
|
-
|
97
|
+
|
98
98
|
described_class.deflate_in_blocks(StringIO.new(input_string), StringIO.new(output_string))
|
99
99
|
expect(output_string).not_to be_empty
|
100
100
|
expect(output_string).not_to end_with([3,0].pack("C*"))
|
101
101
|
end
|
102
|
-
|
102
|
+
|
103
103
|
it 'returns the number of bytes written' do
|
104
104
|
input_string = 'compressible' * 1024 * 1024 * 10
|
105
105
|
output_string = ''
|
106
|
-
|
106
|
+
|
107
107
|
num_bytes = described_class.deflate_in_blocks(StringIO.new(input_string), StringIO.new(output_string))
|
108
108
|
expect(num_bytes).to eq(245016)
|
109
109
|
end
|