zipline 1.0.1 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -0
- data/Gemfile +2 -1
- data/README.md +74 -33
- data/Rakefile +2 -0
- data/lib/zipline.rb +3 -4
- data/lib/zipline/version.rb +1 -1
- data/lib/zipline/zip_generator.rb +35 -11
- data/spec/lib/zipline/zip_generator_spec.rb +110 -3
- data/zipline.gemspec +3 -3
- metadata +11 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60e9847b29f301c7d2a2b7ff9614454f0e3a1193
|
4
|
+
data.tar.gz: b9b391b9478f96f0fa067918e4368a5415332123
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e25c8bba3c7b7d6c093637c1323d0a4bb6660e94457e4445cc3e6798f7125e8c5c0b99a334de0430ef4ced180e00a4001c29b6ab9c92525dd0a6eca689b0dbe7
|
7
|
+
data.tar.gz: db9b45bfb987cf97f025c4c2cbeb818a821bd7968524df56ac44d6eaebea4b8420b634815b6a6cde8ff32976e67b20183f8dfa404a0ffa05b91aef824c645a79
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -4,7 +4,6 @@ source 'https://rubygems.org'
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :development, :test do
|
7
|
-
gem 'rubyzip', '~> 1.2'
|
8
7
|
gem 'rspec', '~> 3'
|
9
8
|
gem 'fog'
|
10
9
|
gem 'fog-aws'
|
@@ -12,4 +11,6 @@ group :development, :test do
|
|
12
11
|
gem 'aws-sdk'
|
13
12
|
gem 'carrierwave'
|
14
13
|
gem 'paperclip'
|
14
|
+
gem 'rake'
|
15
|
+
gem 'rake-rspec'
|
15
16
|
end
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# Zipline
|
2
|
+
[![Build Status](https://travis-ci.org/fringd/zipline.svg?branch=master)](https://travis-ci.org/fringd/zipline)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/zipline.svg)](https://badge.fury.io/rb/zipline)
|
2
4
|
|
3
5
|
A gem to stream dynamically generated zip files from a rails application. Unlike other solutions that generate zips for user download, zipline does not wait for the entire zip file to be created (or even for the entire input file in the cloud to be downloaded) before it begins sending the zip file to the user. It does this by never seeking backwards during zip creation, and streaming the zip file over http as it is constructed. The advantages of this are:
|
4
6
|
|
@@ -19,45 +21,84 @@ And then execute:
|
|
19
21
|
|
20
22
|
## Usage
|
21
23
|
|
22
|
-
Set up some models with [
|
23
|
-
|
24
|
-
[
|
25
|
-
storage and S3 are supported in the case of
|
26
|
-
[paperclip](https://github.com/thoughtbot/paperclip). [Mutiple file storages](http://shrinerb.com/#external) are
|
24
|
+
Set up some models with [ActiveStorage](http://edgeguides.rubyonrails.org/active_storage_overview.html)
|
25
|
+
[carrierwave](https://github.com/jnicklas/carrierwave), [paperclip](https://github.com/thoughtbot/paperclip), or
|
26
|
+
[shrine](https://github.com/janko-m/shrine). Right now only plain file storage and S3 are supported in the case of
|
27
|
+
[carrierwave](https://github.com/jnicklas/carrierwave) and only plain file storage and S3 are supported in the case of
|
28
|
+
[paperclip](https://github.com/thoughtbot/paperclip). [Mutiple file storages](http://shrinerb.com/#external) are
|
29
|
+
supported with [shrine](https://github.com/janko-m/shrine).
|
27
30
|
|
28
31
|
You'll need to be using puma or some other server that supports streaming output.
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
33
|
+
```Ruby
|
34
|
+
class MyController < ApplicationController
|
35
|
+
# enable zipline
|
36
|
+
include Zipline
|
37
|
+
|
38
|
+
def index
|
39
|
+
users = User.all
|
40
|
+
# you can replace user.avatar with any stream or any object that
|
41
|
+
# responds to :url, :path or :file.
|
42
|
+
# :modification_time is an optional third argument you can use.
|
43
|
+
files = users.map{ |user| [user.avatar, "#{user.username}.png", modification_time: 1.day.ago] }
|
44
|
+
zipline(files, 'avatars.zip')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### ActiveStorage
|
50
|
+
|
51
|
+
```Ruby
|
52
|
+
users = User.all
|
53
|
+
files = users.map{ |user| [user.avatar, user.avatar.filename] }
|
54
|
+
zipline(files, 'avatars.zip')
|
55
|
+
```
|
56
|
+
|
57
|
+
### Carrierwave
|
58
|
+
|
59
|
+
```Ruby
|
60
|
+
users = User.all
|
61
|
+
files = users.map{ |user| [user.avatar, user.avatar_identifier] }
|
62
|
+
zipline(files, 'avatars.zip')
|
63
|
+
```
|
64
|
+
|
65
|
+
### Paperclip ([deprecated](https://thoughtbot.com/blog/closing-the-trombone))
|
66
|
+
|
67
|
+
```Ruby
|
68
|
+
users = User.all
|
69
|
+
files = users.map{ |user| [user.avatar, user.avatar_file_name] }
|
70
|
+
zipline(files, 'avatars.zip')
|
71
|
+
```
|
72
|
+
|
73
|
+
### Url
|
74
|
+
|
75
|
+
If you know the URL of the remote file you want to include, you can just pass in the
|
76
|
+
URL directly in place of the attachment object.
|
77
|
+
```Ruby
|
78
|
+
avatars = [
|
79
|
+
['http://www.example.com/user1.png', 'user1.png']
|
80
|
+
['http://www.example.com/user2.png', 'user2.png']
|
81
|
+
['http://www.example.com/user3.png', 'user3.png']
|
82
|
+
]
|
83
|
+
zipline(avatars, 'avatars.zip')
|
84
|
+
```
|
85
|
+
|
86
|
+
### Directories
|
44
87
|
|
45
88
|
For directories, just give the files names like "directory/file".
|
46
89
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
zipline(file_mappings, 'avatars.zip')
|
60
|
-
|
90
|
+
|
91
|
+
```Ruby
|
92
|
+
avatars = [
|
93
|
+
# remote_url zip_path zip_tricks_options
|
94
|
+
[ 'http://www.example.com/user1.png', 'avatars/user1.png', modification_time: Time.now.utc ]
|
95
|
+
[ 'http://www.example.com/user2.png', 'avatars/user2.png', modification_time: 1.day.ago ]
|
96
|
+
[ 'http://www.example.com/user3.png', 'avatars/user3.png' ]
|
97
|
+
]
|
98
|
+
|
99
|
+
zipline(avatars, 'avatars.zip')
|
100
|
+
```
|
101
|
+
|
61
102
|
## Contributing
|
62
103
|
|
63
104
|
1. Fork it
|
data/Rakefile
CHANGED
data/lib/zipline.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
require "zipline/version"
|
2
|
-
require 'curb'
|
3
2
|
require 'zip_tricks'
|
4
3
|
require "zipline/zip_generator"
|
5
4
|
|
6
5
|
# class MyController < ApplicationController
|
7
6
|
# include Zipline
|
8
7
|
# def index
|
9
|
-
# users= User.all
|
10
|
-
# files =
|
11
|
-
# zipline(
|
8
|
+
# users = User.all
|
9
|
+
# files = users.map{ |user| [user.avatar, "#{user.username}.png", modification_time: 1.day.ago] }
|
10
|
+
# zipline(files, 'avatars.zip')
|
12
11
|
# end
|
13
12
|
# end
|
14
13
|
module Zipline
|
data/lib/zipline/version.rb
CHANGED
@@ -15,13 +15,15 @@ module Zipline
|
|
15
15
|
def each(&block)
|
16
16
|
fake_io_writer = ZipTricks::BlockWrite.new(&block)
|
17
17
|
ZipTricks::Streamer.open(fake_io_writer) do |streamer|
|
18
|
-
@files.each
|
18
|
+
@files.each do |file, name, options = {}|
|
19
|
+
handle_file(streamer, file, name, options)
|
20
|
+
end
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
def handle_file(streamer, file, name)
|
24
|
+
def handle_file(streamer, file, name, options)
|
23
25
|
file = normalize(file)
|
24
|
-
write_file(streamer, file, name)
|
26
|
+
write_file(streamer, file, name, options)
|
25
27
|
end
|
26
28
|
|
27
29
|
# This extracts either a url or a local file from the provided file.
|
@@ -44,31 +46,38 @@ module Zipline
|
|
44
46
|
{file: File.open(file.path)}
|
45
47
|
elsif is_io?(file)
|
46
48
|
{file: file}
|
49
|
+
elsif defined?(ActiveStorage::Blob) && file.is_a?(ActiveStorage::Blob)
|
50
|
+
{blob: file}
|
51
|
+
elsif is_active_storage_attachment?(file) || is_active_storage_one?(file)
|
52
|
+
{blob: file.blob}
|
47
53
|
elsif file.respond_to? :url
|
48
54
|
{url: file.url}
|
49
55
|
elsif file.respond_to? :path
|
50
56
|
{file: File.open(file.path)}
|
51
57
|
elsif file.respond_to? :file
|
52
58
|
{file: File.open(file.file)}
|
59
|
+
elsif is_url?(file)
|
60
|
+
{url: file}
|
53
61
|
else
|
54
62
|
raise(ArgumentError, 'Bad File/Stream')
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
58
|
-
def write_file(streamer, file, name)
|
59
|
-
streamer.write_deflated_file(name) do |writer_for_file|
|
66
|
+
def write_file(streamer, file, name, options)
|
67
|
+
streamer.write_deflated_file(name, options) do |writer_for_file|
|
60
68
|
if file[:url]
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
69
|
+
the_remote_uri = URI(file[:url])
|
70
|
+
|
71
|
+
Net::HTTP.get_response(the_remote_uri) do |response|
|
72
|
+
response.read_body do |chunk|
|
73
|
+
writer_for_file << chunk
|
66
74
|
end
|
67
75
|
end
|
68
|
-
c.perform
|
69
76
|
elsif file[:file]
|
70
77
|
IO.copy_stream(file[:file], writer_for_file)
|
71
78
|
file[:file].close
|
79
|
+
elsif file[:blob]
|
80
|
+
file[:blob].download { |chunk| writer_for_file << chunk }
|
72
81
|
else
|
73
82
|
raise(ArgumentError, 'Bad File/Stream')
|
74
83
|
end
|
@@ -78,5 +87,20 @@ module Zipline
|
|
78
87
|
def is_io?(io_ish)
|
79
88
|
io_ish.respond_to? :read
|
80
89
|
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def is_active_storage_attachment?(file)
|
94
|
+
defined?(ActiveStorage::Attachment) && file.is_a?(ActiveStorage::Attachment)
|
95
|
+
end
|
96
|
+
|
97
|
+
def is_active_storage_one?(file)
|
98
|
+
defined?(ActiveStorage::Attached::One) && file.is_a?(ActiveStorage::Attached::One)
|
99
|
+
end
|
100
|
+
|
101
|
+
def is_url?(url)
|
102
|
+
url = URI.parse(url) rescue false
|
103
|
+
url.kind_of?(URI::HTTP) || url.kind_of?(URI::HTTPS)
|
104
|
+
end
|
81
105
|
end
|
82
106
|
end
|
@@ -2,7 +2,6 @@ require 'spec_helper'
|
|
2
2
|
require 'tempfile'
|
3
3
|
|
4
4
|
describe Zipline::ZipGenerator do
|
5
|
-
|
6
5
|
before { Fog.mock! }
|
7
6
|
let(:file_attributes){ {
|
8
7
|
key: 'fog_file_tests',
|
@@ -67,7 +66,7 @@ describe Zipline::ZipGenerator do
|
|
67
66
|
end
|
68
67
|
end
|
69
68
|
context "Paperclip" do
|
70
|
-
context "Local" do
|
69
|
+
context "Local" do
|
71
70
|
let(:file){ Paperclip::Attachment.new(:name, :instance) }
|
72
71
|
it "creates a File" do
|
73
72
|
allow(file).to receive(:path).and_return('spec/fakefile.txt')
|
@@ -76,7 +75,7 @@ describe Zipline::ZipGenerator do
|
|
76
75
|
expect(normalized[:file]).to be_a File
|
77
76
|
end
|
78
77
|
end
|
79
|
-
context "Remote" do
|
78
|
+
context "Remote" do
|
80
79
|
let(:file){ Paperclip::Attachment.new(:name, :instance, storage: :s3) }
|
81
80
|
it "creates a URL" do
|
82
81
|
allow(file).to receive(:expiring_url).and_return('fakeurl')
|
@@ -85,6 +84,72 @@ describe Zipline::ZipGenerator do
|
|
85
84
|
end
|
86
85
|
end
|
87
86
|
end
|
87
|
+
context "ActiveStorage" do
|
88
|
+
module ActiveStorage
|
89
|
+
class Attached
|
90
|
+
class One < Attached
|
91
|
+
end
|
92
|
+
end
|
93
|
+
class Attachment; end
|
94
|
+
class Blob; end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "Attached::One" do
|
98
|
+
it "get blob" do
|
99
|
+
attached = create_attached_one
|
100
|
+
allow_any_instance_of(Object).to receive(:defined?).and_return(true)
|
101
|
+
|
102
|
+
normalized = generator.normalize(attached)
|
103
|
+
|
104
|
+
expect(normalized.keys).to include(:blob)
|
105
|
+
expect(normalized[:blob]).to be_a(ActiveStorage::Blob)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "Attachment" do
|
110
|
+
it "get blob" do
|
111
|
+
attachment = create_attachment
|
112
|
+
allow_any_instance_of(Object).to receive(:defined?).and_return(true)
|
113
|
+
|
114
|
+
normalized = generator.normalize(attachment)
|
115
|
+
|
116
|
+
expect(normalized.keys).to include(:blob)
|
117
|
+
expect(normalized[:blob]).to be_a(ActiveStorage::Blob)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "Blob" do
|
122
|
+
it "get blob" do
|
123
|
+
blob = create_blob
|
124
|
+
allow_any_instance_of(Object).to receive(:defined?).and_return(true)
|
125
|
+
|
126
|
+
normalized = generator.normalize(blob)
|
127
|
+
|
128
|
+
expect(normalized.keys).to include(:blob)
|
129
|
+
expect(normalized[:blob]).to be_a(ActiveStorage::Blob)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def create_attached_one
|
134
|
+
attached = ActiveStorage::Attached::One.new
|
135
|
+
blob = create_blob
|
136
|
+
allow(attached).to receive(:blob).and_return(blob)
|
137
|
+
attached
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_attachment
|
141
|
+
attachment = ActiveStorage::Attachment.new
|
142
|
+
blob = create_blob
|
143
|
+
allow(attachment).to receive(:blob).and_return(blob)
|
144
|
+
attachment
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_blob
|
148
|
+
blob = ActiveStorage::Blob.new
|
149
|
+
allow(blob).to receive(:service_url).and_return('fakeurl')
|
150
|
+
blob
|
151
|
+
end
|
152
|
+
end
|
88
153
|
context "Fog" do
|
89
154
|
it "extracts url" do
|
90
155
|
allow(file).to receive(:url).and_return('fakeurl')
|
@@ -106,4 +171,46 @@ describe Zipline::ZipGenerator do
|
|
106
171
|
end
|
107
172
|
end
|
108
173
|
|
174
|
+
describe 'passing an options hash' do
|
175
|
+
let(:file) { StringIO.new('passthrough') }
|
176
|
+
|
177
|
+
context 'with optional arguments' do
|
178
|
+
let(:mtime) { 1.day.ago }
|
179
|
+
let(:generator) do
|
180
|
+
Zipline::ZipGenerator.new([[file, 'test', modification_time: mtime]])
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'passes the options hash through handle_file' do
|
184
|
+
expect(generator).to receive(:handle_file)
|
185
|
+
.with(anything, anything, anything, modification_time: mtime)
|
186
|
+
generator.each { |_| 'Test' }
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'passes the options hash to ZipTricks' do
|
190
|
+
allow(file).to receive(:url).and_return('fakeurl')
|
191
|
+
expect_any_instance_of(ZipTricks::Streamer).to receive(:write_deflated_file)
|
192
|
+
.with(anything, modification_time: mtime)
|
193
|
+
generator.each { |_| 'Test' }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'without optional arguments' do
|
198
|
+
let(:generator) do
|
199
|
+
Zipline::ZipGenerator.new([[file, 'test']])
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'passes the options hash through handle_file' do
|
203
|
+
expect(generator).to receive(:handle_file)
|
204
|
+
.with(anything, anything, anything, {})
|
205
|
+
generator.each { |_| 'Test' }
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'passes the options hash to ZipTricks' do
|
209
|
+
allow(file).to receive(:url).and_return('fakeurl')
|
210
|
+
expect_any_instance_of(ZipTricks::Streamer).to receive(:write_deflated_file)
|
211
|
+
.with(anything, {})
|
212
|
+
generator.each { |_| 'Test' }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
109
216
|
end
|
data/zipline.gemspec
CHANGED
@@ -14,8 +14,8 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.name = "zipline"
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Zipline::VERSION
|
17
|
+
gem.licenses = ['MIT']
|
17
18
|
|
18
|
-
gem.add_dependency 'zip_tricks', ['>= 4.2.1', '
|
19
|
-
gem.add_dependency 'rails', ['>= 3.2.1', '<
|
20
|
-
gem.add_dependency 'curb', ['>= 0.8.0', '< 0.10']
|
19
|
+
gem.add_dependency 'zip_tricks', ['>= 4.2.1', '< 6.0']
|
20
|
+
gem.add_dependency 'rails', ['>= 3.2.1', '< 6.1']
|
21
21
|
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.
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ram Dobson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zip_tricks
|
@@ -17,9 +17,9 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 4.2.1
|
20
|
-
- - "
|
20
|
+
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
version: '6.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,9 +27,9 @@ dependencies:
|
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 4.2.1
|
30
|
-
- - "
|
30
|
+
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: '6.0'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: rails
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
version: 3.2.1
|
40
40
|
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '
|
42
|
+
version: '6.1'
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,27 +49,7 @@ dependencies:
|
|
49
49
|
version: 3.2.1
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: '
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: curb
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - ">="
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: 0.8.0
|
60
|
-
- - "<"
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0.10'
|
63
|
-
type: :runtime
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 0.8.0
|
70
|
-
- - "<"
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
version: '0.10'
|
52
|
+
version: '6.1'
|
73
53
|
description: a module for streaming dynamically generated zip files
|
74
54
|
email:
|
75
55
|
- ram.dobson@solsystemscompany.com
|
@@ -77,6 +57,7 @@ executables: []
|
|
77
57
|
extensions: []
|
78
58
|
extra_rdoc_files: []
|
79
59
|
files:
|
60
|
+
- ".travis.yml"
|
80
61
|
- Gemfile
|
81
62
|
- LICENSE
|
82
63
|
- README.md
|
@@ -89,7 +70,8 @@ files:
|
|
89
70
|
- spec/spec_helper.rb
|
90
71
|
- zipline.gemspec
|
91
72
|
homepage: http://github.com/fringd/zipline
|
92
|
-
licenses:
|
73
|
+
licenses:
|
74
|
+
- MIT
|
93
75
|
metadata: {}
|
94
76
|
post_install_message:
|
95
77
|
rdoc_options: []
|