zipline 1.5.0 → 1.6.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
  SHA256:
3
- metadata.gz: 8735b5a6429dcffa8672a1a970cc37145eadfd5e5ff13e2cb5b3f08166895d14
4
- data.tar.gz: a4555919f140b0661cb12fd0e6f92900e419319ebf7ec7c376ce19034dffd9a9
3
+ metadata.gz: ca44760a8a5c6019544aa77857692b43d4cd9f429395173a54b779f276f214f2
4
+ data.tar.gz: c9aec0dc791f84a315658ae9405e88f1d5b7348822befa69e61c92e261ff3a69
5
5
  SHA512:
6
- metadata.gz: 758292743ee475bc2a276bd00d953fa4f0b1cb1c4ccd2ceae7b2c7dbeced0e1a901a609d183a68c6eeaa898693045f3d5f4ee23bfece0c35b379fb7ce170cdc2
7
- data.tar.gz: 7383e439111008fa964a2d762f6855351d3a0372df973d453e0b205aee57f53d9fc8aea54e4f7efed6719b3b8318768f8d125e314ad277f2855bac29fa63c5e2
6
+ metadata.gz: 2c46de9ba175df42d54c36a37dceddb77cf7816e058528750fe0b05c54313aba1992b8ba12d40b9f029a33c088c79a214d3e50e56cbd0b295c74062fe49c7781
7
+ data.tar.gz: 5022768afbe367ca08c586b33d16f8a044ad79c7cde80b79be4a3d1c9b18c16d2b0d9cdb9d82f75e3df338d101eda5dce80a099c1261e2e3d828ac637569d790
data/Gemfile CHANGED
@@ -2,14 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in zipline.gemspec
4
4
  gemspec
5
-
6
- group :development, :test do
7
- gem 'rspec', '~> 3'
8
- gem 'fog-aws'
9
- gem 'activesupport'
10
- gem 'actionpack'
11
- gem 'aws-sdk-s3'
12
- gem 'carrierwave'
13
- gem 'paperclip'
14
- gem 'rake'
15
- end
@@ -0,0 +1,31 @@
1
+ module Zipline
2
+ # A body wrapper that emits chunked responses, creating valid
3
+ # "Transfer-Encoding: chunked" HTTP response body. This is copied from Rack::Chunked::Body,
4
+ # because Rack is not going to include that class after version 3.x
5
+ # Rails has a substitute class for this inside ActionController::Streaming,
6
+ # but that module is a private constant in the Rails codebase, and is thus
7
+ # considered "private" from the Rails standpoint. It is not that much code to
8
+ # carry, so we copy it into our code.
9
+ class Chunked
10
+ TERM = "\r\n"
11
+ TAIL = "0#{TERM}"
12
+
13
+ def initialize(body)
14
+ @body = body
15
+ end
16
+
17
+ # For each string yielded by the response body, yield
18
+ # the element in chunked encoding - and finish off with a terminator
19
+ def each
20
+ term = TERM
21
+ @body.each do |chunk|
22
+ size = chunk.bytesize
23
+ next if size == 0
24
+
25
+ yield [size.to_s(16), term, chunk.b, term].join
26
+ end
27
+ yield TAIL
28
+ yield term
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ module Zipline
2
+ # Contains a file handle which can be closed once the response finishes sending.
3
+ # It supports `to_path` so that `Rack::Sendfile` can intercept it
4
+ class TempfileBody
5
+ TEMPFILE_NAME_PREFIX = "zipline-tf-body-"
6
+ attr_reader :tempfile
7
+
8
+ # @param body[#each] the enumerable that yields bytes, usually a `RackBody`.
9
+ # The `body` will be read in full immediately and closed.
10
+ def initialize(env, body)
11
+ @tempfile = Tempfile.new(TEMPFILE_NAME_PREFIX)
12
+ # Rack::TempfileReaper calls close! on tempfiles which get buffered
13
+ # We wil assume that it works fine with Rack::Sendfile (i.e. the path
14
+ # to the file getting served gets used before we unlink the tempfile)
15
+ env['rack.tempfiles'] ||= []
16
+ env['rack.tempfiles'] << @tempfile
17
+
18
+ @tempfile.binmode
19
+
20
+ body.each { |bytes| @tempfile << bytes }
21
+ body.close if body.respond_to?(:close)
22
+
23
+ @tempfile.flush
24
+ end
25
+
26
+ # Returns the size of the contained `Tempfile` so that a correct
27
+ # Content-Length header can be set
28
+ #
29
+ # @return [Integer]
30
+ def size
31
+ @tempfile.size
32
+ end
33
+
34
+ # Returns the path to the `Tempfile`, so that Rack::Sendfile can send this response
35
+ # using the downstream webserver
36
+ #
37
+ # @return [String]
38
+ def to_path
39
+ @tempfile.to_path
40
+ end
41
+
42
+ # Stream the file's contents if `Rack::Sendfile` isn't present.
43
+ #
44
+ # @return [void]
45
+ def each
46
+ @tempfile.rewind
47
+ while chunk = @tempfile.read(16384)
48
+ yield chunk
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,3 @@
1
1
  module Zipline
