zip_tricks 2.6.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|