zip_tricks 4.6.0 → 4.7.0
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/.travis.yml +1 -2
- data/CHANGELOG.md +6 -0
- data/README.md +1 -0
- data/Rakefile +3 -2
- data/examples/deferred_write.rb +58 -0
- data/examples/rack_application.rb +1 -1
- data/lib/zip_tricks/block_write.rb +2 -10
- data/lib/zip_tricks/output_enumerator.rb +45 -0
- data/lib/zip_tricks/rack_body.rb +3 -41
- data/lib/zip_tricks/rails_streaming.rb +3 -0
- data/lib/zip_tricks/streamer.rb +29 -0
- data/lib/zip_tricks/version.rb +1 -1
- data/zip_tricks.gemspec +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec7428d7634361abe1384db4d39a8c7751dd3cdc
|
4
|
+
data.tar.gz: 3a4b3327e9edc0cc4b84e8ea8d064682104603d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9740445e2b7b646a5e7c5670edf61450543fd33f6e687d2d6adfc07a2a8097a244b9ecc9a8122f80eade94e534193ccdda34ad20ea97289b73f741f7e464a20
|
7
|
+
data.tar.gz: 649ffbc1803e3022092bf7da01245e13a838b0e27bf85f2be40e0cc1792249c4207936f1aa83b33eba74ec1f646962c6eb1cf11ca7677f48978c8721fcdb2c87
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## 4.7.0
|
2
|
+
|
3
|
+
* Replace `RackBody` with `OutputEnumerator` since we want to provide a generic way of deferring ZIP output, also when using enumerators.
|
4
|
+
* Remove `RackBody#close` since we got nothing to close 🤷♂️
|
5
|
+
* Hint nginx that response buffering should be disabled when using Rails zip streaming
|
6
|
+
|
1
7
|
## 4.6.0
|
2
8
|
|
3
9
|
* Add `mtime:` option to all Streamer methods for adding files and directories, to permit setting modification time per-entry
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# zip_tricks
|
2
2
|
|
3
3
|
[](https://travis-ci.org/WeTransfer/zip_tricks)
|
4
|
+
[](https://badge.fury.io/rb/zip_tricks)
|
4
5
|
|
5
6
|
Allows streaming, non-rewinding ZIP file output from Ruby.
|
6
7
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/zip_tricks'
|
4
|
+
|
5
|
+
# Using deferred writes (when you want to "pull" from a Streamer)
|
6
|
+
# is also possible with ZipTricks.
|
7
|
+
#
|
8
|
+
# The OutputEnumerator class instead of Streamer is very useful for this
|
9
|
+
# particular purpose. It does not start the archiving immediately,
|
10
|
+
# but waits instead until you start pulling data out of it.
|
11
|
+
#
|
12
|
+
# Let's make a OutputEnumerator that writes a few files with random content. Note that when you create
|
13
|
+
# that body it does not immediately write the ZIP:
|
14
|
+
iterable = ZipTricks::Streamer.output_enum do |zip|
|
15
|
+
(1..5).each do |i|
|
16
|
+
zip.write_stored_file('random_%d04d.bin' % i) do |sink|
|
17
|
+
warn "Starting on file #{i}...\n"
|
18
|
+
sink << Random.new.bytes(1024)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
warn "\n\nOutput using #each"
|
24
|
+
|
25
|
+
# Now we can treat the iterable as any Ruby enumerable object, since
|
26
|
+
# it supports #each yielding every binary string output by the Streamer.
|
27
|
+
# Only when we start using each() will the ZIP start generating. Just using
|
28
|
+
# each() like we do here runs the archiving procedure to completion. See how
|
29
|
+
# the output of the block within OutputEnumerator is interspersed with the stuff
|
30
|
+
# being yielded to each():
|
31
|
+
iterable.each do |_binary_string|
|
32
|
+
$stderr << '.'
|
33
|
+
end
|
34
|
+
|
35
|
+
warn "\n\nOutput Enumerator returned from #each"
|
36
|
+
|
37
|
+
# We now have output the entire archive, so using each() again
|
38
|
+
# will restart the block we gave it. For example, we can user
|
39
|
+
# an Enumerator - via enum_for - to "take" chunks of output when
|
40
|
+
# we find necessary:
|
41
|
+
enum = iterable.each
|
42
|
+
15.times do
|
43
|
+
_bin_str = enum.next # Obtain the subsequent chunk of the ZIP
|
44
|
+
$stderr << '*'
|
45
|
+
end
|
46
|
+
|
47
|
+
# ... or a Fiber
|
48
|
+
|
49
|
+
warn "\n\nOutput using a Fiber"
|
50
|
+
fib = Fiber.new do
|
51
|
+
iterable.each do |binary_string|
|
52
|
+
$stderr << '•'
|
53
|
+
_next_iteration = Fiber.yield(binary_string)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
15.times do
|
57
|
+
fib.resume # Process the subsequent chunk of the ZIP
|
58
|
+
end
|
@@ -36,7 +36,7 @@ class ZipDownload
|
|
36
36
|
|
37
37
|
# Create a suitable Rack response body, that will support each(),
|
38
38
|
# close() and all the other methods. We can then return it up the stack.
|
39
|
-
zip_response_body = ZipTricks::
|
39
|
+
zip_response_body = ZipTricks::Streamer.output_enum do |zip|
|
40
40
|
begin
|
41
41
|
# We are adding only one file to the ZIP here, but you could do that
|
42
42
|
# with an arbitrary number of files of course.
|
@@ -20,7 +20,8 @@ class ZipTricks::BlockWrite
|
|
20
20
|
# Every time this object gets written to, call the Rack body each() block
|
21
21
|
# with the bytes given instead.
|
22
22
|
def <<(buf)
|
23
|
-
|
23
|
+
# Zero-size output has a special meaning when using chunked encoding
|
24
|
+
return if buf.nil? || buf.bytesize.zero?
|
24
25
|
|
25
26
|
# Ensure we ALWAYS write in binary encoding.
|
26
27
|
encoded =
|
@@ -35,16 +36,7 @@ class ZipTricks::BlockWrite
|
|
35
36
|
buf
|
36
37
|
end
|
37
38
|
|
38
|
-
# buf.dup.force_encoding(Encoding::BINARY)
|
39
|
-
# Zero-size output has a special meaning when using chunked encoding
|
40
|
-
return if encoded.bytesize.zero?
|
41
|
-
|
42
39
|
@block.call(encoded)
|
43
40
|
self
|
44
41
|
end
|
45
|
-
|
46
|
-
# Does nothing
|
47
|
-
def close
|
48
|
-
nil
|
49
|
-
end
|
50
42
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Can be used as a Rack response body directly. Will yield
|
4
|
+
# a {ZipTricks::Streamer} for adding entries to the archive and writing
|
5
|
+
# zip entry bodies.
|
6
|
+
class ZipTricks::OutputEnumerator
|
7
|
+
# Prepares a new Rack response body with a Zip output stream.
|
8
|
+
# The block given to the constructor will be called when the response
|
9
|
+
# body will be read by the webserver, and will receive a {ZipTricks::Streamer}
|
10
|
+
# as it's block argument. You can then add entries to the Streamer as usual.
|
11
|
+
# The archive will be automatically closed at the end of the block.
|
12
|
+
#
|
13
|
+
# # Precompute the Content-Length ahead of time
|
14
|
+
# content_length = ZipTricks::SizeEstimator.estimate do | estimator |
|
15
|
+
# estimator.add_stored_entry(filename: 'large.tif', size: 1289894)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Prepare the response body.
|
19
|
+
# # The block will only be called when the
|
20
|
+
# # response starts to be written.
|
21
|
+
# body = ZipTricks::OutputEnumerator.new do | streamer |
|
22
|
+
# streamer.add_stored_entry(filename: 'large.tif', size: 1289894, crc32: 198210)
|
23
|
+
# streamer << large_file.read(1024*1024) until large_file.eof?
|
24
|
+
# ...
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# return [200, {'Content-Type' => 'binary/octet-stream',
|
28
|
+
# 'Content-Length' => content_length.to_s}, body]
|
29
|
+
def initialize(**streamer_options, &blk)
|
30
|
+
@streamer_options = streamer_options.to_h
|
31
|
+
@archiving_block = blk
|
32
|
+
end
|
33
|
+
|
34
|
+
# Executes the block given to the constructor with a {ZipTricks::Streamer}
|
35
|
+
# and passes each written chunk to the block given to the method. This allows one
|
36
|
+
# to "take" output of the ZIP piecewise.
|
37
|
+
def each
|
38
|
+
if block_given?
|
39
|
+
block_write = ZipTricks::BlockWrite.new { |chunk| yield(chunk) }
|
40
|
+
ZipTricks::Streamer.open(block_write, **@streamer_options, &@archiving_block)
|
41
|
+
else
|
42
|
+
enum_for(:each)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/zip_tricks/rack_body.rb
CHANGED
@@ -1,44 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
class ZipTricks::RackBody
|
7
|
-
# Prepares a new Rack response body with a Zip output stream.
|
8
|
-
# The block given to the constructor will be called when the response
|
9
|
-
# body will be read by the webserver, and will receive a {ZipTricks::Streamer}
|
10
|
-
# as it's block argument. You can then add entries to the Streamer as usual.
|
11
|
-
# The archive will be automatically closed at the end of the block.
|
12
|
-
#
|
13
|
-
# # Precompute the Content-Length ahead of time
|
14
|
-
# content_length = ZipTricks::SizeEstimator.estimate do | estimator |
|
15
|
-
# estimator.add_stored_entry(filename: 'large.tif', size: 1289894)
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
# # Prepare the response body. The block will only be called when the
|
19
|
-
# response starts to be written.
|
20
|
-
# body = ZipTricks::RackBody.new do | streamer |
|
21
|
-
# streamer.add_stored_entry(filename: 'large.tif', size: 1289894, crc32: 198210)
|
22
|
-
# streamer << large_file.read(1024*1024) until large_file.eof?
|
23
|
-
# ...
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# return [200, {'Content-Type' => 'binary/octet-stream',
|
27
|
-
# 'Content-Length' => content_length.to_s}, body]
|
28
|
-
def initialize(&blk)
|
29
|
-
@archiving_block = blk
|
30
|
-
end
|
31
|
-
|
32
|
-
# Connects a {ZipTricks::BlockWrite} to the Rack webserver output,
|
33
|
-
# and calls the proc given to the constructor with a {ZipTricks::Streamer}
|
34
|
-
# for archive writing.
|
35
|
-
def each(&body_chunk_block)
|
36
|
-
fake_io = ZipTricks::BlockWrite.new(&body_chunk_block)
|
37
|
-
ZipTricks::Streamer.open(fake_io, &@archiving_block)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Does nothing because nothing has to be deallocated or canceled
|
41
|
-
# even if the zip output is incomplete. The archive gets closed
|
42
|
-
# automatically as part of {ZipTricks::Streamer.open}
|
43
|
-
def close; end
|
3
|
+
# RackBody is actually just another use of the OutputEnumerator, since a Rack body
|
4
|
+
# object must support `#each` yielding successive binary strings.
|
5
|
+
class ZipTricks::RackBody < ZipTricks::OutputEnumerator
|
44
6
|
end
|
@@ -8,7 +8,10 @@ module ZipTricks::RailsStreaming
|
|
8
8
|
# the Rails response stream is going to be closed automatically.
|
9
9
|
# @yield [Streamer] the streamer that can be written to
|
10
10
|
def zip_tricks_stream
|
11
|
+
# Set a reasonable content type
|
11
12
|
response.headers['Content-Type'] = 'application/zip'
|
13
|
+
# Make sure nginx buffering is suppressed - see https://github.com/WeTransfer/zip_tricks/issues/48
|
14
|
+
response.headers['X-Accel-Buffering'] = 'no'
|
12
15
|
# Create a wrapper for the write call that quacks like something you
|
13
16
|
# can << to, used by ZipTricks
|
14
17
|
w = ZipTricks::BlockWrite.new { |chunk| response.stream.write(chunk) }
|
data/lib/zip_tricks/streamer.rb
CHANGED
@@ -99,6 +99,7 @@ class ZipTricks::Streamer
|
|
99
99
|
# directory of the archive to the output.
|
100
100
|
#
|
101
101
|
# @param stream [IO] the destination IO for the ZIP (should respond to `tell` and `<<`)
|
102
|
+
# @param kwargs_for_new [Hash] keyword arguments for {Streamer.new}
|
102
103
|
# @yield [Streamer] the streamer that can be written to
|
103
104
|
def self.open(stream, **kwargs_for_new)
|
104
105
|
archive = new(stream, **kwargs_for_new)
|
@@ -106,6 +107,34 @@ class ZipTricks::Streamer
|
|
106
107
|
archive.close
|
107
108
|
end
|
108
109
|
|
110
|
+
# Creates a new Streamer that writes to a buffer. The buffer can be read from using `each`,
|
111
|
+
# and the creation of the ZIP is in lockstep with the caller calling `each` on the returned
|
112
|
+
# output enumerator object. This can be used when the calling program wants to stream the
|
113
|
+
# output of the ZIP archive and throttle that output, or split it into chunks, or use it
|
114
|
+
# as a generator.
|
115
|
+
#
|
116
|
+
# For example:
|
117
|
+
#
|
118
|
+
# # The block given to {output_enum} won't be executed immediately - rather it
|
119
|
+
# # will only start to execute when the caller starts to read from the output
|
120
|
+
# # by calling `each`
|
121
|
+
# body = ZipTricks::Streamer.output_enum(writer: CustomWriter) do |zip|
|
122
|
+
# streamer.add_stored_entry(filename: 'large.tif', size: 1289894, crc32: 198210)
|
123
|
+
# streamer << large_file.read(1024*1024) until large_file.eof?
|
124
|
+
# ...
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# body.each do |bin_string|
|
128
|
+
# # Send the output somewhere, buffer it in a file etc.
|
129
|
+
# ...
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# @param kwargs_for_new [Hash] keyword arguments for {Streamer.new}
|
133
|
+
# @return [Enumerator] the enumerator you can read bytestrings of the ZIP from using `each`
|
134
|
+
def self.output_enum(**kwargs_for_new, &zip_streamer_block)
|
135
|
+
ZipTricks::OutputEnumerator.new(**kwargs_for_new, &zip_streamer_block)
|
136
|
+
end
|
137
|
+
|
109
138
|
# Creates a new Streamer on top of the given IO-ish object.
|
110
139
|
#
|
111
140
|
# @param stream[IO] the destination IO for the ZIP. Anything that responds to `<<` can be used.
|
data/lib/zip_tricks/version.rb
CHANGED
data/zip_tricks.gemspec
CHANGED
@@ -41,5 +41,5 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_development_dependency 'coderay'
|
42
42
|
spec.add_development_dependency 'benchmark-ips'
|
43
43
|
spec.add_development_dependency 'yard', '~> 0.9'
|
44
|
-
spec.add_development_dependency 'wetransfer_style', '0.
|
44
|
+
spec.add_development_dependency 'wetransfer_style', '0.6.0'
|
45
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zip_tricks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -170,14 +170,14 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - '='
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 0.
|
173
|
+
version: 0.6.0
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - '='
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: 0.
|
180
|
+
version: 0.6.0
|
181
181
|
description: Stream out ZIP files from Ruby
|
182
182
|
email:
|
183
183
|
- me@julik.nl
|
@@ -203,6 +203,7 @@ files:
|
|
203
203
|
- bench/buffered_crc32_bench.rb
|
204
204
|
- examples/archive_size_estimate.rb
|
205
205
|
- examples/config.ru
|
206
|
+
- examples/deferred_write.rb
|
206
207
|
- examples/parallel_compression_with_block_deflate.rb
|
207
208
|
- examples/rack_application.rb
|
208
209
|
- lib/zip_tricks.rb
|
@@ -212,6 +213,7 @@ files:
|
|
212
213
|
- lib/zip_tricks/file_reader/inflating_reader.rb
|
213
214
|
- lib/zip_tricks/file_reader/stored_reader.rb
|
214
215
|
- lib/zip_tricks/null_writer.rb
|
216
|
+
- lib/zip_tricks/output_enumerator.rb
|
215
217
|
- lib/zip_tricks/rack_body.rb
|
216
218
|
- lib/zip_tricks/rails_streaming.rb
|
217
219
|
- lib/zip_tricks/remote_io.rb
|