2
- VERSION = "1.5.0"
2
+ VERSION = "1.6.0"
3
3
  end
@@ -3,44 +3,35 @@
3
3
  module Zipline
4
4
  class ZipGenerator
5
5
  # takes an array of pairs [[uploader, filename], ... ]
6
- def initialize(files, **kwargs_for_new)
7
- @files = files
8
- @kwargs_for_new = kwargs_for_new
9
- end
10
-
11
- #this is supposed to be streamed!
12
- def to_s
13
- throw "stop!"
14
- end
15
-
16
- def each(&block)
17
- fake_io_writer = ZipTricks::BlockWrite.new(&block)
18
- # ZipTricks outputs lots of strings in rapid succession, and with
19
- # servers it can be beneficial to avoid doing too many tiny writes so that
20
- # the number of syscalls is minimized. See https://github.com/WeTransfer/zip_tricks/issues/78
21
- # There is a built-in facility for this in ZipTricks which can be used to implement
22
- # some cheap buffering here (it exists both in version 4 and version 5). The buffer is really
23
- # tiny and roughly equal to the medium Linux socket buffer size (16 KB). Although output
24
- # will be not so immediate with this buffering the overall performance will be better,
25
- # especially with multiple clients being serviced at the same time.
26
- # Note that the WriteBuffer writes the same, retained String object - but the contents
27
- # of that object changes between calls. This should work fine with servers where the
28
- # contents of the string gets written to a socket immediately before the execution inside
29
- # the WriteBuffer resumes), but if the strings get retained somewhere - like in an Array -
30
- # this might pose a problem. Unlikely that it will be an issue here though.
31
- write_buffer_size = 16 * 1024
32
- write_buffer = ZipTricks::WriteBuffer.new(fake_io_writer, write_buffer_size)
33
- ZipTricks::Streamer.open(write_buffer, **@kwargs_for_new) do |streamer|
34
- @files.each do |file, name, options = {}|
6
+ def initialize(files, **kwargs_for_streamer)
7
+ # Use RackBody as it has buffering built-in in zip_tricks 5.x+
8
+ @body = ZipTricks::RackBody.new(**kwargs_for_streamer) do |streamer|
9
+ files.each do |file, name, options = {}|
35
10
  handle_file(streamer, file, name.to_s, options)
36
11
  end
37
12
  end
38
- write_buffer.flush! # for any remaining writes
13
+ end
14
+
15
+ def each(&block)
16
+ return to_enum(:each) unless block_given?
17
+ @body.each(&block)
18
+ rescue => e
19
+ # Since most APM packages do not trace errors occurring within streaming
20
+ # Rack bodies, it can be helpful to print the error to the Rails log at least
21
+ error_message = "zipline: an exception (#{e.inspect}) was raised when serving the ZIP body."
22
+ error_message += " The error occurred when handling #{@filename.inspect}" if @filename
23
+ logger.error(error_message)
24
+ raise
39
25
  end
