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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 387773a063a44c69ac3da83da89fe85731c78331
4
- data.tar.gz: 96d6763cc05e7c21f206ecaa013786f822df0d42
3
+ metadata.gz: ec7428d7634361abe1384db4d39a8c7751dd3cdc
4
+ data.tar.gz: 3a4b3327e9edc0cc4b84e8ea8d064682104603d1
5
5
  SHA512:
6
- metadata.gz: 80f07ed5042f76c6e0edf5843a4b51ee468bde57d5fc4ab28b0a53f9f819a1160e339126173ebbe306add30902ac69270d271e61c81aab5a3261098b5ed79b1c
7
- data.tar.gz: 8203289c9445f009f65ccaa924a7b07f1ecb5257ac9cb4d7fc6ce7a2204e49ed125cba6c4d1345a933203aa61ab562fe2d40b3bab3db074fccc5141636eb8c2e
6
+ metadata.gz: a9740445e2b7b646a5e7c5670edf61450543fd33f6e687d2d6adfc07a2a8097a244b9ecc9a8122f80eade94e534193ccdda34ad20ea97289b73f741f7e464a20
7
+ data.tar.gz: 649ffbc1803e3022092bf7da01245e13a838b0e27bf85f2be40e0cc1792249c4207936f1aa83b33eba74ec1f646962c6eb1cf11ca7677f48978c8721fcdb2c87
data/.travis.yml CHANGED
@@ -14,5 +14,4 @@ matrix:
14
14
  allow_failures:
15
15
  - rvm: jruby-9.0
16
16
  script:
17
- - bundle exec rspec
18
- - bundle exec rubocop
17
+ - bundle exec rake
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
  [![Build Status](https://travis-ci.org/WeTransfer/zip_tricks.svg?branch=master)](https://travis-ci.org/WeTransfer/zip_tricks)
4
+ [![Gem Version](https://badge.fury.io/rb/zip_tricks.svg)](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
@@ -10,6 +10,7 @@ YARD::Rake::YardocTask.new(:doc) do |t|
10
10
  end
11
11
 
12
12
  RSpec::Core::RakeTask.new(:spec)
13
- task default: :spec
14
13
 
15
- RuboCop::RakeTask.new
14
+ RuboCop::RakeTask.new(:rubocop)
15
+
16
+ task default: [:spec, :rubocop]
@@ -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::RackBody.new do |zip|
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
- return if buf.nil?
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
@@ -1,44 +1,6 @@
1
1
  # frozen_string_literal: true
2
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::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) }
@@ -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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZipTricks
4
- VERSION = '4.6.0'
4
+ VERSION = '4.7.0'
5
5
  end
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.5.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.6.0
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-04-12 00:00:00.000000000 Z
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.5.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.5.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