zip_tricks 4.6.0 → 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
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