40
26
 
41
27
  def handle_file(streamer, file, name, options)
42
28
  file = normalize(file)
29
+
30
+ # Store the filename so that a sensible error message can be displayed in the log
31
+ # if writing this particular file fails
32
+ @filename = name
43
33
  write_file(streamer, file, name, options)
34
+ @filename = nil
44
35
  end
45
36
 
46
37
  # This extracts either a url or a local file from the provided file.
@@ -107,6 +98,16 @@ module Zipline
107
98
 
108
99
  private
109
100
 
101
+ def logger
102
+ # Rails is not defined in our tests, and might as well not be defined
103
+ # elsewhere - or the logger might not be configured correctly
104
+ if defined?(Rails.logger) && Rails.logger
105
+ Rails.logger
106
+ else
107
+ Logger.new($stderr)
108
+ end
109
+ end
110
+
110
111
  def is_active_storage_attachment?(file)
111
112
  defined?(ActiveStorage::Attachment) && file.is_a?(ActiveStorage::Attachment)
112
113
  end
data/lib/zipline.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'content_disposition'
2
- require "zipline/version"
2
+ require 'zipline/version'
3
3
  require 'zip_tricks'
4
- require "zipline/zip_generator"
4
+ require 'zipline/zip_generator'
5
+ require 'zipline/chunked_body'
6
+ require 'zipline/tempfile_body'
5
7
 
6
8
  # class MyController < ApplicationController
7
9
  # include Zipline
@@ -18,7 +20,36 @@ module Zipline
18
20
  headers['Content-Type'] = Mime::Type.lookup_by_extension('zip').to_s
19
21
  response.sending_file = true
20
22
  response.cache_control[:public] ||= false
21
- self.response_body = zip_generator
23
+
24
+ # Disables Rack::ETag if it is enabled (prevent buffering)
25
+ # see https://github.com/rack/rack/issues/1619#issuecomment-606315714
22
26
  self.response.headers['Last-Modified'] = Time.now.httpdate
27
+
28
+ if request.get_header("HTTP_VERSION") == "HTTP/1.0"
29
+ # If HTTP/1.0 is used it is not possible to stream, and if that happens it usually will be
30
+ # unclear why buffering is happening. Some info in the log is the least one can do.
31
+ logger.warn { "The downstream HTTP proxy/LB insists on HTTP/1.0 protocol, ZIP response will be buffered." } if logger
32
+
33
+ # If we use Rack::ContentLength it would run through our ZIP block twice - once to calculate the content length
34
+ # of the response, and once - to serve. We can trade performance for disk space and buffer the response into a Tempfile
35
+ # since we are already buffering.
36
+ tempfile_body = TempfileBody.new(request.env, zip_generator)
37
+ headers["Content-Length"] = tempfile_body.size.to_s
38
+ headers["X-Zipline-Output"] = "buffered"
39
+ self.response_body = tempfile_body
40
+ else
41
+ # Disable buffering for both nginx and Google Load Balancer, see
42
+ # https://cloud.google.com/appengine/docs/flexible/how-requests-are-handled?tab=python#x-accel-buffering
43
+ response.headers["X-Accel-Buffering"] = "no"
44
+
45
+ # Make sure Rack::ContentLength does not try to compute a content length,
46
+ # and remove the one already set
47
+ headers.delete("Content-Length")
48
+
49
+ # and send out in chunked encoding
50
+ headers["Transfer-Encoding"] = "chunked"
51
+ headers["X-Zipline-Output"] = "streamed"
52
+ self.response_body = Chunked.new(zip_generator)
53
+ end
23
54
  end
24
55
  end
@@ -185,91 +185,4 @@ describe Zipline::ZipGenerator do
185
185
  end
186
186
  end
187
187
  end
