zipline 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3dced1a62ccd13637dce8896c6809922661aa25d
4
+ data.tar.gz: 32b3a8cb4809dedd83248501f8ac2eb973482a39
5
+ SHA512:
6
+ metadata.gz: 89bf143bc7f27eddf9cba7064d2b43a248b594a35363643796ece7a51bdf96dcc1a61fa2f5fcd946f3b505b5afb2b1be8c5e189d107ddbfa44251f7b355bd414
7
+ data.tar.gz: 032abbeebc338a1959f95d35a3fd21e43d1a06190445bf2861387b53e5b2916f4b56d0157b299b9670f432f47e5983d6c28ed4655d0bdc19ba0e7f4cc3dd269e
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zipline.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ram Dobson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Zipline
2
+
3
+ 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
+
5
+ - Removes need for large disk space or memory allocation to generate zips, even huge zips. So it works on Heroku.
6
+ - The user begins downloading immediately, which decreaceses latency, download time, and timeouts on Heroku.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'zipline'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ ## Usage
19
+
20
+ set up some models with [carrierwave](https://github.com/jnicklas/carrierwave) or [paperclip](https://github.com/thoughtbot/paperclip).
21
+ Right now only plain file storage and S3 are supported in the case of [carrierwave](https://github.com/jnicklas/carrierwave) and only plain file storage, not S3 in the case of [paperclip](https://github.com/thoughtbot/paperclip). Alternatively, you can pass in plain old File objects. Not to be judgy, but plain old file objects are probably not the best option most of the time.
22
+
23
+ You'll need to be using [unicorn](http://unicorn.bogomips.org/) or rainbows or some other server that supports streaming output.
24
+
25
+ class MyController < ApplicationController
26
+ # enable streaming responses
27
+ include ActionController::Streaming
28
+ # enable zipline
29
+ include Zipline
30
+
31
+ def index
32
+ users= User.all
33
+ files = users.map{ |user| [user.avatar, "#{user.username}.png"] }
34
+ zipline( files, 'avatars.zip')
35
+ end
36
+ end
37
+
38
+ For directories, just give the files names like "directory/file".
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
47
+
48
+ ## TODO (possible contributions?)
49
+
50
+ * tests!
51
+ * support rails 4.0 streaming
52
+ * extract library for plain ruby streaming zips, which this will depend on.
53
+ * get my changes to support streaming zips checked in to the rubyzip library.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/lib/zipline.rb ADDED
@@ -0,0 +1,28 @@
1
+ require "zipline/version"
2
+
3
+ require 'zip'
4
+ require 'curb'
5
+
6
+ require "zipline/fake_stream"
7
+ require "zipline/output_stream"
8
+ require "zipline/zip_generator"
9
+
10
+ # class MyController < ApplicationController
11
+ # include Zipline
12
+ # def index
13
+ # users= User.all
14
+ # files = users.map{ |user| [user.avatar, "#{user.username}.png"] }
15
+ # zipline( files, 'avatars.zip')
16
+ # end
17
+ # end
18
+ module Zipline
19
+ def zipline(files, zipname = 'zipline.zip')
20
+ zip_generator = ZipGenerator.new(files)
21
+ headers['Content-Disposition'] = "attachment; filename=#{zipname}"
22
+ headers['Content-Type'] = Mime::Type.lookup_by_extension('zip').to_s
23
+ response.sending_file = true
24
+ response.cache_control[:public] ||= false
25
+ self.response_body = zip_generator
26
+ self.response.headers['Last-Modified'] = Time.now.to_s
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ #this is a class that acts like an IO::Stream, but really puts to the browser
2
+ module Zipline
3
+ class FakeStream
4
+
5
+ # &block is the block that each gets from rails... we pass it strings to send data
6
+ def initialize(&block)
7
+ @block = block
8
+ @pos = 0
9
+ end
10
+
11
+ def tell
12
+ @pos
13
+ end
14
+
15
+ def pos
16
+ @pos
17
+ end
18
+
19
+ def seek
20
+ throw :fit
21
+ end
22
+
23
+ def pos=
24
+ throw :fit
25
+ end
26
+
27
+ def to_s
28
+ throw :fit
29
+ end
30
+
31
+ def <<(x)
32
+ return if x.nil?
33
+ throw "bad class #{x.class}" unless x.class == String
34
+ @pos += x.bytesize
35
+ @block.call(x.to_s)
36
+ end
37
+
38
+ def close
39
+ nil
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,51 @@
1
+ # a ZipOutputStream that never rewinds output
2
+ # in order for that to be possible we store only uncompressed files
3
+ module Zipline
4
+ class OutputStream < Zip::OutputStream
5
+
6
+ #we need to be able to hand out own custom output in order to stream to browser
7
+ def initialize(io)
8
+ # Create an io stream thing
9
+ super StringIO.new, true
10
+ # Overwrite it with my own
11
+ @output_stream = io
12
+ end
13
+
14
+ def stream
15
+ @output_stream
16
+ end
17
+
18
+ def put_next_entry(entry_name, size)
19
+ new_entry = Zip::Entry.new(@file_name, entry_name)
20
+ new_entry.size = size
21
+
22
+ #THIS IS THE MAGIC, tells zip to look after data for size, crc
23
+ new_entry.gp_flags = new_entry.gp_flags | 0x0008
24
+
25
+ super(new_entry)
26
+ end
27
+
28
+ # just reset state, no rewinding required
29
+ def finalize_current_entry
30
+ if current_entry
31
+ entry = current_entry
32
+ super
33
+ write_local_footer(entry)
34
+ end
35
+ end
36
+
37
+ def write_local_footer(entry)
38
+ @output_stream << [ 0x08074b50, entry.crc, entry.compressed_size, entry.size].pack('VVVV')
39
+ end
40
+
41
+ #never need to do this because we set correct sizes up front
42
+ def update_local_headers
43
+ nil
44
+ end
45
+
46
+ # helper to deal with difference between rubyzip 1.0 and 1.1
47
+ def current_entry
48
+ @currentEntry || @current_entry
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module Zipline
2
+ VERSION = "0.0.7"
3
+ end
@@ -0,0 +1,113 @@
1
+ # this class acts as a streaming body for rails
2
+ # initialize it with an array of the files you want to zip
3
+ # right now only carrierwave is supported with file storage or S3
4
+ module Zipline
5
+ class ZipGenerator
6
+ # takes an array of pairs [[uploader, filename], ... ]
7
+ def initialize(files)
8
+ @files = files
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
+ output = new_output(&block)
18
+ OutputStream.open(output) do |zip|
19
+ @files.each do |file, name|
20
+ file = file.file if file.respond_to? :file
21
+
22
+ #normalize file
23
+ if file.class.to_s == 'CarrierWave::Storage::Fog::File'
24
+ file = file.send(:file)
25
+ end
26
+ if file.class.to_s == 'CarrierWave::SanitizedFile'
27
+ path = file.send(:file)
28
+ file = File.open(path)
29
+ end
30
+ if file.class.to_s == 'Paperclip::Attachment'
31
+ path = file.send(:path)
32
+ file = File.open(path)
33
+ end
34
+ throw "bad_file" unless %w{Fog::Storage::AWS::File File}.include? file.class.to_s
35
+
36
+ name = uniquify_name(name)
37
+ write_file(zip, file, name)
38
+ end
39
+ end
40
+ end
41
+
42
+ def new_output(&block)
43
+ FakeStream.new(&block)
44
+ end
45
+
46
+ def write_file(zip, file, name)
47
+ size = get_size(file)
48
+
49
+ zip.put_next_entry name, size
50
+
51
+ if file.is_a? File
52
+ while buffer = file.read(2048)
53
+ zip << buffer
54
+ end
55
+ else
56
+ the_remote_url = file.url(Time.now + 1.minutes)
57
+ c = Curl::Easy.new(the_remote_url) do |curl|
58
+ curl.on_body do |data|
59
+ zip << data
60
+ data.bytesize
61
+ end
62
+ end
63
+ c.perform
64
+ end
65
+ end
66
+
67
+ def get_size(file)
68
+ case file.class.to_s
69
+ when 'File'
70
+ file.size
71
+ when 'Fog::Storage::AWS::FILE'
72
+ file.content_length
73
+ end
74
+ end
75
+
76
+ def uniquify_name(name)
77
+ @used_names ||= Set.new
78
+
79
+
80
+ if @used_names.include?(name)
81
+
82
+ #remove suffix e.g. ".foo"
83
+ parts = name.split '.'
84
+ name, extension =
85
+ if parts.length == 1
86
+ #no suffix, e.g. README
87
+ parts << ''
88
+ else
89
+ extension = parts.pop
90
+ [parts.join('.'), ".#{extension}"]
91
+ end
92
+
93
+ #trailing _#{number}
94
+ pattern = /_(\d+)$/
95
+
96
+ unless name.match pattern
97
+ name = "#{name}_1"
98
+ end
99
+
100
+ while @used_names.include? name + extension
101
+ #increment trailing number
102
+ name = name.sub( pattern ) { |x| "_#{$1.to_i + 1}" }
103
+ end
104
+
105
+ #reattach suffix
106
+ name += extension
107
+ end
108
+
109
+ @used_names << name
110
+ name
111
+ end
112
+ end
113
+ end
data/zipline.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/zipline/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Ram Dobson"]
6
+ gem.email = ["ram.dobson@solsystemscompany.com"]
7
+ gem.description = %q{this is a giant pile of hax that may let you stream dynamically generated zip files}
8
+ gem.summary = %q{stream zip files from rails}
9
+ gem.homepage = "http://github.com/fringd/zipline"
10
+
11
+ gem.files = `git ls-files`.split($\) - %w{.gitignore}
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "zipline"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Zipline::VERSION
17
+
18
+ gem.add_dependency 'rubyzip', ['>= 1.0', '<= 1.1.2']
19
+ gem.add_dependency 'rails', '>= 3.2.1'
20
+ gem.add_dependency 'curb'
21
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zipline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Ram Dobson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubyzip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ - - <=
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
30
+ - - <=
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: rails
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: 3.2.1
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 3.2.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: curb
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ description: this is a giant pile of hax that may let you stream dynamically generated
62
+ zip files
63
+ email:
64
+ - ram.dobson@solsystemscompany.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - Gemfile
70
+ - LICENSE
71
+ - README.md
72
+ - Rakefile
73
+ - lib/zipline.rb
74
+ - lib/zipline/fake_stream.rb
75
+ - lib/zipline/output_stream.rb
76
+ - lib/zipline/version.rb
77
+ - lib/zipline/zip_generator.rb
78
+ - zipline.gemspec
79
+ homepage: http://github.com/fringd/zipline
80
+ licenses: []
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.0.0.rc.2
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: stream zip files from rails
102
+ test_files: []