188
-
189
- describe '.write_file' do
190
- let(:file) { StringIO.new('passthrough') }
191
-
192
- context 'when passing an ActiveStorage::Filename object as filename' do
193
- let(:filename) { ActiveStorage::Filename.new('test') }
194
-
195
- let(:generator) do
196
- Zipline::ZipGenerator.new([[file, filename]])
197
- end
198
-
199
- it 'passes a string as filename to ZipTricks' do
200
- allow(file).to receive(:url).and_return('fakeurl')
201
- expect_any_instance_of(ZipTricks::Streamer).to receive(:write_deflated_file)
202
- .with('test')
203
- generator.each { |_| 'Test' }
204
- end
205
- end
206
- end
207
-
208
- describe 'passing an options hash' do
209
- let(:file) { StringIO.new('passthrough') }
210
-
211
- context 'with optional arguments' do
212
- let(:mtime) { 1.day.ago }
213
- let(:generator) do
214
- Zipline::ZipGenerator.new([[file, 'test', modification_time: mtime]])
215
- end
216
-
217
- it 'passes the options hash through handle_file' do
218
- expect(generator).to receive(:handle_file)
219
- .with(anything, anything, anything, { modification_time: mtime })
220
- generator.each { |_| 'Test' }
221
- end
222
-
223
- it 'passes the options hash to ZipTricks as kwargs' do
224
- allow(file).to receive(:url).and_return('fakeurl')
225
- expect_any_instance_of(ZipTricks::Streamer).to receive(:write_deflated_file)
226
- .with(anything, modification_time: mtime)
227
- generator.each { |_| 'Test' }
228
- end
229
- end
230
-
231
- context 'without optional arguments' do
232
- let(:generator) do
233
- Zipline::ZipGenerator.new([[file, 'test']])
234
- end
235
-
236
- it 'passes the options hash through handle_file' do
237
- expect(generator).to receive(:handle_file)
238
- .with(anything, anything, anything, {})
239
- generator.each { |_| 'Test' }
240
- end
241
-
242
- it 'passes the options hash to ZipTricks as kwargs' do
243
- allow(file).to receive(:url).and_return('fakeurl')
244
- expect_any_instance_of(ZipTricks::Streamer).to receive(:write_deflated_file)
245
- .with(anything)
246
- generator.each { |_| 'Test' }
247
- end
248
- end
249
-
250
- context 'with extra invalid options' do
251
- let(:mtime) { 1.day.ago }
252
- let(:generator) do
253
- Zipline::ZipGenerator.new([[file, 'test', modification_time: mtime, extra: 'invalid']])
254
- end
255
-
256
- it 'passes the whole options hash through handle_file' do
257
- expect(generator).to receive(:handle_file)
258
- .with(anything, anything, anything, { modification_time: mtime, extra: 'invalid' })
259
- generator.each { |_| 'Test' }
260
- end
261
-
262
- it 'only passes the kwargs to ZipTricks that it expects (i.e., :modification_time)' do
263
- allow(file).to receive(:url).and_return('fakeurl')
264
- expect_any_instance_of(ZipTricks::Streamer).to receive(:write_deflated_file)
265
- .with(anything, modification_time: mtime)
266
- generator.each { |_| 'Test' }
267
- end
268
- end
269
- end
270
- it 'passes along constructor options to ZipTricks streamer' do
271
- expect(ZipTricks::Streamer).to receive(:open).with(anything, { :some => 'options' })
272
- generator = Zipline::ZipGenerator.new([file, 'somefile'], :some => 'options')
273
- generator.each { |_| 'Test' }
274
- end
275
188
  end
@@ -1,29 +1,68 @@
1
1
  require 'spec_helper'
2
- require 'ostruct'
2
+ require 'action_controller'
3
3
 
4
4
  describe Zipline do
5
5
  before { Fog.mock! }
6
6
 
7
- let (:undertest) {
8
- class TestZipline
7
+ class FakeController < ActionController::Base
8
+ include Zipline
9
+ def download_zip
10
+ files = [
11
+ [StringIO.new("File content goes here"), "one.txt"],
12
+ [StringIO.new("Some other content goes here"), "two.txt"]
13
+ ]
14
+ zipline(files, 'myfiles.zip', auto_rename_duplicate_filenames: false)
15
+ end
9
16
 
10
- attr_accessor :headers
11
- attr_accessor :response
12
- attr_accessor :response_body
13
- def initialize
14
- @headers = {}
15
- @response = OpenStruct.new(:cache_control => {}, :headers => {} )
17
+ class FailingIO < StringIO
18
+ def read(*)
19
+ raise "Something wonky"
16
20
  end
17
- include Zipline
18
- end
19
- return TestZipline.new()
20
- }
21
+ end
21
22
 
23
+ def download_zip_with_error_during_streaming
24
+ files = [
25
+ [StringIO.new("File content goes here"), "one.txt"],
26
+ [FailingIO.new("This will fail half-way"), "two.txt"]
27
+ ]
28
+ zipline(files, 'myfiles.zip', auto_rename_duplicate_filenames: false)
29
+ end
30
+ end
31
+
32
+ it 'passes keyword parameters to ZipTricks::Streamer' do
33
+ fake_rack_env = {
34
+ "HTTP_VERSION" => "HTTP/1.0",
35
+ "REQUEST_METHOD" => "GET",
36
+ "SCRIPT_NAME" => "",
37
+ "PATH_INFO" => "/download",
38
+ "QUERY_STRING" => "",
39
+ "SERVER_NAME" => "host.example",
40
+ "rack.input" => StringIO.new,
41
+ }
42
+ expect(ZipTricks::Streamer).to receive(:new).with(anything, auto_rename_duplicate_filenames: false).and_call_original
43
+
44
+ status, headers, body = FakeController.action(:download_zip).call(fake_rack_env)
45
+
46
+ expect(headers['Content-Disposition']).to eq("attachment; filename=\"myfiles.zip\"; filename*=UTF-8''myfiles.zip")
47
+ end
48
+
49
+ it 'sends the exception raised in the streaming body to the Rails logger' do
50
+ fake_rack_env = {
51
+ "HTTP_VERSION" => "HTTP/1.0",
52
+ "REQUEST_METHOD" => "GET",
53
+ "SCRIPT_NAME" => "",
54
+ "PATH_INFO" => "/download",
55
+ "QUERY_STRING" => "",
56
+ "SERVER_NAME" => "host.example",
57
+ "rack.input" => StringIO.new,
58
+ }
59
+ expect(ZipTricks::Streamer).to receive(:new).with(anything, auto_rename_duplicate_filenames: false).and_call_original
60
+ fake_logger = double()
61
+ expect(Logger).to receive(:new).and_return(fake_logger)
62
+ expect(fake_logger).to receive(:error).with(instance_of(String))
22
63
 
23
- it 'passes arguments along' do
24
- expect(Zipline::ZipGenerator).to receive(:new)
25
- .with(['some', 'fake', 'files'], { some: 'options' })
26
- undertest.zipline(['some', 'fake', 'files'], 'myfiles.zip', some: 'options')
27
- expect(undertest.headers['Content-Disposition']).to eq("attachment; filename=\"myfiles.zip\"; filename*=UTF-8''myfiles.zip")
64
+ expect {
65
+ FakeController.action(:download_zip_with_error_during_streaming).call(fake_rack_env)
66
+ }.to raise_error(/Something wonky/)
28
67
  end
29
68
  end
data/spec/spec_helper.rb CHANGED
@@ -8,17 +8,16 @@ require 'paperclip'
8
8
  require 'fog-aws'
9
9
  require 'carrierwave'
10
10
 
11
- Dir["#{File.expand_path('..', __FILE__)}/support/**/*.rb"].each { |f| require f }
11
+ Dir["#{File.expand_path('..', __FILE__)}/support/**/*.rb"].sort.each { |f| require f }
12
12
 
13
13
  CarrierWave.configure do |config|
14
14
  config.fog_provider = 'fog/aws'
15
15
  config.fog_credentials = {
16
- provider: 'AWS',
17
- aws_access_key_id: 'dummy',
18
- aws_secret_access_key: 'data',
19
- region: 'us-west-2',
20
- }
21
-
16
+ provider: 'AWS',
17
+ aws_access_key_id: 'dummy',
18
+ aws_secret_access_key: 'data',
19
+ region: 'us-west-2',
20
+ }
22
21
  end
23
22
 
24
23
  RSpec.configure do |config|
data/zipline.gemspec CHANGED
@@ -20,8 +20,15 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.add_dependency 'actionpack', ['>= 6.0', '< 8.0']
22
22
  gem.add_dependency 'content_disposition', '~> 1.0'
23
- gem.add_dependency 'zip_tricks', ['>= 4.2.1', '< 6.0']
23
+ gem.add_dependency 'zip_tricks', ['~> 4.8', '< 6'] # Minimum to 4.8.3 which is the last-released MIT version
24
+
25
+ gem.add_development_dependency 'rspec', '~> 3'
26
+ gem.add_development_dependency 'fog-aws'
27
+ gem.add_development_dependency 'aws-sdk-s3'
28
+ gem.add_development_dependency 'carrierwave'
29
+ gem.add_development_dependency 'paperclip'
30
+ gem.add_development_dependency 'rake'
24
31
 
25
32
  # https://github.com/rspec/rspec-mocks/issues/1457
26
- gem.add_development_dependency 'rspec-mocks', '3.10.2'
33
+ gem.add_development_dependency 'rspec-mocks', '~> 3.12'
27
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zipline
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ram Dobson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-24 00:00:00.000000000 Z
11
+ date: 2024-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -48,36 +48,120 @@ dependencies:
48
48
  name: zip_tricks
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - ">="
51
+ - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: 4.2.1
53
+ version: '4.8'
54
54
  - - "<"
55
55
  - !ruby/object:Gem::Version
56
- version: '6.0'
56
+ version: '6'
57
57
  type: :runtime
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - ">="
61
+ - - "~>"
62
62
  - !ruby/object:Gem::Version
63
- version: 4.2.1
63
+ version: '4.8'
64
64
  - - "<"
65
65
  - !ruby/object:Gem::Version
66
- version: '6.0'
66
+ version: '6'
67
+ - !ruby/object:Gem::Dependency
68
+ name: rspec
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '3'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '3'
81
+ - !ruby/object:Gem::Dependency
82
+ name: fog-aws
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: aws-sdk-s3
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: carrierwave
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: paperclip
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ - !ruby/object:Gem::Dependency
138
+ name: rake
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
67
151
  - !ruby/object:Gem::Dependency
68
152
  name: rspec-mocks
69
153
  requirement: !ruby/object:Gem::Requirement
70
154
  requirements:
71
- - - '='
155
+ - - "~>"
72
156
  - !ruby/object:Gem::Version
73
- version: 3.10.2
157
+ version: '3.12'
74
158
  type: :development
75
159
  prerelease: false
76
160
  version_requirements: !ruby/object:Gem::Requirement
77
161
  requirements:
78
- - - '='
162
+ - - "~>"
79
163
  - !ruby/object:Gem::Version
80
- version: 3.10.2
164
+ version: '3.12'
81
165
  description: a module for streaming dynamically generated zip files
82
166
  email:
83
167
  - ram.dobson@solsystemscompany.com
@@ -91,6 +175,8 @@ files:
91
175
  - README.md
92
176
  - Rakefile
93
177
  - lib/zipline.rb
178
+ - lib/zipline/chunked_body.rb
179
+ - lib/zipline/tempfile_body.rb
94
180
  - lib/zipline/version.rb
95
181
  - lib/zipline/zip_generator.rb
96
182
  - spec/fakefile.txt
@@ -117,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
203
  - !ruby/object:Gem::Version
118
204
  version: '0'
119
205
  requirements: []
120
- rubygems_version: 3.3.11
206
+ rubygems_version: 3.3.26
121
207
  signing_key:
122
208
  specification_version: 4
123
209
  summary: stream zip files from rails